<?php
/**
 * fix_acl - aplicar.php
 * ======================
 * Parche para corregir conf/acl.auth.php en todas las wikis.
 * Agrega/actualiza reglas base estándar, preserva reglas custom.
 *
 * Endpoints AJAX:
 *   action=scan      -> Escanea todas las wikis, retorna estado ACL
 *   action=preview   -> Muestra preview de cambios para una wiki
 *   action=fix_one   -> Corrige una wiki (POST con wiki path)
 */

// ============================================================
// ERROR HANDLER GLOBAL
// ============================================================

// Detectar AJAX antes de definir handlers (evita llamar función no definida)
$_IS_AJAX = !empty($_SERVER['HTTP_X_REQUESTED_WITH'])
    && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest';

set_error_handler(function($severity, $message, $file, $line) {
    if (!(error_reporting() & $severity)) return false;
    throw new ErrorException($message, 0, $severity, $file, $line);
});

set_exception_handler(function($e) {
    global $_IS_AJAX;
    if ($_IS_AJAX) {
        if (!headers_sent()) header('Content-Type: application/json; charset=UTF-8');
        echo json_encode([
            'ok' => false,
            'error' => $e->getMessage() . ' en ' . basename($e->getFile()) . ':' . $e->getLine()
        ], JSON_UNESCAPED_UNICODE);
    } else {
        if (!headers_sent()) header('Content-Type: text/html; charset=utf-8');
        echo '<div style="background:#3d0000;color:#ff6b6b;padding:20px;border-radius:8px;margin:20px;">';
        echo '<strong>Error fatal:</strong> ' . htmlspecialchars($e->getMessage());
        echo '<br><small>' . htmlspecialchars(basename($e->getFile())) . ':' . $e->getLine() . '</small>';
        echo '</div>';
    }
    exit(1);
});

