<?php

namespace Mtc\Plugins\BlockManager\Services;

/**
 * Compiles a container instance's `style_props` tree into scoped CSS.
 *
 * Mirrors the Designer's variant pipeline (VariantPreviewService +
 * VariantLessGenerator) but scopes rules to a per-instance class
 * (e.g. `.bm-ci-el-123`) instead of a variant class, and emits real `@media`
 * blocks so all three breakpoints render simultaneously (variants lean on the
 * active breakpoint tab; an instance on the page cannot).
 *
 * Two modes via $resolveTokens:
 *   - true  (composer preview): tokens resolve to literal values, bare numeric
 *           lengths gain `px`, and media queries use literal pixel widths — the
 *           output is injected raw into the canvas as a <style> block.
 *   - false (ship → LESS file): token references survive as `@varname`, raw
 *           values pass through untouched, and media queries use the
 *           `@medium_width`/`@mobile_width` LESS variables — the LESS compiler
 *           substitutes everything at site-build time.
 *
 * Selectors are emitted FLAT (`{selector}:{state}`) so the identical string is
 * valid both as plain CSS (preview) and inside a LESS file (ship).
 *
 * @property_json shape: { base: { [state]: { [breakpoint]: { [panel]: { [prop]: {token|raw} } } } } }
 */
class ContainerCssCompiler
{
    public function __construct(private DesignTokenService $tokens)
    {
    }

    /**
     * @param array<string, mixed> $styleProps
     * @return array{css: string, warnings: array<int, string>}
     */
    public function compile(array $styleProps, string $selector, bool $resolveTokens): array
    {
        $warnings = [];
        $blocks = [];

        foreach (self::BREAKPOINTS as $breakpoint => $media) {
            // $media: null = base (no query); otherwise [resolved px, less var]
            foreach (self::STATES as $state) {
                $cell = $this->cell($styleProps, $state, $breakpoint);
                $rules = $this->buildRulesFromCell($cell, $resolveTokens, $warnings);
                if ($rules === []) {
                    continue;
                }
                $sel = $state === 'default' ? $selector : "{$selector}:{$state}";
                $blocks[] = [
                    'media'    => $media === null ? null : ($resolveTokens ? $media[0] : $media[1]),
                    'selector' => $sel,
                    'rules'    => $rules,
                ];
            }
        }

        return ['css' => $this->renderBlocks($blocks), 'warnings' => $warnings];
    }

    /**
     * @param array<int, array{media: ?string, selector: string, rules: array<int,string>}> $blocks
     */
    private function renderBlocks(array $blocks): string
    {
        $out = '';
        foreach ($blocks as $block) {
            $ruleText = implode(' ', $block['rules']);
            $rule = "{$block['selector']} { {$ruleText} }";
            if ($block['media'] !== null) {
                $rule = "@media (max-width: {$block['media']}) { {$rule} }";
            }
            $out .= ($out === '' ? '' : "\n") . $rule;
        }
        return $out;
    }

    /**
     * @param array<string, mixed> $styleProps
     * @return array<string, mixed>
     */
    private function cell(array $styleProps, string $state, string $breakpoint): array
    {
        $cell = $styleProps['base'][$state][$breakpoint] ?? null;
        return is_array($cell) ? $cell : [];
    }

    /**
     * @param array<string, mixed> $cell
     * @param array<int, string>   $warnings
     * @return array<int, string>
     */
    private function buildRulesFromCell(array $cell, bool $resolveTokens, array &$warnings): array
    {
        $rules = [];
        foreach (self::PANEL_TO_CSS as $panel => $propertyMap) {
            $panelValues = is_array($cell[$panel] ?? null) ? $cell[$panel] : [];
            foreach ($panelValues as $propertyName => $valueWrap) {
                $cssTargets = $propertyMap[$propertyName] ?? null;
                if ($cssTargets === null) {
                    continue;
                }
                $resolved = $this->resolveValue($valueWrap, $resolveTokens, $warnings);
                if ($resolved === null) {
                    continue;
                }
                $resolved = $this->postProcess($panel, (string) $propertyName, $resolved, $resolveTokens);
                foreach ((array) $cssTargets as $cssProperty) {
                    $rules[] = "{$cssProperty}: {$resolved};";
                }
            }
        }
        return $rules;
    }

