<?php
declare(strict_types=1);

final class AppDocGenerator
{
    private string $root;

    /** @var array<string,array<string,mixed>> */
    private array $catalog;

    /** @var array<string,array<string,string>> */
    private array $baseCatalog;

    public function __construct(string $root)
    {
        $this->root = rtrim(str_replace('\\', '/', $root), '/');
        $this->catalog = $this->buildCatalog();
        $this->baseCatalog = $this->buildBaseCatalog();
    }

    public function run(): void
    {
        foreach ($this->discoverAppRoots() as $appRoot) {
            $meta = $this->analyzeApp($appRoot);
            $this->writeFile($appRoot . '/MANUAL_USO.md', $this->renderManual($meta));
            $this->writeFile($appRoot . '/ANALISIS_FUNCIONAL.md', $this->renderFunctionalAnalysis($meta));
            $this->writeFile($appRoot . '/ANALISIS_REQUISITOS.md', $this->renderRequirementsAnalysis($meta));
            echo "OK {$meta['kind']} {$meta['slug']} -> {$appRoot}\n";
        }
    }

    /**
     * @return list<string>
     */
    private function discoverAppRoots(): array
    {
        $roots = [];

        foreach ($this->listDirectories($this->root . '/base') as $dir) {
            $slug = basename($dir);
            if ($slug === '') {
                continue;
            }
            $roots[] = $dir;
        }

        foreach ($this->listDirectories($this->root . '/apps') as $dir) {
            $slug = basename($dir);
            if ($slug === 'backups' || $slug === '') {
                continue;
            }
            $roots[] = $dir;
        }

        foreach ($this->listDirectories($this->root . '/plugins') as $pluginDir) {
            $appParent = $pluginDir . '/apps';
            foreach ($this->listDirectories($appParent) as $dir) {
                $roots[] = $dir;
            }
        }

        sort($roots, SORT_NATURAL | SORT_FLAG_CASE);
        return array_values(array_unique($roots));
    }

    /**
     * @return list<string>
     */
    private function listDirectories(string $path): array
    {
        if (!is_dir($path)) {
            return [];
        }

        $items = scandir($path);
        if (!is_array($items)) {
            return [];
        }

        $dirs = [];
        foreach ($items as $item) {
            if ($item === '.' || $item === '..') {
                continue;
            }
            $full = $path . '/' . $item;
            if (is_dir($full)) {
                $dirs[] = str_replace('\\', '/', $full);
            }
        }
        sort($dirs, SORT_NATURAL | SORT_FLAG_CASE);
        return $dirs;
    }

    /**
     * @return array<string,mixed>
     */
    private function analyzeApp(string $appRoot): array
    {
        $appRoot = str_replace('\\', '/', $appRoot);
        $slug = basename($appRoot);
        $kind = $this->detectKind($appRoot);
        $appJson = $this->readJsonFile($appRoot . '/app.json');
        $pluginJson = $this->locatePluginJson($appRoot);
        $pluginMeta = $pluginJson !== null ? $this->readJsonFile($pluginJson) : [];
        $catalogKey = $kind === 'plugin' ? $slug : $slug;
        $known = $this->catalog[$catalogKey] ?? [];
        $baseKnown = $this->baseCatalog[$slug] ?? [];

        $name = $this->pickFirstNonEmpty([
            (string) ($appJson['name'] ?? ''),
            (string) ($pluginMeta['name'] ?? ''),
            (string) ($known['name'] ?? ''),
            (string) ($baseKnown['name'] ?? ''),
            $this->humanizeSlug($slug),
        ]);

        $description = $this->pickFirstNonEmpty([
            $this->cleanText((string) ($appJson['description'] ?? '')),
            $this->cleanText((string) ($pluginMeta['description'] ?? '')),
            (string) ($known['description'] ?? ''),
            (string) ($baseKnown['description'] ?? ''),
        ]);

        $roles = $this->extractRoles($appJson, $pluginMeta);
        $sqlFiles = $this->listFilesByExtension($appRoot, 'sql');
        $entryFiles = $this->listTopFiles($appRoot, ['php', 'js', 'css', 'md']);
        $taskFiles = $this->detectTaskFiles($appRoot);
        $tables = $this->detectTables($appRoot, $sqlFiles);
        $featureBullets = $this->buildFeatureBullets($slug, $description, $entryFiles, $taskFiles, $sqlFiles, $kind);
        $actors = $this->buildActors($slug, $roles, $kind);
        $dataEntities = $this->buildDataEntities($slug, $tables, $sqlFiles);
        $integrations = $this->buildIntegrations($slug, $description, $taskFiles, $sqlFiles, $entryFiles);
        $requirements = $this->buildRequirements($slug, $featureBullets, $roles, $taskFiles, $sqlFiles, $kind);
        $usageSteps = $this->buildUsageSteps($slug, $featureBullets, $taskFiles);

        return [
            'slug' => $slug,
            'kind' => $kind,
            'name' => $name,
            'description' => $description !== '' ? $description : 'Aplicación interna de la intranet.',
            'roles' => $roles,
            'sql_files' => $sqlFiles,
            'entry_files' => $entryFiles,
            'task_files' => $taskFiles,
            'tables' => $tables,
            'feature_bullets' => $featureBullets,
            'actors' => $actors,
            'data_entities' => $dataEntities,
            'integrations' => $integrations,
            'requirements' => $requirements,
            'usage_steps' => $usageSteps,
            'app_root' => $appRoot,
            'doc_note' => 'Documento generado a partir del código, metadatos y estructura actual de la aplicación. Conviene validarlo funcionalmente con el área responsable.',
        ];
    }