register_shutdown_function(function() {
    global $_IS_AJAX;
    $err = error_get_last();
    if ($err && in_array($err['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR])) {
        if ($_IS_AJAX) {
            if (!headers_sent()) header('Content-Type: application/json; charset=utf-8');
            echo json_encode(['ok' => false, 'error' => 'Fatal: ' . $err['message']], JSON_UNESCAPED_UNICODE);
        } else {
            if (!headers_sent()) header('Content-Type: text/html; charset=utf-8');
            echo '<div style="background:#3d0000;color:#ff6b6b;padding:20px;font-family:monospace">';
            echo '<h2>Error Fatal</h2><pre>' . htmlspecialchars($err['message']) . '</pre></div>';
        }
    }
});

require_once __DIR__ . '/config.php';

set_time_limit(MAX_EXECUTION_TIME);
ini_set('memory_limit', MEMORY_LIMIT);

// ============================================================
// UTILIDADES
// ============================================================

function isAjax() {
    global $_IS_AJAX;
    return $_IS_AJAX;
}

function writeLog($msg) {
    @file_put_contents(LOG_FILE, date('[Y-m-d H:i:s] ') . $msg . "\n", FILE_APPEND);
}

function jsonResponse($data) {
    header('Content-Type: application/json; charset=UTF-8');
    echo json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
    exit;
}

// ============================================================
// ESCANEO DE WIKIS
// ============================================================

function scanDirectory($path) {
    $sites = [];
    if (!is_dir($path)) return $sites;
    $items = @scandir($path);
    if ($items === false) return $sites;

    foreach ($items as $item) {
        if ($item === '.' || $item === '..') continue;
        if ($item[0] === '_') continue;
        $fullPath = $path . '/' . $item;
        if (is_link($fullPath)) continue;
        if (!is_dir($fullPath)) continue;
        if (isDokuWiki($fullPath)) {
            $sites[] = $fullPath;
        }
    }
    sort($sites);
    return $sites;
}

function isDokuWiki($path) {
    return file_exists($path . '/doku.php')
        && (file_exists($path . '/conf/local.php') || file_exists($path . '/conf/dokuwiki.php'))
        && is_dir($path . '/data');
}

// ============================================================
// LÓGICA ACL
// ============================================================

/**
 * Parsea un archivo ACL y retorna array de reglas.
 * Cada regla: ['ns' => namespace, 'group' => '@group', 'perm' => int, 'raw' => linea original]
 */
function parseAcl($lines) {
    $rules = [];
    foreach ($lines as $line) {
        $trimmed = trim($line);
        // Saltar vacías y comentarios
        if ($trimmed === '' || $trimmed[0] === '#') continue;
        // Saltar la línea de seguridad PHP
        if (strpos($trimmed, '<?php') !== false) continue;

        // Formato: namespace\tgroup\tpermission
        // o namespace  group  permission (tabs o espacios)
        $parts = preg_split('/\s+/', $trimmed);
        if (count($parts) >= 3) {
            $rules[] = [
                'ns'    => $parts[0],
                'group' => $parts[1],
                'perm'  => intval($parts[2]),
                'raw'   => $trimmed,
            ];
        }
    }
    return $rules;
}

/**
 * Determina si un grupo es "estándar" (gestionado por el template)
 */
function isStandardGroup($group) {
    global $STANDARD_GROUPS, $GROUPS_TO_REMOVE;
    $upper = strtoupper($group);
    foreach ($STANDARD_GROUPS as $sg) {
        if (strtoupper($sg) === $upper) return true;
    }
    foreach ($GROUPS_TO_REMOVE as $rg) {
        if (strtoupper($rg) === $upper) return true;
    }
    return false;
}

/**
 * Analiza el ACL de una wiki y retorna estado detallado.
 */
function analyzeAcl($wikiPath) {
    global $STANDARD_RULES, $GROUPS_TO_REMOVE;

    $aclFile = $wikiPath . '/conf/acl.auth.php';
    $result = [
        'wiki'          => basename($wikiPath),
        'path'          => $wikiPath,
        'acl_exists'    => file_exists($aclFile),
        'acl_readable'  => is_readable($aclFile),
        'acl_writable'  => is_writable(dirname($aclFile)),
        'base_present'  => 0,
        'base_missing'  => 0,
        'base_wrong'    => 0,
        'custom_rules'  => 0,
        'sie_rules'     => 0,
        'total_rules'   => 0,
        'status'        => 'OK',
        'status_class'  => 'status-ok',
        'details'       => [],
    ];

    if (!$result['acl_exists']) {
        $result['status'] = 'CRITICO';
        $result['status_class'] = 'status-critical';
        $result['base_missing'] = count($STANDARD_RULES);
        $result['details'][] = 'Archivo ACL no existe';
        return $result;
    }

    if (!$result['acl_readable']) {
        $result['status'] = 'CRITICO';
        $result['status_class'] = 'status-critical';
        $result['details'][] = 'Archivo ACL no legible';
        return $result;
    }

    $lines = file($aclFile, FILE_IGNORE_NEW_LINES);
    $rules = parseAcl($lines);
    $result['total_rules'] = count($rules);

    // Construir mapa de reglas existentes: "ns|group" => perm
    $existingMap = [];
    foreach ($rules as $r) {
        $key = $r['ns'] . '|' . strtoupper($r['group']);
        $existingMap[$key] = $r['perm'];
    }

    // Verificar reglas base
    foreach ($STANDARD_RULES as $sr) {
        $key = $sr[0] . '|' . strtoupper($sr[1]);
        if (!isset($existingMap[$key])) {
            $result['base_missing']++;
        } elseif ($existingMap[$key] !== $sr[2]) {
            $result['base_wrong']++;
            $result['base_present']++;
        } else {
            $result['base_present']++;
        }
    }

    // Contar custom rules y @sie rules
    foreach ($rules as $r) {
        $groupUp = strtoupper($r['group']);
        $isSie = false;
        foreach ($GROUPS_TO_REMOVE as $rg) {
            if (strtoupper($rg) === $groupUp) { $isSie = true; break; }
        }
        if ($isSie) {
            $result['sie_rules']++;
        } elseif (!isStandardGroup($r['group'])) {
            $result['custom_rules']++;
        }
    }

    // Determinar status
    $totalBase = count($STANDARD_RULES);
    if ($result['base_missing'] > 0 || $result['base_wrong'] > 0 || $result['sie_rules'] > 0) {
        if ($result['base_present'] === 0 && $result['base_missing'] === $totalBase) {
            $result['status'] = 'CRITICO';
            $result['status_class'] = 'status-critical';
        } else {
            $result['status'] = 'INCOMPLETO';
            $result['status_class'] = 'status-incomplete';
        }
    }

    return $result;
}

/**
 * Genera preview de cambios para una wiki.
 * Retorna array de líneas con tipo: 'add', 'update', 'keep', 'remove', 'custom'
 */
function previewChanges($wikiPath) {
    global $STANDARD_RULES, $GROUPS_TO_REMOVE;

    $aclFile = $wikiPath . '/conf/acl.auth.php';
    $lines = file_exists($aclFile) ? file($aclFile, FILE_IGNORE_NEW_LINES) : [];
    $rules = parseAcl($lines);

    // Mapa existente
    $existingMap = [];
    foreach ($rules as $r) {
        $key = $r['ns'] . '|' . strtoupper($r['group']);
        $existingMap[$key] = $r;
    }

    $preview = [];

    // 1. Reglas base: verificar cada una
    foreach ($STANDARD_RULES as $sr) {
        $key = $sr[0] . '|' . strtoupper($sr[1]);
        $ruleText = $sr[0] . "\t" . $sr[1] . "\t" . $sr[2];

        if (!isset($existingMap[$key])) {
            $preview[] = ['type' => 'add', 'text' => $ruleText, 'detail' => 'Nueva regla'];
        } elseif ($existingMap[$key]['perm'] !== $sr[2]) {
            $oldPerm = $existingMap[$key]['perm'];
            $preview[] = ['type' => 'update', 'text' => $ruleText, 'detail' => "Permiso cambia: $oldPerm -> $sr[2]"];
        } else {
            $preview[] = ['type' => 'keep_base', 'text' => $ruleText, 'detail' => 'Sin cambios'];
        }
    }

    // 2. Reglas @sie: marcar para eliminar
    foreach ($rules as $r) {
        $groupUp = strtoupper($r['group']);
        foreach ($GROUPS_TO_REMOVE as $rg) {
            if (strtoupper($rg) === $groupUp) {
                $preview[] = ['type' => 'remove', 'text' => $r['raw'], 'detail' => 'Grupo @sie eliminado'];
                break;
            }
        }
    }

    // 3. Reglas custom: preservar
    foreach ($rules as $r) {
        if (!isStandardGroup($r['group'])) {
            $preview[] = ['type' => 'custom', 'text' => $r['raw'], 'detail' => 'Regla custom preservada'];
        }
    }

    return $preview;
}

/**
 * Corrige el ACL de una wiki.
 * Retorna ['ok' => bool, 'message' => string, 'changes' => int]
 */
function fixAcl($wikiPath) {
    global $STANDARD_RULES, $GROUPS_TO_REMOVE, $ACL_HEADER;

    $aclFile = $wikiPath . '/conf/acl.auth.php';
    $confDir = $wikiPath . '/conf';
    $wikiName = basename($wikiPath);

    // Asegurar que conf/ existe
    if (!is_dir($confDir)) {
        return ['ok' => false, 'message' => "Directorio conf/ no existe en $wikiName", 'changes' => 0];
    }

    // Leer ACL existente
    $lines = file_exists($aclFile) ? file($aclFile, FILE_IGNORE_NEW_LINES) : [];
    $existingRules = parseAcl($lines);

    // Mapa de reglas existentes
    $existingMap = [];
    foreach ($existingRules as $r) {
        $key = $r['ns'] . '|' . strtoupper($r['group']);
        $existingMap[$key] = $r;
    }

    // Separar reglas custom (no estándar, no @sie)
    $customRules = [];
    foreach ($existingRules as $r) {
        if (!isStandardGroup($r['group'])) {
            $customRules[] = $r['raw'];
        }
    }

    // Contar cambios
    $changes = 0;
    foreach ($STANDARD_RULES as $sr) {
        $key = $sr[0] . '|' . strtoupper($sr[1]);
        if (!isset($existingMap[$key])) {
            $changes++; // nueva regla
        } elseif ($existingMap[$key]['perm'] !== $sr[2]) {
            $changes++; // regla actualizada
        }
    }
    // Contar @sie eliminados
    foreach ($existingRules as $r) {
        $groupUp = strtoupper($r['group']);
        foreach ($GROUPS_TO_REMOVE as $rg) {
            if (strtoupper($rg) === $groupUp) { $changes++; break; }
        }
    }

    // Backup del original
    if (file_exists($aclFile)) {
        $bakFile = $aclFile . '.bak_' . date('Ymd_His');
        if (!@copy($aclFile, $bakFile)) {
            return ['ok' => false, 'message' => "No se pudo crear backup de acl.auth.php en $wikiName", 'changes' => 0];
        }
    }

    // Construir nuevo ACL
    $header = str_replace('%DATE%', date('Y-m-d H:i:s'), $ACL_HEADER);
    $output = $header . "\n";

    // Reglas base
    $output .= "# BASE: Sin acceso por defecto\n";
    $output .= "*\t@ALL\t0\n\n";

    $output .= "# GRUPO LECTURA - Solo lectura\n";
    $output .= "*\t@lectura\t1\n\n";

    $output .= "# GRUPO ADMIN - Administracion (sin acceso a proyectos)\n";
    $output .= "*\t@admin\t8\n";
    $output .= "proyectos:*\t@admin\t1\n";
    $output .= "es:*\t@admin\t1\n\n";

    $output .= "# GRUPO DIRECTOR - Lider de operacion\n";
    $output .= "*\t@director\t8\n";
    $output .= "proyectos:*\t@director\t255\n";
    $output .= "es:*\t@director\t255\n";
    $output .= "comercial:*\t@director\t255\n\n";

    $output .= "# GRUPO EDITARPROYECTO - Gestion proyectos donde participa\n";
    $output .= "*\t@editarproyecto\t1\n";
    $output .= "proyectos:*\t@editarproyecto\t255\n\n";

    $output .= "# GRUPO EDITARTAREAS - Tareas en proyectos donde participa\n";
    $output .= "*\t@editartareas\t1\n";
    $output .= "proyectos:*\t@editartareas\t8\n";

    // Reglas custom
    if (!empty($customRules)) {
        $output .= "\n# ============================================================\n";
        $output .= "# REGLAS CUSTOM (preservadas del ACL original)\n";
        $output .= "# ============================================================\n";
        foreach ($customRules as $cr) {
            $output .= $cr . "\n";
        }
    }

    // Escribir
    $written = @file_put_contents($aclFile, $output);
    if ($written === false) {
        return ['ok' => false, 'message' => "No se pudo escribir acl.auth.php en $wikiName", 'changes' => 0];
    }

    writeLog("FIX $wikiName: $changes cambios, " . count($customRules) . " custom preservadas");

    return [
        'ok'       => true,
        'message'  => "$wikiName: $changes cambios aplicados, " . count($customRules) . " reglas custom preservadas",
        'changes'  => $changes,
        'custom'   => count($customRules),
    ];
}

// ============================================================
// ROUTER AJAX
// ============================================================

$action = isset($_GET['action']) ? $_GET['action'] : (isset($_POST['action']) ? $_POST['action'] : '');

if ($action === 'scan') {
    $sites = scanDirectory(SEARCH_PATH);
    $results = [];
    foreach ($sites as $sitePath) {
        $results[] = analyzeAcl($sitePath);
    }
    // Resumen
    $summary = [
        'total'      => count($results),
        'ok'         => 0,
        'incomplete' => 0,
        'critical'   => 0,
    ];
    foreach ($results as $r) {
        if ($r['status'] === 'OK') $summary['ok']++;
        elseif ($r['status'] === 'INCOMPLETO') $summary['incomplete']++;
        else $summary['critical']++;
    }
    jsonResponse(['ok' => true, 'summary' => $summary, 'wikis' => $results]);
}

if ($action === 'preview') {
    $wiki = isset($_GET['wiki']) ? basename(trim($_GET['wiki'])) : '';
    if (empty($wiki)) jsonResponse(['ok' => false, 'error' => 'Falta parametro wiki']);

    $wikiPath = SEARCH_PATH . '/' . $wiki;
    if (!isDokuWiki($wikiPath)) jsonResponse(['ok' => false, 'error' => "No es una wiki valida: $wiki"]);

    $preview = previewChanges($wikiPath);
    $analysis = analyzeAcl($wikiPath);
    jsonResponse(['ok' => true, 'wiki' => $wiki, 'analysis' => $analysis, 'preview' => $preview]);
}

if ($action === 'fix_one') {
    $wiki = isset($_POST['wiki']) ? $_POST['wiki'] : (isset($_GET['wiki']) ? $_GET['wiki'] : '');
    $wiki = basename(trim($wiki));
    if (empty($wiki)) jsonResponse(['ok' => false, 'error' => 'Falta parametro wiki']);

    $wikiPath = SEARCH_PATH . '/' . $wiki;
    if (!isDokuWiki($wikiPath)) jsonResponse(['ok' => false, 'error' => "No es una wiki valida: $wiki"]);

    $result = fixAcl($wikiPath);
    jsonResponse($result);
}

// ============================================================
// UI HTML (si no es AJAX)
// ============================================================
?><!DOCTYPE html>
<html lang="es">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Fix ACL - SIE Deployer</title>
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body {
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
            background: #0a0e27;
            color: #e0e0e0;
            padding: 20px;
            min-height: 100vh;
        }
        .container { max-width: 1200px; margin: 0 auto; }

        h1 { text-align: center; color: #fff; margin-bottom: 5px; font-size: 1.8em; }
        .subtitle { text-align: center; color: #888; margin-bottom: 25px; font-size: 0.9em; }

        /* Cards */
        .card {
            background: #1a1e3a;
            border-radius: 10px;
            padding: 20px;
            margin-bottom: 20px;
            border: 1px solid #2a2e4a;
        }
        .card h2 { color: #00e5ff; font-size: 1.2em; margin-bottom: 15px; }

        /* Botones */
        .btn {
            display: inline-block;
            padding: 10px 22px;
            border-radius: 8px;
            border: none;
            font-weight: 600;
            font-size: 0.95em;
            cursor: pointer;
            text-decoration: none;
            transition: opacity 0.2s, transform 0.1s;
            color: #fff;
        }
        .btn:hover { opacity: 0.85; transform: translateY(-1px); }
        .btn:active { transform: translateY(0); }
        .btn:disabled { opacity: 0.4; cursor: not-allowed; transform: none; }

        .btn-scan { background: #00e5ff; color: #0a0e27; }
        .btn-fix  { background: #6bcb77; color: #0a0e27; }
        .btn-fixall { background: #ff9f43; color: #0a0e27; }
        .btn-cancel { background: #ff6b6b; color: #fff; }
        .btn-preview { background: #a29bfe; color: #0a0e27; }
        .btn-sm { padding: 5px 12px; font-size: 0.8em; border-radius: 5px; }

        /* Modo selector */
        .modes {
            display: flex;
            gap: 10px;
            margin-bottom: 20px;
            flex-wrap: wrap;
        }
        .mode-btn {
            flex: 1;
            min-width: 200px;
            padding: 15px;
            background: #1a1e3a;
            border: 2px solid #2a2e4a;
            border-radius: 10px;
            cursor: pointer;
            text-align: center;
            transition: all 0.2s;
        }
        .mode-btn:hover { border-color: #00e5ff; }
        .mode-btn.active { border-color: #00e5ff; background: #1a2e4a; }
        .mode-btn h3 { color: #00e5ff; margin-bottom: 5px; }
        .mode-btn p { color: #888; font-size: 0.85em; }

        /* Summary stats */
        .stats {
            display: flex;
            gap: 15px;
            flex-wrap: wrap;
            margin-bottom: 20px;
        }
        .stat-box {
            flex: 1;
            min-width: 120px;
            background: #1a1e3a;
            border-radius: 8px;
            padding: 15px;
            text-align: center;
            border: 1px solid #2a2e4a;
        }
        .stat-value { font-size: 2em; font-weight: bold; }
        .stat-label { font-size: 0.8em; color: #888; margin-top: 5px; }
        .val-blue   { color: #00e5ff; }
        .val-green  { color: #6bcb77; }
        .val-red    { color: #ff6b6b; }
        .val-orange { color: #ff9f43; }
        .val-purple { color: #a29bfe; }

        /* Status badges */
        .status-ok         { background: #6bcb77; color: #000; padding: 3px 10px; border-radius: 12px; font-size: 0.8em; font-weight: 600; }
        .status-incomplete { background: #ff9f43; color: #000; padding: 3px 10px; border-radius: 12px; font-size: 0.8em; font-weight: 600; }
        .status-critical   { background: #ff6b6b; color: #fff; padding: 3px 10px; border-radius: 12px; font-size: 0.8em; font-weight: 600; }

        /* Table */
        .wiki-table {
            width: 100%;
            border-collapse: collapse;
            font-size: 0.9em;
        }
        .wiki-table th {
            background: #0f1335;
            color: #00e5ff;
            padding: 10px 12px;
            text-align: left;
            position: sticky;
            top: 0;
            z-index: 2;
        }
        .wiki-table td {
            padding: 8px 12px;
            border-bottom: 1px solid #2a2e4a;
        }
        .wiki-table tr:hover { background: #1f2350; }
        .wiki-table .wiki-name { font-weight: 600; color: #fff; }
        .wiki-table .rules-info { color: #888; font-size: 0.85em; }

        /* Scrollable table container */
        .table-wrap {
            max-height: 500px;
            overflow-y: auto;
            border-radius: 8px;
            border: 1px solid #2a2e4a;
        }
        .table-wrap::-webkit-scrollbar { width: 8px; }
        .table-wrap::-webkit-scrollbar-track { background: #0a0e27; }
        .table-wrap::-webkit-scrollbar-thumb { background: #2a2e4a; border-radius: 4px; }

        /* Progress bar */
        .progress-bar {
            width: 100%;
            height: 30px;
            background: #0f1335;
            border-radius: 15px;
            overflow: hidden;
            margin: 15px 0;
        }
        .progress-fill {
            height: 100%;
            background: linear-gradient(90deg, #00e5ff, #6bcb77);
            border-radius: 15px;
            transition: width 0.3s;
            display: flex;
            align-items: center;
            justify-content: center;
            font-size: 0.85em;
            font-weight: 600;
            color: #0a0e27;
            min-width: 60px;
        }

        /* Preview panel */
        .preview-panel {
            background: #0f1335;
            border-radius: 8px;
            padding: 15px;
            font-family: 'Consolas', 'Monaco', monospace;
            font-size: 0.85em;
            max-height: 400px;
            overflow-y: auto;
            line-height: 1.6;
        }
        .preview-panel::-webkit-scrollbar { width: 8px; }
        .preview-panel::-webkit-scrollbar-track { background: #0a0e27; }
        .preview-panel::-webkit-scrollbar-thumb { background: #2a2e4a; border-radius: 4px; }
        .preview-add    { color: #6bcb77; }
        .preview-update { color: #ffd93d; }
        .preview-keep   { color: #e0e0e0; }
        .preview-remove { color: #ff6b6b; text-decoration: line-through; }
        .preview-custom { color: #a29bfe; }

        /* Log */
        .log-box {
            background: #0f1335;
            border-radius: 8px;
            padding: 12px;
            max-height: 300px;
            overflow-y: auto;
            font-family: monospace;
            font-size: 0.8em;
            line-height: 1.5;
        }
        .log-box::-webkit-scrollbar { width: 8px; }
        .log-box::-webkit-scrollbar-track { background: #0a0e27; }
        .log-box::-webkit-scrollbar-thumb { background: #2a2e4a; border-radius: 4px; }
        .log-ok   { color: #6bcb77; }
        .log-err  { color: #ff6b6b; }
        .log-warn { color: #ffd93d; }
        .log-info { color: #00e5ff; }

        /* Loading spinner */
        .spinner {
            display: inline-block;
            width: 20px;
            height: 20px;
            border: 3px solid #2a2e4a;
            border-top-color: #00e5ff;
            border-radius: 50%;
            animation: spin 0.8s linear infinite;
            vertical-align: middle;
            margin-right: 8px;
        }
        @keyframes spin { to { transform: rotate(360deg); } }

        /* Hidden */
        .hidden { display: none !important; }

        /* Section panels */
        .panel { display: none; }
        .panel.active { display: block; }

        /* Responsive */
        @media (max-width: 768px) {
            .modes { flex-direction: column; }
            .stats { flex-direction: column; }
        }

        /* Filter input */
        .filter-input {
            width: 100%;
            padding: 10px 15px;
            background: #0f1335;
            border: 1px solid #2a2e4a;
            border-radius: 8px;
            color: #e0e0e0;
            font-size: 0.95em;
            margin-bottom: 15px;
            outline: none;
        }
        .filter-input:focus { border-color: #00e5ff; }
        .filter-input::placeholder { color: #555; }
    </style>
</head>
<body>
<div class="container">
    <h1>Fix ACL - Deployer SIE</h1>
    <p class="subtitle">Corregir conf/acl.auth.php en todas las wikis</p>

    <!-- MODE SELECTOR -->
    <div class="modes">
        <div class="mode-btn active" onclick="switchMode('scan')" id="modeBtn-scan">
            <h3>1. Escanear</h3>
            <p>Listar wikis y estado de ACL</p>
        </div>
        <div class="mode-btn" onclick="switchMode('individual')" id="modeBtn-individual">
            <h3>2. Corregir Individual</h3>
            <p>Corregir una wiki a la vez</p>
        </div>
        <div class="mode-btn" onclick="switchMode('all')" id="modeBtn-all">
            <h3>3. Corregir Todos</h3>
            <p>Corregir todas las wikis</p>
        </div>
    </div>

    <!-- ========== PANEL ESCANEAR ========== -->
    <div class="panel active" id="panel-scan">
        <div class="card">
            <h2>Escaneo de Wikis</h2>
            <button class="btn btn-scan" onclick="doScan()" id="btnScan">Escanear Wikis</button>
            <span id="scanStatus" style="margin-left:15px;color:#888;"></span>
        </div>

        <div id="scanResults" class="hidden">
            <div class="stats" id="scanStats"></div>

            <input type="text" class="filter-input" id="filterInput" placeholder="Filtrar por nombre de wiki..." oninput="filterTable()">

            <div class="table-wrap">
                <table class="wiki-table" id="wikiTable">
                    <thead>
                        <tr>
                            <th>#</th>
                            <th>Wiki</th>
                            <th>Estado</th>
                            <th>Base</th>
                            <th>Custom</th>
                            <th>@sie</th>
                            <th>Acciones</th>
                        </tr>
                    </thead>
                    <tbody id="wikiTableBody"></tbody>
                </table>
            </div>
        </div>
    </div>

    <!-- ========== PANEL INDIVIDUAL ========== -->
    <div class="panel" id="panel-individual">
        <div class="card">
            <h2>Corregir Wiki Individual</h2>
            <p style="color:#888;margin-bottom:15px;">Seleccione una wiki del escaneo, o escriba el nombre:</p>
            <div style="display:flex;gap:10px;flex-wrap:wrap;">
                <input type="text" class="filter-input" id="individualWiki" placeholder="Nombre de la wiki (ej: miempresa)" style="flex:1;margin-bottom:0;">
                <button class="btn btn-preview" onclick="doPreview()">Vista Previa</button>
                <button class="btn btn-fix" onclick="doFixOne()" id="btnFixOne" disabled>Aplicar Correcciones</button>
            </div>
        </div>

        <div id="previewCard" class="card hidden">
            <h2>Vista Previa de Cambios</h2>
            <div id="previewInfo" style="margin-bottom:10px;"></div>
            <div class="preview-panel" id="previewContent"></div>
            <div style="margin-top:15px;">
                <span class="preview-add">&#9632;</span> Agregar &nbsp;
                <span class="preview-update">&#9632;</span> Actualizar &nbsp;
                <span class="preview-keep">&#9632;</span> Sin cambio &nbsp;
                <span class="preview-remove">&#9632;</span> Eliminar &nbsp;
                <span class="preview-custom">&#9632;</span> Custom preservada
            </div>
        </div>

        <div id="fixOneResult" class="card hidden">
            <h2>Resultado</h2>
            <div id="fixOneResultContent"></div>
        </div>
    </div>

    <!-- ========== PANEL TODOS ========== -->
    <div class="panel" id="panel-all">
        <div class="card">
            <h2>Corregir Todas las Wikis</h2>
            <p style="color:#888;margin-bottom:15px;">Ejecuta la corrección ACL en todas las wikis secuencialmente. Se crea backup antes de cada cambio.</p>
            <div style="display:flex;gap:10px;">
                <button class="btn btn-fixall" onclick="doFixAll()" id="btnFixAll">Iniciar Corrección Masiva</button>
                <button class="btn btn-cancel hidden" onclick="cancelFixAll()" id="btnCancel">Cancelar</button>
            </div>
        </div>

        <div id="fixAllProgress" class="card hidden">
            <h2>Progreso</h2>
            <div class="progress-bar">
                <div class="progress-fill" id="fixAllBar" style="width:0%">0%</div>
            </div>
            <div id="fixAllStats" style="display:flex;gap:20px;margin:10px 0;flex-wrap:wrap;">
                <span>Procesadas: <strong id="fixAllProcessed">0</strong></span>
                <span>Corregidas: <strong id="fixAllFixed" style="color:#6bcb77;">0</strong></span>
                <span>Sin cambios: <strong id="fixAllSkipped" style="color:#888;">0</strong></span>
                <span>Errores: <strong id="fixAllErrors" style="color:#ff6b6b;">0</strong></span>
            </div>
        </div>

        <div id="fixAllLog" class="card hidden">
            <h2>Log</h2>
            <div class="log-box" id="fixAllLogContent"></div>
        </div>

        <div id="fixAllComplete" class="card hidden">
            <h2>Corrección Completada</h2>
            <div id="fixAllCompleteContent"></div>
        </div>
    </div>
</div>

<script>
// ============================================================
// STATE
// ============================================================
var wikis = [];
var cancelRequested = false;

// ============================================================
// MODE SWITCHING
// ============================================================
function switchMode(mode) {
    document.querySelectorAll('.panel').forEach(function(p) { p.classList.remove('active'); });
    document.querySelectorAll('.mode-btn').forEach(function(b) { b.classList.remove('active'); });
    document.getElementById('panel-' + mode).classList.add('active');
    document.getElementById('modeBtn-' + mode).classList.add('active');

    // Si no hay escaneo y se va a individual/all, escanear automáticamente
    if (wikis.length === 0 && mode !== 'scan') {
        doScan(function() {
            if (mode === 'scan') return;
        });
    }
}

// ============================================================
// AJAX HELPER
// ============================================================
function ajax(method, url, data, callback) {
    var xhr = new XMLHttpRequest();
    xhr.open(method, url, true);
    xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
    xhr.timeout = 58000; // 58s (bajo el límite Cloudflare de 60s)
    xhr.onreadystatechange = function() {
        if (xhr.readyState === 4) {
            if (xhr.status === 200) {
                try {
                    var json = JSON.parse(xhr.responseText);
                    callback(null, json);
                } catch(e) {
                    callback('Respuesta no válida: ' + xhr.responseText.substring(0, 200));
                }
            } else {
                callback('HTTP ' + xhr.status + ': ' + xhr.statusText);
            }
        }
    };
    xhr.ontimeout = function() {
        callback('Timeout: la petición tardó más de 58 segundos');
    };
    if (method === 'POST' && data) {
        xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
        xhr.send(data);
    } else {
        xhr.send();
    }
}

// ============================================================
// SCAN
// ============================================================
function doScan(callback) {
    var btn = document.getElementById('btnScan');
    var status = document.getElementById('scanStatus');
    btn.disabled = true;
    status.innerHTML = '<span class="spinner"></span>Escaneando wikis...';

    ajax('GET', 'aplicar.php?action=scan', null, function(err, data) {
        btn.disabled = false;
        if (err) {
            status.innerHTML = '<span style="color:#ff6b6b;">Error: ' + err + '</span>';
            return;
        }
        if (!data.ok) {
            status.innerHTML = '<span style="color:#ff6b6b;">Error: ' + (data.error || 'Desconocido') + '</span>';
            return;
        }

        wikis = data.wikis;
        status.innerHTML = '<span style="color:#6bcb77;">Escaneo completo: ' + data.summary.total + ' wikis</span>';

        // Stats
        var statsHtml = '';
        statsHtml += '<div class="stat-box"><div class="stat-value val-blue">' + data.summary.total + '</div><div class="stat-label">Total Wikis</div></div>';
        statsHtml += '<div class="stat-box"><div class="stat-value val-green">' + data.summary.ok + '</div><div class="stat-label">ACL OK</div></div>';
        statsHtml += '<div class="stat-box"><div class="stat-value val-orange">' + data.summary.incomplete + '</div><div class="stat-label">Incompletos</div></div>';
        statsHtml += '<div class="stat-box"><div class="stat-value val-red">' + data.summary.critical + '</div><div class="stat-label">Criticos</div></div>';
        document.getElementById('scanStats').innerHTML = statsHtml;

        // Table
        renderTable(wikis);
        document.getElementById('scanResults').classList.remove('hidden');

        if (callback) callback();
    });
}

function renderTable(list) {
    var tbody = document.getElementById('wikiTableBody');
    var html = '';
    for (var i = 0; i < list.length; i++) {
        var w = list[i];
        var totalBase = <?php echo count($STANDARD_RULES); ?>;
        html += '<tr data-wiki="' + escHtml(w.wiki) + '">';
        html += '<td>' + (i + 1) + '</td>';
        html += '<td class="wiki-name">' + escHtml(w.wiki) + '</td>';
        html += '<td><span class="' + escHtml(w.status_class) + '">' + escHtml(w.status) + '</span></td>';
        html += '<td class="rules-info">' + w.base_present + '/' + totalBase;
        if (w.base_wrong > 0) html += ' <span style="color:#ffd93d;">(' + w.base_wrong + ' incorrectas)</span>';
        if (w.base_missing > 0) html += ' <span style="color:#ff6b6b;">(' + w.base_missing + ' faltan)</span>';
        html += '</td>';
        html += '<td class="rules-info">' + w.custom_rules + '</td>';
        html += '<td class="rules-info">' + (w.sie_rules > 0 ? '<span style="color:#ff6b6b;">' + w.sie_rules + '</span>' : '0') + '</td>';
        html += '<td>';
        html += '<button class="btn btn-preview btn-sm" onclick="selectWiki(\'' + escHtml(w.wiki) + '\')">Preview</button> ';
        if (w.status !== 'OK') {
            html += '<button class="btn btn-fix btn-sm" onclick="quickFix(\'' + escHtml(w.wiki) + '\', this)">Fix</button>';
        }
        html += '</td>';
        html += '</tr>';
    }
    tbody.innerHTML = html;
}

function filterTable() {
    var filter = document.getElementById('filterInput').value.toLowerCase();
    var filtered = wikis.filter(function(w) {
        return w.wiki.toLowerCase().indexOf(filter) !== -1;
    });
    renderTable(filtered);
}

function escHtml(s) {
    var div = document.createElement('div');
    div.textContent = s;
    return div.innerHTML;
}

// ============================================================
// PREVIEW
// ============================================================
function selectWiki(name) {
    switchMode('individual');
    document.getElementById('individualWiki').value = name;
    doPreview();
}

function doPreview() {
    var wiki = document.getElementById('individualWiki').value.trim();
    if (!wiki) { alert('Escriba el nombre de la wiki'); return; }

    var card = document.getElementById('previewCard');
    var content = document.getElementById('previewContent');
    var info = document.getElementById('previewInfo');

    card.classList.remove('hidden');
    content.innerHTML = '<span class="spinner"></span>Cargando preview...';

    ajax('GET', 'aplicar.php?action=preview&wiki=' + encodeURIComponent(wiki), null, function(err, data) {
        if (err) {
            content.innerHTML = '<span style="color:#ff6b6b;">Error: ' + err + '</span>';
            return;
        }
        if (!data.ok) {
            content.innerHTML = '<span style="color:#ff6b6b;">Error: ' + (data.error || 'Desconocido') + '</span>';
            return;
        }

        var a = data.analysis;
        var totalBase = <?php echo count($STANDARD_RULES); ?>;
        info.innerHTML = 'Wiki: <strong>' + escHtml(data.wiki) + '</strong> | ' +
            'Estado: <span class="' + escHtml(a.status_class) + '">' + escHtml(a.status) + '</span> | ' +
            'Base: ' + a.base_present + '/' + totalBase + ' | ' +
            'Custom: ' + a.custom_rules + ' | ' +
            '@sie: ' + a.sie_rules;

        var html = '';
        var addCount = 0, updateCount = 0, removeCount = 0;
        for (var i = 0; i < data.preview.length; i++) {
            var p = data.preview[i];
            var cls = 'preview-keep';
            var prefix = '  ';
            if (p.type === 'add')       { cls = 'preview-add';    prefix = '+ '; addCount++; }
            if (p.type === 'update')    { cls = 'preview-update'; prefix = '~ '; updateCount++; }
            if (p.type === 'remove')    { cls = 'preview-remove'; prefix = '- '; removeCount++; }
            if (p.type === 'custom')    { cls = 'preview-custom'; prefix = '  '; }
            if (p.type === 'keep_base') { cls = 'preview-keep';   prefix = '  '; }
            html += '<div class="' + cls + '" title="' + escHtml(p.detail) + '">' + prefix + escHtml(p.text) + '</div>';
        }
        content.innerHTML = html;

        // Habilitar botón fix si hay cambios
        var hasChanges = (addCount > 0 || updateCount > 0 || removeCount > 0);
        document.getElementById('btnFixOne').disabled = !hasChanges;
        if (!hasChanges) {
            info.innerHTML += ' <span style="color:#6bcb77;font-weight:600;"> - Sin cambios necesarios</span>';
        }
    });
}

// ============================================================
// FIX ONE
// ============================================================
function doFixOne() {
    var wiki = document.getElementById('individualWiki').value.trim();
    if (!wiki) return;
    if (!confirm('Corregir ACL de "' + wiki + '"?\nSe creará un backup antes de modificar.')) return;

    var resultCard = document.getElementById('fixOneResult');
    var resultContent = document.getElementById('fixOneResultContent');
    resultCard.classList.remove('hidden');
    resultContent.innerHTML = '<span class="spinner"></span>Aplicando correcciones...';

    ajax('POST', 'aplicar.php?action=fix_one', 'wiki=' + encodeURIComponent(wiki), function(err, data) {
        if (err) {
            resultContent.innerHTML = '<div class="log-err">Error: ' + err + '</div>';
            return;
        }
        if (!data.ok) {
            resultContent.innerHTML = '<div class="log-err">Error: ' + (data.error || data.message) + '</div>';
            return;
        }
        resultContent.innerHTML = '<div class="log-ok">' + escHtml(data.message) + '</div>';
        // Refrescar preview
        doPreview();
    });
}

function quickFix(wiki, btn) {
    if (!confirm('Corregir ACL de "' + wiki + '"?\nSe creará un backup antes de modificar.')) return;
    btn.disabled = true;
    btn.textContent = '...';

    ajax('POST', 'aplicar.php?action=fix_one', 'wiki=' + encodeURIComponent(wiki), function(err, data) {
        if (err) {
            btn.textContent = 'ERR';
            btn.style.background = '#ff6b6b';
            alert('Error: ' + err);
            return;
        }
        if (!data.ok) {
            btn.textContent = 'ERR';
            btn.style.background = '#ff6b6b';
            alert('Error: ' + (data.error || data.message));
            return;
        }
        btn.textContent = 'OK';
        btn.style.background = '#6bcb77';
        // Actualizar status en la fila
        var row = btn.closest('tr');
        if (row) {
            var statusCell = row.children[2];
            statusCell.innerHTML = '<span class="status-ok">OK</span>';
        }
    });
}

// ============================================================
// FIX ALL
// ============================================================
function doFixAll() {
    if (wikis.length === 0) {
        alert('Primero escanee las wikis');
        doScan(function() {
            if (wikis.length > 0) doFixAll();
        });
        return;
    }

    // Filtrar solo las que no están OK
    var toFix = wikis.filter(function(w) { return w.status !== 'OK'; });
    if (toFix.length === 0) {
        alert('Todas las wikis ya tienen el ACL correcto. No hay nada que corregir.');
        return;
    }

    if (!confirm('Se corregirán ' + toFix.length + ' wikis (de ' + wikis.length + ' total).\n' +
        'Se creará backup de cada ACL antes de modificar.\n\nContinuar?')) return;

    cancelRequested = false;
    document.getElementById('btnFixAll').classList.add('hidden');
    document.getElementById('btnCancel').classList.remove('hidden');
    document.getElementById('fixAllProgress').classList.remove('hidden');
    document.getElementById('fixAllLog').classList.remove('hidden');
    document.getElementById('fixAllComplete').classList.add('hidden');

    var processed = 0, fixed = 0, skipped = 0, errors = 0;
    var logEl = document.getElementById('fixAllLogContent');
    logEl.innerHTML = '';

    function processNext(index) {
        if (cancelRequested) {
            addLog('warn', 'Cancelado por el usuario en wiki #' + (index + 1));
            finishFixAll(processed, fixed, skipped, errors, true);
            return;
        }

        if (index >= toFix.length) {
            finishFixAll(processed, fixed, skipped, errors, false);
            return;
        }

        var w = toFix[index];
        addLog('info', '[' + (index + 1) + '/' + toFix.length + '] Procesando: ' + w.wiki + '...');

        ajax('POST', 'aplicar.php?action=fix_one', 'wiki=' + encodeURIComponent(w.wiki), function(err, data) {
            processed++;
            updateFixAllStats(processed, fixed, skipped, errors, toFix.length);

            if (err) {
                errors++;
                addLog('err', w.wiki + ': ' + err);
            } else if (!data.ok) {
                errors++;
                addLog('err', w.wiki + ': ' + (data.error || data.message));
            } else if (data.changes === 0) {
                skipped++;
                addLog('info', w.wiki + ': sin cambios');
            } else {
                fixed++;
                addLog('ok', w.wiki + ': ' + data.changes + ' cambios, ' + (data.custom || 0) + ' custom preservadas');
            }

            updateFixAllStats(processed, fixed, skipped, errors, toFix.length);
            // Siguiente wiki
            processNext(index + 1);
        });
    }

    processNext(0);
}

function cancelFixAll() {
    cancelRequested = true;
    document.getElementById('btnCancel').disabled = true;
    document.getElementById('btnCancel').textContent = 'Cancelando...';
}

function updateFixAllStats(processed, fixed, skipped, errors, total) {
    var pct = total > 0 ? Math.round((processed / total) * 100) : 0;
    document.getElementById('fixAllBar').style.width = pct + '%';
    document.getElementById('fixAllBar').textContent = pct + '%';
    document.getElementById('fixAllProcessed').textContent = processed;
    document.getElementById('fixAllFixed').textContent = fixed;
    document.getElementById('fixAllSkipped').textContent = skipped;
    document.getElementById('fixAllErrors').textContent = errors;
}

function addLog(type, msg) {
    var logEl = document.getElementById('fixAllLogContent');
    var div = document.createElement('div');
    div.className = 'log-' + type;
    div.textContent = msg;
    logEl.appendChild(div);
    logEl.scrollTop = logEl.scrollHeight;
}

function finishFixAll(processed, fixed, skipped, errors, cancelled) {
    document.getElementById('btnFixAll').classList.remove('hidden');
    document.getElementById('btnFixAll').disabled = false;
    document.getElementById('btnCancel').classList.add('hidden');
    document.getElementById('btnCancel').disabled = false;
    document.getElementById('btnCancel').textContent = 'Cancelar';

    var completeCard = document.getElementById('fixAllComplete');
    var content = document.getElementById('fixAllCompleteContent');
    completeCard.classList.remove('hidden');

    var status = cancelled ? '<span style="color:#ffd93d;">CANCELADO</span>' : '<span style="color:#6bcb77;">COMPLETADO</span>';
    content.innerHTML =
        '<p style="font-size:1.3em;font-weight:600;">' + status + '</p>' +
        '<p>Procesadas: <strong>' + processed + '</strong></p>' +
        '<p>Corregidas: <strong style="color:#6bcb77;">' + fixed + '</strong></p>' +
        '<p>Sin cambios: <strong>' + skipped + '</strong></p>' +
        '<p>Errores: <strong style="color:#ff6b6b;">' + errors + '</strong></p>';
}

</script>
</body>
</html>
