<?php
declare(strict_types=1);

namespace ROITheme\Shared\Infrastructure\Validators;

/**
 * Validador de Fase 01: Schema JSON
 *
 * Valida que el archivo JSON del componente cumple con:
 * - Estructura correcta
 * - Campos obligatorios presentes
 * - Tipos de datos válidos
 * - Grupos y campos requeridos
 */
final class Phase01Validator implements PhaseValidatorInterface
{
    private const ALLOWED_TYPES = ['boolean', 'text', 'textarea', 'url', 'select', 'color'];
    private const ALLOWED_PRIORITIES = [10, 20, 30, 40, 50, 60, 70, 80, 90];
    private const STANDARD_GROUPS = [
        'visibility', 'content', 'typography', 'colors', 'spacing',
        'visual_effects', 'behavior', 'layout', 'links', 'icons', 'media', 'forms'
    ];

    /**
     * Componentes especiales que NO requieren grupo visibility
     *
     * Estos son componentes de inyeccion (no visuales) que:
     * - NO renderizan HTML visual
     * - Inyectan codigo en hooks (wp_head, wp_footer)
     * - Siempre estan activos (controlados por campos vacios/llenos)
     */
    private const INJECTION_COMPONENTS = ['theme-settings'];

    public function validate(string $componentName, string $themePath): ValidationResult
    {
        $result = new ValidationResult();

        // Construir ruta al schema
        $schemaPath = $themePath . '/Schemas/' . $componentName . '.json';

        $result->addInfo("Validando Schema JSON: schemas/{$componentName}.json");

        // 1. Verificar que el archivo existe
        if (!file_exists($schemaPath)) {
            $result->addError("Schema JSON no encontrado: {$schemaPath}");
            return $result;
        }

        // 2. Leer y parsear JSON
        $jsonContent = file_get_contents($schemaPath);
        $schema = json_decode($jsonContent, true);

        if (json_last_error() !== JSON_ERROR_NONE) {
            $result->addError("JSON inválido: " . json_last_error_msg());
            return $result;
        }

        // 3. Validar estructura top-level
        $this->validateTopLevelStructure($schema, $componentName, $result);

        // 4. Validar grupos
        if (isset($schema['groups'])) {
            $this->validateGroups($schema['groups'], $result);
        }

        // 5. Validar campos obligatorios de visibilidad (excepto componentes de inyeccion)
        $this->validateVisibilityFields($schema, $componentName, $result);

        // Estadísticas
        $totalFields = $this->countTotalFields($schema);
        $totalGroups = isset($schema['groups']) ? count($schema['groups']) : 0;

        $result->setStat('Archivo', "schemas/{$componentName}.json");
        $result->setStat('Grupos totales', $totalGroups);
        $result->setStat('Campos totales', $totalFields);
        $result->setStat('Tamaño JSON', strlen($jsonContent) . ' bytes');

        return $result;
    }

    private function validateTopLevelStructure(array $schema, string $componentName, ValidationResult $result): void
    {
        // Campos obligatorios
        $requiredFields = ['component_name', 'version', 'description', 'groups'];

        foreach ($requiredFields as $field) {
            if (!isset($schema[$field])) {
                $result->addError("Campo obligatorio faltante: '{$field}'");
            }
        }

        // Validar component_name coincide con archivo
        if (isset($schema['component_name']) && $schema['component_name'] !== $componentName) {
            $result->addError(
                "component_name '{$schema['component_name']}' no coincide con nombre de archivo '{$componentName}'"
            );
        }

        // Validar versión semver
        if (isset($schema['version'])) {
            if (!preg_match('/^\d+\.\d+\.\d+$/', $schema['version'])) {
                $result->addError("Versión '{$schema['version']}' no es semver válido (debe ser X.Y.Z)");
            }
        }
    }

