<?php
declare(strict_types=1);

namespace ROITheme\Shared\Infrastructure\Validators;

/**
 * Validador de Estructura de Carpetas Clean Architecture (PSR-4 PascalCase)
 *
 * Valida que:
 * - Solo existen contextos permitidos (Admin/, Public/, Shared/)
 * - Nombres de módulos en PascalCase (PSR-4 standard)
 * - Shared Kernel: Infrastructure/ obligatoria, Domain/Application/ a nivel contexto
 * - Carpetas dentro de capas son solo las arquitectónicamente permitidas
 * - NO hay carpetas inventadas (Helpers/, Utils/, Lib/, etc.)
 * - Niveles de profundidad correctos
 */
final class FolderStructureValidator implements PhaseValidatorInterface
{
    private const ALLOWED_CONTEXTS = ['Admin', 'Public', 'Shared'];

    private const REQUIRED_LAYERS = ['Domain', 'Application', 'Infrastructure'];

    private const ALLOWED_IN_DOMAIN = ['Contracts', 'ValueObjects', 'Exceptions'];

    private const ALLOWED_IN_APPLICATION = ['UseCases', 'Dtos', 'Contracts', 'Services'];

    private const ALLOWED_IN_INFRASTRUCTURE = ['Persistence', 'Api', 'Ui', 'Services', 'WordPress', 'Traits', 'Di', 'Scripts', 'Validators'];

    private const FORBIDDEN_NAMES = ['Helpers', 'Utils', 'Utilities', 'Lib', 'Libs', 'Core', 'Common', 'Base', 'Models'];

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

        $result->addInfo("Validando estructura de carpetas para: {$componentName}");

        // Buscar el módulo en Admin/ o Public/
        $modulePath = $this->findModulePath($componentName, $themePath);

        if ($modulePath === null) {
            $result->addError("Módulo no encontrado en Admin/ ni Public/");
            return $result;
        }

        $context = basename(dirname($modulePath));
        $result->addInfo("Módulo encontrado en: {$context}/{$componentName}/");

        // Validar nombre del módulo (debe coincidir con PascalCase esperado)
        $this->validateModuleName($componentName, $modulePath, $result);

        // Validar capas obligatorias
        $this->validateRequiredLayers($modulePath, $result);

        // Validar contenido de cada capa
        $this->validateLayerContents($modulePath, $result);

        // Validar profundidad de carpetas
        $this->validateDepth($modulePath, $result);

        // Buscar carpetas prohibidas
        $this->findForbiddenFolders($modulePath, $result);

