<?php
declare(strict_types=1);

namespace App\Theme;

use InvalidArgumentException;

final class ThemeTokenService
{
    /** @return array<string,array<string,string>> */
    public static function defaults(): array
    {
        return [
            'colors' => [
                'bg' => '#F6F7F9', 'surface' => '#FFFFFF', 'surface_soft' => '#F6F7F9',
                'text' => '#171A21', 'muted' => '#5B6270', 'border' => '#E7E9EE',
                'primary' => '#0E6F5C', 'primary_hover' => '#0A5546', 'secondary' => '#3977C9',
                'accent' => '#C99A3B', 'success' => '#1F7A55', 'warning' => '#9A6700', 'danger' => '#A84848',
            ],
            'typography' => [
                'font_body' => 'system', 'font_heading' => 'system', 'font_arabic' => 'cairo',
                'body_size' => '1rem', 'body_line_height' => '1.7',
                'h1_size' => '2rem', 'h1_weight' => '800',
                'h2_size' => '1.5rem', 'h2_weight' => '750',
                'h3_size' => '1.2rem', 'h3_weight' => '700',
            ],
            'shape' => [
                'radius_sm' => '0.5rem', 'radius_md' => '0.75rem', 'radius_lg' => '1rem', 'radius_xl' => '1.5rem',
            ],
            'shadow' => [
                'sm' => '0 1px 2px rgba(15,23,42,.06)',
                'md' => '0 10px 30px rgba(15,23,42,.08)',
                'lg' => '0 22px 60px rgba(15,23,42,.12)',
            ],
            'components' => [
                'button_radius' => 'var(--radius-md)', 'button_primary_bg' => 'var(--color-primary)', 'button_primary_text' => '#FFFFFF',
                'card_bg' => 'var(--color-surface)', 'card_border' => 'var(--color-border)', 'card_radius' => 'var(--radius-lg)', 'card_shadow' => 'var(--shadow-sm)',
                'table_header_bg' => 'var(--color-surface-soft)', 'table_border' => 'var(--color-border)',
                'header_bg' => 'var(--color-surface)', 'header_border' => 'var(--color-border)',
                'sidebar_bg' => 'var(--color-surface)', 'sidebar_border' => 'var(--color-border)',
            ],
            'pages' => [
                'home_feed_gap' => '1rem', 'question_max_width' => '72rem', 'topic_max_width' => '72rem', 'article_max_width' => '52rem',
            ],
            'layout' => [
                'home_preset' => 'community', 'question_preset' => 'classic', 'topic_preset' => 'discussion',
                'header_preset' => 'standard', 'density' => 'comfortable', 'sidebar_position' => 'end',
            ],
        ];
    }

    /** @return array<string,array<string,string>> */
    public static function fromLegacyAppearance(array $appearance): array
    {
        $tokens = self::defaults();
        $map = [
            'primary' => ['colors','primary'], 'primary_dark' => ['colors','primary_hover'], 'accent' => ['colors','accent'],
            'surface' => ['colors','surface'], 'surface_soft' => ['colors','surface_soft'], 'ink' => ['colors','text'],
            'ink_muted' => ['colors','muted'], 'line' => ['colors','border'], 'danger' => ['colors','danger'],
        ];
        foreach ($map as $legacy => [$section,$key]) {
            $value = strtoupper(trim((string) ($appearance[$legacy] ?? '')));
            if (preg_match('/^#[0-9A-F]{6}$/', $value) === 1) $tokens[$section][$key] = $value;
        }
        if (isset($appearance['surface_soft'])) $tokens['colors']['bg'] = $tokens['colors']['surface_soft'];
        foreach (['radius_md','radius_lg'] as $key) {
            $value = trim((string) ($appearance[$key] ?? ''));
            if ($value !== '' && is_numeric($value)) $tokens['shape'][$key] = self::number((float) $value) . 'rem';
        }
        $font = ThemeAppearanceService::resolveFontStack((string) ($appearance['font_stack'] ?? 'system'));
        if (in_array($font, ThemeAppearanceService::fontStacks(), true)) $tokens['typography']['font_body'] = $font;
        $baseSize = trim((string) ($appearance['font_size_base'] ?? ''));
        if ($baseSize !== '' && is_numeric($baseSize)) $tokens['typography']['body_size'] = self::number((float) $baseSize) . 'rem';
        return $tokens;
    }

