<?php

namespace FleekDash\Services\Theme;

use InvalidArgumentException;

defined('ABSPATH') || exit;

/**
 * ThemeService
 *
 * Facade combining bundled registry metadata and custom storage.
 */
class ThemeService
{
    private ThemeStorage $storage;

    private ThemePresentation $presentation;

    /**
     * @var array<string, ?array<string, mixed>>
     */
    private array $themeFormattedCache = [];

    /**
     * @var array<string, ?array<string, mixed>>
     */
    private array $paletteFormattedCache = [];

    public function __construct(?ThemeStorage $storage = null)
    {
        $this->storage = $storage ?? new ThemeStorage();
        $this->presentation = new ThemePresentation($this->storage);
    }

    /**
     * Get active theme settings (theme + palette).
     *
     * @return array{theme: string, palette: string}
     */
    public function getActiveThemeSettings(): array
    {
        $settings = $this->storage->getActiveThemeSettings();
        $themeId = \sanitize_key($settings['theme'] ?? '');

        $theme = $this->getTheme($themeId);
        if (!$theme) {
            $themeId = 'starter';
            $theme = $this->getTheme($themeId);
        }

        if (!$theme) {
            return [
                'theme' => 'starter',
                'palette' => 'starter-fresh',
            ];
        }

        $paletteId = \sanitize_key($settings['palette'] ?? '');
        $belongs = $this->presentation->paletteBelongsToTheme(
            $paletteId,
            $themeId,
            fn (string $id) => $this->getTheme($id),
            fn (string $id) => $this->getPalette($id),
        );

        if ($paletteId === '' || !$belongs) {
            $paletteId = $this->presentation->resolveDefaultPaletteForTheme(
                $themeId,
                fn (string $id) => $this->getTheme($id),
            );
        }

        return [
            'theme' => $themeId,
            'palette' => $paletteId,
        ];
    }

    /**
     * Active settings plus formatted theme and palette (single resolution path for REST).
     *
     * @return array{settings: array{theme: string, palette: string}, theme: ?array<string, mixed>, palette: ?array<string, mixed>}
     */
    public function getActiveThemeContext(): array
    {
        $settings = $this->getActiveThemeSettings();

        return [
            'settings' => $settings,
            'theme' => $this->getTheme($settings['theme']),
            'palette' => $this->getPalette($settings['palette']),
        ];
    }

    /**
     * Apply active theme and palette.
     *
     * @return array<string, mixed>
     */
    public function applyActiveTheme(string $themeId, ?string $paletteId = null): array
    {
        $themeId = \sanitize_key($themeId);

        $theme = $this->getTheme($themeId);
        if (!$theme) {
            throw new InvalidArgumentException(__('Theme not found.', 'fleekdash'));
        }

        $targetPaletteId = \sanitize_key($paletteId ?? '');
        $belongs = $this->presentation->paletteBelongsToTheme(
            $targetPaletteId,
            $themeId,
            fn (string $id) => $this->getTheme($id),
            fn (string $id) => $this->getPalette($id),
        );

        if ($targetPaletteId === '' || !$belongs) {
            $targetPaletteId = $this->presentation->resolveDefaultPaletteForTheme(
                $themeId,
                fn (string $id) => $this->getTheme($id),
            );
        }

        $this->storage->setActiveThemeSettings($themeId, $targetPaletteId);

        return [
            'theme' => $themeId,
            'palette' => $targetPaletteId,
            'theme_data' => $theme,
            'palette_data' => $this->getPalette($targetPaletteId),
        ];
    }

    /**
     * Get all themes (bundled + custom).
     *
     * @return array<int, array<string, mixed>>
     */
    public function getThemes(): array
    {
        $customPalettes = $this->storage->getCustomPalettes();
        $customByBundledId = [];
        foreach ($customPalettes as $row) {
            $tid = \sanitize_key($row['theme'] ?? '');
            if ($tid === '') {
                continue;
            }
            if (!isset($customByBundledId[$tid])) {
                $customByBundledId[$tid] = [];
            }
            $customByBundledId[$tid][] = $row;
        }

        $themes = [];
        foreach (ThemeRegistry::getBundledThemes() as $theme) {
            $tid = \sanitize_key($theme['id'] ?? '');
            if ($tid === '') {
                continue;
            }
            $extras = $customByBundledId[$tid] ?? [];
            $themes[] = $this->presentation->formatBundledTheme($theme, $extras);
        }

        foreach ($this->storage->getCustomThemes() as $theme) {
            $themes[] = $this->presentation->formatCustomTheme(
                $theme,
                fn (string $id) => $this->getPalette($id),
            );
        }

        return $themes;
    }

