<?php

namespace App\Helpers;

use App\Models\SystemSetting;
use Illuminate\Support\Facades\Cache;

/**
 * Theme Studio — single source of truth for runtime-customisable colors.
 *
 * Everything that renders a color anywhere in the platform (frontend
 * marketing pages, admin panel, user dashboard, buttons, links, alerts)
 * ultimately resolves to one of the CSS custom properties this helper
 * emits. The admin Theme Studio page writes the JSON blob stored under
 * `system_settings.theme_config`; layouts read it back via renderCss()
 * which prints a single <style> block in <head>.
 *
 * Why a JSON blob instead of 28 individual rows:
 * - One DB read, one cache key — page load cost is constant regardless
 *   of how many tokens we add later.
 * - Atomic save: half-applied themes can't happen mid-write.
 * - Easy import/export of theme presets later (just dump/restore JSON).
 *
 * Why CSS variables (not Tailwind config):
 * - Tailwind config bakes at build time. Customers don't run npm build.
 * - CSS vars override at runtime — admin saves, refresh, themed.
 * - .dark class scoping gives us light/dark for free.
 */
class ThemeHelper
{
    /**
     * Cache key for the resolved theme config. Cleared on save/reset.
     */
    protected const CACHE_KEY = 'theme_studio_config_v1';
    protected const CACHE_TTL = 3600;

    /**
     * Default palette for both modes. These match the values that were
     * previously hardcoded in resources/css/app.css before Theme Studio
     * shipped, so a fresh install with no saved config looks identical
     * to the old static theme.
     *
     * Token name → meaning:
     *   primary        — main brand color (buttons, links, focused states)
     *   primary_strong — hover / pressed / emphasis (≈ primary darker by ~10%)
     *   secondary      — secondary brand (subtle accents, badges)
     *   accent         — highlights, gradients, decorative elements
     *   success        — green confirmations
     *   warning        — amber warnings
     *   danger         — red destructive / errors
     *   info           — blue informational
     *   surface        — page background
     *   surface_2      — card / panel background (one layer up)
     *   ink            — primary text color (high contrast on surface)
     *   muted          — secondary text color (labels, captions)
     *   border         — divider / outline color
     */
    public static function getDefaults(): array
    {
        return [
            'light' => [
                'primary'        => '#6366F1',
                'primary_strong' => '#4F46E5',
                'secondary'      => '#8B5CF6',
                'accent'         => '#B1AAFA',
                'success'        => '#10B981',
                'warning'        => '#F59E0B',
                'danger'         => '#EF4444',
                'info'           => '#2563EB',
                'surface'        => '#F8F9FC',
                'surface_2'      => '#FFFFFF',
                'ink'            => '#1E293B',
                'muted'          => '#9CA3AF',
                'border'         => '#E5E7EB',
            ],
            'dark' => [
                'primary'        => '#818CF8',
                'primary_strong' => '#6366F1',
                'secondary'      => '#A78BFA',
                'accent'         => '#B1AAFA',
                'success'        => '#34D399',
                'warning'        => '#FBBF24',
                'danger'         => '#F87171',
                'info'           => '#60A5FA',
                'surface'        => '#0F1117',
                'surface_2'      => '#1A1D27',
                'ink'            => '#E5E7EB',
                'muted'          => '#9CA3AF',
                'border'         => '#2D3039',
            ],
        ];
    }

    /**
     * Return the active config: saved JSON merged on top of defaults so
     * partial saves (only `light.primary` set) still produce a complete,
     * valid theme.
     *
     * Cached for 1 hour — invalidated immediately on save/reset.
     */
    public static function getConfig(): array
    {
        return Cache::remember(self::CACHE_KEY, self::CACHE_TTL, function () {
            $defaults = self::getDefaults();

            try {
                $raw = SystemSetting::get('theme_config', '');
            } catch (\Throwable $e) {
                return $defaults;
            }

            if ($raw === '') {
                return $defaults;
            }

            $saved = json_decode($raw, true);
            if (!is_array($saved)) {
                return $defaults;
            }

            // Per-mode merge — saved keys win, missing keys fall back to default
            return [
                'light' => array_merge($defaults['light'], $saved['light'] ?? []),
                'dark'  => array_merge($defaults['dark'],  $saved['dark']  ?? []),
            ];
        });
    }

