<?php

use dokuwiki\Extension\AdminPlugin;

/**
 * Project ACL Manager - Administración de permisos de proyectos
 *
 * Plugin para gestionar permisos de acceso a proyectos de forma separada
 * del administrador ACL principal para evitar sobrecarga del sistema.
 */
class admin_plugin_projectacl extends AdminPlugin
{
    /** @var helper_plugin_sqlite */
    protected $sqlite = null;

    /** @var array Lista de usuarios del sistema */
    protected $users = [];

    /** @var array Lista de proyectos */
    protected $projects = [];

    /** @var int Proyecto seleccionado */
    protected $selected_project = 0;

    /** @var string Mensaje de estado */
    protected $message = '';

    /** @var string Tipo de mensaje (success/error) */
    protected $message_type = '';

    /** @var bool Flag para saber si ya intentamos conectar */
    protected $db_initialized = false;

    /**
     * Inicializa la conexión a la base de datos
     */
    protected function getDatabase()
    {
        if ($this->db_initialized) {
            return $this->sqlite;
        }

        $this->db_initialized = true;

        /** @var helper_plugin_bez_db $db_helper */
        $db_helper = plugin_load('helper', 'bez_db');
        if ($db_helper) {
            try {
                $this->sqlite = $db_helper->getDB();
            } catch (Exception $e) {
                $this->sqlite = null;
            }
        }

        return $this->sqlite;
    }

    /**
     * Retorna el texto del menú de administración
     */
    public function getMenuText($language)
    {
        return $this->getLang('menu');
    }

    /**
     * Orden en el menú de administración (después de usermanager=1, acl=2)
     */
    public function getMenuSort()
    {
        return 3;
    }

    /**
     * Solo para administradores
     */
    public function forAdminOnly()
    {
        return true;
    }

    /**
     * Icono del menú - rueda dentada (gear/cog)
     */
    public function getMenuIcon()
    {
        return DOKU_INC . 'lib/images/menu/settings.svg';
    }

    /**
     * Maneja las peticiones del usuario
     */
    public function handle()
    {
        global $INPUT;

        // Cargar usuarios
        $this->loadUsers();

        // Cargar proyectos
        $this->loadProjects();

        // Proyecto seleccionado
        $this->selected_project = $INPUT->int('project', 0);

        // Manejar exportación CSV
        if ($INPUT->str('action') === 'export' && checkSecurityToken()) {
            $this->exportCSV();
            exit;
        }

        // Manejar importación CSV
        if ($INPUT->str('action') === 'import' && checkSecurityToken()) {
            $this->importCSV();
        }

        // Manejar guardado de membresías
        if ($INPUT->str('action') === 'save' && checkSecurityToken()) {
            $this->saveUserMemberships($INPUT);
        }

        // Manejar eliminación de grupo de proyecto
        if ($INPUT->str('action') === 'delete_group' && checkSecurityToken()) {
            $this->deleteProjectGroup($INPUT->int('project_id'));
        }

        // Manejar depuración del archivo ACL
        if ($INPUT->str('action') === 'cleanup' && checkSecurityToken()) {
            $this->cleanupACLFile();
        }

        // Manejar migración de grupos antiguos a ACL
        if ($INPUT->str('action') === 'migrate' && checkSecurityToken()) {
            $this->migrateGroupsToACL();
        }
    }

    /**
     * Carga los usuarios del sistema
     */
    protected function loadUsers()
    {
        global $auth;

        if (!$auth) return;

        // Obtener todos los usuarios (0, 0 = sin límite)
        if (method_exists($auth, 'retrieveUsers')) {
            $this->users = $auth->retrieveUsers(0, 0, []);
        }
    }

    /**
     * Carga los proyectos desde la base de datos
     */
    protected function loadProjects()
    {
        $sqlite = $this->getDatabase();

        if (!$sqlite) {
            return;
        }

        // Cargar todos los threads (proyectos pueden estar como 'issue' o 'project')
        // Filtramos por títulos que parecen proyectos (Revisoría, Outsourcing, Asesoría, etc.)
        $sql = "SELECT id, title, coordinator, type, label_name
                FROM thread_view
                ORDER BY id DESC";

        $result = $sqlite->query($sql);
        if ($result) {
            $this->projects = $sqlite->res2arr($result);
        }
    }

    /**
     * Guarda los permisos ACL de usuarios para un proyecto
     * y sincroniza participantes en la tabla thread_participant de BEZ.
     */
    protected function saveUserMemberships($INPUT)
    {
        $project_id = $INPUT->int('project');
        $permissions = $INPUT->arr('perms');

        if (empty($project_id)) {
            $this->message = $this->getLang('no_project_selected');
            $this->message_type = 'error';
            return;
        }

        $acl_file = DOKU_CONF . 'acl.auth.php';
        $project_namespace = 'es:bez:thread:id:' . $project_id;

        // Leer archivo ACL actual
        $acl_lines = [];
        if (file_exists($acl_file)) {
            $acl_lines = file($acl_file);
        }

        // Filtrar líneas existentes para este proyecto (usuarios individuales, no grupos)
        $new_acl_lines = [];
        foreach ($acl_lines as $line) {
            $trimmed = trim($line);

            // Mantener comentarios y líneas vacías
            if (empty($trimmed) || $trimmed[0] === '#') {
                $new_acl_lines[] = $line;
                continue;
            }

            // Verificar si es una línea de este proyecto para un usuario (no grupo)
            if (preg_match('/^' . preg_quote($project_namespace, '/') . '\s+([^@\s]+)\s+\d+/', $trimmed)) {
                // Omitir - será reemplazada con los nuevos valores
                continue;
            }

            $new_acl_lines[] = $line;
        }

        // Agregar nuevos permisos (solo si el nivel > 0)
        $users_updated = 0;
        foreach ($permissions as $username => $level) {
            $level = (int)$level;
            if ($level > 0) {
                $new_acl_lines[] = $project_namespace . "\t" . $username . "\t" . $level . "\n";
                $users_updated++;
            }
        }

        // Guardar archivo ACL
        if (file_put_contents($acl_file, implode('', $new_acl_lines)) !== false) {
            $this->message = sprintf($this->getLang('permissions_saved'), $users_updated);
            $this->message_type = 'success';

            // Sincronizar participantes en BEZ (thread_participant).
            $this->syncBezParticipants($project_id, $permissions);
        } else {
            $this->message = $this->getLang('save_error');
            $this->message_type = 'error';
        }
    }