    /**
     * Payload for GET /themes (themes list + active settings).
     *
     * @return array{themes: array<int, array<string, mixed>>, active: array{theme: string, palette: string}}
     */
    public function getThemesListPayload(): array
    {
        return [
            'themes' => $this->getThemes(),
            'active' => $this->getActiveThemeSettings(),
        ];
    }

    /**
     * Get a theme by ID.
     */
    public function getTheme(string $themeId): ?array
    {
        $k = \sanitize_key($themeId);
        if ($k === '') {
            return null;
        }

        if (\array_key_exists($k, $this->themeFormattedCache)) {
            return $this->themeFormattedCache[$k];
        }

        $resolved = $this->resolveThemeFormatted($k);
        $this->themeFormattedCache[$k] = $resolved;

        return $resolved;
    }

    /**
     * @return ?array<string, mixed>
     */
    private function resolveThemeFormatted(string $themeId): ?array
    {
        $bundled = ThemeRegistry::getBundledTheme($themeId);
        if ($bundled) {
            return $this->presentation->formatBundledTheme($bundled, null);
        }

        $custom = $this->storage->getCustomTheme($themeId);
        if ($custom) {
            return $this->presentation->formatCustomTheme(
                $custom,
                fn (string $id) => $this->getPalette($id),
            );
        }

        return null;
    }

    /**
     * Persist a custom theme (create or update).
     *
     * @param array<string, mixed> $payload
     */
    public function saveCustomTheme(array $payload): array
    {
        $themeId = \sanitize_key($payload['id'] ?? '');
        if ($themeId === '') {
            throw new InvalidArgumentException('Theme ID is required.');
        }

        if ($this->isBundledTheme($themeId)) {
            throw new InvalidArgumentException('Bundled themes cannot be modified.');
        }

        $defaultPalette = \sanitize_key($payload['default_palette'] ?? '');
        $palettes = $this->presentation->sanitizePaletteIds($payload['palettes'] ?? []);

        if ($defaultPalette !== '') {
            $palettes[] = $defaultPalette;
        }

        $palettes = array_values(array_unique($palettes));

        $validPaletteIds = $this->buildExistingPaletteIdLookup();
        foreach ($palettes as $paletteId) {
            if (!isset($validPaletteIds[$paletteId])) {
                throw new InvalidArgumentException(sprintf('Palette "%s" does not exist.', $paletteId));
            }
        }

        $payload['id'] = $themeId;
        $payload['palettes'] = $palettes;
        $payload['default_palette'] = $defaultPalette ?: ($palettes[0] ?? '');
        $payload['updated_at'] = \current_time('mysql');

        $existing = $this->storage->getCustomTheme($themeId);
        if (!$existing) {
            $payload['created_at'] = \current_time('mysql');
        } else {
            $payload['created_at'] = $existing['created_at'] ?? \current_time('mysql');
        }

        $this->storage->saveCustomTheme($payload);

        return $this->presentation->formatCustomTheme(
            $this->storage->getCustomTheme($themeId) ?? [],
            fn (string $id) => $this->getPalette($id),
        );
    }

    /**
     * @return array<string, true>
     */
    private function buildExistingPaletteIdLookup(): array
    {
        $lookup = [];
        foreach (ThemeRegistry::getBundledPalettes() as $row) {
            $id = \sanitize_key($row['id'] ?? '');
            if ($id !== '') {
                $lookup[$id] = true;
            }
        }
        foreach ($this->storage->getCustomPalettes() as $row) {
            $id = \sanitize_key($row['id'] ?? '');
            if ($id !== '') {
                $lookup[$id] = true;
            }
        }

        return $lookup;
    }

    /**
     * Delete custom theme.
     */
    public function deleteCustomTheme(string $themeId): void
    {
        $themeId = \sanitize_key($themeId);
        if ($themeId === '' || $this->isBundledTheme($themeId)) {
            throw new InvalidArgumentException('Invalid theme ID.');
        }

        $this->storage->deleteCustomTheme($themeId);
    }

    /**
     * Get all palettes (bundled + custom).
     *
     * @return array<int, array<string, mixed>>
     */
    public function getPalettes(): array
    {
        $palettes = [];

        foreach (ThemeRegistry::getBundledPalettes() as $palette) {
            $palettes[] = $this->presentation->formatBundledPalette($palette);
        }

        foreach ($this->storage->getCustomPalettes() as $palette) {
            $palettes[] = $this->presentation->formatCustomPalette($palette);
        }

        return $palettes;
    }