    /** @param array<string,mixed> $tokens @return array<string,array<string,string>> */
    public static function applyLegacyAppearance(array $tokens, array $appearance): array
    {
        $safe = self::normalize($tokens);
        $legacy = self::fromLegacyAppearance($appearance);
        foreach (['bg','surface','surface_soft','text','muted','border','primary','primary_hover','accent','danger'] as $key) {
            $safe['colors'][$key] = $legacy['colors'][$key];
        }
        foreach (['radius_md','radius_lg'] as $key) $safe['shape'][$key] = $legacy['shape'][$key];
        $safe['typography']['font_body'] = $legacy['typography']['font_body'];
        $safe['typography']['body_size'] = $legacy['typography']['body_size'];
        return self::normalize($safe);
    }

    /** @param array<string,mixed> $input @param array<string,mixed> $fallback @return array<string,array<string,string>> */
    public static function normalize(array $input, array $fallback = []): array
    {
        $base = self::defaults();
        if ($fallback !== []) $base = self::mergeKnown($base, $fallback);
        $merged = self::mergeKnown($base, $input);

        foreach ($merged['colors'] as $key => $value) {
            $value = strtoupper(trim((string) $value));
            if (preg_match('/^#[0-9A-F]{6}$/', $value) !== 1) throw new InvalidArgumentException("Invalid color token: {$key}");
            $merged['colors'][$key] = $value;
        }
        foreach (['font_body','font_heading','font_arabic'] as $key) {
            $font = ThemeAppearanceService::resolveFontStack((string) $merged['typography'][$key]);
            if (!in_array($font, ThemeAppearanceService::fontStacks(), true)) throw new InvalidArgumentException("Invalid font token: {$key}");
            $merged['typography'][$key] = $font;
        }
        foreach ($merged['typography'] as $key => $value) {
            if (str_starts_with($key, 'font_')) continue;
            if (str_ends_with($key, '_weight')) {
                $weight = (int) $value;
                if ($weight < 400 || $weight > 900) throw new InvalidArgumentException("Invalid typography token: {$key}");
                $merged['typography'][$key] = (string) $weight;
                continue;
            }
            if (!self::safeDimension((string) $value, str_ends_with($key, '_line_height'))) throw new InvalidArgumentException("Invalid typography token: {$key}");
        }
        foreach ($merged['shape'] as $key => $value) if (!self::safeDimension((string) $value)) throw new InvalidArgumentException("Invalid shape token: {$key}");
        foreach ($merged['pages'] as $key => $value) if (!self::safeDimension((string) $value)) throw new InvalidArgumentException("Invalid page token: {$key}");
        foreach ($merged['shadow'] as $key => $value) if (!self::safeCssValue((string) $value)) throw new InvalidArgumentException("Invalid shadow token: {$key}");
        foreach ($merged['components'] as $key => $value) if (!self::safeCssValue((string) $value)) throw new InvalidArgumentException("Invalid component token: {$key}");

        $layoutMap = ['home_preset'=>'home','question_preset'=>'question','topic_preset'=>'topic','header_preset'=>'header','density'=>'density','sidebar_position'=>'sidebar_position'];
        foreach ($layoutMap as $key => $kind) $merged['layout'][$key] = LayoutPresetService::normalize($kind, $merged['layout'][$key]);
        return $merged;
    }

    /** @param array<string,mixed> $tokens @return list<string> */
    public static function validateForApply(array $tokens): array
    {
        try { $safe = self::normalize($tokens); } catch (InvalidArgumentException) { return ['tokens.invalid']; }
        $errors = [];
        if (self::contrastRatio($safe['colors']['text'], $safe['colors']['surface']) < 4.5) $errors[] = 'colors.text_on_surface_contrast';
        if (self::contrastRatio($safe['colors']['text'], $safe['colors']['bg']) < 4.5) $errors[] = 'colors.text_on_background_contrast';
        return $errors;
    }