    private function validateGroups(array $groups, ValidationResult $result): void
    {
        if (empty($groups)) {
            $result->addError("Schema debe tener al menos un grupo");
            return;
        }

        foreach ($groups as $groupName => $group) {
            // Validar nombre de grupo es snake_case
            if (!preg_match('/^[a-z_]+$/', $groupName)) {
                $result->addError("Nombre de grupo '{$groupName}' debe estar en snake_case (solo minúsculas y _)");
            }

            // Advertencia si grupo no es estándar
            if (!in_array($groupName, self::STANDARD_GROUPS, true)) {
                $result->addWarning("Grupo '{$groupName}' no es estándar (considerar usar: " . implode(', ', self::STANDARD_GROUPS) . ")");
            }

            // Validar estructura del grupo
            if (!isset($group['label'])) {
                $result->addError("Grupo '{$groupName}' no tiene 'label'");
            }

            if (!isset($group['priority'])) {
                $result->addError("Grupo '{$groupName}' no tiene 'priority'");
            } elseif (!in_array($group['priority'], self::ALLOWED_PRIORITIES, true)) {
                $result->addError(
                    "Grupo '{$groupName}' tiene priority inválido ({$group['priority']}). " .
                    "Debe ser uno de: " . implode(', ', self::ALLOWED_PRIORITIES)
                );
            }

            if (!isset($group['fields'])) {
                $result->addError("Grupo '{$groupName}' no tiene 'fields'");
            } elseif (!is_array($group['fields']) || empty($group['fields'])) {
                $result->addError("Grupo '{$groupName}' debe tener al menos un campo");
            } else {
                $this->validateFields($groupName, $group['fields'], $result);
            }
        }
    }

    private function validateFields(string $groupName, array $fields, ValidationResult $result): void
    {
        foreach ($fields as $fieldName => $field) {
            $fullFieldName = "{$groupName}.{$fieldName}";

            // Validar nombre de campo es snake_case
            if (!preg_match('/^[a-z_]+$/', $fieldName)) {
                $result->addError("Campo '{$fullFieldName}' debe estar en snake_case (solo minúsculas y _)");
            }

            // Campos obligatorios
            if (!isset($field['type'])) {
                $result->addError("Campo '{$fullFieldName}' no tiene 'type'");
            } elseif (!in_array($field['type'], self::ALLOWED_TYPES, true)) {
                $result->addError(
                    "Campo '{$fullFieldName}' tiene type inválido '{$field['type']}'. " .
                    "Debe ser uno de: " . implode(', ', self::ALLOWED_TYPES)
                );
            }

            if (!isset($field['label'])) {
                $result->addError("Campo '{$fullFieldName}' no tiene 'label'");
            }

            if (!array_key_exists('default', $field)) {
                $result->addError("Campo '{$fullFieldName}' no tiene 'default'");
            }

            if (!isset($field['editable'])) {
                $result->addError("Campo '{$fullFieldName}' no tiene 'editable'");
            } elseif (!is_bool($field['editable'])) {
                $result->addError("Campo '{$fullFieldName}' tiene 'editable' que no es boolean");
            }

            // Si type es select, debe tener options
            if (isset($field['type']) && $field['type'] === 'select') {
                if (!isset($field['options']) || !is_array($field['options']) || empty($field['options'])) {
                    $result->addError("Campo '{$fullFieldName}' es type 'select' pero no tiene array 'options' válido");
                }
            }

            // Si tiene required, debe ser boolean
            if (isset($field['required']) && !is_bool($field['required'])) {
                $result->addError("Campo '{$fullFieldName}' tiene 'required' que no es boolean");
            }
        }
    }

    private function validateVisibilityFields(array $schema, string $componentName, ValidationResult $result): void
    {
        // Componentes de inyeccion no requieren grupo visibility
        if (in_array($componentName, self::INJECTION_COMPONENTS, true)) {
            $result->addInfo("✓ Componente de inyección '{$componentName}' - grupo visibility no requerido");
            return;
        }

        if (!isset($schema['groups']['visibility'])) {
            $result->addError("Grupo 'visibility' es obligatorio y no está presente");
            return;
        }

        $visibilityFields = $schema['groups']['visibility']['fields'] ?? [];

        // Campos obligatorios de visibilidad
        $requiredVisibilityFields = [
            'is_enabled' => 'boolean',
            'show_on_desktop' => 'boolean',
            'show_on_mobile' => 'boolean',
        ];

        foreach ($requiredVisibilityFields as $fieldName => $expectedType) {
            if (!isset($visibilityFields[$fieldName])) {
                $result->addError("Campo obligatorio de visibilidad faltante: 'visibility.{$fieldName}'");
            } elseif (isset($visibilityFields[$fieldName]['type']) && $visibilityFields[$fieldName]['type'] !== $expectedType) {
                $result->addError(
                    "Campo 'visibility.{$fieldName}' debe ser type '{$expectedType}' " .
                    "(encontrado: '{$visibilityFields[$fieldName]['type']}')"
                );
            }
        }
    }

    private function countTotalFields(array $schema): int
    {
        $count = 0;

        if (isset($schema['groups'])) {
            foreach ($schema['groups'] as $group) {
                if (isset($group['fields'])) {
                    $count += count($group['fields']);
                }
            }
        }

        return $count;
    }

    public function getPhaseNumber(): int|string
    {
        return 1;
    }

    public function getPhaseDescription(): string
    {
        return 'Schema JSON';
    }
}
