<?php
declare(strict_types=1);

namespace ROITheme\Shared\Infrastructure\Services;

use ROITheme\Shared\Domain\Contracts\CSSGeneratorInterface;

/**
 * CriticalCSSService
 *
 * Genera CSS crítico para componentes above-the-fold (LCP).
 *
 * RESPONSABILIDAD:
 * - Consultar BD para obtener componentes con is_critical=true
 * - Generar CSS usando los Renderers públicos (sin duplicar lógica)
 * - Inyectar CSS en <head> via wp_head (priority 1)
 *
 * FLUJO:
 * 1. wp_head (priority 1) → render()
 * 2. Consulta BD: componentes con visibility.is_critical = true
 * 3. Para cada componente: obtiene datos y llama Renderer->generateCSS()
 * 4. Output: <style id="roi-critical-css">...</style>
 *
 * IMPORTANTE:
 * - Este servicio se ejecuta ANTES de que los componentes rendericen
 * - Los Renderers detectan is_critical y omiten CSS inline (ya está en <head>)
 *
 * @package ROITheme\Shared\Infrastructure\Services
 */
final class CriticalCSSService
{
    /**
     * Instancia singleton
     */
    private static ?self $instance = null;

    /**
     * Tabla de configuraciones
     */
    private string $tableName;

    /**
     * Cache de instancias de renderers
     * @var array<string, object>
     */
    private array $rendererInstances = [];

    /**
     * Mapa de componentes críticos y sus clases Renderer
     * @var array<string, string>
     */
    private const CRITICAL_RENDERERS = [
        'top-notification-bar' => \ROITheme\Public\TopNotificationBar\Infrastructure\Ui\TopNotificationBarRenderer::class,
        'navbar' => \ROITheme\Public\Navbar\Infrastructure\Ui\NavbarRenderer::class,
        'cta-lets-talk' => \ROITheme\Public\CtaLetsTalk\Infrastructure\Ui\CtaLetsTalkRenderer::class,        
        'hero' => \ROITheme\Public\Hero\Infrastructure\Ui\HeroRenderer::class,
        'featured-image' => \ROITheme\Public\FeaturedImage\Infrastructure\Ui\FeaturedImageRenderer::class,
        'table-of-contents' => \ROITheme\Public\TableOfContents\Infrastructure\Ui\TableOfContentsRenderer::class
    ];

    /**
     * Constructor privado (singleton)
     */
    private function __construct(
        private \wpdb $wpdb,
        private CSSGeneratorInterface $cssGenerator
    ) {
        $this->tableName = $this->wpdb->prefix . 'roi_theme_component_settings';
    }

    /**
     * Obtiene la instancia singleton
     *
     * @param \wpdb $wpdb Instancia de WordPress Database
     * @param CSSGeneratorInterface $cssGenerator Generador de CSS
     * @return self
     */
    public static function getInstance(\wpdb $wpdb, CSSGeneratorInterface $cssGenerator): self
    {
        if (self::$instance === null) {
            self::$instance = new self($wpdb, $cssGenerator);
        }
        return self::$instance;
    }

    /**
     * Renderiza CSS crítico en wp_head
     *
     * Este método se llama desde wp_head con priority 1 (muy temprano).
     * Genera y outputea el CSS de todos los componentes marcados como críticos.
     *
     * @return void
     */
    public function render(): void
    {
        $criticalComponents = $this->getCriticalComponents();

        if (empty($criticalComponents)) {
            return;
        }

        $allCSS = [];

        foreach ($criticalComponents as $componentName) {
            $css = $this->generateComponentCSS($componentName);
            if (!empty($css)) {
                $allCSS[] = "/* {$componentName} */\n" . $css;
            }
        }

        if (empty($allCSS)) {
            return;
        }

        $combinedCSS = implode("\n\n", $allCSS);

        printf(
            '<style id="roi-critical-css">%s</style>' . "\n",
            $combinedCSS
        );
    }

    /**
     * Obtiene lista de componentes con is_critical=true
     *
     * Consulta la BD para encontrar qué componentes tienen
     * visibility.is_critical = 1 (true)
     *
     * @return array<string> Nombres de componentes críticos
     */
    private function getCriticalComponents(): array
    {
        $sql = $this->wpdb->prepare(
            "SELECT DISTINCT component_name
             FROM {$this->tableName}
             WHERE group_name = %s
               AND attribute_name = %s
               AND attribute_value = %s",
            'visibility',
            'is_critical',
            '1'
        );

        $rows = $this->wpdb->get_col($sql);

        return $rows ?: [];
    }

    /**
     * Genera CSS para un componente específico
     *
     * @param string $componentName Nombre del componente (kebab-case)
     * @return string CSS generado o string vacío
     */
    private function generateComponentCSS(string $componentName): string
    {
        // Verificar que el componente tenga Renderer definido
        if (!isset(self::CRITICAL_RENDERERS[$componentName])) {
            return '';
        }

        // Obtener datos del componente desde BD
        $data = $this->getComponentData($componentName);

        if (empty($data)) {
            return '';
        }

        // Verificar que esté habilitado
        if (!($data['visibility']['is_enabled'] ?? false)) {
            return '';
        }

        // Obtener o crear instancia del Renderer
        $renderer = $this->getRendererInstance($componentName);

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

        // Llamar al método público generateCSS() del Renderer
        return $renderer->generateCSS($data);
    }

    /**
     * Obtiene datos del componente desde BD
     *
     * @param string $componentName Nombre del componente
     * @return array<string, array<string, mixed>> Datos agrupados
     */
    private function getComponentData(string $componentName): array
    {
        $sql = $this->wpdb->prepare(
            "SELECT group_name, attribute_name, attribute_value
             FROM {$this->tableName}
             WHERE component_name = %s
             ORDER BY group_name, attribute_name",
            $componentName
        );

        $rows = $this->wpdb->get_results($sql, ARRAY_A);

        if (empty($rows)) {
            return [];
        }

        // Agrupar por grupo
        $settings = [];
        foreach ($rows as $row) {
            $groupName = $row['group_name'];
            $attributeName = $row['attribute_name'];
            $value = $this->unserializeValue($row['attribute_value']);

            if (!isset($settings[$groupName])) {
                $settings[$groupName] = [];
            }

            $settings[$groupName][$attributeName] = $value;
        }

        return $settings;
    }

    /**
     * Obtiene o crea instancia del Renderer
     *
     * @param string $componentName Nombre del componente
     * @return object|null Instancia del Renderer o null
     */
    private function getRendererInstance(string $componentName): ?object
    {
        // Usar cache si ya existe
        if (isset($this->rendererInstances[$componentName])) {
            return $this->rendererInstances[$componentName];
        }

        $rendererClass = self::CRITICAL_RENDERERS[$componentName] ?? null;

        if ($rendererClass === null || !class_exists($rendererClass)) {
            return null;
        }

        // Crear instancia inyectando CSSGeneratorInterface
        $this->rendererInstances[$componentName] = new $rendererClass($this->cssGenerator);

        return $this->rendererInstances[$componentName];
    }

    /**
     * Deserializa un valor desde la BD
     *
     * @param string $value Valor serializado
     * @return mixed Valor deserializado
     */
    private function unserializeValue(string $value): mixed
    {
        // Intentar decodificar JSON
        if (str_starts_with($value, '{') || str_starts_with($value, '[')) {
            $decoded = json_decode($value, true);
            if (json_last_error() === JSON_ERROR_NONE) {
                return $decoded;
            }
        }

        // Convertir booleanos
        if ($value === '1' || $value === '0') {
            return $value === '1';
        }

        return $value;
    }
}
