<?php
/**
 * Blocksify Tokens — PHP-side seed registry mirroring the JS data
 * stores at `src/shared/stores/spacing-tokens.js` + `font-size-tokens.js`.
 *
 * Mode B (CSS-var token resolution) requires PHP CSS emitters to:
 *   1. Recognize `{ token: 'sm' }` shape on stored block attrs.
 *   2. Emit `var(--blocksify--spacing--{slug}, {fallback-literal})` so
 *      frontend renders correctly even before the `:root` declaration
 *      ships (later phase will emit `:root` from a wp_options-backed
 *      override map).
 *
 * For now this class holds the SEED defaults — same constants as JS
 * `SPACING_PRESETS` + `FONT_SIZE_PRESETS`. When wp_options persistence
 * lands, `get_*_token()` reads option overrides on top of the seed,
 * matching JS `defaultEdits` semantics.
 *
 * @package Blocksify
 */

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

if ( ! class_exists( 'Blocksify_Tokens' ) ) {

	class Blocksify_Tokens {

		/**
		 * Seed spacing tokens — slug → { label, value, unit }.
		 *
		 * Slugs match `DEFAULT_SLUG_MAP` in `spacing-tokens.js`. When
		 * the JS-side preset id changes (e.g. id='40' → 'sm' rename),
		 * keep this map in sync — token references in saved block
		 * attrs use the slug as the CSS-var key.
		 */
		public static function get_spacing_seeds() {
			return array(
				'none' => array( 'label' => 'NONE', 'value' => 0, 'unit' => 'px' ),
				'2xs'  => array( 'label' => '2XS', 'value' => 0.5, 'unit' => 'rem' ),
				'xs'   => array( 'label' => 'XS', 'value' => 1, 'unit' => 'rem' ),
				'sm'   => array( 'label' => 'SM', 'value' => 1.5, 'unit' => 'rem' ),
				'md'   => array( 'label' => 'MD', 'value' => 2, 'unit' => 'rem' ),
				'lg'   => array( 'label' => 'LG', 'value' => 3, 'unit' => 'rem' ),
				'xl'   => array( 'label' => 'XL', 'value' => 4, 'unit' => 'rem' ),
				'2xl'  => array( 'label' => '2XL', 'value' => 5, 'unit' => 'rem' ),
				'3xl'  => array( 'label' => '3XL', 'value' => 6.5, 'unit' => 'rem' ),
				'4xl'  => array( 'label' => '4XL', 'value' => 8, 'unit' => 'rem' ),
				'5xl'  => array( 'label' => '5XL', 'value' => 10, 'unit' => 'rem' ),
			);
		}

		/**
		 * Seed font-size tokens. Mirrors `FONT_SIZE_PRESETS` in JS.
		 */
		public static function get_font_size_seeds() {
			return array(
				'xs'  => array( 'label' => 'XS', 'value' => 0.75, 'unit' => 'rem' ),
				'sm'  => array( 'label' => 'SM', 'value' => 0.875, 'unit' => 'rem' ),
				'md'  => array( 'label' => 'MD', 'value' => 1, 'unit' => 'rem' ),
				'lg'  => array( 'label' => 'LG', 'value' => 1.125, 'unit' => 'rem' ),
				'xl'  => array( 'label' => 'XL', 'value' => 1.25, 'unit' => 'rem' ),
				'2xl' => array( 'label' => '2XL', 'value' => 1.5, 'unit' => 'rem' ),
				'3xl' => array( 'label' => '3XL', 'value' => 2, 'unit' => 'rem' ),
				'4xl' => array( 'label' => '4XL', 'value' => 2.5, 'unit' => 'rem' ),
				'5xl' => array( 'label' => '5XL', 'value' => 3, 'unit' => 'rem' ),
				'6xl' => array( 'label' => '6XL', 'value' => 4, 'unit' => 'rem' ),
			);
		}

		/**
		 * Resolve a token slug to its current literal value as a CSS
		 * length string (e.g. `'1.5rem'`, `'0'`). Returns empty when
		 * the slug isn't recognized — caller emits `var()` without a
		 * fallback in that case.
		 *
		 * @param string $kind 'spacing' | 'font-size'.
		 * @param string $slug Token slug.
		 * @return string CSS length or ''.
		 */
		public static function get_literal( $kind, $slug ) {
			$seeds = ( 'font-size' === $kind )
				? self::get_font_size_seeds()
				: self::get_spacing_seeds();
			if ( ! isset( $seeds[ $slug ] ) ) {
				return '';
			}
			$token = $seeds[ $slug ];
			$value = (float) $token['value'];
			if ( 0.0 === $value ) {
				return '0';
			}
			$unit    = isset( $token['unit'] ) ? $token['unit'] : '';
			$num_str = ( $value == (int) $value ) ? (string) (int) $value : (string) $value;
			return $num_str . $unit;
		}

		/**
		 * Build a `var(--blocksify--{kind}--{slug}, fallback)` string.
		 * Empty fallback when slug is unknown — fragile but explicit
		 * (var resolves to whatever `:root` ships later, or to `null`
		 * which CSS treats as the property's initial value).
		 *
		 * @param string $kind 'spacing' | 'font-size'.
		 * @param string $slug Token slug.
		 * @return string CSS var() expression.
		 */
		public static function var_expression( $kind, $slug ) {
			$prefix   = ( 'font-size' === $kind ) ? 'font-size' : 'spacing';
			$fallback = self::get_literal( $kind, $slug );
			$var_name = '--blocksify--' . $prefix . '--' . $slug;
			return '' !== $fallback
				? 'var(' . $var_name . ', ' . $fallback . ')'
				: 'var(' . $var_name . ')';
		}

		/**
		 * Emit a single `<style>:root { ... }</style>` block carrying
		 * every spacing + font-size token's seed value. Frontend hook
		 * — gives saved blocks a resolution target for their `var()`
		 * references. Editor uses the JS-side injector instead (which
		 * stays reactive to live edits).
		 *
		 * Output is conservative: tiny inline style on every page (one
		 * declaration per token, ~22 declarations total ≈ 1KB
		 * uncompressed, ~300B gzipped). Negligible payload.
		 */
		public static function render_root_styles() {
			$decls = array();
			foreach ( self::get_spacing_seeds() as $slug => $token ) {
				$literal = self::get_literal( 'spacing', $slug );
				if ( '' !== $literal ) {
					$decls[] = '--blocksify--spacing--' . $slug . ': ' . $literal . ';';
				}
			}
			foreach ( self::get_font_size_seeds() as $slug => $token ) {
				$literal = self::get_literal( 'font-size', $slug );
				if ( '' !== $literal ) {
					$decls[] = '--blocksify--font-size--' . $slug . ': ' . $literal . ';';
				}
			}
			if ( empty( $decls ) ) {
				return;
			}
			$css = ':root { ' . implode( ' ', $decls ) . ' }';
			echo '<style id="bsy-tokens-root">' . $css . '</style>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
		}
	}

	// Frontend (post viewer) — emit `:root` token CSS so block
	// `var(--blocksify--spacing--md, 2rem)` references resolve. Skip
	// admin (editor-side `:root` is injected by `inject-global-spacing-
	// tokens.js` / `inject-global-font-size-tokens.js` instead, which
	// react to live store edits).
	//
	// INVARIANT (file-mode CSS): keep this UNCONDITIONAL on every frontend
	// page. Per-instance block CSS bakes token values only as `var()`
	// fallbacks; the cached CSS file relies on this live `:root` for fresh
	// values, so token edits don't invalidate files. Gating it (used-only /
	// non-singular) REQUIRES bumping `blocksify_css_generation`.
	// See FOUNDATION §9.4 + docs/SPEC-css-output-mode.md §3.1.
	add_action( 'wp_head', array( 'Blocksify_Tokens', 'render_root_styles' ), 1 );
}
