<?php
declare(strict_types=1);

namespace ROITheme\Shared\Domain\ValueObjects;

use ROITheme\Shared\Domain\Exceptions\InvalidComponentException;

/**
 * ComponentConfiguration - Value Object inmutable para configuración de componente
 *
 * RESPONSABILIDAD: Representar la configuración completa de un componente agrupada por categorías
 *
 * REGLAS DE NEGOCIO:
 * - La configuración se organiza en grupos: visibility, content, styles, general
 * - Cada grupo contiene pares clave-valor
 * - Los valores pueden ser: string, boolean, integer, float, array
 * - Si existe cta_url debe existir cta_text (y viceversa)
 * - Los colores deben estar en formato hexadecimal válido
 *
 * INVARIANTES:
 * - Una vez creada, la configuración no puede cambiar (inmutable)
 * - La estructura de grupos siempre está presente (aunque vacía)
 * - Los tipos de datos se preservan correctamente
 *
 * ESTRUCTURA:
 * ```php
 * [
 *     'visibility' => [
 *         'enabled' => true,
 *         'visible_desktop' => true,
 *         'visible_mobile' => false
 *     ],
 *     'content' => [
 *         'message_text' => 'Welcome!',
 *         'cta_text' => 'Click here',
 *         'cta_url' => 'https://example.com'
 *     ],
 *     'styles' => [
 *         'background_color' => '#000000',
 *         'text_color' => '#ffffff',
 *         'height' => 50
 *     ],
 *     'general' => [
 *         'priority' => 10,
 *         'version' => '1.0.0'
 *     ]
 * ]
 * ```
 *
 * @package ROITheme\Shared\Domain\ValueObjects
 */