    private function detectKind(string $appRoot): string
    {
        if (strpos($appRoot, '/plugins/') !== false) {
            return 'plugin';
        }
        if (strpos($appRoot, '/base/') !== false) {
            return 'base';
        }
        return 'app';
    }

    /**
     * @return array<string,mixed>
     */
    private function readJsonFile(string $path): array
    {
        if (!is_file($path)) {
            return [];
        }
        $raw = file_get_contents($path);
        if (!is_string($raw) || $raw === '') {
            return [];
        }
        $data = json_decode($raw, true);
        return is_array($data) ? $data : [];
    }

    private function locatePluginJson(string $appRoot): ?string
    {
        if (strpos($appRoot, '/plugins/') === false) {
            return null;
        }
        $pluginRoot = dirname(dirname($appRoot));
        $path = $pluginRoot . '/plugin.json';
        return is_file($path) ? $path : null;
    }

    /**
     * @param array<string,mixed> $appJson
     * @param array<string,mixed> $pluginJson
     * @return list<string>
     */
    private function extractRoles(array $appJson, array $pluginJson): array
    {
        $roles = [];
        foreach ([$appJson['roles_json'] ?? null, $pluginJson['roles_json'] ?? null] as $candidate) {
            if (is_array($candidate)) {
                foreach ($candidate as $role) {
                    $role = trim((string) $role);
                    if ($role !== '') {
                        $roles[] = $role;
                    }
                }
            }
        }
        $roles = array_values(array_unique($roles));
        sort($roles, SORT_NATURAL | SORT_FLAG_CASE);
        return $roles;
    }

    /**
     * @return list<string>
     */
    private function listFilesByExtension(string $appRoot, string $extension): array
    {
        if (!is_dir($appRoot)) {
            return [];
        }
        $out = [];
        $it = new RecursiveIteratorIterator(
            new RecursiveDirectoryIterator($appRoot, FilesystemIterator::SKIP_DOTS)
        );
        foreach ($it as $file) {
            if (!$file->isFile()) {
                continue;
            }
            $path = str_replace('\\', '/', $file->getPathname());
            if (preg_match('~/(vendor|node_modules|\.git|uploads|backups|cache|tmp|temp|logs)/~', $path)) {
                continue;
            }
            if (strtolower((string) $file->getExtension()) === strtolower($extension)) {
                $out[] = substr($path, strlen($appRoot) + 1);
            }
        }
        sort($out, SORT_NATURAL | SORT_FLAG_CASE);
        return $out;
    }

    /**
     * @param list<string> $extensions
     * @return list<string>
     */
    private function listTopFiles(string $appRoot, array $extensions): array
    {
        if (!is_dir($appRoot)) {
            return [];
        }
        $items = scandir($appRoot);
        if (!is_array($items)) {
            return [];
        }
        $out = [];
        foreach ($items as $item) {
            if ($item === '.' || $item === '..') {
                continue;
            }
            if (in_array($item, ['MANUAL_USO.md', 'ANALISIS_FUNCIONAL.md', 'ANALISIS_REQUISITOS.md'], true)) {
                continue;
            }
            $path = $appRoot . '/' . $item;
            if (!is_file($path)) {
                continue;
            }
            $ext = strtolower(pathinfo($item, PATHINFO_EXTENSION));
            if (in_array($ext, $extensions, true)) {
                $out[] = $item;
            }
        }
        sort($out, SORT_NATURAL | SORT_FLAG_CASE);
        return $out;
    }

    /**
     * @return list<string>
     */
    private function detectTaskFiles(string $appRoot): array
    {
        $out = [];
        $it = new RecursiveIteratorIterator(
            new RecursiveDirectoryIterator($appRoot, FilesystemIterator::SKIP_DOTS)
        );
        foreach ($it as $file) {
            if (!$file->isFile()) {
                continue;
            }
            $path = str_replace('\\', '/', $file->getPathname());
            if (preg_match('~/(vendor|node_modules|\.git|uploads|backups|cache|tmp|temp|logs)/~', $path)) {
                continue;
            }
            $rel = substr($path, strlen($appRoot) + 1);
            if (preg_match('~(^|/)(cron|worker|workers|job|jobs|queue|task|tasks|schedule|scheduler|cli|bin|command)(/|[^/]*$)~i', $rel)) {
                $out[] = $rel;
            }
        }
        sort($out, SORT_NATURAL | SORT_FLAG_CASE);
        return array_values(array_unique($out));
    }

