<?php

namespace FleekDash\Services\Theme;

defined('ABSPATH') || exit;

/**
 * ThemeStylesService
 *
 * Injects active palette CSS variables on the server so that every session
 * (even without localStorage) immediately uses the saved theme configuration.
 */
class ThemeStylesService
{
    private static ?ThemeStylesService $instance = null;

    private ThemeService $themeService;

    private const COLOR_MODE_STORAGE_KEY = 'fleekdash-dark-mode';

    private const COLOR_MODE_PREFERENCE_KEY = 'fleekdash-color-mode-preference';

    public function __construct(?ThemeService $themeService = null)
    {
        $this->themeService = $themeService ?? new ThemeService();
    }

    public static function init(): void
    {
        if (self::$instance instanceof self) {
            return;
        }

        self::$instance = new self();
        self::$instance->registerHooks();
    }

    private function registerHooks(): void
    {
        \add_action('admin_enqueue_scripts', [$this, 'maybeScheduleInjection'], 1);
    }

    /**
     * Ensure styles are injected only on FleekDash surfaces.
     */
    public function maybeScheduleInjection(string $hook): void
    {
        if (!$this->shouldInjectForRequest($hook)) {
            return;
        }

        \add_action('admin_head', [$this, 'injectDarkModeBootstrap'], 0);
        \add_action('admin_head', [$this, 'injectActivePaletteVariables'], 1);
        \add_filter('admin_body_class', [$this, 'appendBodyClasses'], 20);
    }

    public function injectDarkModeBootstrap(): void
    {
        $script = <<<JS
        (function() {
            try {
                var root = document.documentElement;
                if (!root) { return; }
                var resolvedKey = '%s';
                var preferenceKey = '%s';
                var preference = null;
                var stored = null;
                try {
                    if (window.localStorage) {
                        preference = localStorage.getItem(preferenceKey);
                        stored = localStorage.getItem(resolvedKey);
                    }
                } catch (e) {
                    preference = null;
                    stored = null;
                }

                var applyDark = function(flag) {
                    if (flag) {
                        root.classList.add('dark');
                        root.classList.remove('light');
                        root.setAttribute('data-fleekdash-dark-mode', 'true');
                    } else {
                        root.classList.remove('dark');
                        root.classList.add('light');
                        root.setAttribute('data-fleekdash-dark-mode', 'false');
                    }
                };

                if (preference === 'dark' || stored === 'true') {
                    applyDark(true);
                    return;
                }

                if (preference === 'light' || stored === 'false') {
                    applyDark(false);
                    return;
                }

                var prefersDark = false;
                try {
                    prefersDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
                } catch (e) {
                    prefersDark = false;
                }

                applyDark(prefersDark);
                root.setAttribute('data-fleekdash-dark-mode', prefersDark ? 'system-dark' : 'system-light');
            } catch (error) {
                // Fail silently - React will correct during hydration
            }
        })();
        JS;

        printf(
            '<script id="fleekdash-dark-mode-bootstrap">%s</script>',
            sprintf(
                $script,
                \esc_js(self::COLOR_MODE_STORAGE_KEY),
                \esc_js(self::COLOR_MODE_PREFERENCE_KEY),
            )
        );
    }

    public function injectActivePaletteVariables(): void
    {
        $modes = $this->resolveActivePaletteModes();
        if ($modes === null) {
            return;
        }

        $style = $this->buildStyleBlock($modes);
        if ($style === '') {
            return;
        }

        echo '<style id="fleekdash-active-palette-vars">' . $style . '</style>';
    }

    /**
     * Get the palette CSS variables as a string (for login page or other public pages).
     *
     * @return string CSS string with palette variables
     */
    public function getPaletteCss(): string
    {
        $modes = $this->resolveActivePaletteModes();
        if (!$modes) {
            return '';
        }

        return $this->buildStyleBlock($modes);
    }

    /**
     * Append useful classes for debugging (e.g., fleekdash-theme-starter).
     */
    public function appendBodyClasses(string $classes): string
    {
        $settings = $this->themeService->getActiveThemeSettings();
        $themeId = \sanitize_key($settings['theme'] ?? '');
        $paletteId = \sanitize_key($settings['palette'] ?? '');

        if ($themeId !== '') {
            $classes .= ' fleekdash-theme-' . $themeId;
        }

        if ($paletteId !== '') {
            $classes .= ' fleekdash-palette-' . $paletteId;
        }

        return $classes;
    }