    /**
     * Persist the config and bust the cache so the next page load shows
     * the new theme without waiting for TTL expiry.
     *
     * Only known token keys are accepted — anything else is silently
     * dropped so a malformed payload can't pollute the saved JSON.
     */
    public static function save(array $config): void
    {
        $defaults = self::getDefaults();
        $allowed  = array_keys($defaults['light']);

        $clean = ['light' => [], 'dark' => []];
        foreach (['light', 'dark'] as $mode) {
            foreach ($allowed as $key) {
                if (isset($config[$mode][$key]) && self::isHex((string) $config[$mode][$key])) {
                    $clean[$mode][$key] = strtoupper((string) $config[$mode][$key]);
                }
            }
        }

        SystemSetting::set('theme_config', json_encode($clean), 'theme');
        Cache::forget(self::CACHE_KEY);
    }

    /**
     * Restore the defaults by clearing the saved row.
     */
    public static function reset(): void
    {
        SystemSetting::set('theme_config', '', 'theme');
        Cache::forget(self::CACHE_KEY);
    }

    /**
     * Render the runtime <style> block that overrides app.css defaults.
     *
     * Layouts include this once in <head>. The output is intentionally
     * minimal (no whitespace beyond what aids debugging) because it ships
     * on every page render and we don't want to bloat HTML payloads.
     *
     * Strategy:
     *  - :root sets light-mode vars (Tailwind reads these via @theme alias)
     *  - .dark, [data-theme="dark"] override the same vars when the dark
     *    class is on <html>. Same scoping app.css already uses, so the
     *    cascade order is unsurprising.
     *
     * We also derive a few aliased vars (--color-brand, --color-primary)
     * because parts of the codebase reference both names — keeping them
     * in sync here avoids a 200-grep refactor.
     */
    public static function renderCss(): string
    {
        $cfg = self::getConfig();
        $light = $cfg['light'];
        $dark  = $cfg['dark'];

        $emit = function (array $c): string {
            return implode('', [
                '--color-primary:'        . $c['primary']        . ';',
                '--color-primary-strong:' . $c['primary_strong'] . ';',
                '--color-secondary:'      . $c['secondary']      . ';',
                '--color-accent:'         . $c['accent']         . ';',
                '--color-success:'        . $c['success']        . ';',
                '--color-warning:'        . $c['warning']        . ';',
                '--color-danger:'         . $c['danger']         . ';',
                '--color-error:'          . $c['danger']         . ';',
                '--color-info:'           . $c['info']           . ';',
                '--color-surface:'        . $c['surface']        . ';',
                '--color-surface-2:'      . $c['surface_2']      . ';',
                '--color-ink:'            . $c['ink']            . ';',
                '--color-muted:'          . $c['muted']          . ';',
                '--color-border:'         . $c['border']         . ';',
                // Aliases so legacy class names keep resolving
                '--color-brand:'          . $c['primary']        . ';',
                '--color-brand-strong:'   . $c['primary_strong'] . ';',
                '--color-base-100:'       . $c['surface_2']      . ';',
                '--color-base-200:'       . $c['surface']        . ';',
                '--color-base-300:'       . $c['border']         . ';',
                '--color-base-content:'   . $c['ink']            . ';',
            ]);
        };

        return '<style id="theme-studio-vars">'
            . ':root{' . $emit($light) . '}'
            . '.dark,[data-theme="dark"]{' . $emit($dark) . '}'
            . '</style>';
    }

    /**
     * Validator — only #RGB or #RRGGBB allowed. Keeps the saved JSON
     * clean so renderCss() can interpolate values directly without
     * escaping (no quotes, no parens, no script injection surface).
     */
    protected static function isHex(string $v): bool
    {
        return (bool) preg_match('/^#([0-9a-f]{3}|[0-9a-f]{6})$/i', $v);
    }
}