    /**
     * @param list<string> $sqlFiles
     * @return list<string>
     */
    private function detectTables(string $appRoot, array $sqlFiles): array
    {
        $tables = [];
        foreach ($sqlFiles as $rel) {
            $path = $appRoot . '/' . ltrim($rel, '/');
            $raw = @file_get_contents($path);
            if (!is_string($raw) || $raw === '') {
                continue;
            }
            if (preg_match_all('~\bCREATE\s+TABLE(?:\s+IF\s+NOT\s+EXISTS)?\s+(?:`?[A-Za-z_][A-Za-z0-9_]*`?\.)?`?([A-Za-z_][A-Za-z0-9_]*)`?~i', $raw, $m)) {
                foreach ((array) ($m[1] ?? []) as $table) {
                    $table = strtolower(trim((string) $table));
                    if ($table !== '') {
                        $tables[$table] = true;
                    }
                }
            }
        }
        $out = array_keys($tables);
        sort($out, SORT_NATURAL | SORT_FLAG_CASE);
        return $out;
    }

    /**
     * @param list<string> $entryFiles
     * @param list<string> $taskFiles
     * @param list<string> $sqlFiles
     * @return list<string>
     */
    private function buildFeatureBullets(string $slug, string $description, array $entryFiles, array $taskFiles, array $sqlFiles, string $kind): array
    {
        $features = [];
        foreach ((array) ($this->catalog[$slug]['features'] ?? []) as $item) {
            $features[] = (string) $item;
        }
        foreach ((array) ($this->baseCatalog[$slug]['features'] ?? []) as $item) {
            $features[] = (string) $item;
        }

        if ($description !== '') {
            $features[] = $description;
        }
        if (!empty($sqlFiles)) {
            $features[] = 'Gestiona persistencia en base de datos y requiere mantener coherencia entre pantallas, procesos y scripts SQL.';
        }
        if (!empty($taskFiles)) {
            $features[] = 'Incluye procesos batch, workers o tareas programadas que deben revisarse en despliegue y operación.';
        }
        if ($kind === 'base') {
            $features[] = 'Forma parte del núcleo operativo de la intranet y suele estar orientada a administración o servicios transversales.';
        }
        if (in_array('download.php', $entryFiles, true) || in_array('export.php', $entryFiles, true)) {
            $features[] = 'Puede producir salidas descargables o exportaciones para el usuario final.';
        }

        $features = array_values(array_unique(array_map([$this, 'normalizeSentence'], $features)));
        return array_slice(array_values(array_filter($features)), 0, 8);
    }

    /**
     * @param list<string> $roles
     * @return list<string>
     */
    private function buildActors(string $slug, array $roles, string $kind): array
    {
        $actors = [];
        if (!empty($roles)) {
            foreach ($roles as $role) {
                $actors[] = 'Usuarios con rol `' . $role . '`';
            }
        } else {
            $actors[] = $kind === 'base' ? 'Personal administrador de la intranet' : 'Usuarios autorizados de la intranet';
        }

        foreach ((array) ($this->catalog[$slug]['actors'] ?? []) as $actor) {
            $actors[] = (string) $actor;
        }
        foreach ((array) ($this->baseCatalog[$slug]['actors'] ?? []) as $actor) {
            $actors[] = (string) $actor;
        }

        $actors = array_values(array_unique($actors));
        return $actors;
    }

    /**
     * @param list<string> $tables
     * @param list<string> $sqlFiles
     * @return list<string>
     */
    private function buildDataEntities(string $slug, array $tables, array $sqlFiles): array
    {
        $entities = [];
        foreach ((array) ($this->catalog[$slug]['entities'] ?? []) as $entity) {
            $entities[] = (string) $entity;
        }
        foreach ((array) ($this->baseCatalog[$slug]['entities'] ?? []) as $entity) {
            $entities[] = (string) $entity;
        }
        foreach ($tables as $table) {
            $entities[] = 'Tabla `' . $table . '`';
        }
        if (empty($entities) && !empty($sqlFiles)) {
            $entities[] = 'Tablas y vistas definidas por los scripts SQL incluidos en la aplicación.';
        }
        if (empty($entities)) {
            $entities[] = 'Parámetros de entrada, configuración y resultados operativos gestionados por la aplicación.';
        }
        $entities = array_values(array_unique($entities));
        return $entities;
    }