    /**
     * Retrieve merged light/dark CSS variable sets for the active palette.
     *
     * @return array{light: array<string, string>, dark: array<string, string>}|null
     */
    private function resolveActivePaletteModes(): ?array
    {
        $settings = $this->themeService->getActiveThemeSettings();
        $paletteId = $settings['palette'] ?? '';

        if ($paletteId === '') {
            return null;
        }

        $palette = $this->themeService->getPalette($paletteId);
        if (!$palette) {
            return null;
        }

        $preset = PalettePresets::getModes($paletteId);
        $light = $this->mergeModeValues($preset['light'] ?? [], $palette['modes']['light'] ?? null);
        $dark = $this->mergeModeValues($preset['dark'] ?? [], $palette['modes']['dark'] ?? null);

        return [
            'light' => $light,
            'dark' => $dark,
        ];
    }

    /**
     * Merge default preset values with stored overrides.
     *
     * @param array<string, string> $preset
     * @param mixed $override
     *
     * @return array<string, string>
     */
    private function mergeModeValues(array $preset, $override): array
    {
        if (!is_array($override)) {
            $override = [];
        }

        // CRITICAL: Start with preset, then override with saved values
        // This ensures saved values (including custom images/colors) take priority
        $merged = array_merge($preset, $override);

        // Filter out auto-generated variables that should not be overridden
        $autoGeneratedVars = ['--background-2', '--background-5', '--background-10', '--background-20', '--background-alpha-50', '--card-2', '--card-5', '--card-10', '--card-20', '--card-50', '--border-alpha-50'];

        $filtered = array_filter(
            $merged,
            static function ($value, $key) use ($autoGeneratedVars) {
                if (!is_string($value) || $value === '') {
                    return false;
                }

                $normalizedKey = strtolower(trim($key));
                if (strpos($normalizedKey, '--') !== 0) {
                    $normalizedKey = '--' . ltrim($normalizedKey, '-');
                }

                // Remove auto-generated variables
                return !in_array($normalizedKey, $autoGeneratedVars, true);
            },
            ARRAY_FILTER_USE_BOTH
        );

        // CRITICAL: Normalize --main-gradient if present (convert URLs to url() format)
        if (isset($filtered['--main-gradient'])) {
            $filtered['--main-gradient'] = $this->normalizeMainGradientValue($filtered['--main-gradient']);
        }

        return $filtered;
    }

    /**
     * Build final CSS string applied to the document.
     *
     * @param array{light: array<string, string>, dark: array<string, string>} $modes
     */
    private function buildStyleBlock(array $modes): string
    {
        $sections = [];
        $light = $this->buildDeclarations($modes['light'] ?? []);
        $dark = $this->buildDeclarations($modes['dark'] ?? []);
        $aliasDeclarations = $this->buildModeAliasDeclarations($modes);

        if ($light !== '' || $aliasDeclarations !== '') {
            $sections[] = ':root {' . $light . $aliasDeclarations . '}';
        }

        if ($light !== '') {
            $sections[] = 'html {' . $light . '}';
            $sections[] = 'html body {' . $light . '}';
        }

        if ($dark !== '') {
            $sections[] = 'html.dark {' . $dark . '}';
            $sections[] = 'html.dark body {' . $dark . '}';
        }

        return implode('', $sections);
    }

    /**
     * Normalize --main-gradient value for CSS usage.
     * Converts image URLs to url() format and preserves gradients/colors.
     *
     * @param string $value The raw gradient/image/color value
     * @return string Normalized value ready for CSS, or empty string if invalid
     */
    private function normalizeMainGradientValue(string $value): string
    {
        $trimmed = trim($value);
        $normalized = '';

        if ($trimmed !== '') {
            $alreadyNormalized = strpos($trimmed, 'url(') === 0 || strpos($trimmed, 'gradient') !== false;
            $isHttpUrl = strpos($trimmed, 'http://') === 0 || strpos($trimmed, 'https://') === 0;

            if ($alreadyNormalized) {
                $normalized = $trimmed;
            } elseif ($isHttpUrl) {
                $normalized = 'url(' . $trimmed . ')';
            } else {
                // For solid colors or other values, return as-is
                $normalized = $trimmed;
            }
        }

        return $normalized;
    }