    /**
     * Get palette by ID.
     */
    public function getPalette(string $paletteId): ?array
    {
        $k = \sanitize_key($paletteId);
        if ($k === '') {
            return null;
        }

        if (\array_key_exists($k, $this->paletteFormattedCache)) {
            return $this->paletteFormattedCache[$k];
        }

        $resolved = $this->resolvePaletteFormatted($k);
        $this->paletteFormattedCache[$k] = $resolved;

        return $resolved;
    }

    /**
     * @return ?array<string, mixed>
     */
    private function resolvePaletteFormatted(string $paletteId): ?array
    {
        $bundled = ThemeRegistry::getBundledPalette($paletteId);
        if ($bundled) {
            return $this->presentation->formatBundledPalette($bundled);
        }

        $custom = $this->storage->getCustomPalette($paletteId);
        if ($custom) {
            return $this->presentation->formatCustomPalette($custom);
        }

        return null;
    }

    /**
     * Persist a custom palette.
     *
     * @param array<string, mixed> $payload
     */
    public function saveCustomPalette(array $payload): array
    {
        $paletteId = \sanitize_key($payload['id'] ?? '');
        if ($paletteId === '') {
            throw new InvalidArgumentException('Palette ID is required.');
        }

        if ($this->isBundledPalette($paletteId)) {
            throw new InvalidArgumentException('Bundled palettes cannot be modified.');
        }

        $themeId = \sanitize_key($payload['theme'] ?? '');
        if ($themeId === '') {
            throw new InvalidArgumentException('Palette must be associated with a theme.');
        }

        if (!$this->getTheme($themeId)) {
            throw new InvalidArgumentException(sprintf('Theme "%s" does not exist.', $themeId));
        }

        $modes = $payload['modes'] ?? [];
        if (!is_array($modes) || empty($modes)) {
            throw new InvalidArgumentException('Palette modes are required.');
        }

        if (!isset($modes['light']) || !is_array($modes['light'])) {
            throw new InvalidArgumentException('Palette must define a light mode.');
        }

        if (!isset($modes['dark']) || !is_array($modes['dark'])) {
            throw new InvalidArgumentException('Palette must define a dark mode.');
        }

        $payload['id'] = $paletteId;
        $payload['theme'] = $themeId;
        $payload['source'] = 'custom';
        $payload['updated_at'] = \current_time('mysql');

        $existing = $this->storage->getCustomPalette($paletteId);
        if (!$existing) {
            $payload['created_at'] = \current_time('mysql');
        } else {
            $payload['created_at'] = $existing['created_at'] ?? \current_time('mysql');
        }

        $this->storage->saveCustomPalette($payload);

        return $this->presentation->formatCustomPalette(
            $this->storage->getCustomPalette($paletteId) ?? [],
        );
    }

    /**
     * Delete custom palette (if not used).
     */
    public function deleteCustomPalette(string $paletteId): void
    {
        $paletteId = \sanitize_key($paletteId);
        if ($paletteId === '' || $this->isBundledPalette($paletteId)) {
            throw new InvalidArgumentException('Invalid palette ID.');
        }

        if ($this->storage->isPaletteInUse($paletteId)) {
            throw new InvalidArgumentException('Palette is currently in use by a theme and cannot be deleted.');
        }

        $this->storage->deleteCustomPalette($paletteId);
    }

    /**
     * Check if theme ID belongs to bundled registry.
     */
    public function isBundledTheme(string $themeId): bool
    {
        return ThemeRegistry::getBundledTheme(\sanitize_key($themeId)) !== null;
    }

    /**
     * Check if palette ID belongs to bundled registry.
     */
    public function isBundledPalette(string $paletteId): bool
    {
        return ThemeRegistry::getBundledPalette(\sanitize_key($paletteId)) !== null;
    }

    /**
     * Determine if palette exists (bundled or custom).
     */
    public function paletteExists(string $paletteId): bool
    {
        $paletteId = \sanitize_key($paletteId);
        if ($paletteId === '') {
            return false;
        }

        return $this->isBundledPalette($paletteId)
            || $this->storage->getCustomPalette($paletteId) !== null;
    }

    /**
     * Clear internal caches.
     */
    public function clearCaches(): void
    {
        $this->themeFormattedCache = [];
        $this->paletteFormattedCache = [];
        $this->storage->clearThemeCache();
        $this->storage->clearPaletteCache();
    }
}