    /**
     * @param list<string> $taskFiles
     * @param list<string> $sqlFiles
     * @param list<string> $entryFiles
     * @return list<string>
     */
    private function buildIntegrations(string $slug, string $description, array $taskFiles, array $sqlFiles, array $entryFiles): array
    {
        $integrations = [];
        foreach ((array) ($this->catalog[$slug]['integrations'] ?? []) as $item) {
            $integrations[] = (string) $item;
        }
        foreach ((array) ($this->baseCatalog[$slug]['integrations'] ?? []) as $item) {
            $integrations[] = (string) $item;
        }

        $signals = strtolower($slug . ' ' . $description . ' ' . implode(' ', $entryFiles));
        if (strpos($signals, 'ldap') !== false || strpos($signals, 'unlock') !== false || strpos($signals, 'usuarios') !== false) {
            $integrations[] = 'Directorio corporativo o servicios de identidad (LDAP / Active Directory) cuando aplique.';
        }
        if (strpos($signals, 'pdf') !== false) {
            $integrations[] = 'Generación, tratamiento o distribución de documentos PDF.';
        }
        if (strpos($signals, 'mail') !== false || strpos($signals, 'correo') !== false || strpos($signals, 'smtp') !== false) {
            $integrations[] = 'Servicios de correo o notificación para envíos y avisos.';
        }
        if (strpos($signals, 'banc') !== false || in_array($slug, ['iban', 'bic'], true)) {
            $integrations[] = 'Ficheros o formatos bancarios para validación o enriquecimiento de datos financieros.';
        }
        if (!empty($sqlFiles)) {
            $integrations[] = 'Base de datos MySQL/MariaDB de la intranet.';
        }
        if (!empty($taskFiles)) {
            $integrations[] = 'Planificación mediante cron o ejecución manual de scripts de proceso en segundo plano.';
        }

        $integrations = array_values(array_unique($integrations));
        return $integrations;
    }

    /**
     * @param list<string> $featureBullets
     * @param list<string> $roles
     * @param list<string> $taskFiles
     * @param list<string> $sqlFiles
     * @return array{functional:list<string>,non_functional:list<string>,operational:list<string>}
     */
    private function buildRequirements(string $slug, array $featureBullets, array $roles, array $taskFiles, array $sqlFiles, string $kind): array
    {
        $functional = [];
        foreach ($featureBullets as $feature) {
            $functional[] = 'La aplicación debe permitir ' . $this->toInfinitiveStyle($feature);
        }
        if (empty($functional)) {
            $functional[] = 'La aplicación debe permitir ejecutar su proceso principal de forma trazable y segura.';
        }
        if (!empty($roles)) {
            $functional[] = 'La aplicación debe restringir el acceso a los roles autorizados: ' . implode(', ', array_map(static fn(string $r): string => '`' . $r . '`', $roles)) . '.';
        }
        if (!empty($taskFiles)) {
            $functional[] = 'La aplicación debe contemplar la ejecución correcta de tareas programadas o procesos batch asociados.';
        }

        $nonFunctional = [
            'Debe mantener compatibilidad con la autenticación y sesión de la intranet.',
            'Debe registrar errores de forma diagnóstica sin exponer información sensible al usuario.',
            'Debe preservar codificación UTF-8 y consistencia visual con los estilos corporativos.',
        ];
        if (!empty($sqlFiles)) {
            $nonFunctional[] = 'Debe mantener integridad de datos y compatibilidad con los scripts SQL incluidos.';
        }
        if ($kind === 'plugin') {
            $nonFunctional[] = 'Debe seguir siendo instalable y actualizable mediante el gestor de plugins y el Plugin Builder.';
        }

        $operational = [
            'Debe documentar el procedimiento de despliegue, actualización y reversión.',
            'Debe validarse con el área funcional responsable antes de cambios mayores.',
        ];
        if (!empty($taskFiles)) {
            $operational[] = 'Debe documentar qué scripts requieren cron, periodicidad y comandos de ejecución.';
        }
        if ($slug === 'cartas-informativas') {
            $operational[] = 'Debe verificarse especialmente la gestión de colas de envío, PDFs y cargas masivas.';
        }

        return [
            'functional' => array_values(array_unique($functional)),
            'non_functional' => array_values(array_unique($nonFunctional)),
            'operational' => array_values(array_unique($operational)),
        ];
    }

    /**
     * @param list<string> $featureBullets
     * @param list<string> $taskFiles
     * @return list<string>
     */
    private function buildUsageSteps(string $slug, array $featureBullets, array $taskFiles): array
    {
        $steps = [
            'Acceder a la aplicación desde la intranet con un usuario autorizado.',
            'Revisar el contexto inicial de trabajo, filtros, datos de entrada o configuración visible en la pantalla principal.',
        ];

        foreach ((array) ($this->catalog[$slug]['usage_steps'] ?? []) as $step) {
            $steps[] = (string) $step;
        }
        foreach ((array) ($this->baseCatalog[$slug]['usage_steps'] ?? []) as $step) {
            $steps[] = (string) $step;
        }

        if (!empty($featureBullets)) {
            $steps[] = 'Ejecutar la operación principal de la aplicación y validar el resultado mostrado o generado.';
        }
        if (!empty($taskFiles)) {
            $steps[] = 'Si la app depende de procesos en segundo plano, verificar que las tareas programadas asociadas estén configuradas y operativas.';
        }
        $steps[] = 'Comprobar mensajes de confirmación, salidas descargables o cambios persistidos antes de finalizar la sesión.';

        return array_values(array_unique($steps));
    }

