<?php

namespace Mtc\Plugins\BlockManager\Services;

use Mtc\Plugins\BlockManager\Models\BlockManagerVariantDraft;

/**
 * Renders a Designer variant draft to HTML + CSS for the live preview canvas.
 *
 * Pure read service — never persists. Token references in the property tree
 * resolve via DesignTokenService against variables.less; unknown tokens go to
 * `warnings[]` and their CSS rule is skipped. Raw values pass through.
 *
 * Mirrors the panel/property names declared by PropertySchemaService (Phase 9).
 * The right rail and the preview share their vocabulary through the schema —
 * adding a property here means updating both ends in lockstep.
 */
class VariantPreviewService
{
    public function __construct(private DesignTokenService $tokens)
    {
    }

    /**
     * @param array<string, mixed>|null $propertiesOverride  In-memory override; if null, uses $draft->properties_json
     * @param string|null               $sampleContent       Replaces the default sample text inside the rendered element
     * @param string|null               $state               One of default/hover/focus/active/disabled; null = default
     * @param string|null               $breakpoint          One of desktop/tablet/mobile; null = desktop
     * @return array{html: string, css: string, warnings: array<int, string>}
     */
    public function render(
        BlockManagerVariantDraft $draft,
        ?array $propertiesOverride = null,
        ?string $sampleContent = null,
        ?string $state = null,
        ?string $breakpoint = null
    ): array {
        $properties = $propertiesOverride ?? $draft->properties_json ?? [];

        $warnings = [];
        $rules = [];

        $normalisedState = is_string($state) ? strtolower($state) : null;
        $hasState = $normalisedState !== null && $normalisedState !== '' && $normalisedState !== 'default';

        $normalisedBp = is_string($breakpoint) ? strtolower($breakpoint) : null;
        $hasBreakpoint = $normalisedBp !== null && $normalisedBp !== '' && $normalisedBp !== 'desktop';

        // CSS source order handles the cascade — later overlays win on properties
        // they touch, earlier rules pass through.
        //
        //   1. base.default.desktop      ← always the foundation
        //   2. base.default.{breakpoint} ← breakpoint override on default state
        //   3. base.{state}.desktop      ← state override on desktop
        //   4. base.{state}.{breakpoint} ← cross-cell override (state × breakpoint)

        $rules = array_merge($rules, $this->buildRulesFromCell(
            $this->cell($properties, 'default', 'desktop'),
            $warnings
        ));

        if ($hasBreakpoint) {
            $rules = array_merge($rules, $this->buildRulesFromCell(
                $this->cell($properties, 'default', $normalisedBp),
                $warnings
            ));
        }

        if ($hasState) {
            $rules = array_merge($rules, $this->buildRulesFromCell(
                $this->cell($properties, $normalisedState, 'desktop'),
                $warnings
            ));
        }

        if ($hasState && $hasBreakpoint) {
            $rules = array_merge($rules, $this->buildRulesFromCell(
                $this->cell($properties, $normalisedState, $normalisedBp),
                $warnings
            ));
        }

        $customCss = $this->customCss($properties);
        if ($customCss !== null) {
            $rules[] = $customCss;
        }

        $css = '.bm-preview { ' . implode(' ', $rules) . ' }';
        $html = $this->htmlFor($draft->element_type, $sampleContent);

        return ['html' => $html, 'css' => $css, 'warnings' => $warnings];
    }

    /**
     * @param array<string, mixed> $properties
     */
    private function customCss(array $properties): ?string
    {
        $raw = $this->cell($properties, 'default', 'desktop')['custom_css']['css']['raw'] ?? null;
        if (!is_string($raw) || trim($raw) === '') {
            return null;
        }
        return trim($raw);
    }

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

    /**
     * Walk one (state, breakpoint) cell and produce CSS rule strings.
     *
     * @param array<string, mixed> $cell      panel name => property name => value wrap
     * @param array<int, string>   $warnings  collected by reference; unknown tokens etc.
     * @return array<int, string>
     */
    private function buildRulesFromCell(array $cell, 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, $warnings);
                if ($resolved === null) {
                    continue;
                }

                $resolved = $this->postProcess($panel, (string) $propertyName, $resolved);
                foreach ((array) $cssTargets as $cssProperty) {
                    $rules[] = "{$cssProperty}: {$resolved};";
                }
            }
        }
        return $rules;
    }

    /**
     * @param mixed $value
     * @param array<int, string> $warnings
     */
    private function resolveValue($value, array &$warnings): ?string
    {
        if (!is_array($value)) {
            return null;
        }
        if (isset($value['token']) && is_string($value['token']) && $value['token'] !== '') {
            $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;
    }

    /**
     * Two cases need rewriting after the raw resolve:
     *  - background.image: raw URL → `url(<v>)` (gradients pass through untouched)
     *  - bare-integer values on length-typed properties → append `px` so
     *    `@base_font_size: 16` renders as `font-size: 16px`
     */
    private function postProcess(string $panel, string $property, string $value): string
    {
        if ($panel === 'background' && $property === 'image') {
            if (!str_starts_with($value, 'url(') && !str_starts_with($value, 'linear-gradient')) {
                return "url({$value})";
            }
        }
        if ($panel === 'effects' && $property === 'blur') {
            $inner = $value;
            if (preg_match('/^-?\d+(\.\d+)?$/', $inner)) {
                $inner .= 'px';
            }
            return "blur({$inner})";
        }
        if (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, [
            'typography.font_size',
            'typography.line_height',
            'typography.letter_spacing',
            '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 function htmlFor(string $elementType, ?string $sampleContent): string
    {
        $samples = [
            'heading'   => ['tag' => 'h2',     'default' => 'Sample Heading'],
            'paragraph' => ['tag' => 'p',      'default' => 'Sample paragraph text — quick brown fox.'],
            'button'    => ['tag' => 'button', 'default' => 'Sample Button'],
        ];
        $sample = $samples[$elementType] ?? ['tag' => 'div', 'default' => 'Sample'];
        $text = ($sampleContent !== null && $sampleContent !== '') ? $sampleContent : $sample['default'];

        $escaped = htmlspecialchars($text, ENT_QUOTES);
        return "<{$sample['tag']} class=\"bm-preview\">{$escaped}</{$sample['tag']}>";
    }

    /**
     * Panel + property → CSS property name(s). Multiple targets fan out to
     * one rule each (e.g. padding_x → padding-left + padding-right).
     */
    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',
            'justify_content' => 'justify-content',
            'align_items'     => 'align-items',
            'gap'             => 'gap',
        ],
        'effects' => [
            'box_shadow' => 'box-shadow',
            'opacity'    => 'opacity',
            'blur'       => 'filter',
        ],
    ];
}