    /**
     * Convert CSS variable array to declarations.
     *
     * @param array<string, string> $variables
     */
    private function buildDeclarations(array $variables): string
    {
        if (empty($variables)) {
            return '';
        }

        $parts = [];

        foreach ($variables as $name => $value) {
            if (!is_string($name) || !is_string($value) || $value === '') {
                continue;
            }

            $normalizedName = strtolower(trim($name));
            if (strpos($normalizedName, '--') !== 0) {
                $normalizedName = '--' . ltrim($normalizedName, '-');
            }

            $normalizedName = preg_replace('/[^a-z0-9\-\_]/', '', $normalizedName);
            if ($normalizedName === '' || strpos($normalizedName, '--') !== 0) {
                continue;
            }

            // Normalize --main-gradient value (convert URLs to url() format)
            $normalizedValue = $value;
            if ($normalizedName === '--main-gradient') {
                $normalized = $this->normalizeMainGradientValue($value);
                if ($normalized === '') {
                    continue; // Skip invalid values
                }
                $normalizedValue = $normalized;
            }

            $parts[] = $normalizedName . ': ' . \esc_attr($normalizedValue) . ';';
        }

        return implode(' ', $parts);
    }

    /**
     * Build alias declarations for palette modes.
     *
     * @param array<string, array<string, string>> $modes
     */
    private function buildModeAliasDeclarations(array $modes): string
    {
        $parts = [];

        foreach ($modes as $mode => $variables) {
            $this->appendModeAliasDeclarations($parts, $mode, $variables);
        }

        return implode(' ', $parts);
    }

    /**
     * @param array<int, string> $parts
     * @param array<string, mixed> $variables
     */
    private function appendModeAliasDeclarations(array &$parts, string|int $mode, array $variables): void
    {
        $modeKey = \sanitize_key((string) $mode);
        if ($modeKey === '') {
            return;
        }

        foreach ($variables as $name => $value) {
            $declaration = $this->buildAliasDeclaration($modeKey, $name, $value);
            if ($declaration !== null) {
                $parts[] = $declaration;
            }
        }

        $this->appendDarkPopoverOverrides($parts, $modeKey, $variables);
    }

    /**
     * @param mixed $name
     * @param mixed $value
     */
    private function buildAliasDeclaration(string $modeKey, $name, $value): ?string
    {
        $normalizedName = $this->normalizeAliasVariableName($name);
        if (
            $normalizedName === null ||
            $normalizedName === 'popover' ||
            $normalizedName === 'popover-foreground'
        ) {
            return null;
        }

        $normalizedValue = $this->normalizeAliasVariableValue($normalizedName, $value);
        if ($normalizedValue === null) {
            return null;
        }

        $aliasName = '--palette-' . $modeKey . '-' . $normalizedName;

        return $aliasName . ': ' . \esc_attr($normalizedValue) . ';';
    }

    /**
     * @param mixed $name
     */
    private function normalizeAliasVariableName($name): ?string
    {
        if (!is_string($name)) {
            return null;
        }

        $normalizedName = strtolower(trim($name));
        if (strpos($normalizedName, '--') === 0) {
            $normalizedName = substr($normalizedName, 2);
        } else {
            $normalizedName = ltrim($normalizedName, '-');
        }

        return $normalizedName !== '' ? $normalizedName : null;
    }

    /**
     * @param mixed $value
     */
    private function normalizeAliasVariableValue(string $normalizedName, $value): ?string
    {
        if (!is_string($value) || $value === '') {
            return null;
        }

        if ($normalizedName !== 'main-gradient') {
            return $value;
        }

        $normalized = $this->normalizeMainGradientValue($value);

        return $normalized !== '' ? $normalized : null;
    }

    /**
     * @param array<int, string> $parts
     * @param array<string, mixed> $variables
     */
    private function appendDarkPopoverOverrides(array &$parts, string $modeKey, array $variables): void
    {
        // CRITICAL: Always override popover aliases with card values for local dark mode
        // This ensures popover inherits from the customized card color, not preset
        if ($modeKey !== 'dark') {
            return;
        }

        $cardValue = $variables['--card'] ?? null;
        $cardForegroundValue = $variables['--card-foreground'] ?? null;

        if (is_string($cardValue) && $cardValue !== '') {
            $parts[] = '--palette-dark-popover: ' . \esc_attr($cardValue) . ';';
        }

        if (is_string($cardForegroundValue) && $cardForegroundValue !== '') {
            $parts[] = '--palette-dark-popover-foreground: ' . \esc_attr($cardForegroundValue) . ';';
        }
    }

    private function shouldInjectForRequest(string $hook): bool
    {
        if (strpos($hook, 'fleekdash') !== false) {
            return true;
        }

        $page = isset($_GET['page']) ? \sanitize_key((string) $_GET['page']) : '';
        if ($page !== '' && strpos($page, 'fleekdash') === 0) {
            return true;
        }

        /**
         * Allow integrations to force palette injection when needed.
         */
        return (bool) \apply_filters('fleekdash_force_theme_variables', false);
    }
}