    /**
     * @param array<string,mixed> $meta
     */
    private function renderManual(array $meta): string
    {
        $lines = [];
        $lines[] = '# Manual de uso';
        $lines[] = '';
        $lines[] = 'Aplicación: **' . $meta['name'] . '**';
        $lines[] = '';
        $lines[] = 'Tipo: `' . $meta['kind'] . '`';
        $lines[] = '';
        $lines[] = $meta['doc_note'];
        $lines[] = '';
        $lines[] = '## Finalidad';
        $lines[] = '';
        $lines[] = $meta['description'];
        $lines[] = '';
        $lines[] = '## Usuarios previstos';
        $lines[] = '';
        foreach ($meta['actors'] as $actor) {
            $lines[] = '- ' . $actor;
        }
        $lines[] = '';
        $lines[] = '## Acceso';
        $lines[] = '';
        $lines[] = '- Directorio raíz de la aplicación: `' . $this->relativeToRoot($meta['app_root']) . '`';
        if (!empty($meta['roles'])) {
            $lines[] = '- Roles autorizados detectados: ' . implode(', ', array_map(static fn(string $r): string => '`' . $r . '`', $meta['roles'])) . '.';
        } else {
            $lines[] = '- Los roles deben validarse en el catálogo de aplicaciones y en la configuración de seguridad de la intranet.';
        }
        $lines[] = '';
        $lines[] = '## Uso habitual';
        $lines[] = '';
        foreach ($meta['usage_steps'] as $index => $step) {
            $lines[] = ($index + 1) . '. ' . $step;
        }
        $lines[] = '';
        $lines[] = '## Funciones principales';
        $lines[] = '';
        foreach ($meta['feature_bullets'] as $item) {
            $lines[] = '- ' . $item;
        }
        $lines[] = '';
        $lines[] = '## Ficheros y puntos de entrada relevantes';
        $lines[] = '';
        if (!empty($meta['entry_files'])) {
            foreach ($meta['entry_files'] as $file) {
                $lines[] = '- `' . $file . '`';
            }
        } else {
            $lines[] = '- Revisar la carpeta raíz para identificar los puntos de entrada concretos.';
        }
        if (!empty($meta['task_files'])) {
            $lines[] = '';
            $lines[] = '## Procesos programados o batch';
            $lines[] = '';
            foreach ($meta['task_files'] as $file) {
                $lines[] = '- `' . $file . '`';
            }
        }
        $lines[] = '';
        $lines[] = '## Observaciones operativas';
        $lines[] = '';
        $lines[] = '- Validar permisos, datos y resultados antes de usar la aplicación en entornos de producción.';
        $lines[] = '- Revisar el análisis funcional y el análisis de requisitos asociados para cambios o ampliaciones.';
        return implode("\n", $lines) . "\n";
    }

    /**
     * @param array<string,mixed> $meta
     */
    private function renderFunctionalAnalysis(array $meta): string
    {
        $lines = [];
        $lines[] = '# Análisis funcional';
        $lines[] = '';
        $lines[] = 'Aplicación: **' . $meta['name'] . '**';
        $lines[] = '';
        $lines[] = $meta['doc_note'];
        $lines[] = '';
        $lines[] = '## Objetivo funcional';
        $lines[] = '';
        $lines[] = $meta['description'];
        $lines[] = '';
        $lines[] = '## Actores';
        $lines[] = '';
        foreach ($meta['actors'] as $actor) {
            $lines[] = '- ' . $actor;
        }
        $lines[] = '';
        $lines[] = '## Capacidades observadas';
        $lines[] = '';
        foreach ($meta['feature_bullets'] as $item) {
            $lines[] = '- ' . $item;
        }
        $lines[] = '';
        $lines[] = '## Datos y entidades';
        $lines[] = '';
        foreach ($meta['data_entities'] as $entity) {
            $lines[] = '- ' . $entity;
        }
        $lines[] = '';
        $lines[] = '## Integraciones y dependencias';
        $lines[] = '';
        foreach ($meta['integrations'] as $integration) {
            $lines[] = '- ' . $integration;
        }
        $lines[] = '';
        $lines[] = '## Flujo funcional resumido';
        $lines[] = '';
        $lines[] = '1. El usuario accede a la aplicación con los permisos correspondientes.';
        $lines[] = '2. La aplicación presenta información, formularios o procesos específicos de su dominio.';
        $lines[] = '3. Se realizan operaciones sobre datos, validaciones, consultas o acciones de negocio.';
        if (!empty($meta['task_files'])) {
            $lines[] = '4. Los procesos programados complementan o finalizan tareas asíncronas asociadas a la aplicación.';
            $lines[] = '5. El usuario revisa el estado final, resultado o salida generada.';
        } else {
            $lines[] = '4. El usuario revisa el estado final, resultado o salida generada.';
        }
        $lines[] = '';
        $lines[] = '## Puntos de validación funcional';
        $lines[] = '';
        $lines[] = '- Confirmar que los permisos mostrados en la intranet coinciden con el uso esperado del área responsable.';
        $lines[] = '- Revisar la trazabilidad de altas, modificaciones, exportaciones o acciones masivas.';
        if (!empty($meta['tables'])) {
            $lines[] = '- Contrastar que las tablas SQL detectadas siguen reflejando el modelo funcional vigente.';
        }
        if (!empty($meta['task_files'])) {
            $lines[] = '- Verificar manualmente el comportamiento de cron, jobs o workers cuando formen parte del proceso de negocio.';
        }
        return implode("\n", $lines) . "\n";
    }