    /**
     * Sincroniza los usuarios asignados en ACL con la tabla thread_participant de BEZ.
     * - Usuarios con nivel > 0: se agregan como 'subscribent' si no existen, o se reactivan si estaban removidos.
     * - Usuarios con nivel = 0: se marcan como removed=1, SOLO si no son coordinator ni task_assignee.
     *
     * @param int   $project_id  ID del proyecto/thread en BEZ
     * @param array $permissions Array [username => nivel_permiso]
     */
    protected function syncBezParticipants($project_id, $permissions)
    {
        // Conectar a la BD SQLite de BEZ.
        $db_bez = plugin_load('helper', 'sqlite');
        if (!$db_bez || !$db_bez->init('b3p', DOKU_PLUGIN . 'bez/db/')) {
            return; // Si no se puede conectar, no bloquear el guardado ACL.
        }

        $fechaActual = date('c');
        $addedBy = $_SERVER['REMOTE_USER'] ?? 'projectacl';

        foreach ($permissions as $username => $level) {
            $level = (int)$level;

            // Verificar si el usuario ya es participante del thread.
            $res = $db_bez->query(
                'SELECT user_id, coordinator, task_assignee, removed FROM thread_participant WHERE thread_id = ? AND user_id = ?',
                $project_id, $username
            );
            $existing = $db_bez->res2row($res);

            if ($level > 0) {
                // Usuario CON permisos → asegurar que sea participante.
                if ($existing === false) {
                    // No existe → insertar como subscribent.
                    $db_bez->query(
                        'INSERT INTO thread_participant (thread_id, user_id, original_poster, coordinator, commentator, task_assignee, subscribent, added_by, added_date, removed)
                         VALUES (?, ?, 0, 0, 0, 0, 1, ?, ?, 0)',
                        $project_id, $username, $addedBy, $fechaActual
                    );
                } else if ($existing['removed'] == '1') {
                    // Existe pero estaba removido → reactivar.
                    $db_bez->query(
                        'UPDATE thread_participant SET removed = 0, subscribent = 1 WHERE thread_id = ? AND user_id = ?',
                        $project_id, $username
                    );
                }
                // Si ya existe y no está removido, no hacer nada (respetar flags actuales).
            } else {
                // Usuario SIN permisos (nivel=0) → marcar como removido si no es coordinador ni task_assignee.
                if ($existing !== false && $existing['removed'] == '0') {
                    if ($existing['coordinator'] == '0' && $existing['task_assignee'] == '0') {
                        $db_bez->query(
                            'UPDATE thread_participant SET removed = 1 WHERE thread_id = ? AND user_id = ?',
                            $project_id, $username
                        );
                    }
                }
            }
        }
    }

    /**
     * Elimina todos los permisos ACL de un proyecto
     */
    protected function deleteProjectGroup($project_id)
    {
        if (empty($project_id)) return;

        $project_namespace = 'es:bez:thread:id:' . $project_id;

        // Remover todos los permisos ACL del proyecto
        $acl_file = DOKU_CONF . 'acl.auth.php';
        if (file_exists($acl_file)) {
            $acl_lines = file($acl_file);
            $new_acl_lines = [];
            $removed_count = 0;

            foreach ($acl_lines as $line) {
                $trimmed = trim($line);

                // Mantener comentarios y líneas vacías
                if (empty($trimmed) || $trimmed[0] === '#') {
                    $new_acl_lines[] = $line;
                    continue;
                }

                // Verificar si es una línea de este proyecto
                if (preg_match('/^' . preg_quote($project_namespace, '/') . '\s+/', $trimmed)) {
                    $removed_count++;
                    continue; // No agregar esta línea
                }

                $new_acl_lines[] = $line;
            }

            file_put_contents($acl_file, implode('', $new_acl_lines));

            $this->message = sprintf($this->getLang('permissions_deleted'), $project_id, $removed_count);
            $this->message_type = 'success';
        }
    }

    /**
     * Depura el archivo ACL eliminando entradas duplicadas y proyectos eliminados
     * También elimina carpetas físicas de proyectos huérfanos
     */
    protected function cleanupACLFile()
    {
        $acl_file = DOKU_CONF . 'acl.auth.php';

        if (!file_exists($acl_file)) {
            $this->message = $this->getLang('cleanup_no_file');
            $this->message_type = 'error';
            return;
        }

        // Obtener IDs de proyectos existentes en la base de datos
        $valid_project_ids = $this->getValidProjectIds();

        $lines = file($acl_file);
        $seen = [];
        $new_lines = [];
        $duplicates_removed = 0;
        $empty_removed = 0;
        $deleted_projects_removed = 0;

        foreach ($lines as $line) {
            $trimmed = trim($line);

            // Mantener comentarios
            if (!empty($trimmed) && $trimmed[0] === '#') {
                $new_lines[] = $line;
                continue;
            }

            // Eliminar líneas vacías
            if (empty($trimmed)) {
                $empty_removed++;
                continue;
            }

            // Normalizar la línea (reemplazar múltiples espacios/tabs por un tab)
            $normalized = preg_replace('/\s+/', "\t", $trimmed);

            // Verificar si es una entrada de proyecto y si el proyecto existe
            if (preg_match('/^es:bez:thread:id:(\d+)\s/', $normalized, $matches)) {
                $project_id = (int)$matches[1];
                if (!in_array($project_id, $valid_project_ids)) {
                    $deleted_projects_removed++;
                    continue; // Proyecto eliminado, no incluir
                }
            }

            // Verificar si ya existe (duplicado)
            if (isset($seen[$normalized])) {
                $duplicates_removed++;
                continue;
            }

            $seen[$normalized] = true;
            $new_lines[] = $normalized . "\n";
        }

        // Guardar archivo limpio
        $acl_saved = file_put_contents($acl_file, implode('', $new_lines)) !== false;

        // Eliminar carpetas físicas de proyectos huérfanos
        $folders_removed = $this->cleanupOrphanProjectFolders($valid_project_ids);

        if ($acl_saved) {
            $this->message = sprintf(
                $this->getLang('cleanup_success'),
                $duplicates_removed,
                $empty_removed,
                $deleted_projects_removed
            );
            if ($folders_removed > 0) {
                $this->message .= ' ' . sprintf($this->getLang('cleanup_folders_removed'), $folders_removed);
            }
            $this->message_type = 'success';
        } else {
            $this->message = $this->getLang('cleanup_error');
            $this->message_type = 'error';
        }
    }