    /**
     * @param mixed $value
     * @param array<int, string> $warnings
     */
    private function resolveValue($value, bool $resolveTokens, array &$warnings): ?string
    {
        if (!is_array($value)) {
            return null;
        }
        if (isset($value['token']) && is_string($value['token']) && $value['token'] !== '') {
            if (!$resolveTokens) {
                return $value['token']; // preserve @varname for the LESS compiler
            }
            $token = $this->tokens->get($value['token']);
            if ($token === null) {
                $warnings[] = "Unknown token: {$value['token']}";
                return null;
            }
            return (string) ($token['value'] ?? '');
        }
        if (array_key_exists('raw', $value) && $value['raw'] !== '' && $value['raw'] !== null) {
            return (string) $value['raw'];
        }
        return null;
    }

    private function postProcess(string $panel, string $property, string $value, bool $resolveTokens): string
    {
        // Friendly grid "columns": integer N → repeat(N, 1fr).
        if ($panel === 'layout' && $property === 'columns' && preg_match('/^\d+$/', $value)) {
            return "repeat({$value}, 1fr)";
        }
        if ($panel === 'background' && $property === 'image') {
            if (
                !str_starts_with($value, 'url(') &&
                !str_starts_with($value, 'linear-gradient') &&
                !str_starts_with($value, '@')
            ) {
                return "url({$value})";
            }
        }
        // Resolve mode only: append px to bare-number length values (parity with
        // VariantPreviewService). Preserve mode trusts raw/token to be unit-complete.
        if ($resolveTokens
            && preg_match('/^-?\d+(\.\d+)?$/', $value)
            && $this->isLengthProperty($panel, $property)
        ) {
            return $value . 'px';
        }
        return $value;
    }

    private function isLengthProperty(string $panel, string $property): bool
    {
        return in_array($panel . '.' . $property, [
            'box.width', 'box.height',
            'box.padding_x', 'box.padding_y',
            'box.margin_x', 'box.margin_y',
            'border.border_width', 'border.border_radius',
            'layout.gap',
        ], true);
    }

    private const STATES = ['default', 'hover', 'focus', 'active', 'disabled'];

    /** breakpoint => null (base) | [literalPx, lessVar] */
    private const BREAKPOINTS = [
        'desktop' => null,
        'tablet'  => ['800px', '@medium_width'],
        'mobile'  => ['640px', '@mobile_width'],
    ];

    /**
     * Panel + property → CSS property name(s). Superset of the Designer's map,
     * adding flex-wrap, align-content and the grid properties the container
     * panel exposes.
     */
    private const PANEL_TO_CSS = [
        'typography' => [
            'color'           => 'color',
            'font_family'     => 'font-family',
            'font_size'       => 'font-size',
            'font_weight'     => 'font-weight',
            'line_height'     => 'line-height',
            'letter_spacing'  => 'letter-spacing',
            'text_transform'  => 'text-transform',
            'text_decoration' => 'text-decoration',
            'text_align'      => 'text-align',
        ],
        'box' => [
            'width'     => 'width',
            'height'    => 'height',
            'padding_x' => ['padding-left', 'padding-right'],
            'padding_y' => ['padding-top',  'padding-bottom'],
            'margin_x'  => ['margin-left',  'margin-right'],
            'margin_y'  => ['margin-top',   'margin-bottom'],
        ],
        'border' => [
            'border_width'  => 'border-width',
            'border_style'  => 'border-style',
            'border_color'  => 'border-color',
            'border_radius' => 'border-radius',
        ],
        'background' => [
            'color'    => 'background-color',
            'image'    => 'background-image',
            'gradient' => 'background-image',
        ],
        'layout' => [
            'display'               => 'display',
            'flex_direction'        => 'flex-direction',
            'flex_wrap'             => 'flex-wrap',
            'justify_content'       => 'justify-content',
            'align_items'           => 'align-items',
            'align_content'         => 'align-content',
            'gap'                   => 'gap',
            'columns'               => 'grid-template-columns',
            'grid_template_columns' => 'grid-template-columns',
            'grid_auto_flow'        => 'grid-auto-flow',
            'justify_items'         => 'justify-items',
        ],
        'effects' => [
            'box_shadow' => 'box-shadow',
            'opacity'    => 'opacity',
            'blur'       => 'filter',
        ],
    ];
}