    /**
     * @param array<string,mixed> $meta
     */
    private function renderRequirementsAnalysis(array $meta): string
    {
        $lines = [];
        $lines[] = '# Análisis de requisitos';
        $lines[] = '';
        $lines[] = 'Aplicación: **' . $meta['name'] . '**';
        $lines[] = '';
        $lines[] = $meta['doc_note'];
        $lines[] = '';
        $lines[] = '## Alcance';
        $lines[] = '';
        $lines[] = $meta['description'];
        $lines[] = '';
        $lines[] = '## Requisitos funcionales';
        $lines[] = '';
        foreach ($meta['requirements']['functional'] as $item) {
            $lines[] = '- ' . $item;
        }
        $lines[] = '';
        $lines[] = '## Requisitos no funcionales';
        $lines[] = '';
        foreach ($meta['requirements']['non_functional'] as $item) {
            $lines[] = '- ' . $item;
        }
        $lines[] = '';
        $lines[] = '## Requisitos operativos y de explotación';
        $lines[] = '';
        foreach ($meta['requirements']['operational'] as $item) {
            $lines[] = '- ' . $item;
        }
        $lines[] = '';
        $lines[] = '## Datos y persistencia';
        $lines[] = '';
        if (!empty($meta['sql_files'])) {
            $lines[] = '- Scripts SQL detectados: ' . implode(', ', array_map(static fn(string $f): string => '`' . $f . '`', $meta['sql_files'])) . '.';
        } else {
            $lines[] = '- No se han detectado scripts SQL en la carpeta analizada.';
        }
        if (!empty($meta['tables'])) {
            $lines[] = '- Entidades SQL identificadas: ' . implode(', ', array_map(static fn(string $t): string => '`' . $t . '`', $meta['tables'])) . '.';
        }
        $lines[] = '';
        $lines[] = '## Requisitos de seguridad';
        $lines[] = '';
        if (!empty($meta['roles'])) {
            $lines[] = '- El acceso debe quedar restringido a: ' . implode(', ', array_map(static fn(string $r): string => '`' . $r . '`', $meta['roles'])) . '.';
        } else {
            $lines[] = '- Debe definirse el conjunto exacto de roles autorizados antes de desplegar o modificar esta aplicación.';
        }
        $lines[] = '- Debe respetarse la autenticación centralizada y la sesión de la intranet.';
        $lines[] = '- Deben evitarse fugas de información en mensajes de error, descargas y procesos batch.';
        $lines[] = '';
        $lines[] = '## Pendientes recomendados';
        $lines[] = '';
        $lines[] = '- Validar este análisis con la persona responsable funcional de la aplicación.';
        $lines[] = '- Completar casos de uso detallados, reglas de negocio y criterios de aceptación si se va a evolucionar la app.';
        $lines[] = '- Actualizar estos documentos cuando cambien procesos, tablas, permisos o tareas programadas.';
        return implode("\n", $lines) . "\n";
    }

    private function writeFile(string $path, string $content): void
    {
        $dir = dirname($path);
        if (!is_dir($dir)) {
            mkdir($dir, 0775, true);
        }
        file_put_contents($path, $content);
    }

    private function relativeToRoot(string $path): string
    {
        $path = str_replace('\\', '/', $path);
        if (strpos($path, $this->root . '/') === 0) {
            return substr($path, strlen($this->root) + 1);
        }
        return $path;
    }

    private function humanizeSlug(string $slug): string
    {
        return ucwords(str_replace(['-', '_'], ' ', $slug));
    }

    private function pickFirstNonEmpty(array $values): string
    {
        foreach ($values as $value) {
            $value = trim((string) $value);
            if ($value !== '') {
                return $value;
            }
        }
        return '';
    }

    private function cleanText(string $text): string
    {
        $text = preg_replace('/\s+/', ' ', $text) ?? '';
        return trim($text);
    }

    private function normalizeSentence(string $text): string
    {
        $text = trim($text);
        if ($text === '') {
            return '';
        }
        $text = rtrim($text, '.');
        return $text . '.';
    }

    private function toInfinitiveStyle(string $text): string
    {
        $text = trim($text);
        $text = preg_replace('/\.$/', '', $text) ?? $text;
        if (function_exists('mb_strtolower')) {
            $text = mb_strtolower($text, 'UTF-8');
        } else {
            $text = strtolower($text);
        }
        return $text . '.';
    }