    /**
     * Elimina carpetas físicas de proyectos que ya no existen en la base de datos
     * Primero hace backup en la carpeta "eliminados"
     */
    protected function cleanupOrphanProjectFolders($valid_project_ids)
    {
        global $conf;
        $folders_removed = 0;

        // Obtener rutas base
        $datadir = $this->normalizePath($conf['datadir'] ?? (DOKU_INC . 'data/pages'));
        $metadir = $this->normalizePath($conf['metadir'] ?? (DOKU_INC . 'data/meta'));
        $olddir = $this->normalizePath($conf['olddir'] ?? (DOKU_INC . 'data/attic'));
        $mediadir = $this->normalizePath($conf['mediadir'] ?? (DOKU_INC . 'data/media'));
        $ds = DIRECTORY_SEPARATOR;

        // Crear carpeta de eliminados si no existe
        $eliminados_dir = $this->normalizePath($datadir . $ds . 'proyectos' . $ds . 'eliminados');
        if (!is_dir($eliminados_dir)) {
            mkdir($eliminados_dir, 0755, true);
        }

        // Lista de rutas base donde buscar carpetas de proyectos
        $base_paths = [
            'proyectos/control',
            'proyectos/generales',
            'proyectos/auditorias/programas_auditoria/general',
            'proyectos/auditorias/programas_auditoria/28_efectivo',
            'proyectos/auditorias/programas_auditoria/29_inversiones',
            'proyectos/auditorias/programas_auditoria/31_cuentas_cobrar',
            'proyectos/auditorias/programas_auditoria/32_activos_intangibles',
            'proyectos/auditorias/programas_auditoria/33_gastos_anticipado',
            'proyectos/auditorias/programas_auditoria/34_pasivos_provisiones',
            'proyectos/auditorias/programas_auditoria/35_impuestos_ganancias',
            'proyectos/auditorias/programas_auditoria/36_ingresos_operacionales',
            'proyectos/auditorias/programas_auditoria/37_contingencias',
            'proyectos/auditorias/programas_auditoria/38_patrimonio',
            'proyectos/auditorias/programas_auditoria/39_gastos_operacionales',
            'proyectos/auditorias/programas_auditoria/40_costo_ventas',
            'proyectos/auditorias/programas_auditoria/41_propiedad',
            'proyectos/auditorias/programas_auditoria/43_estimaciones',
            'proyectos/auditorias/programas_auditoria/44_gastos_personales',
            'proyectos/auditorias/programas_auditoria/45_inventarios',
            'proyectos/auditorias/programas_auditoria/46_otros_asuntos',
            'proyectos/auditorias/programas_auditoria/47_ica_bimestral',
            'proyectos/auditorias/programas_auditoria/48_iva_bimestral',
            'proyectos/auditorias/programas_auditoria/49_rete_fte',
            'proyectos/auditorias/programas_auditoria/50_rte_ica',
            'proyectos/auditorias/programas_auditoria/51_regimen_simple',
            'proyectos/auditorias/programas_auditoria/auditoria_de_cierre',
            'proyectos/auditorias/programas_auditoria/auditoria_de_cierre2',
            'proyectos/auditorias/programas_auditoria/bancos',
            'proyectos/auditorias/programas_auditoria/bonos',
            'proyectos/auditorias/programas_auditoria/cartera',
            'proyectos/auditorias/programas_auditoria/consolidado',
            'proyectos/auditorias/programas_auditoria/cuentas_por_cobrar',
            'proyectos/auditorias/programas_auditoria/cuentas_por_cobrar2',
            'proyectos/auditorias/programas_auditoria/cuentas_por_pagar',
            'proyectos/auditorias/programas_auditoria/diferidos',
            'proyectos/auditorias/programas_auditoria/disponible',
            'proyectos/auditorias/programas_auditoria/disponible2',
            'proyectos/auditorias/programas_auditoria/impuesto_al_patrimonio',
            'proyectos/auditorias/programas_auditoria/impuestos',
            'proyectos/auditorias/programas_auditoria/impuestos1',
            'proyectos/auditorias/programas_auditoria/impuestos2',
            'proyectos/auditorias/programas_auditoria/impuestos3',
            'proyectos/auditorias/programas_auditoria/impuestos4',
            'proyectos/auditorias/programas_auditoria/impuestos5',
            'proyectos/auditorias/programas_auditoria/impuestos6',
            'proyectos/auditorias/programas_auditoria/impuestos7',
            'proyectos/auditorias/programas_auditoria/impuestos8',
            'proyectos/auditorias/programas_auditoria/impuestos9',
            'proyectos/auditorias/programas_auditoria/impuestos10',
            'proyectos/auditorias/programas_auditoria/impuestos11',
            'proyectos/auditorias/programas_auditoria/impuestos12',
            'proyectos/auditorias/programas_auditoria/gasnomina',
            'proyectos/auditorias/programas_auditoria/nomina',
            'proyectos/auditorias/programas_auditoria/aportes',
            'proyectos/auditorias/programas_auditoria/liquidacion',
            'proyectos/auditorias/programas_auditoria/cumplimiento2',
            'proyectos/auditorias/programas_auditoria/inventarios',
            'proyectos/auditorias/programas_auditoria/inventarios2',
            'proyectos/auditorias/programas_auditoria/inversiones',
            'proyectos/auditorias/programas_auditoria/normatividad',
            'proyectos/auditorias/programas_auditoria/obligaciones_financieras',
            'proyectos/auditorias/programas_auditoria/otros_activos',
            'proyectos/auditorias/programas_auditoria/pasivos_laborales',
            'proyectos/auditorias/programas_auditoria/patrimonio',
            'proyectos/auditorias/programas_auditoria/ppe',
            'proyectos/auditorias/programas_auditoria/prestaciones',
            'proyectos/auditorias/programas_auditoria/reestructuracion',
        ];

        // Encontrar todos los proyectos huérfanos primero
        $orphan_projects = [];

        // Directorios a limpiar: pages, meta, attic, media
        $directories = [
            'pages' => $datadir,
            'meta' => $metadir,
            'attic' => $olddir,
            'media' => $mediadir
        ];

        foreach ($directories as $dir_type => $base_dir) {
            foreach ($base_paths as $base_path) {
                $base_path_os = $this->normalizePath($base_path);
                $full_path = $this->normalizePath($base_dir . $ds . $base_path_os);

                if (!is_dir($full_path)) continue;

                $items = scandir($full_path);
                if ($items === false) continue;

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

                    if (is_numeric($item)) {
                        $project_id = (int)$item;

                        if (!in_array($project_id, $valid_project_ids)) {
                            if (!isset($orphan_projects[$project_id])) {
                                $orphan_projects[$project_id] = [];
                            }
                            $orphan_projects[$project_id][] = [
                                'type' => $dir_type,
                                'base_path' => $base_path,
                                'full_path' => $this->normalizePath($full_path . $ds . $item)
                            ];
                        }
                    }
                }
            }
        }

        // Procesar cada proyecto huérfano: backup y luego eliminar
        $timestamp = date('Y-m-d_His');

        foreach ($orphan_projects as $project_id => $folders) {
            // Crear carpeta de backup para este proyecto
            $backup_base = $this->normalizePath($eliminados_dir . $ds . $project_id . '_' . $timestamp);

            foreach ($folders as $folder_info) {
                // Crear estructura de backup: eliminados/{id}_{timestamp}/{type}/{base_path}
                $backup_path = $this->normalizePath(
                    $backup_base . $ds . $folder_info['type'] . $ds . $this->normalizePath($folder_info['base_path'])
                );

                // Hacer backup de la carpeta
                $this->copyDirectoryRecursive($folder_info['full_path'], $backup_path);

                // Eliminar la carpeta original
                if ($this->deleteDirectoryRecursive($folder_info['full_path'])) {
                    $folders_removed++;
                }
            }
        }