        return $result;
    }

    private function findModulePath(string $componentName, string $themePath): ?string
    {
        // Convertir kebab-case a PascalCase (PSR-4 standard)
        $pascalCaseName = str_replace('-', '', ucwords($componentName, '-'));

        // Intentar en Public/ con PascalCase
        $publicPath = $themePath . '/Public/' . $pascalCaseName;
        if (is_dir($publicPath)) {
            return $publicPath;
        }

        // Intentar en Admin/ con PascalCase
        $adminPath = $themePath . '/Admin/' . $pascalCaseName;
        if (is_dir($adminPath)) {
            return $adminPath;
        }

        return null;
    }

    private function validateModuleName(string $componentName, string $modulePath, ValidationResult $result): void
    {
        // Convertir a PascalCase esperado
        $pascalCaseExpected = str_replace('-', '', ucwords($componentName, '-'));
        $actualFolderName = basename($modulePath);

        // El nombre de la carpeta debe ser PascalCase (PSR-4 standard)
        if ($actualFolderName !== $pascalCaseExpected) {
            $result->addError("Nombre de carpeta '{$actualFolderName}' no coincide con PascalCase esperado '{$pascalCaseExpected}'");
        } else {
            $result->addInfo("✓ Nombre de carpeta en PascalCase: {$actualFolderName}");
        }
    }

    private function validateRequiredLayers(string $modulePath, ValidationResult $result): void
    {
        // SHARED KERNEL PATTERN:
        // Los módulos individuales solo requieren Infrastructure/
        // Domain/ y Application/ están a nivel de contexto (Admin/, Public/)

        // Infrastructure/ es OBLIGATORIA
        $infrastructurePath = $modulePath . '/Infrastructure';
        if (!is_dir($infrastructurePath)) {
            $result->addError("Falta capa obligatoria: Infrastructure/");
        } else {
            $result->addInfo("✓ Capa Infrastructure/ existe");
        }

        // Domain/ y Application/ son OPCIONALES (Shared Kernel a nivel contexto)
        foreach (['Domain', 'Application'] as $optionalLayer) {
            $layerPath = $modulePath . '/' . $optionalLayer;
            if (is_dir($layerPath)) {
                $result->addInfo("✓ Capa {$optionalLayer}/ existe (específica del módulo)");
            }
        }

        $result->addInfo("ℹ️  Arquitectura Shared Kernel: Domain/ y Application/ a nivel contexto (Admin/, Public/)");
    }

    private function validateLayerContents(string $modulePath, ValidationResult $result): void
    {
        // Validar Domain/
        $this->validateLayerContent(
            $modulePath . '/Domain',
            'Domain',
            self::ALLOWED_IN_DOMAIN,
            $result
        );

        // Validar Application/
        $this->validateLayerContent(
            $modulePath . '/Application',
            'Application',
            self::ALLOWED_IN_APPLICATION,
            $result
        );

        // Validar Infrastructure/
        $this->validateLayerContent(
            $modulePath . '/Infrastructure',
            'Infrastructure',
            self::ALLOWED_IN_INFRASTRUCTURE,
            $result
        );
    }

    private function validateLayerContent(string $layerPath, string $layerName, array $allowedFolders, ValidationResult $result): void
    {
        if (!is_dir($layerPath)) {
            return; // Ya reportado en validateRequiredLayers
        }

        $items = scandir($layerPath);
        $folders = array_filter($items, function($item) use ($layerPath) {
            return $item !== '.' && $item !== '..' && is_dir($layerPath . '/' . $item);
        });

        foreach ($folders as $folder) {
            // Validar que la carpeta está en la lista permitida (PascalCase)
            if (!in_array($folder, $allowedFolders, true)) {
                $result->addError("Carpeta NO permitida en {$layerName}/: '{$folder}' (permitidas: " . implode(', ', $allowedFolders) . ")");
            }
        }

        // Validaciones especiales para infrastructure/ui/
        if ($layerName === 'infrastructure' && in_array('ui', $folders, true)) {
            $this->validateUIStructure($layerPath . '/ui', $result);
        }

        // Validaciones especiales para infrastructure/api/
        if ($layerName === 'infrastructure' && in_array('api', $folders, true)) {
            $this->validateAPIStructure($layerPath . '/api', $result);
        }
    }

    private function validateUIStructure(string $uiPath, ValidationResult $result): void
    {
        if (!is_dir($uiPath)) {
            return;
        }

        $items = scandir($uiPath);
        $folders = array_filter($items, function($item) use ($uiPath) {
            return $item !== '.' && $item !== '..' && is_dir($uiPath . '/' . $item);
        });

        $allowedInUI = ['assets', 'templates', 'views'];

        foreach ($folders as $folder) {
            if (!in_array($folder, $allowedInUI, true)) {
                $result->addWarning("Carpeta en ui/: '{$folder}' (esperadas: " . implode(', ', $allowedInUI) . ")");
            }
        }

        // Si existe assets/, validar que tiene css/ y/o js/
        if (in_array('assets', $folders, true)) {
            $assetsPath = $uiPath . '/assets';
            $assetsItems = scandir($assetsPath);
            $assetsFolders = array_filter($assetsItems, function($item) use ($assetsPath) {
                return $item !== '.' && $item !== '..' && is_dir($assetsPath . '/' . $item);
            });

            $allowedInAssets = ['css', 'js', 'images', 'fonts'];

            foreach ($assetsFolders as $folder) {
                if (!in_array($folder, $allowedInAssets, true)) {
                    $result->addWarning("Carpeta en ui/assets/: '{$folder}' (esperadas: " . implode(', ', $allowedInAssets) . ")");
                }
            }
        }
    }

    private function validateAPIStructure(string $apiPath, ValidationResult $result): void
    {
        if (!is_dir($apiPath)) {
            return;
        }

        $items = scandir($apiPath);
        $folders = array_filter($items, function($item) use ($apiPath) {
            return $item !== '.' && $item !== '..' && is_dir($apiPath . '/' . $item);
        });

        $allowedInAPI = ['wordpress'];

        foreach ($folders as $folder) {
            if (!in_array($folder, $allowedInAPI, true)) {
                $result->addWarning("Carpeta en api/: '{$folder}' (esperada: wordpress)");
            }
        }
    }

    private function validateDepth(string $modulePath, ValidationResult $result): void
    {
        // Máximo 5 niveles desde módulo: module/layer/category/subcategory/file.php
        $maxDepth = 5;
        $deepPaths = [];

        $this->scanDirectoryDepth($modulePath, $modulePath, 0, $maxDepth, $deepPaths);

        if (!empty($deepPaths)) {
            foreach ($deepPaths as $path => $depth) {
                $result->addWarning("Ruta muy profunda ({$depth} niveles): {$path}");
            }
        }
    }

    private function scanDirectoryDepth(string $basePath, string $currentPath, int $currentDepth, int $maxDepth, array &$deepPaths): void
    {
        if ($currentDepth > $maxDepth) {
            $relativePath = str_replace($basePath . '/', '', $currentPath);
            $deepPaths[$relativePath] = $currentDepth;
            return;
        }

        if (!is_dir($currentPath)) {
            return;
        }

        $items = scandir($currentPath);

        foreach ($items as $item) {
            if ($item === '.' || $item === '..') {
                continue;
            }

            $itemPath = $currentPath . '/' . $item;

            if (is_dir($itemPath)) {
                $this->scanDirectoryDepth($basePath, $itemPath, $currentDepth + 1, $maxDepth, $deepPaths);
            }
        }
    }

    private function findForbiddenFolders(string $modulePath, ValidationResult $result): void
    {
        $foundForbidden = [];

        $this->scanForForbiddenNames($modulePath, $modulePath, $foundForbidden);

        if (!empty($foundForbidden)) {
            foreach ($foundForbidden as $path) {
                $relativePath = str_replace($modulePath . '/', '', $path);
                $folderName = basename($path);
                $result->addError("❌ Carpeta PROHIBIDA encontrada: {$relativePath}/ ('{$folderName}' NO es arquitectónicamente válido)");
            }
        } else {
            $result->addInfo("✓ Sin carpetas prohibidas (helpers, utils, etc.)");
        }
    }

    private function scanForForbiddenNames(string $basePath, string $currentPath, array &$foundForbidden): void
    {
        if (!is_dir($currentPath)) {
            return;
        }

        $items = scandir($currentPath);

        foreach ($items as $item) {
            if ($item === '.' || $item === '..') {
                continue;
            }

            $itemPath = $currentPath . '/' . $item;

            if (is_dir($itemPath)) {
                // Verificar si el nombre de la carpeta está prohibido
                if (in_array(strtolower($item), self::FORBIDDEN_NAMES, true)) {
                    $foundForbidden[] = $itemPath;
                }

                // Recursivo
                $this->scanForForbiddenNames($basePath, $itemPath, $foundForbidden);
            }
        }
    }

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

    public function getPhaseDescription(): string
    {
        return 'Folder Structure (Clean Architecture)';
    }
}