    /**
     * @return array<string,array<string,mixed>>
     */
    private function buildCatalog(): array
    {
        return [
            'ad-unlock-dept' => [
                'name' => 'Desbloqueo departamental de usuarios',
                'features' => [
                    'Permite desbloquear cuentas de usuario y operar sobre credenciales o estado de acceso cuando la organización lo autoriza',
                    'Centraliza operaciones delicadas de soporte y control de acceso para personal con permisos específicos',
                ],
                'actors' => ['Personal de soporte o administración de cuentas'],
                'integrations' => ['Active Directory / LDAP corporativo'],
                'entities' => ['Solicitudes u operaciones sobre cuentas de usuario'],
                'usage_steps' => [
                    'Buscar el usuario o cuenta afectada y revisar su estado antes de actuar.',
                    'Ejecutar la operación de desbloqueo, restablecimiento o ajuste habilitada por la interfaz.',
                ],
            ],
            'bic' => [
                'name' => 'Enriquecimiento BIC',
                'features' => [
                    'Completa o valida códigos BIC / SWIFT sobre ficheros bancarios',
                    'Prepara salidas listas para su remisión o tratamiento posterior',
                ],
                'integrations' => ['Ficheros XML bancarios y catálogos de códigos BIC/SWIFT'],
                'entities' => ['Ficheros bancarios de entrada', 'Resultados enriquecidos con código BIC'],
            ],
            'cartas-informativas' => [
                'name' => 'Cartas informativas',
                'features' => [
                    'Gestiona envíos masivos o individuales de cartas PDF vinculadas a NIF y canales de contacto',
                    'Coordina importaciones, colas, validaciones y trazabilidad de envíos',
                ],
                'integrations' => ['Correo electrónico corporativo', 'Documentos PDF', 'Ejecución de jobs o workers'],
                'entities' => ['Campañas de cartas', 'Jobs de envío', 'Destinatarios', 'Registro de envíos'],
                'usage_steps' => [
                    'Crear o seleccionar una campaña y cargar la documentación o los destinatarios necesarios.',
                    'Lanzar el proceso manual o esperar al worker para completar los envíos pendientes.',
                ],
            ],
            'claves' => [
                'name' => 'Claves',
                'features' => [
                    'Genera propuestas de contraseñas seguras y recordables con distintos niveles de complejidad',
                    'Permite variar idioma, longitud, número de palabras y estilo de generación',
                ],
                'actors' => ['Personal interno que necesite generar contraseñas'],
                'entities' => ['Parámetros de generación', 'Propuestas de claves'],
            ],
            'compras' => [
                'name' => 'Compras',
                'features' => [
                    'Gestiona solicitudes de compra, material o aprovisionamiento entre departamentos y personas responsables',
                    'Permite registrar estado, seguimiento y resolución de peticiones',
                ],
                'actors' => ['Personal solicitante', 'Responsables de compras o validación'],
                'entities' => ['Solicitudes de compra', 'Departamentos', 'Estados de tramitación'],
            ],
            'docling-markdown' => [
                'name' => 'Conversor Docling Markdown',
                'features' => [
                    'Convierte documentos PDF, Office, HTML o imágenes a Markdown para su uso en la intranet',
                    'Facilita la normalización documental y la reutilización de contenidos',
                ],
                'integrations' => ['Herramientas de conversión documental'],
                'entities' => ['Documentos de entrada', 'Ficheros Markdown de salida'],
            ],
            'documentacion-locales' => [
                'name' => 'Documentación de locales',
                'features' => [
                    'Gestiona locales, documentación asociada y control administrativo del patrimonio o sedes',
                    'Permite altas, consulta y actualización de expedientes vinculados a cada local',
                ],
                'entities' => ['Locales', 'Documentación asociada', 'Expedientes o anexos'],
            ],
            'entregas' => [
                'name' => 'Entregas',
                'features' => [
                    'Registra entregas de objetos a afiliados o personas usuarias',
                    'Mantiene catálogo de objetos, destinatarios y trazabilidad de entregas',
                ],
                'entities' => ['Objetos', 'Entregas', 'Destinatarios'],
            ],
            'gastos' => [
                'name' => 'Gastos',
                'features' => [
                    'Gestiona hojas de gasto, justificantes y seguimiento económico asociado',
                    'Ayuda a controlar importes, estados y documentación justificativa',
                ],
                'entities' => ['Hojas de gasto', 'Justificantes', 'Estados de validación'],
            ],
            'gestion-flota' => [
                'name' => 'Gestión de flota',
                'features' => [
                    'Administra vehículos, revisiones, documentación y uso de la flota corporativa',
                    'Centraliza información de mantenimiento, seguros y disponibilidad',
                ],
                'entities' => ['Vehículos', 'Revisiones', 'Documentación', 'Incidencias de flota'],
            ],
            'iban' => [
                'name' => 'IBAN',
                'features' => [
                    'Calcula, valida o transforma números IBAN y códigos asociados',
                    'Sirve de apoyo operativo para tareas administrativas o financieras',
                ],
                'integrations' => ['Normativa y formatos bancarios'],
                'entities' => ['Cuentas bancarias', 'IBAN', 'Códigos SWIFT/BIC'],
            ],
            'incidencias' => [
                'name' => 'Incidencias',
                'features' => [
                    'Permite registrar incidencias y comunicarlas al departamento de informática u otras áreas de soporte',
                    'Facilita el seguimiento del ciclo de vida de cada petición',
                ],
                'actors' => ['Usuarios solicitantes', 'Equipo de soporte o informática'],
                'entities' => ['Incidencias', 'Estados', 'Mensajes o seguimientos'],
            ],
            'irpf-certificados' => [
                'name' => 'Certificados IRPF',
                'features' => [
                    'Gestiona generación o envío automatizado de certificados IRPF',
                    'Controla privacidad, distribución y trazabilidad documental',
                ],
                'integrations' => ['Correo electrónico', 'Documentos PDF', 'Procesos batch'],
                'entities' => ['Certificados', 'Destinatarios', 'Envíos'],
            ],
            'lectura-foco' => [
                'name' => 'Lectura foco',
                'features' => [
                    'Ofrece lectura guiada y rápida de documentos o contenidos visuales',
                    'Prioriza accesibilidad, concentración y experiencia de lectura asistida',
                ],
                'entities' => ['Documentos o textos importados', 'Opciones de lectura'],
            ],
            'listin' => [
                'name' => 'Listín',
                'features' => [
                    'Mantiene el listín telefónico o directorio de contactos de la organización',
                    'Permite localizar personas, sedes, extensiones o datos de contacto',
                ],
                'entities' => ['Contactos', 'Teléfonos', 'Delegaciones'],
            ],
            'permisos' => [
                'name' => 'Permisos',
                'features' => [
                    'Gestiona días de permisos, vacaciones y asuntos propios',
                    'Ayuda a controlar saldo, solicitudes y validaciones del calendario laboral',
                ],
                'entities' => ['Solicitudes de permiso', 'Calendario', 'Saldos'],
            ],
            'tareas-sector' => [
                'name' => 'Tareas de sector',
                'features' => [
                    'Recoge memoria, actividad y seguimiento de tareas desarrolladas por un sector',
                    'Facilita organización, consulta histórica y trazabilidad del trabajo realizado',
                ],
                'entities' => ['Tareas', 'Sectores', 'Estados', 'Histórico'],
            ],
            'usuarios' => [
                'name' => 'Usuarios',
                'features' => [
                    'Gestiona peticiones de alta de usuarios, cuentas o accesos',
                    'Ayuda a tramitar y controlar el aprovisionamiento de identidades',
                ],
                'integrations' => ['Directorio corporativo o sistemas de cuentas'],
                'entities' => ['Peticiones de alta', 'Usuarios', 'Cuentas', 'Estados'],
            ],
        ];
    }