final readonly class ComponentConfiguration
{
    /**
     * Grupos de configuración válidos
     *
     * Basado en 10.00-flujo-de-trabajo-fuente-de-verdad.md líneas 304-348
     * 12 grupos estándar + grupos adicionales en uso
     */
    private const VALID_GROUPS = [
        // 12 Grupos estándar del flujo de trabajo
        'visibility',      // Control de visibilidad
        'content',         // Textos y contenido
        'typography',      // Estilos de texto y semántica HTML
        'colors',          // Paleta de colores
        'spacing',         // Márgenes y padding
        'visual_effects',  // Efectos visuales decorativos
        'behavior',        // Comportamiento interactivo
        'layout',          // Estructura y posicionamiento
        'links',           // Enlaces y URLs
        'icons',           // Configuración de íconos
        'media',           // Multimedia
        'forms',           // Formularios

        // Grupos adicionales/legacy en uso
        'styles',          // Legacy: combina colors, spacing, visual_effects
        'general',         // Configuración general del componente
        'menu',            // Específico para navbar
        'categories',      // Categorización
        'title',           // Título del componente
        'networks',        // Específico para social-share

        // Grupos específicos para contact-form
        'contact_info',    // Info de contacto (teléfono, email, ubicación)
        'form_labels',     // Labels y placeholders del formulario
        'integration',     // Configuración de webhook
        'messages',        // Mensajes de éxito/error/validación

        // Grupos específicos para footer
        'widget_1',        // Widget 1 del footer (menú)
        'widget_1b',       // Widget 1B del footer (menú secundario columna 1)
        'widget_2',        // Widget 2 del footer (menú)
        'widget_3',        // Widget 3 del footer (menú)
        'newsletter',      // Sección newsletter del footer
        'footer_bottom',   // Pie del footer (copyright)

        // Sistema de visibilidad por página
        '_page_visibility', // Visibilidad por tipo de página (home, posts, pages, archives, search)

        // Sistema de exclusiones (Plan 99.11)
        '_exclusions',      // Reglas de exclusión por categoría, post ID, URL pattern
    ];

    /**
     * @param array $configuration Configuración agrupada
     * @throws InvalidComponentException
     */
    public function __construct(private array $configuration)
    {
        $this->validate();
    }

    /**
     * Obtener toda la configuración
     *
     * @return array
     */
    public function all(): array
    {
        return $this->configuration;
    }

    /**
     * Obtener configuración de un grupo específico
     *
     * @param string $group Nombre del grupo (visibility, content, styles, general)
     * @return array
     */
    public function getGroup(string $group): array
    {
        return $this->configuration[$group] ?? [];
    }

    /**
     * Obtener valor de una clave específica dentro de un grupo
     *
     * @param string $group Nombre del grupo
     * @param string $key Clave de configuración
     * @param mixed $default Valor por defecto si no existe
     * @return mixed
     */
    public function get(string $group, string $key, mixed $default = null): mixed
    {
        return $this->configuration[$group][$key] ?? $default;
    }

    /**
     * Verificar si existe una clave en un grupo
     *
     * @param string $group
     * @param string $key
     * @return bool
     */
    public function has(string $group, string $key): bool
    {
        return isset($this->configuration[$group][$key]);
    }

    /**
     * Verificar si el componente tiene CTA URL
     *
     * @return bool
     */
    public function hasCTAURL(): bool
    {
        return $this->has('content', 'cta_url') && !empty($this->get('content', 'cta_url'));
    }

    /**
     * Verificar si el componente tiene CTA text
     *
     * @return bool
     */
    public function hasCTAText(): bool
    {
        return $this->has('content', 'cta_text') && !empty($this->get('content', 'cta_text'));
    }

    /**
     * Verificar si el componente está habilitado
     *
     * @return bool
     */
    public function isEnabled(): bool
    {
        return (bool) $this->get('visibility', 'enabled', false);
    }

    /**
     * Crear nueva configuración con un valor actualizado
     * (Inmutabilidad: retorna nueva instancia)
     *
     * @param string $group
     * @param string $key
     * @param mixed $value
     * @return self
     */
    public function withValue(string $group, string $key, mixed $value): self
    {
        $newConfiguration = $this->configuration;

        if (!isset($newConfiguration[$group])) {
            $newConfiguration[$group] = [];
        }

        $newConfiguration[$group][$key] = $value;

        return new self($newConfiguration);
    }

    /**
     * Crear nueva configuración con un grupo actualizado
     * (Inmutabilidad: retorna nueva instancia)
     *
     * @param string $group
     * @param array $values
     * @return self
     */
    public function withGroup(string $group, array $values): self
    {
        $newConfiguration = $this->configuration;
        $newConfiguration[$group] = $values;

        return new self($newConfiguration);
    }

    /**
     * Validar reglas de negocio de la configuración
     *
     * @throws InvalidComponentException
     * @return void
     */
    private function validate(): void
    {
        // Regla 1: Debe ser un array
        if (!is_array($this->configuration)) {
            throw new InvalidComponentException('Configuration must be an array');
        }

        // Regla 2: Los grupos deben ser válidos
        foreach (array_keys($this->configuration) as $group) {
            if (!in_array($group, self::VALID_GROUPS, true)) {
                throw new InvalidComponentException(
                    sprintf(
                        'Invalid configuration group "%s". Valid groups are: %s',
                        $group,
                        implode(', ', self::VALID_GROUPS)
                    )
                );
            }
        }

        // Regla 3: Si existe cta_url debe existir cta_text (y viceversa)
        if ($this->hasCTAURL() && !$this->hasCTAText()) {
            throw new InvalidComponentException('CTA URL requires CTA text');
        }

        if ($this->hasCTAText() && !$this->hasCTAURL()) {
            throw new InvalidComponentException('CTA text requires CTA URL');
        }

        // Regla 4: Los colores deben ser hexadecimales válidos
        $this->validateColors();
    }

    /**
     * Validar que los colores estén en formato hexadecimal
     *
     * @throws InvalidComponentException
     * @return void
     */
    private function validateColors(): void
    {
        $styles = $this->getGroup('styles');

        foreach ($styles as $key => $value) {
            if (str_ends_with($key, '_color') && !empty($value)) {
                if (!preg_match('/^#[0-9A-Fa-f]{6}$/', $value)) {
                    throw new InvalidComponentException(
                        sprintf(
                            'Color "%s" must be in hexadecimal format (e.g., #000000). Got: %s',
                            $key,
                            $value
                        )
                    );
                }
            }
        }
    }

    /**
     * Crear desde array flat (legacy)
     *
     * Convierte de:
     * ```php
     * [
     *     'enabled' => true,
     *     'message_text' => 'Hello',
     *     'background_color' => '#000'
     * ]
     * ```
     *
     * A estructura agrupada.
     *
     * @param array $flatConfig
     * @return self
     */
    public static function fromFlat(array $flatConfig): self
    {
        $grouped = [
            'visibility' => [],
            'content' => [],
            'styles' => [],
            'general' => []
        ];

        foreach ($flatConfig as $key => $value) {
            $group = self::inferGroup($key);
            $grouped[$group][$key] = $value;
        }

        return new self($grouped);
    }

    /**
     * Crear desde array agrupado
     *
     * Espera estructura ya agrupada:
     * ```php
     * [
     *     'visibility' => ['enabled' => true, ...],
     *     'content' => ['message_text' => 'Hello', ...],
     *     'styles' => ['background_color' => '#000', ...],
     *     'general' => ['priority' => 10, ...]
     * ]
     * ```
     *
     * @param array $groupedConfig Configuración ya agrupada
     * @return self
     */
    public static function fromArray(array $groupedConfig): self
    {
        // Si ya está agrupado, usarlo directamente
        return new self($groupedConfig);
    }

    /**
     * Inferir grupo desde clave (heurística)
     *
     * @param string $key
     * @return string
     */
    private static function inferGroup(string $key): string
    {
        // Visibility
        if (in_array($key, ['enabled', 'visible_desktop', 'visible_mobile', 'visible_tablet'], true)) {
            return 'visibility';
        }

        // Content
        if (str_starts_with($key, 'message_') ||
            str_starts_with($key, 'cta_') ||
            str_starts_with($key, 'title_')) {
            return 'content';
        }

        // Styles
        if (str_ends_with($key, '_color') ||
            str_ends_with($key, '_height') ||
            str_ends_with($key, '_width') ||
            str_ends_with($key, '_size') ||
            str_ends_with($key, '_font')) {
            return 'styles';
        }

        // Fallback
        return 'general';
    }

    /**
     * Crear configuración vacía
     *
     * @return self
     */
    public static function empty(): self
    {
        return new self([
            'visibility' => [],
            'content' => [],
            'styles' => [],
            'general' => []
        ]);
    }
}