    /** @param array<string,mixed> $tokens */
    public static function compileCss(array $tokens): string
    {
        $safe = self::normalize($tokens);
        $vars = [];
        foreach ($safe['colors'] as $key => $value) $vars['--color-' . str_replace('_','-',$key)] = $value;
        foreach ($safe['shape'] as $key => $value) $vars['--' . str_replace('_','-',$key)] = $value;
        foreach ($safe['shadow'] as $key => $value) $vars['--shadow-' . str_replace('_','-',$key)] = $value;
        foreach ($safe['components'] as $key => $value) $vars['--' . str_replace('_','-',$key)] = $value;
        foreach ($safe['pages'] as $key => $value) $vars['--' . str_replace('_','-',$key)] = $value;
        foreach ($safe['typography'] as $key => $value) {
            $name = '--' . str_replace('_','-',$key);
            $vars[$name] = str_starts_with($key,'font_') ? ThemeAppearanceService::fontStackCss($value) : $value;
        }
        // Transitional aliases keep the existing stylesheet and third-party themes
        // working while components move to semantic Theme System 2.0 variables.
        $vars['--teal'] = $safe['colors']['primary']; $vars['--teal-dark'] = $safe['colors']['primary_hover'];
        $vars['--gold'] = $safe['colors']['accent']; $vars['--surface'] = $safe['colors']['surface'];
        $vars['--surface-soft'] = $safe['colors']['surface_soft']; $vars['--ink'] = $safe['colors']['text'];
        $vars['--ink-muted'] = $safe['colors']['muted']; $vars['--line'] = $safe['colors']['border']; $vars['--danger'] = $safe['colors']['danger'];
        $vars['--font-stack'] = ThemeAppearanceService::fontStackCss($safe['typography']['font_body']);
        $declarations = [];
        foreach ($vars as $name => $value) $declarations[] = $name . ':' . $value;
        return ':root{' . implode(';', $declarations) . '}';
    }

    public static function contrastRatio(string $a, string $b): float
    {
        $la = self::luminance($a); $lb = self::luminance($b);
        return (max($la,$lb)+0.05)/(min($la,$lb)+0.05);
    }

    /** @param array<string,array<string,string>> $base @param array<string,mixed> $input @return array<string,array<string,string>> */
    private static function mergeKnown(array $base, array $input): array
    {
        foreach ($base as $section => $values) {
            if (!is_array($input[$section] ?? null)) continue;
            foreach ($values as $key => $_) if (array_key_exists($key, $input[$section])) $base[$section][$key] = trim((string) $input[$section][$key]);
        }
        return $base;
    }

    private static function safeDimension(string $value, bool $unitless = false): bool
    {
        $value = trim($value);
        if ($unitless && preg_match('/^(?:0|[1-2](?:\.\d{1,2})?)$/', $value) === 1) return true;
        return preg_match('/^(?:0|\d{1,3}(?:\.\d{1,3})?(?:px|rem|em|%|vw|vh))$/', $value) === 1;
    }

    private static function safeCssValue(string $value): bool
    {
        $value = trim($value);
        if ($value === '' || strlen($value) > 180 || preg_match('/[{};<>]/', $value) === 1) return false;
        return preg_match('/^[#(),.%\-\s\w]+$/u', $value) === 1;
    }

    private static function luminance(string $hex): float
    {
        $hex = ltrim($hex, '#');
        $parts = [hexdec(substr($hex,0,2))/255, hexdec(substr($hex,2,2))/255, hexdec(substr($hex,4,2))/255];
        $parts = array_map(static fn(float $v): float => $v <= .03928 ? $v/12.92 : (($v+.055)/1.055) ** 2.4, $parts);
        return .2126*$parts[0]+.7152*$parts[1]+.0722*$parts[2];
    }

    private static function number(float $value): string
    {
        $formatted = number_format($value, 3, '.', '');
        return rtrim(rtrim($formatted, '0'), '.') ?: '0';
    }
}