    /**
     * @return array<string,array<string,string>>
     */
    private function buildBaseCatalog(): array
    {
        return [
            'color-manager' => [
                'name' => 'Gestión del color',
                'description' => 'Módulo base para configurar o mantener parámetros de color, tema o identidad visual de la intranet.',
            ],
            'enlaces' => [
                'name' => 'Enlaces',
                'description' => 'Módulo base orientado a consulta o servicio transversal de enlaces corporativos.',
            ],
            'gestion_apps' => [
                'name' => 'Gestión de aplicaciones',
                'description' => 'Administración del catálogo de aplicaciones, grupos, visibilidad y configuración general del ecosistema de apps.',
            ],
            'gestion_enlaces' => [
                'name' => 'Gestión de enlaces',
                'description' => 'Administración centralizada de enlaces, grupos y publicación de accesos visibles en la intranet.',
            ],
            'gestion_firmas_correo' => [
                'name' => 'Gestión de firmas de correo',
                'description' => 'Módulo de gestión de firmas corporativas de correo y sus parámetros asociados.',
            ],
            'links' => [
                'name' => 'Links',
                'description' => 'Servicio base relacionado con accesos directos o recursos enlazados en la intranet.',
            ],
            'nntt' => [
                'name' => 'NNTT',
                'description' => 'Módulo base del área de nuevas tecnologías o servicios asociados.',
            ],
            'novedades' => [
                'name' => 'Novedades',
                'description' => 'Publicación y gestión de novedades, avisos o contenidos informativos del portal interno.',
            ],
            'plugins' => [
                'name' => 'Gestor de plugins',
                'description' => 'Gestión de instalación, actualización, activación y desinstalación de plugins de la intranet.',
            ],
            'plugin_builder' => [
                'name' => 'Plugin Builder',
                'description' => 'Herramienta para empaquetar aplicaciones como plugins, generar ZIP, publicar en el store y mantener metadatos técnicos.',
            ],
            'preferences' => [
                'name' => 'Preferencias',
                'description' => 'Gestión de preferencias personales de la intranet como tema, diseño de menú o sesión.',
            ],
            'sectores' => [
                'name' => 'Sectores',
                'description' => 'Módulo de soporte a la clasificación o mantenimiento de sectores organizativos.',
            ],
            'stats' => [
                'name' => 'Estadísticas',
                'description' => 'Consulta de estadísticas de uso, actividad o indicadores de la intranet.',
            ],
            'theme-color-manager' => [
                'name' => 'Gestor de colores de tema',
                'description' => 'Administración del sistema de color y variantes visuales de la intranet.',
            ],
            'updates' => [
                'name' => 'Actualizaciones',
                'description' => 'Módulo base para gestionar actualizaciones del núcleo o de componentes del portal.',
            ],
        ];
    }
}

$root = realpath(__DIR__ . '/..');
if ($root === false) {
    fwrite(STDERR, "No se pudo resolver la raíz del proyecto.\n");
    exit(1);
}

$generator = new AppDocGenerator($root);
$generator->run();