        return $folders_removed;
    }

    /**
     * Copia un directorio recursivamente
     */
    protected function copyDirectoryRecursive($source, $dest)
    {
        $source = $this->normalizePath($source);
        $dest = $this->normalizePath($dest);

        clearstatcache(true, $source);
        if (!is_dir($source)) return;

        if (!is_dir($dest)) {
            mkdir($dest, 0755, true);
        }

        $files = scandir($source);
        if ($files === false) return;

        foreach ($files as $file) {
            if ($file === '.' || $file === '..') continue;

            $src_path = $this->normalizePath($source . DIRECTORY_SEPARATOR . $file);
            $dst_path = $this->normalizePath($dest . DIRECTORY_SEPARATOR . $file);

            clearstatcache(true, $src_path);
            if (is_dir($src_path)) {
                $this->copyDirectoryRecursive($src_path, $dst_path);
            } else {
                copy($src_path, $dst_path);
            }
        }
    }

    /**
     * Normaliza una ruta para el sistema operativo actual
     */
    protected function normalizePath($path)
    {
        $path = str_replace(['/', '\\'], DIRECTORY_SEPARATOR, $path);
        $path = preg_replace('#' . preg_quote(DIRECTORY_SEPARATOR, '#') . '+#', DIRECTORY_SEPARATOR, $path);
        return rtrim($path, DIRECTORY_SEPARATOR);
    }

    /**
     * Elimina un directorio y todo su contenido recursivamente
     */
    protected function deleteDirectoryRecursive($dir)
    {
        clearstatcache();
        $dir = rtrim($dir, '/\\');

        if (!file_exists($dir)) {
            return true;
        }

        if (!is_dir($dir)) {
            return unlink($dir);
        }

        try {
            $iterator = new \RecursiveIteratorIterator(
                new \RecursiveDirectoryIterator($dir, \RecursiveDirectoryIterator::SKIP_DOTS),
                \RecursiveIteratorIterator::CHILD_FIRST
            );

            foreach ($iterator as $item) {
                clearstatcache(true, $item->getPathname());

                if ($item->isDir()) {
                    rmdir($item->getPathname());
                } else {
                    if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
                        chmod($item->getPathname(), 0777);
                    }
                    unlink($item->getPathname());
                }
            }
        } catch (\Exception $e) {
            $files = scandir($dir);
            if ($files === false) {
                return false;
            }

            foreach ($files as $file) {
                if ($file === '.' || $file === '..') continue;

                $path = $dir . DIRECTORY_SEPARATOR . $file;
                clearstatcache(true, $path);

                if (is_dir($path)) {
                    $this->deleteDirectoryRecursive($path);
                } else {
                    if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
                        chmod($path, 0777);
                    }
                    unlink($path);
                }
            }
        }

        clearstatcache(true, $dir);
        return rmdir($dir);
    }

    /**
     * Obtiene los IDs de proyectos válidos de la base de datos
     */
    protected function getValidProjectIds()
    {
        $ids = [];
        $sqlite = $this->getDatabase();

        if (!$sqlite) {
            // Si no hay conexión a BD, usar los proyectos ya cargados
            foreach ($this->projects as $project) {
                $ids[] = (int)$project['id'];
            }
            return $ids;
        }

        $sql = "SELECT id FROM thread_view";
        $result = $sqlite->query($sql);
        if ($result) {
            $rows = $sqlite->res2arr($result);
            foreach ($rows as $row) {
                $ids[] = (int)$row['id'];
            }
        }

        return $ids;
    }

    /**
     * Migra usuarios de grupos de proyecto (@proyecto{id} o @p{id}) al sistema ACL
     */
    protected function migrateGroupsToACL()
    {
        $users_file = DOKU_CONF . 'users.auth.php';
        $acl_file = DOKU_CONF . 'acl.auth.php';

        if (!file_exists($users_file)) {
            $this->message = $this->getLang('migrate_no_users');
            $this->message_type = 'error';
            return;
        }

        // Encontrar todos los usuarios con grupos de proyecto
        $migrations = [];
        $lines = file($users_file);

        foreach ($lines as $line) {
            $trimmed = trim($line);
            if (empty($trimmed) || $trimmed[0] === '#') continue;

            $parts = explode(':', $trimmed);
            if (count($parts) >= 5) {
                $username = $parts[0];
                $groups = array_map('trim', explode(',', $parts[4]));

                foreach ($groups as $group) {
                    // Soportar ambos patrones: "proyecto{id}" y "p{id}"
                    if (preg_match('/^(?:proyecto|p)(\d+)$/', $group, $matches)) {
                        $project_id = $matches[1];
                        if (!isset($migrations[$project_id])) {
                            $migrations[$project_id] = [];
                        }
                        // Evitar duplicados si el usuario está en ambos grupos
                        if (!in_array($username, $migrations[$project_id])) {
                            $migrations[$project_id][] = $username;
                        }
                    }
                }
            }
        }

        if (empty($migrations)) {
            $this->message = $this->getLang('migrate_nothing');
            $this->message_type = 'info';
            return;
        }

        // Leer ACL actual
        $acl_lines = [];
        if (file_exists($acl_file)) {
            $acl_lines = file($acl_file);
        }

        // Crear un conjunto de permisos existentes para evitar duplicados
        $existing_perms = [];
        foreach ($acl_lines as $line) {
            $trimmed = trim($line);
            if (!empty($trimmed) && $trimmed[0] !== '#') {
                $normalized = preg_replace('/\s+/', "\t", $trimmed);
                $existing_perms[$normalized] = true;
            }
        }

        // Agregar nuevos permisos
        $added_count = 0;
        foreach ($migrations as $project_id => $users) {
            $namespace = 'es:bez:thread:id:' . $project_id;
            foreach ($users as $username) {
                $new_entry = $namespace . "\t" . $username . "\t16"; // Permiso de borrar
                if (!isset($existing_perms[$new_entry])) {
                    $acl_lines[] = $new_entry . "\n";
                    $existing_perms[$new_entry] = true;
                    $added_count++;
                }
            }
        }

        // Guardar ACL
        if (file_put_contents($acl_file, implode('', $acl_lines)) !== false) {
            $this->message = sprintf($this->getLang('migrate_success'), $added_count, count($migrations));
            $this->message_type = 'success';
        } else {
            $this->message = $this->getLang('migrate_error');
            $this->message_type = 'error';
        }
    }

    /**
     * Obtiene los usuarios que tienen permisos en un proyecto (desde ACL)
     */
    protected function getProjectMembers($project_id)
    {
        $members = [];

        // 1. Primero obtener usuarios del sistema de grupos antiguo
        $legacy_members = $this->getLegacyGroupMembers($project_id);
        foreach ($legacy_members as $username) {
            $members[$username] = true;
        }

        // 2. Luego agregar usuarios del ACL
        $acl_file = DOKU_CONF . 'acl.auth.php';
        if (file_exists($acl_file)) {
            $project_namespace = 'es:bez:thread:id:' . $project_id;

            $lines = file($acl_file);
            foreach ($lines as $line) {
                $trimmed = trim($line);
                if (empty($trimmed) || $trimmed[0] === '#') continue;

                // Buscar permisos para este proyecto (solo usuarios, no grupos)
                if (preg_match('/^' . preg_quote($project_namespace, '/') . '\s+([^@\s]+)\s+(\d+)/', $trimmed, $matches)) {
                    $username = $matches[1];
                    $perm_level = (int)$matches[2];
                    if ($perm_level > 0) {
                        $members[$username] = true;
                    }
                }
            }
        }

        return array_keys($members);
    }

    /**
     * Genera la salida HTML
     */
    public function html()
    {
        global $ID;

        echo '<div id="projectacl_manager">';
        echo '<h1>' . $this->getLang('menu') . '</h1>';

        // Mostrar información si no hay proyectos
        if (empty($this->projects)) {
            $sqlite = $this->getDatabase();
            if (!$sqlite) {
                echo '<div class="error">No se pudo conectar a la base de datos.</div>';
            } else {
                echo '<div class="info">No se encontraron proyectos en la base de datos.</div>';
            }
        }

        // Mostrar mensaje si existe
        if ($this->message) {
            $class = $this->message_type === 'success' ? 'notify' : 'error';
            echo '<div class="' . $class . '">' . hsc($this->message) . '</div>';
        }

        echo '<div class="level1">';

        // Mostrar tabla de proyectos
        $this->printProjectsTable();

        echo '</div>';

        // Si hay proyecto seleccionado, mostrar formulario de usuarios
        if ($this->selected_project) {
            $this->printUserAssignmentForm();
        }

        echo '</div>';

        // Estilos CSS
        $this->printStyles();
    }

    /**
     * Imprime los botones de exportar/importar
     */
    protected function printExportImportButtons()
    {
        global $ID;

        echo '<div class="export-import-section">';

        // Botón de exportar
        echo '<div class="export-box">';
        echo '<h3>' . $this->getLang('export_title') . '</h3>';
        echo '<p>' . $this->getLang('export_desc') . '</p>';
        echo '<form method="post" action="' . wl($ID) . '">';
        echo '<input type="hidden" name="do" value="admin" />';
        echo '<input type="hidden" name="page" value="projectacl" />';
        echo '<input type="hidden" name="action" value="export" />';
        echo '<input type="hidden" name="sectok" value="' . getSecurityToken() . '" />';
        echo '<button type="submit" class="btn btn-primary">' . $this->getLang('export_button') . '</button>';
        echo '</form>';
        echo '</div>';

        // Formulario de importar
        echo '<div class="import-box">';
        echo '<h3>' . $this->getLang('import_title') . '</h3>';
        echo '<p>' . $this->getLang('import_desc') . '</p>';
        echo '<form method="post" action="' . wl($ID) . '" enctype="multipart/form-data">';
        echo '<input type="hidden" name="do" value="admin" />';
        echo '<input type="hidden" name="page" value="projectacl" />';
        echo '<input type="hidden" name="action" value="import" />';
        echo '<input type="hidden" name="sectok" value="' . getSecurityToken() . '" />';
        echo '<input type="file" name="csvfile" accept=".csv" required />';
        echo ' <button type="submit" class="btn btn-info">' . $this->getLang('import_button') . '</button>';
        echo '</form>';
        echo '</div>';

        echo '</div>';

        // Segunda fila: Migrar y Depurar
        echo '<div class="export-import-section">';

        // Botón de migrar grupos antiguos
        echo '<div class="migrate-box">';
        echo '<h3>' . $this->getLang('migrate_title') . '</h3>';
        echo '<p>' . $this->getLang('migrate_desc') . '</p>';
        echo '<form method="post" action="' . wl($ID) . '">';
        echo '<input type="hidden" name="do" value="admin" />';
        echo '<input type="hidden" name="page" value="projectacl" />';
        echo '<input type="hidden" name="action" value="migrate" />';
        echo '<input type="hidden" name="sectok" value="' . getSecurityToken() . '" />';
        echo '<button type="submit" class="btn btn-warning">' . $this->getLang('migrate_button') . '</button>';
        echo '</form>';
        echo '</div>';

        // Botón de depurar ACL
        echo '<div class="cleanup-box">';
        echo '<h3>' . $this->getLang('cleanup_title') . '</h3>';
        echo '<p>' . $this->getLang('cleanup_desc') . '</p>';
        echo '<form method="post" action="' . wl($ID) . '">';
        echo '<input type="hidden" name="do" value="admin" />';
        echo '<input type="hidden" name="page" value="projectacl" />';
        echo '<input type="hidden" name="action" value="cleanup" />';
        echo '<input type="hidden" name="sectok" value="' . getSecurityToken() . '" />';
        echo '<button type="submit" class="btn btn-danger">' . $this->getLang('cleanup_button') . '</button>';
        echo '</form>';
        echo '</div>';

        echo '</div>';
    }

    /**
     * Imprime la tabla de proyectos
     */
    protected function printProjectsTable()
    {
        global $ID;

        echo '<h2>' . $this->getLang('projects_list') . '</h2>';

        // Botones de exportar/importar
        $this->printExportImportButtons();

        if (empty($this->projects)) {
            echo '<p class="info">No se encontraron proyectos en la base de datos.</p>';
            return;
        }

        echo '<div class="table">';
        echo '<table class="inline">';
        echo '<thead>';
        echo '<tr>';
        echo '<th>' . $this->getLang('project_id') . '</th>';
        echo '<th>' . $this->getLang('client_name') . '</th>';
        echo '<th>' . $this->getLang('project_type') . '</th>';
        echo '<th>' . $this->getLang('framework') . '</th>';
        echo '<th>' . $this->getLang('year') . '</th>';
        echo '<th>' . $this->getLang('coordinator') . '</th>';
        echo '<th>' . $this->getLang('assigned_users') . '</th>';
        echo '<th>' . $this->getLang('actions') . '</th>';
        echo '</tr>';
        echo '</thead>';
        echo '<tbody>';

        foreach ($this->projects as $project) {
            $project_id = $project['id'];
            $members = $this->getProjectMembers($project_id);
            $project_type = $this->extractProjectType($project['title']);
            $framework = $this->extractFramework($project['title']);
            $year = $this->extractYear($project['title']);

            // URL al proyecto en bez
            $project_url = wl('es:bez:thread:id:' . $project_id);

            $row_class = ($this->selected_project == $project_id) ? ' class="selected"' : '';

            echo '<tr' . $row_class . '>';
            echo '<td><a href="' . $project_url . '" title="Ver proyecto"><strong>P:' . hsc($project_id) . '</strong></a></td>';
            echo '<td>' . hsc($project['label_name'] ?? 'Sin cliente') . '</td>';
            echo '<td>' . hsc($project_type) . '</td>';
            echo '<td>' . hsc($framework) . '</td>';
            echo '<td>' . hsc($year) . '</td>';
            echo '<td>' . hsc($project['coordinator']) . '</td>';
            echo '<td>';
            if (!empty($members)) {
                echo '<span class="member-count">' . count($members) . ' usuario(s)</span>';
                echo '<br><small>' . hsc(implode(', ', array_slice($members, 0, 3)));
                if (count($members) > 3) echo '...';
                echo '</small>';
            } else {
                echo '<span class="no-members">Sin asignar</span>';
            }
            echo '</td>';
            echo '<td style="text-align:center;">';
            echo '<a href="' . wl($ID, ['do' => 'admin', 'page' => 'projectacl', 'project' => $project_id]) . '" class="btn btn-icon" title="' . $this->getLang('manage_users') . '">';
            echo '&#9998;'; // Pencil icon
            echo '</a>';
            echo '</td>';
            echo '</tr>';
        }

        echo '</tbody>';
        echo '</table>';
        echo '</div>';
    }

    /**
     * Extrae el marco normativo del título (NIAS, NAGAS, etc.)
     */
    protected function extractFramework($title)
    {
        if (stripos($title, 'NAGAS') !== false) {
            return 'NAGAS';
        }
        if (stripos($title, 'NIAS') !== false || stripos($title, 'Revisoría') !== false) {
            return 'NIAS';
        }
        if (stripos($title, 'Outsourcing') !== false || stripos($title, 'Asesoría') !== false) {
            return 'N/A';
        }
        return '-';
    }

    /**
     * Extrae el año del título
     */
    protected function extractYear($title)
    {
        if (preg_match('/Año:\s*(\d{4})/', $title, $matches)) {
            return $matches[1];
        }
        if (preg_match('/(\d{4})/', $title, $matches)) {
            return $matches[1];
        }
        return '-';
    }

    /**
     * Niveles de permiso ACL de DokuWiki
     */
    protected function getPermissionLevels()
    {
        return [
            0  => 'perm_none',    // Ninguno
            1  => 'perm_read',    // Leer
            2  => 'perm_edit',    // Editar
            4  => 'perm_create',  // Crear
            8  => 'perm_upload',  // Subir archivo
            16 => 'perm_delete',  // Borrar
        ];
    }

    /**
     * Obtiene los permisos ACL de usuarios para un proyecto
     * También verifica usuarios del sistema de grupos antiguo (@proyecto{id})
     */
    protected function getUserPermissionsForProject($project_id)
    {
        $permissions = [];
        $acl_file = DOKU_CONF . 'acl.auth.php';

        // 1. Primero obtener usuarios del sistema de grupos antiguo
        $legacy_users = $this->getLegacyGroupMembers($project_id);
        foreach ($legacy_users as $username) {
            // Asignar permiso de Borrar (16) por defecto a usuarios del sistema antiguo
            $permissions[$username] = 16;
        }

        if (!file_exists($acl_file)) return $permissions;

        // 2. Luego sobrescribir con permisos explícitos del ACL
        $project_pattern = 'es:bez:thread:id:' . $project_id;

        $lines = file($acl_file);
        foreach ($lines as $line) {
            $trimmed = trim($line);
            if (empty($trimmed) || $trimmed[0] === '#') continue;

            // Formato: namespace    user_or_@group    permission
            if (preg_match('/^' . preg_quote($project_pattern, '/') . '\s+([^\s]+)\s+(\d+)/', $trimmed, $matches)) {
                $user_or_group = $matches[1];
                $perm_level = (int)$matches[2];

                // Solo usuarios (no grupos que empiezan con @)
                if ($user_or_group[0] !== '@') {
                    $permissions[$user_or_group] = $perm_level;
                }
            }
        }

        return $permissions;
    }

    /**
     * Obtiene usuarios del sistema de grupos de proyecto (@proyecto{id} o @p{id})
     */
    protected function getLegacyGroupMembers($project_id)
    {
        $members = [];
        // Soportar ambos patrones: el antiguo "proyecto{id}" y el nuevo "p{id}"
        $group_patterns = [
            'proyecto' . $project_id,
            'p' . $project_id
        ];
        $users_file = DOKU_CONF . 'users.auth.php';

        if (!file_exists($users_file)) return $members;

        $lines = file($users_file);
        foreach ($lines as $line) {
            $trimmed = trim($line);
            if (empty($trimmed) || $trimmed[0] === '#') continue;

            $parts = explode(':', $trimmed);
            if (count($parts) >= 5) {
                $username = $parts[0];
                $groups = array_map('trim', explode(',', $parts[4]));
                // Verificar si el usuario pertenece a alguno de los patrones de grupo
                foreach ($group_patterns as $group_name) {
                    if (in_array($group_name, $groups)) {
                        $members[] = $username;
                        break; // Evitar duplicados si está en ambos grupos
                    }
                }
            }
        }

        return $members;
    }

    /**
     * Imprime el formulario de asignación de usuarios con tabla de permisos
     */
    protected function printUserAssignmentForm()
    {
        global $ID;

        // Buscar el proyecto seleccionado
        $project = null;
        foreach ($this->projects as $p) {
            if ($p['id'] == $this->selected_project) {
                $project = $p;
                break;
            }
        }

        if (!$project) return;

        $user_permissions = $this->getUserPermissionsForProject($this->selected_project);
        $project_type = $this->extractProjectType($project['title']);
        $perm_levels = $this->getPermissionLevels();

        echo '<div class="level2">';
        echo '<h2>' . sprintf($this->getLang('users_for_project'), 'P:' . $this->selected_project) . '</h2>';

        // Info del proyecto
        echo '<div class="project-info">';
        echo '<p><strong>' . $this->getLang('client_name') . ':</strong> ' . hsc($project['label_name'] ?? 'Sin cliente') . '</p>';
        echo '<p><strong>' . $this->getLang('project_type') . ':</strong> ' . hsc($project_type) . '</p>';
        echo '<p><strong>' . $this->getLang('coordinator') . ':</strong> ' . hsc($project['coordinator']) . '</p>';
        echo '<p><strong>Namespace:</strong> es:bez:thread:id:' . $this->selected_project . '</p>';
        echo '</div>';

        // Formulario
        echo '<form method="post" action="' . wl($ID) . '">';
        echo '<input type="hidden" name="do" value="admin" />';
        echo '<input type="hidden" name="page" value="projectacl" />';
        echo '<input type="hidden" name="action" value="save" />';
        echo '<input type="hidden" name="project" value="' . $this->selected_project . '" />';
        echo '<input type="hidden" name="sectok" value="' . getSecurityToken() . '" />';

        // Tabla de permisos
        echo '<div class="table permissions-table-wrapper">';
        echo '<table class="inline permissions-table">';
        echo '<thead>';
        echo '<tr>';
        echo '<th>' . $this->getLang('user_name') . '</th>';
        foreach ($perm_levels as $level => $lang_key) {
            echo '<th class="perm-col">' . $this->getLang($lang_key) . '</th>';
        }
        echo '</tr>';
        echo '</thead>';
        echo '<tbody>';

        // Cargar usuarios
        $users_list = $this->getAllUsers();

        foreach ($users_list as $username => $realname) {
            $current_perm = isset($user_permissions[$username]) ? $user_permissions[$username] : 0;
            $display_name = $realname ? $realname . ' (' . $username . ')' : $username;

            // Resaltar si tiene algún permiso
            $row_class = ($current_perm > 0) ? ' class="has-permission"' : '';

            echo '<tr' . $row_class . '>';
            echo '<td class="user-cell">' . hsc($display_name) . '</td>';

            foreach ($perm_levels as $level => $lang_key) {
                $checked = ($current_perm == $level) ? ' checked="checked"' : '';
                $input_id = 'perm_' . $username . '_' . $level;
                echo '<td class="perm-col">';
                echo '<input type="radio" name="perms[' . hsc($username) . ']" value="' . $level . '" id="' . $input_id . '"' . $checked . ' />';
                echo '</td>';
            }

            echo '</tr>';
        }

        echo '</tbody>';
        echo '</table>';
        echo '</div>';

        echo '<p class="buttons">';
        echo '<button type="submit" class="btn btn-primary">' . $this->getLang('save') . '</button>';
        echo ' <a href="' . wl($ID, ['do' => 'admin', 'page' => 'projectacl']) . '" class="btn">' . $this->getLang('cancel') . '</a>';
        echo '</p>';

        echo '</form>';
        echo '</div>';
    }

    /**
     * Obtiene todos los usuarios del sistema
     */
    protected function getAllUsers()
    {
        $users = [];
        $users_file = DOKU_CONF . 'users.auth.php';

        if (file_exists($users_file)) {
            $lines = file($users_file);
            foreach ($lines as $line) {
                $trimmed = trim($line);
                if (empty($trimmed) || $trimmed[0] === '#') continue;

                $parts = explode(':', $trimmed);
                if (count($parts) >= 4) {
                    $username = $parts[0];
                    $realname = $parts[2];
                    $users[$username] = $realname;
                }
            }
        }

        return $users;
    }

    /**
     * Extrae el tipo de proyecto del título
     */
    protected function extractProjectType($title)
    {
        $types = [
            'Revisoría fiscal' => 'Revisoría fiscal',
            'Outsourcing' => 'Outsourcing',
            'Asesoría' => 'Asesoría',
            'Otros proyectos' => 'Otros proyectos',
            'Reporte' => 'Reporte de gestión',
        ];

        foreach ($types as $search => $type) {
            if (stripos($title, $search) !== false) {
                return $type;
            }
        }

        return 'Otro';
    }

    /**
     * Exporta los permisos a CSV
     */
    protected function exportCSV()
    {
        // Headers para descarga de archivo
        header('Content-Type: text/csv; charset=utf-8');
        header('Content-Disposition: attachment; filename="permisos_proyectos_' . date('Y-m-d_His') . '.csv"');
        header('Pragma: no-cache');
        header('Expires: 0');

        // BOM para Excel (UTF-8)
        echo "\xEF\xBB\xBF";

        $output = fopen('php://output', 'w');

        // Leyenda de permisos
        fputcsv($output, ['# Niveles de permiso: 0=Ninguno, 1=Leer, 2=Editar, 4=Crear, 8=Subir, 16=Borrar'], ';');

        // Cabecera: Usuario, Nombre, P:1, P:2, P:3...
        $header = ['Usuario', 'Nombre'];
        foreach ($this->projects as $project) {
            $header[] = 'P:' . $project['id'];
        }
        fputcsv($output, $header, ';');

        // Obtener permisos de todos los proyectos
        $all_permissions = [];
        foreach ($this->projects as $project) {
            $all_permissions[$project['id']] = $this->getUserPermissionsForProject($project['id']);
        }

        // Leer usuarios del archivo
        $users_list = $this->getAllUsers();

        foreach ($users_list as $username => $realname) {
            $row = [$username, $realname];

            // Para cada proyecto, obtener el nivel de permiso
            foreach ($this->projects as $project) {
                $perm_level = isset($all_permissions[$project['id']][$username])
                    ? $all_permissions[$project['id']][$username]
                    : 0;
                $row[] = $perm_level;
            }

            fputcsv($output, $row, ';');
        }

        fclose($output);
    }

    /**
     * Importa permisos desde CSV
     */
    protected function importCSV()
    {
        if (!isset($_FILES['csvfile']) || $_FILES['csvfile']['error'] !== UPLOAD_ERR_OK) {
            $this->message = $this->getLang('import_no_file');
            $this->message_type = 'error';
            return;
        }

        $file = $_FILES['csvfile']['tmp_name'];
        $handle = fopen($file, 'r');

        if (!$handle) {
            $this->message = $this->getLang('import_error');
            $this->message_type = 'error';
            return;
        }

        // Saltar línea de leyenda si existe
        $first_line = fgetcsv($handle, 0, ';');
        if ($first_line && strpos($first_line[0], '#') === 0) {
            // Es una línea de comentario, leer la siguiente como cabecera
            $header = fgetcsv($handle, 0, ';');
        } else {
            // La primera línea es la cabecera
            $header = $first_line;
        }

        if (!$header || count($header) < 3) {
            // Intentar con coma si el punto y coma no funciona
            rewind($handle);
            fgetcsv($handle, 0, ','); // Saltar primera línea
            $header = fgetcsv($handle, 0, ',');
        }

        if (!$header || count($header) < 3) {
            $this->message = $this->getLang('import_invalid_format');
            $this->message_type = 'error';
            fclose($handle);
            return;
        }

        // Limpiar BOM si existe
        $header[0] = preg_replace('/^\xEF\xBB\xBF/', '', $header[0]);

        // Extraer IDs de proyectos de la cabecera (P:1, P:2, etc.)
        $project_columns = [];
        for ($i = 2; $i < count($header); $i++) {
            if (preg_match('/P:(\d+)/', $header[$i], $matches)) {
                $project_columns[$i] = $matches[1];
            }
        }

        if (empty($project_columns)) {
            $this->message = $this->getLang('import_no_projects');
            $this->message_type = 'error';
            fclose($handle);
            return;
        }

        // Leer datos del CSV
        $csv_data = [];
        while (($row = fgetcsv($handle, 0, ';')) !== false) {
            if (count($row) < 3) {
                // Intentar con coma
                $row = str_getcsv(implode(';', $row), ',');
            }
            // Ignorar líneas de comentario
            if (!empty($row[0]) && strpos($row[0], '#') !== 0) {
                $csv_data[$row[0]] = $row;
            }
        }
        fclose($handle);

        // Leer archivo ACL actual
        $acl_file = DOKU_CONF . 'acl.auth.php';
        $acl_lines = [];
        if (file_exists($acl_file)) {
            $acl_lines = file($acl_file);
        }

        // Filtrar líneas de proyectos importados (usuarios individuales)
        $new_acl_lines = [];
        $project_ids = array_values($project_columns);

        foreach ($acl_lines as $line) {
            $trimmed = trim($line);

            // Mantener comentarios y líneas vacías
            if (empty($trimmed) || $trimmed[0] === '#') {
                $new_acl_lines[] = $line;
                continue;
            }

            // Verificar si es una línea de alguno de los proyectos importados
            $is_project_line = false;
            foreach ($project_ids as $pid) {
                $namespace = 'es:bez:thread:id:' . $pid;
                if (preg_match('/^' . preg_quote($namespace, '/') . '\s+([^@\s]+)\s+\d+/', $trimmed)) {
                    $is_project_line = true;
                    break;
                }
            }

            if (!$is_project_line) {
                $new_acl_lines[] = $line;
            }
        }

        // Agregar nuevos permisos desde CSV
        $permissions_added = 0;
        foreach ($csv_data as $username => $row) {
            foreach ($project_columns as $col_index => $project_id) {
                $perm_level = isset($row[$col_index]) ? (int)trim($row[$col_index]) : 0;
                if ($perm_level > 0) {
                    $namespace = 'es:bez:thread:id:' . $project_id;
                    $new_acl_lines[] = $namespace . "\t" . $username . "\t" . $perm_level . "\n";
                    $permissions_added++;
                }
            }
        }

        // Guardar archivo ACL
        if (file_put_contents($acl_file, implode('', $new_acl_lines)) !== false) {
            $this->message = sprintf($this->getLang('import_success'), $permissions_added);
            $this->message_type = 'success';
        } else {
            $this->message = $this->getLang('import_error');
            $this->message_type = 'error';
        }
    }

    /**
     * Imprime los estilos CSS
     */
    protected function printStyles()
    {
        echo '<style>
            /* Ocultar información de versión de DokuWiki */
            .dokuwiki .docInfo,
            .docInfo,
            #dokuwiki__footer .doc,
            #dokuwiki__footer .docInfo,
            /* Ocultar sidebar derecho completo en admin */
            .mode_admin #dokuwiki__aside,
            .mode_admin .dokuwiki__aside,
            .mode_admin aside#dokuwiki__aside,
            .mode_admin .aside,
            #dokuwiki__aside,
            /* Ocultar info de versión específica */
            .wrapper .aside,
            div.doc,
            p.doc {
                display: none !important;
            }

            /* Estilos para el menú de administración */
            .dokuwiki .mode_admin #admin__menu,
            #admin__menu {
                background: linear-gradient(145deg, #1e3a5f 0%, #2d5a87 50%, #1e3a5f 100%);
                border-radius: 16px;
                padding: 24px 20px;
                box-shadow: 0 20px 60px rgba(30, 58, 95, 0.4);
                border: 1px solid rgba(255, 255, 255, 0.1);
            }
            .dokuwiki .mode_admin #admin__menu ul,
            #admin__menu ul {
                list-style: none !important;
                padding: 0 !important;
                margin: 0 !important;
                display: flex !important;
                flex-direction: column !important;
                gap: 10px !important;
            }
            .dokuwiki .mode_admin #admin__menu li,
            #admin__menu li {
                margin: 0 !important;
                padding: 0 !important;
                list-style: none !important;
            }
            .dokuwiki .mode_admin #admin__menu li a,
            #admin__menu li a {
                display: flex !important;
                align-items: center !important;
                gap: 14px !important;
                padding: 16px 22px !important;
                background: linear-gradient(135deg, rgba(255,255,255,0.95) 0%, rgba(248,250,252,0.95) 100%) !important;
                border-radius: 12px !important;
                text-decoration: none !important;
                color: #1e3a5f !important;
                font-weight: 600 !important;
                font-size: 14px !important;
                letter-spacing: 0.3px !important;
                transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1) !important;
                border: 2px solid transparent !important;
                box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1), inset 0 1px 0 rgba(255,255,255,0.8) !important;
                position: relative !important;
                overflow: hidden !important;
            }
            .dokuwiki .mode_admin #admin__menu li a::before,
            #admin__menu li a::before {
                content: "" !important;
                position: absolute !important;
                left: 0 !important;
                top: 0 !important;
                width: 4px !important;
                height: 100% !important;
                background: linear-gradient(180deg, #3b82f6, #1d4ed8) !important;
                transform: scaleY(0) !important;
                transition: transform 0.3s ease !important;
                border-radius: 0 4px 4px 0 !important;
            }
            .dokuwiki .mode_admin #admin__menu li a:hover::before,
            #admin__menu li a:hover::before {
                transform: scaleY(1) !important;
            }
            .dokuwiki .mode_admin #admin__menu li a:hover,
            #admin__menu li a:hover {
                transform: translateX(8px) scale(1.02) !important;
                background: linear-gradient(135deg, #ffffff 0%, #f0f7ff 100%) !important;
                border-color: #3b82f6 !important;
                color: #1d4ed8 !important;
                box-shadow: 0 12px 35px rgba(59, 130, 246, 0.3), inset 0 1px 0 rgba(255,255,255,1) !important;
            }
            .dokuwiki .mode_admin #admin__menu li a:active,
            #admin__menu li a:active {
                transform: translateX(6px) scale(1.01) !important;
            }
            .dokuwiki .mode_admin #admin__menu li a svg,
            .dokuwiki .mode_admin #admin__menu li a img,
            #admin__menu li a svg,
            #admin__menu li a img {
                width: 24px !important;
                height: 24px !important;
                opacity: 0.85 !important;
                transition: all 0.3s ease !important;
                filter: drop-shadow(0 2px 4px rgba(0,0,0,0.1)) !important;
            }
            .dokuwiki .mode_admin #admin__menu li a:hover svg,
            .dokuwiki .mode_admin #admin__menu li a:hover img,
            #admin__menu li a:hover svg,
            #admin__menu li a:hover img {
                opacity: 1 !important;
                transform: scale(1.15) rotate(5deg) !important;
            }
            /* Estilo para elemento activo */
            .dokuwiki .mode_admin #admin__menu li.active a,
            #admin__menu li.active a {
                background: linear-gradient(135deg, #3b82f6 0%, #1d4ed8 100%) !important;
                color: white !important;
                border-color: transparent !important;
                box-shadow: 0 8px 30px rgba(59, 130, 246, 0.5) !important;
            }
            .dokuwiki .mode_admin #admin__menu li.active a:hover,
            #admin__menu li.active a:hover {
                box-shadow: 0 12px 40px rgba(59, 130, 246, 0.6) !important;
            }
            .dokuwiki .mode_admin #admin__menu li.active a svg,
            .dokuwiki .mode_admin #admin__menu li.active a img,
            #admin__menu li.active a svg,
            #admin__menu li.active a img {
                filter: brightness(0) invert(1) drop-shadow(0 2px 4px rgba(0,0,0,0.2)) !important;
            }

            #projectacl_manager { max-width: 100%; }
            #projectacl_manager .notify {
                background: #dff0d8;
                border: 1px solid #3c763d;
                color: #3c763d;
                padding: 10px;
                margin: 10px 0;
                border-radius: 4px;
            }
            #projectacl_manager .error {
                background: #f2dede;
                border: 1px solid #a94442;
                color: #a94442;
                padding: 10px;
                margin: 10px 0;
                border-radius: 4px;
            }
            #projectacl_manager .info {
                background: #d9edf7;
                border: 1px solid #31708f;
                color: #31708f;
                padding: 10px;
                margin: 10px 0;
                border-radius: 4px;
            }
            #projectacl_manager table.inline { width: 100%; }
            #projectacl_manager table.inline th { background: #f5f5f5; text-align: left; padding: 8px; }
            #projectacl_manager table.inline td { padding: 8px; }
            #projectacl_manager table.inline tr.selected { background: #e8f4fc; }
            #projectacl_manager table.inline tr:hover { background: #f9f9f9; }
            #projectacl_manager .member-count {
                background: #5cb85c;
                color: white;
                padding: 2px 8px;
                border-radius: 10px;
                font-size: 12px;
            }
            #projectacl_manager .no-members {
                color: #999;
                font-style: italic;
            }
            #projectacl_manager .btn {
                display: inline-block;
                padding: 5px 10px;
                background: #f5f5f5;
                border: 1px solid #ddd;
                border-radius: 3px;
                text-decoration: none;
                color: #333;
                cursor: pointer;
                font-size: 13px;
            }
            #projectacl_manager .btn:hover { background: #e8e8e8; }
            #projectacl_manager .btn-primary {
                background: #337ab7;
                color: white;
                border-color: #2e6da4;
            }
            #projectacl_manager .btn-primary:hover { background: #286090; }
            #projectacl_manager .btn-info {
                background: #5bc0de;
                color: white;
                border-color: #46b8da;
            }
            #projectacl_manager .btn-info:hover { background: #31b0d5; }
            #projectacl_manager .btn-warning {
                background: #f0ad4e;
                color: white;
                border-color: #eea236;
            }
            #projectacl_manager .btn-warning:hover { background: #ec971f; }
            #projectacl_manager .btn-danger {
                background: #d9534f;
                color: white;
                border-color: #d43f3a;
            }
            #projectacl_manager .btn-danger:hover { background: #c9302c; }
            #projectacl_manager .btn-small { padding: 3px 8px; font-size: 12px; }
            #projectacl_manager .btn-icon {
                padding: 4px 10px;
                font-size: 16px;
                line-height: 1;
            }
            #projectacl_manager .btn-icon:hover {
                background: #337ab7;
                color: white;
            }
            #projectacl_manager .project-info {
                background: #f9f9f9;
                padding: 15px;
                border: 1px solid #ddd;
                border-radius: 4px;
                margin-bottom: 20px;
            }
            #projectacl_manager .project-info p { margin: 5px 0; }
            #projectacl_manager .user-list {
                max-height: 400px;
                overflow-y: auto;
                border: 1px solid #ddd;
                padding: 10px;
                background: white;
            }
            #projectacl_manager .user-checkbox {
                display: block;
                padding: 5px;
                cursor: pointer;
            }
            #projectacl_manager .user-checkbox:hover { background: #f5f5f5; }
            #projectacl_manager .buttons { margin-top: 15px; }
            #projectacl_manager fieldset { margin-bottom: 20px; padding: 15px; }
            #projectacl_manager legend { font-weight: bold; padding: 0 10px; }
            #projectacl_manager .export-import-section {
                display: flex;
                gap: 20px;
                margin-bottom: 20px;
                flex-wrap: wrap;
            }
            #projectacl_manager .export-box,
            #projectacl_manager .import-box,
            #projectacl_manager .migrate-box,
            #projectacl_manager .cleanup-box {
                flex: 1;
                min-width: 250px;
                background: #f9f9f9;
                border: 1px solid #ddd;
                border-radius: 4px;
                padding: 15px;
            }
            #projectacl_manager .migrate-box {
                background: #fcf8e3;
                border-color: #faebcc;
            }
            #projectacl_manager .cleanup-box {
                background: #f2dede;
                border-color: #ebccd1;
            }
            #projectacl_manager .export-box h3,
            #projectacl_manager .import-box h3,
            #projectacl_manager .migrate-box h3,
            #projectacl_manager .cleanup-box h3 {
                margin-top: 0;
                font-size: 14px;
                color: #333;
            }
            #projectacl_manager .export-box p,
            #projectacl_manager .import-box p,
            #projectacl_manager .migrate-box p,
            #projectacl_manager .cleanup-box p {
                font-size: 12px;
                color: #666;
                margin: 5px 0 10px;
            }
            #projectacl_manager input[type="file"] {
                font-size: 13px;
                margin-right: 10px;
            }
            /* Tabla de permisos */
            #projectacl_manager .permissions-table-wrapper {
                max-height: 500px;
                overflow-y: auto;
                margin-bottom: 15px;
            }
            #projectacl_manager .permissions-table {
                width: 100%;
                border-collapse: collapse;
            }
            #projectacl_manager .permissions-table th {
                background: #f5f5f5;
                padding: 10px 8px;
                text-align: center;
                font-weight: bold;
                position: sticky;
                top: 0;
                border-bottom: 2px solid #ddd;
            }
            #projectacl_manager .permissions-table th:first-child {
                text-align: left;
                min-width: 200px;
            }
            #projectacl_manager .permissions-table td {
                padding: 8px;
                border-bottom: 1px solid #eee;
            }
            #projectacl_manager .permissions-table .user-cell {
                text-align: left;
            }
            #projectacl_manager .permissions-table .perm-col {
                text-align: center;
                width: 70px;
            }
            #projectacl_manager .permissions-table tr:hover {
                background: #f9f9f9;
            }
            #projectacl_manager .permissions-table tr.has-permission {
                background: #e8f5e9;
            }
            #projectacl_manager .permissions-table tr.has-permission:hover {
                background: #c8e6c9;
            }
            #projectacl_manager .permissions-table input[type="radio"] {
                cursor: pointer;
                width: 16px;
                height: 16px;
            }
        </style>';
    }
}
