<?php
/**
 * AxcelersBlocksCss — generates all inline CSS for a block instance.
 *
 * One instance per render call; state is fully isolated between instances.
 * Use the static factory:
 *
 *   $rules = AxcelersBlocksCss::generate($unique_class, $devices, $attributes, $context);
 *
 * $context keys (all optional):
 *   has_icon  (bool) — skip icon/SVG selectors when false
 *   is_linked (bool) — skip link/link-hover selectors when false
 *
 * File layout:
 *   class-block-css.php         — this file: state, constructor, emitter, core add()
 *   trait-block-css-builders.php — build*() methods and addMany/Spacing/Background/Border/etc.
 *   trait-block-css-effects.php  — effectBoxShadow/Transition/Filter/TextShadow + shorthand utils
 *
 * @package AxcelersBlocks
 */

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

if ( class_exists( 'AxcelersBlocksCss' ) ) {
	return;
}

require_once __DIR__ . '/trait-block-css-effects.php';
require_once __DIR__ . '/trait-block-css-builders.php';

class AxcelersBlocksCss {

	use AxcelersBlocksCssEffects;
	use AxcelersBlocksCssBuilders;

	// ─── State ───────────────────────────────────────────────────────────────

	private string $uniqueClass;
	private string $selectorSuffix;
	private string $elementSuffix;
	private array  $devices;
	private array  $attributes;
	private bool   $hasIcon;
	private bool   $isLinked;

	/** @var array<string, array<string, list<string>>> Per-target, per-device declaration lists. */
	private array $buckets = [];

	/** @var array<string, array<string, string>> Cascade state: last emitted value per property per target. */
	private array $cascades = [];

	/** @var list<string> Non-media-query rules (e.g. iconColor). */
	private array $globalRules = [];

	/** @var list<string> Final collected output. */
	private array $output = [];

	// ─── Public API ──────────────────────────────────────────────────────────

	/**
	 * Generate all CSS rules for a block instance.
	 *
	 * @param string $uniqueClass Block-scoped unique class.
	 * @param array  $devices     Device map from axcelersblocks_get_responsive_devices().
	 * @param array  $attributes  Full block attributes.
	 * @param array  $context     { has_icon?: bool, is_linked?: bool }
	 * @return list<string> CSS rule strings (already wrapped in media queries where needed).
	 */
	public static function generate(
		string $uniqueClass,
		array  $devices,
		array  $attributes,
		array  $context = []
	): array {
		return ( new self( $uniqueClass, $devices, $attributes, $context ) )->run();
	}

	// ─── Constructor ─────────────────────────────────────────────────────────

	private function __construct( string $uniqueClass, array $devices, array $attributes, array $context ) {
		$this->uniqueClass    = $uniqueClass;
		$this->selectorSuffix = $context['selector_suffix'] ?? '';
		// Optional descendant selector (e.g. ' .axcelersblocks-bodycopy__content') so a
		// sub-element can carry its own full style set via the main bucket.
		$this->elementSuffix  = $context['element_suffix'] ?? '';
		$this->devices        = $devices;
		$this->attributes     = $this->backfillMobile( $attributes );
		$this->hasIcon        = $context['has_icon']  ?? ! empty( $attributes['iconSvg'] );
		$this->isLinked       = $context['is_linked'] ?? false;

		foreach ( [ 'main', 'icon', 'svg', 'link', 'linkHover', 'linkFocus', 'linkActive' ] as $target ) {
			$this->buckets[ $target ]  = [ 'mobile' => [], 'tablet' => [], 'desktop' => [] ];
			$this->cascades[ $target ] = [];
		}
	}

	// ─── Run ─────────────────────────────────────────────────────────────────

	private function run(): array {
		$this->buildMain();

		if ( $this->hasIcon ) {
			$this->buildIcon();
			$this->buildSvg();
		}

		if ( $this->isLinked ) {
			$this->buildLink();
		}

		$this->emitAll();
		return $this->output;
	}

	// ─── Emitter ─────────────────────────────────────────────────────────────

	private function emitAll(): void {
		$c  = sanitize_html_class( $this->uniqueClass );
		$sx = $this->selectorSuffix;

		// Element suffix (descendant) comes before the pseudo-state suffix so sub-element
		// hover selectors read e.g. `.uc table td:hover`, not `.uc:hover table td`.
		$this->emitBucket( 'main', ".{$c}{$this->elementSuffix}{$sx}" );

		if ( $this->hasIcon ) {
			$this->emitBucket( 'icon', ".{$c}{$sx} .icon" );
			$this->emitBucket( 'svg',  ".{$c}{$sx} .icon svg" );
		}

		if ( $this->isLinked ) {
			$this->emitBucket( 'link',      ".$c-link" );
			$this->emitBucket( 'linkHover', ".$c-link:hover" );
			$this->emitBucket( 'linkFocus', ".$c-link:focus" );
			$this->emitBucket( 'linkActive', ".$c-link:active" );
		}

		if ( ! empty( $this->globalRules ) ) {
			$this->output = array_merge( $this->output, $this->globalRules );
		}
	}

	/**
	 * Flush one bucket to $this->output.
	 * Skips empty device arrays — no empty @media wrapper is ever emitted.
	 */
	private function emitBucket( string $target, string $selector ): void {
		foreach ( $this->devices as $device => $config ) {
			$rules = $this->buckets[ $target ][ $device ] ?? [];
			if ( empty( $rules ) ) {
				continue;
			}
			$css            = implode( ' ', $rules );
			$this->output[] = empty( $config['media'] )
				? "$selector { $css }"
				: "{$config['media']} { $selector { $css } }";
		}
	}

	// ─── Core primitive ───────────────────────────────────────────────────────

	/**
	 * Add one CSS declaration to a target bucket, respecting the cascade.
	 * Identical values on tablet/desktop are de-duplicated automatically.
	 */
	private function add( string $target, string $device, string $property, $value ): void {
		$value = axcelersblocks_sanitize_css_value( $value );
		if ( $value === '' ) {
			return;
		}
		$cascade = &$this->cascades[ $target ];
		if ( $device !== 'mobile' && isset( $cascade[ $property ] ) && $cascade[ $property ] === $value ) {
			return;
		}
		$cascade[ $property ]                  = $value;
		$this->buckets[ $target ][ $device ][] = "$property: $value;";
	}
}
