<?php
/**
 * massolaguard_full.php
 *
 * MassolaGuard™ Ultimate - Explorador de Archivos + Editor + Consola SQL +
 * Generadores y Herramientas (archivo único)
 *
 * WARNING: este archivo permite ejecutar SQL y crear/escribir archivos.
 * Protégelo: renombrar, contraseña fuerte, restricción por IP/.htaccess.
 *
 * Requisitos: PHP 7.4+, PDO MySQL, permiso de escritura en el directorio.
 */

session_start();
// DEBUG: activar solo en pruebas
error_reporting(E_ALL);
ini_set('display_errors', 1);

date_default_timezone_set('America/Havana');

// ---------------- CONFIG ----------------
define('MG_VERSION', '1.3.0-massola');
define('MG_PASSWORD', 'Luyano8906*'); // CAMBIAR en producción
define('MG_BASE_DIR', __DIR__);
define('MG_SESSION_TIMEOUT', 7200);
define('MG_MAX_EDITORS', 6);
define('MG_SCAN_MAX_DEPTH', 8);

// Auto DB connection (optional)
define('MG_DB_AUTO_CONNECT', true);
define('MG_DB_HOST', 'localhost');
define('MG_DB_PORT', 3306);
define('MG_DB_NAME', 'massolag_vikingos_diagnostico');
define('MG_DB_USER', 'massolag_amassola');
define('MG_DB_PASS', 'Luyano8906*');

// Files for history/logs
define('SQL_HISTORY_FILE', MG_BASE_DIR . '/historial_sql.log');
define('SQL_ERROR_FILE', MG_BASE_DIR . '/errores_sql.log');

// Ensure directories
foreach (['logs','backups','generated'] as $d) {
    $p = MG_BASE_DIR . '/' . $d;
    if (!is_dir($p)) @mkdir($p, 0755, true);
}

// ---------------- MassolGuard class (files + DB helpers) ----------------
class MassolGuard {
    private $pdo = null;
    private $dbConnected = false;
    private $dbError = '';
    public $dbConfig = null;

    public function __construct() {}

    public function connectDynamicDB($host, $db, $user, $pass, $port = 3306, $charset = 'utf8mb4') {
        try {
            $dsn = "mysql:host={$host};port={$port};dbname={$db};charset={$charset}";
            $pdo = new PDO($dsn, $user, $pass, [
                PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
                PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
            ]);
            $this->pdo = $pdo;
            $this->dbConnected = true;
            $this->dbError = '';
            $this->dbConfig = ['host'=>$host,'user'=>$user,'password'=>$pass,'name'=>$db,'port'=>$port];
            return ['success' => true, 'pdo' => $pdo];
        } catch (PDOException $e) {
            $this->pdo = null;
            $this->dbConnected = false;
            $this->dbError = $e->getMessage();
            return ['success' => false, 'message' => $e->getMessage()];
        }
    }

    public function connectDB($host = null, $user = null, $pass = null, $db = null, $port = 3306) {
        $host = $host ?? ($this->dbConfig['host'] ?? 'localhost');
        $user = $user ?? ($this->dbConfig['user'] ?? '');
        $pass = $pass ?? ($this->dbConfig['password'] ?? '');
        $db   = $db   ?? ($this->dbConfig['name'] ?? '');
        $port = $port ?? ($this->dbConfig['port'] ?? 3306);
        return $this->connectDynamicDB($host, $db, $user, $pass, $port);
    }

    public function getPDO() { return $this->pdo; }
    public function isDbConnected() { return $this->dbConnected; }
    public function getDbError() { return $this->dbError; }

    // Files explorer
    public function scanFiles($dir = null, $level = 0, $maxDepth = 8, $includeHidden = false, $excludeDirs = []) {
        if ($dir === null) $dir = MG_BASE_DIR;
        $files = [];
        if ($level > $maxDepth) return $files;
        $dir = rtrim($dir, '/');
        $real = realpath($dir);
        if ($real === false) return $files;
        $baseReal = realpath(MG_BASE_DIR);
        if ($baseReal === false) return $files;
        if (strpos($real, $baseReal) !== 0) return $files;
        if (!is_dir($real) || !is_readable($real)) return $files;
        $items = @scandir($real);
        if (!$items) return $files;
        $defaultExcludes = ['vendor', '.git', 'node_modules', 'backups', 'tmp', 'log', 'logs', 'mg_backups', 'mg_logs'];
        $excludeDirs = array_merge($defaultExcludes, $excludeDirs);
        foreach ($items as $item) {
            if ($item === '.' || $item === '..') continue;
            if (!$includeHidden && substr($item, 0, 1) === '.') continue;
            if (in_array($item, $excludeDirs)) continue;
            $fullPath = $real . '/' . $item;
            $relativePath = ltrim(str_replace($baseReal, '', $fullPath), '/');
            if (is_dir($fullPath)) {
                $files[] = [
                    'type' => 'folder', 'name' => $item, 'path' => $relativePath,
                    'children' => $this->scanFiles($fullPath, $level + 1, $maxDepth, $includeHidden, $excludeDirs)
                ];
            } else {
                $ext = pathinfo($item, PATHINFO_EXTENSION);
                $files[] = [
                    'type' => 'file','name' => $item,'path' => $relativePath,
                    'ext' => $ext,'size' => @filesize($fullPath),'modified' => @filemtime($fullPath),
                    'writable' => is_writable($fullPath)
                ];
            }
        }
        return $files;
    }

    public function readFile($filepath) {
        $fullPath = realpath(MG_BASE_DIR . '/' . ltrim((string)$filepath, '/'));
        $baseReal = realpath(MG_BASE_DIR);
        if ($fullPath === false || $baseReal === false || strpos($fullPath, $baseReal) !== 0) {
            return ['success'=>false,'message'=>'Ruta no permitida'];
        }
        if (!file_exists($fullPath) || !is_file($fullPath)) {
            return ['success'=>false,'message'=>'Archivo no encontrado','filepath'=>$filepath];
        }
        $content = @file_get_contents($fullPath);
        if ($content === false) return ['success'=>false,'message'=>'No se pudo leer el archivo'];
        return ['success'=>true,'content'=>$content,'filepath'=>ltrim(str_replace($baseReal, '', $fullPath), '/'),
                'filename'=>basename($fullPath),'size'=>filesize($fullPath),'modified'=>date('Y-m-d H:i:s', filemtime($fullPath)),
                'writable'=>is_writable($fullPath)];
    }

    public function saveFile($filepath, $content) {
        $fullPath = realpath(MG_BASE_DIR . '/' . ltrim((string)$filepath, '/'));
        $baseReal = realpath(MG_BASE_DIR);
        if ($fullPath === false || $baseReal === false || strpos($fullPath, $baseReal) !== 0) {
            return ['success'=>false,'message'=>'Ruta no permitida'];
        }
        if (!file_exists($fullPath)) return ['success'=>false,'message'=>'El archivo no existe'];
        if (!is_writable($fullPath)) return ['success'=>false,'message'=>'Sin permisos de escritura'];
        $backupDir = MG_BASE_DIR . '/backups';
        if (!is_dir($backupDir)) @mkdir($backupDir, 0755, true);
        $backupFile = $backupDir . '/' . str_replace(['/', '\\'], '_', $filepath) . '.' . time() . '.backup';
        @copy($fullPath, $backupFile);
        if (file_put_contents($fullPath, $content) !== false) {
            return ['success'=>true,'message'=>'Guardado correctamente','size'=>filesize($fullPath)];
        }
        return ['success'=>false,'message'=>'Error al guardar'];
    }

    public function getProjectStats($dir = null) {
        $files = $this->scanFiles($dir);
        $stats = ['total_files'=>0,'total_folders'=>0,'total_size'=>0,'php'=>0,'css'=>0,'js'=>0,'other'=>0];
        $this->countItems($files, $stats);
        return $stats;
    }

    private function countItems($items, &$stats) {
        foreach ($items as $item) {
            if ($item['type'] === 'folder') {
                $stats['total_folders']++;
                if (!empty($item['children'])) $this->countItems($item['children'], $stats);
            } else {
                $stats['total_files']++;
                $stats['total_size'] += $item['size'] ?? 0;
                switch (strtolower($item['ext'])) {
                    case 'php': $stats['php']++; break;
                    case 'css': $stats['css']++; break;
                    case 'js': $stats['js']++; break;
                    default: $stats['other']++;
                }
            }
        }
    }

    public function log($action, $details = '') {
        $logDir = MG_BASE_DIR . '/logs';
        if (!is_dir($logDir)) @mkdir($logDir, 0755, true);
        $logFile = $logDir . '/massolaguard.log';
        $entry = sprintf("[%s] [%s] %s%s\n", date('Y-m-d H:i:s'), $_SESSION['mg_user'] ?? 'system', $action, $details ? " - $details" : '');
        @file_put_contents($logFile, $entry, FILE_APPEND);
    }
}

// ---------------- INSTANTIATE ----------------
$mg = new MassolGuard();

// auto connect principal if set
if (defined('MG_DB_AUTO_CONNECT') && MG_DB_AUTO_CONNECT) {
    $dbRes = $mg->connectDynamicDB(MG_DB_HOST, MG_DB_NAME, MG_DB_USER, MG_DB_PASS, MG_DB_PORT);
    if (empty($dbRes['success'])) $mg->log('DB_CONNECT_FAIL', $dbRes['message'] ?? 'Unknown');
}

// ---------------- HELPERS for handlers ----------------
$getPdoWithFallback = function() use ($mg) {
    $pdo = $mg->getPDO();
    if ($pdo) return $pdo;
    $host = $_POST['host'] ?? null;
    $user = $_POST['user'] ?? null;
    $pass = $_POST['pass'] ?? null;
    $port = intval($_POST['port'] ?? MG_DB_PORT);
    $dbtmp = $_POST['database'] ?? 'information_schema';
    if ($host && $user) {
        $tmp = $mg->connectDynamicDB($host, $dbtmp, $user, $pass, $port);
        if (!empty($tmp['success'])) return $tmp['pdo'];
    }
    return null;
};

$useDatabaseIfProvided = function($pdo) {
    $db = $_POST['database'] ?? null;
    if (!$db) return true;
    if (!preg_match('/^[a-zA-Z0-9_\-]+$/', $db)) return false;
    try {
        $pdo->exec("USE `{$db}`");
        return true;
    } catch (Exception $e) {
        return false;
    }
};

function appendSqlHistoryLine($line) {
    @file_put_contents(SQL_HISTORY_FILE, $line . PHP_EOL, FILE_APPEND | LOCK_EX);
}
function appendSqlErrorLine($line) {
    @file_put_contents(SQL_ERROR_FILE, $line . PHP_EOL, FILE_APPEND | LOCK_EX);
}

// ---------------- POST HANDLERS (API) ----------------
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
    header('Content-Type: application/json; charset=utf-8');
    $action = $_POST['action'];

    // sanitize path
    $sanitizePath = function($path) {
        $p = trim((string)$path);
        $p = ltrim($p, "/\\");
        if ($p === '') return '';
        $real = realpath(MG_BASE_DIR . '/' . $p);
        if ($real === false) return false;
        $baseReal = realpath(MG_BASE_DIR);
        if ($baseReal === false) return false;
        if (strpos($real, $baseReal) !== 0) return false;
        return ltrim(str_replace($baseReal, '', $real), '/');
    };

    // ---- Files: scan/read/save ----
    if ($action === 'files_scan') {
        $path = $_POST['path'] ?? '';
        $rel = $path ? $sanitizePath($path) : '';
        if ($path && $rel === false) { echo json_encode(['success'=>false,'message'=>'Ruta no permitida']); exit; }
        $start = $rel !== '' ? MG_BASE_DIR . '/' . $rel : MG_BASE_DIR;
        $maxDepth = intval($_POST['maxDepth'] ?? MG_SCAN_MAX_DEPTH);
        $result = $mg->scanFiles($start, 0, $maxDepth);
        echo json_encode(['success'=>true,'files'=>$result]); exit;
    }

    if ($action === 'files_read') {
        $filepath = $_POST['filepath'] ?? '';
        $rel = $sanitizePath($filepath);
        if ($rel === false) { echo json_encode(['success'=>false,'message'=>'Ruta no permitida']); exit; }
        $res = $mg->readFile($rel);
        echo json_encode($res); exit;
    }

    if ($action === 'files_save') {
        $filepath = $_POST['filepath'] ?? '';
        $content = $_POST['content'] ?? '';
        $rel = $sanitizePath($filepath);
        if ($rel === false) { echo json_encode(['success'=>false,'message'=>'Ruta no permitida']); exit; }
        $res = $mg->saveFile($rel, $content);
        echo json_encode($res); exit;
    }

    if ($action === 'project_stats') {
        $stats = $mg->getProjectStats();
        echo json_encode(['success'=>true,'stats'=>$stats]); exit;
    }

    // ---- DB handlers: list/tables/describe/select/insert/update/delete/execute ----
    if ($action === 'db_list') {
        $pdo = $getPdoWithFallback();
        if (!$pdo) { echo json_encode(['success'=>false,'message'=>'Sin conexión a BD']); exit; }
        try { $stmt = $pdo->query("SHOW DATABASES"); $dbs = $stmt->fetchAll(PDO::FETCH_COLUMN); echo json_encode(['success'=>true,'databases'=>$dbs]); exit; }
        catch (Exception $e) { echo json_encode(['success'=>false,'message'=>$e->getMessage()]); exit; }
    }

    if ($action === 'db_tables') {
        $db = $_POST['database'] ?? '';
        if (!preg_match('/^[a-zA-Z0-9_\-]+$/', $db)) { echo json_encode(['success'=>false,'message'=>'Nombre BD inválido']); exit; }
        $pdo = $getPdoWithFallback(); if (!$pdo) { echo json_encode(['success'=>false,'message'=>'Sin conexión a BD']); exit; }
        try { $pdo->exec("USE `{$db}`"); $stmt = $pdo->query("SHOW TABLES"); $tables = $stmt->fetchAll(PDO::FETCH_COLUMN); echo json_encode(['success'=>true,'tables'=>$tables]); exit; }
        catch (Exception $e) { echo json_encode(['success'=>false,'message'=>$e->getMessage()]); exit; }
    }

    if ($action === 'db_describe') {
        $table = $_POST['table'] ?? '';
        if (!preg_match('/^[a-zA-Z0-9_]+$/', $table)) { echo json_encode(['success'=>false,'message'=>'Nombre de tabla inválido']); exit; }
        $pdo = $getPdoWithFallback(); if (!$pdo) { echo json_encode(['success'=>false,'message'=>'Sin conexión a BD']); exit; }
        if (!($useDatabaseIfProvided)($pdo)) { echo json_encode(['success'=>false,'message'=>'Nombre de BD inválido o no accesible']); exit; }
        try { $stmt = $pdo->query("DESCRIBE `{$table}`"); $desc = $stmt->fetchAll(PDO::FETCH_ASSOC); echo json_encode(['success'=>true,'describe'=>$desc]); exit; }
        catch (Exception $e) { echo json_encode(['success'=>false,'message'=>$e->getMessage()]); exit; }
    }

    if ($action === 'db_select') {
        $table = $_POST['table'] ?? ''; $limit = intval($_POST['limit'] ?? 100); $offset = intval($_POST['offset'] ?? 0);
        if (!preg_match('/^[a-zA-Z0-9_]+$/', $table)) { echo json_encode(['success'=>false,'message'=>'Nombre de tabla inválido']); exit; }
        $pdo = $getPdoWithFallback(); if (!$pdo) { echo json_encode(['success'=>false,'message'=>'Sin conexión a BD']); exit; }
        if (!($useDatabaseIfProvided)($pdo)) { echo json_encode(['success'=>false,'message'=>'Nombre de BD inválido o no accesible']); exit; }
        try { $stmt = $pdo->prepare("SELECT * FROM `{$table}` LIMIT :lim OFFSET :off"); $stmt->bindValue(':lim',$limit,PDO::PARAM_INT); $stmt->bindValue(':off',$offset,PDO::PARAM_INT); $stmt->execute(); $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); echo json_encode(['success'=>true,'rows'=>$rows,'count'=>count($rows)]); exit; }
        catch (Exception $e) { echo json_encode(['success'=>false,'message'=>$e->getMessage()]); exit; }
    }

    if ($action === 'db_get_row') {
        $table = $_POST['table'] ?? ''; $pk = $_POST['pk'] ?? ''; $pk_value = $_POST['pk_value'] ?? null;
        if (!preg_match('/^[a-zA-Z0-9_]+$/', $table) || !preg_match('/^[a-zA-Z0-9_]+$/', $pk)) { echo json_encode(['success'=>false,'message'=>'Datos inválidos']); exit; }
        $pdo = $getPdoWithFallback(); if (!$pdo) { echo json_encode(['success'=>false,'message'=>'Sin conexión a BD']); exit; }
        if (!($useDatabaseIfProvided)($pdo)) { echo json_encode(['success'=>false,'message'=>'Nombre de BD inválido o no accesible']); exit; }
        try { $stmt = $pdo->prepare("SELECT * FROM `{$table}` WHERE `{$pk}` = :v LIMIT 1"); $stmt->execute([':v'=>$pk_value]); $row = $stmt->fetch(PDO::FETCH_ASSOC); if ($row === false) echo json_encode(['success'=>false,'message'=>'Fila no encontrada']); else echo json_encode(['success'=>true,'row'=>$row]); exit; }
        catch (Exception $e) { echo json_encode(['success'=>false,'message'=>$e->getMessage()]); exit; }
    }

    if ($action === 'db_update_row') {
        $table = $_POST['table'] ?? ''; $pk = $_POST['pk'] ?? ''; $pk_value = $_POST['pk_value'] ?? null; $data = $_POST['data'] ?? null;
        if (!preg_match('/^[a-zA-Z0-9_]+$/', $table) || !preg_match('/^[a-zA-Z0-9_]+$/', $pk)) { echo json_encode(['success'=>false,'message'=>'Datos inválidos']); exit; }
        $pdo = $getPdoWithFallback(); if (!$pdo) { echo json_encode(['success'=>false,'message'=>'Sin conexión a BD']); exit; }
        if (!($useDatabaseIfProvided)($pdo)) { echo json_encode(['success'=>false,'message'=>'Nombre de BD inválido o no accesible']); exit; }
        $assoc = is_string($data) ? json_decode($data, true) : $data; if (!is_array($assoc)) { echo json_encode(['success'=>false,'message'=>'Datos inválidos']); exit; }
        $set=[]; $params=[]; foreach($assoc as $col=>$val){ if(!preg_match('/^[a-zA-Z0-9_]+$/',$col)) continue; $ph=':col_'.$col; $set[]="`{$col}`={$ph}"; $params[$ph]=$val; }
        if (empty($set)) { echo json_encode(['success'=>false,'message'=>'Sin columnas para actualizar']); exit; }
        $params[':pk_val'] = $pk_value; $sql = "UPDATE `{$table}` SET ".implode(', ',$set)." WHERE `{$pk}` = :pk_val";
        try { $stmt = $pdo->prepare($sql); $stmt->execute($params); echo json_encode(['success'=>true,'affected'=>$stmt->rowCount()]); exit; }
        catch (Exception $e) { echo json_encode(['success'=>false,'message'=>$e->getMessage()]); exit; }
    }

    if ($action === 'db_insert_row') {
        $table = $_POST['table'] ?? ''; $data = $_POST['data'] ?? null;
        if (!preg_match('/^[a-zA-Z0-9_]+$/', $table)) { echo json_encode(['success'=>false,'message'=>'Nombre de tabla inválido']); exit; }
        $pdo = $getPdoWithFallback(); if (!$pdo) { echo json_encode(['success'=>false,'message'=>'Sin conexión a BD']); exit; }
        if (!($useDatabaseIfProvided)($pdo)) { echo json_encode(['success'=>false,'message'=>'Nombre de BD inválido o no accesible']); exit; }
        $assoc = is_string($data) ? json_decode($data, true) : $data; if (!is_array($assoc)) { echo json_encode(['success'=>false,'message'=>'Datos inválidos']); exit; }
        $cols=[]; $place=[]; $params=[];
        foreach($assoc as $col=>$val){ if(!preg_match('/^[a-zA-Z0-9_]+$/',$col)) continue; $cols[]="`{$col}`"; $ph=":col_".$col; $place[]=$ph; $params[$ph]=$val; }
        if(empty($cols)){ echo json_encode(['success'=>false,'message'=>'Sin columnas para insertar']); exit; }
        $sql = "INSERT INTO `{$table}` (".implode(',', $cols).") VALUES (".implode(',', $place).")";
        try { $stmt=$pdo->prepare($sql); $stmt->execute($params); echo json_encode(['success'=>true,'insert_id'=>$pdo->lastInsertId()]); exit; }
        catch (Exception $e){ echo json_encode(['success'=>false,'message'=>$e->getMessage()]); exit; }
    }

    if ($action === 'db_delete_row') {
        $table = $_POST['table'] ?? ''; $pk = $_POST['pk'] ?? ''; $pk_value = $_POST['pk_value'] ?? null;
        if (!preg_match('/^[a-zA-Z0-9_]+$/', $table) || !preg_match('/^[a-zA-Z0-9_]+$/', $pk)) { echo json_encode(['success'=>false,'message'=>'Datos inválidos']); exit; }
        $pdo = $getPdoWithFallback(); if (!$pdo) { echo json_encode(['success'=>false,'message'=>'Sin conexión a BD']); exit; }
        if (!($useDatabaseIfProvided)($pdo)) { echo json_encode(['success'=>false,'message'=>'Nombre de BD inválido o no accesible']); exit; }
        try { $stmt = $pdo->prepare("DELETE FROM `{$table}` WHERE `{$pk}` = :v"); $stmt->execute([':v'=>$pk_value]); echo json_encode(['success'=>true,'affected'=>$stmt->rowCount()]); exit; }
        catch (Exception $e) { echo json_encode(['success'=>false,'message'=>$e->getMessage()]); exit; }
    }

    // ---- SQL console / history ----
    if ($action === 'sql_execute') {
        $sql = $_POST['sql'] ?? ''; if (!is_string($sql) || trim($sql) === '') { echo json_encode(['success'=>false,'message'=>'SQL vacío']); exit; }
        $pdo = $getPdoWithFallback(); if (!$pdo) { echo json_encode(['success'=>false,'message'=>'Sin conexión a BD']); exit; }
        if (!($useDatabaseIfProvided)($pdo)) { echo json_encode(['success'=>false,'message'=>'Nombre de BD inválido o no accesible']); exit; }
        $line = date("Y-m-d H:i:s") . " | " . trim($sql); appendSqlHistoryLine($line);
        try {
            $trim = ltrim($sql);
            if (preg_match('/^SELECT/i', $trim)) { $stmt=$pdo->query($sql); $rows=$stmt->fetchAll(PDO::FETCH_ASSOC); echo json_encode(['success'=>true,'type'=>'select','rows'=>$rows,'count'=>count($rows)]); exit; }
            else { $affected = $pdo->exec($sql); echo json_encode(['success'=>true,'type'=>'exec','affected'=>$affected]); exit; }
        } catch (Exception $e) { $err = date("Y-m-d H:i:s") . " | ERROR: " . $e->getMessage() . " | SQL: " . trim($sql); appendSqlErrorLine($err); echo json_encode(['success'=>false,'message'=>$e->getMessage()]); exit; }
    }

    if ($action === 'sql_history_read') { $content = is_file(SQL_HISTORY_FILE) ? file_get_contents(SQL_HISTORY_FILE) : ''; $lines = $content === '' ? [] : explode("\n", trim($content)); echo json_encode(['success'=>true,'history'=>$lines]); exit; }
    if ($action === 'sql_errors_read') { $content = is_file(SQL_ERROR_FILE) ? file_get_contents(SQL_ERROR_FILE) : ''; $lines = $content === '' ? [] : explode("\n", trim($content)); echo json_encode(['success'=>true,'errors'=>$lines]); exit; }

    // ---- Create Project (handler) ----
    if ($action === 'create_project') {
        $proyectoRaw = $_POST['proyecto'] ?? ''; $carpetasRaw = $_POST['carpetas'] ?? ''; $archivosRaw = $_POST['archivos'] ?? '';
        $proyecto = trim($proyectoRaw); $proyecto = trim($proyecto, "/\\");
        if ($proyecto === '' || strpos($proyecto, '..') !== false) { echo json_encode(['success'=>false,'message'=>'Nombre de proyecto inválido']); exit; }
        $projectDir = rtrim(MG_BASE_DIR, '/\\') . DIRECTORY_SEPARATOR . $proyecto;
        $results = ['created_dirs'=>[], 'created_files'=>[], 'errors'=>[]];
        if (!is_dir($projectDir)) { if (!@mkdir($projectDir, 0755, true)) { $results['errors'][]="No se pudo crear la carpeta principal: {$projectDir}"; echo json_encode(['success'=>false,'result'=>$results]); exit; } }
        $normalize = function($line) { $s=trim((string)$line); $s=trim($s,"/\\"); if($s===''||strpos($s,'..')!==false) return ''; return $s; };
        if (trim($carpetasRaw) !== '') {
            $lines = preg_split('/\r\n|\r|\n/', $carpetasRaw);
            foreach ($lines as $line) { $seg = $normalize($line); if($seg==='') continue; $rutaRel = str_replace(['/','\\'], DIRECTORY_SEPARATOR, $seg); $ruta = $projectDir . DIRECTORY_SEPARATOR . $rutaRel; if (!is_dir($ruta)) { if (@mkdir($ruta, 0755, true)) $results['created_dirs'][] = $rutaRel; else $results['errors'][] = "Error creando carpeta: {$rutaRel}"; } else $results['created_dirs'][] = $rutaRel . ' (ya existía)'; }
        }
        if (trim($archivosRaw) !== '') {
            $lines = preg_split('/\r\n|\r|\n/', $archivosRaw);
            foreach ($lines as $line) { $seg = $normalize($line); if ($seg==='') continue; $rutaRel = str_replace(['/','\\'], DIRECTORY_SEPARATOR, $seg); $fullpath = $projectDir . DIRECTORY_SEPARATOR . $rutaRel; $dir = dirname($fullpath); if (!is_dir($dir)) @mkdir($dir, 0755, true); if (@file_put_contents($fullpath, "") !== false) { @chmod($fullpath, 0644); $results['created_files'][] = $rutaRel; } else { $results['errors'][] = "No se pudo crear archivo: {$rutaRel}"; } }
        }
        echo json_encode(['success'=>empty($results['errors']),'result'=>$results]); exit;
    }

    // ---- DB config generator + test connection + write file ----
    if ($action === 'test_db_connection') {
        $host = $_POST['host'] ?? 'localhost'; $user = $_POST['user'] ?? ''; $pass = $_POST['pass'] ?? ''; $base = $_POST['base'] ?? '';
        $port = intval($_POST['port'] ?? MG_DB_PORT);
        $result = ['success'=>false];
        // Use mysqli for testing
        $mysqli = @new mysqli($host, $user, $pass, $base, $port);
        if ($mysqli->connect_errno) { $result['message'] = $mysqli->connect_error; echo json_encode($result); exit; }
        $mysqli->set_charset("utf8");
        $result['success'] = true; $result['message'] = 'Conexión OK'; echo json_encode($result); $mysqli->close(); exit;
    }

    if ($action === 'generate_db_file') {
        $variant = $_POST['variant'] ?? 'simple'; // simple,function,class,silent
        $host = $_POST['host'] ?? 'localhost'; $user = $_POST['user'] ?? ''; $pass = $_POST['pass'] ?? ''; $base = $_POST['base'] ?? '';
        $filename = trim($_POST['filename'] ?? 'generated/database.php');
        // sanitize filename to be inside generated dir
        $targetBase = realpath(MG_BASE_DIR) . DIRECTORY_SEPARATOR . 'generated';
        if (!is_dir($targetBase)) @mkdir($targetBase, 0755, true);
        $filename = str_replace(['..','\\'], '', $filename);
        $target = $targetBase . DIRECTORY_SEPARATOR . ltrim($filename, "/\\");
        $dir = dirname($target); if (!is_dir($dir)) @mkdir($dir, 0755, true);

        // Build content
        $content = "<?php\n";
        if ($variant === 'simple') {
            $content .= <<<PHP
// database.php - simple connection (mysqli)
\$host = "{$host}";
\$usuario = "{$user}";
\$clave = "{$pass}";
\$base = "{$base}";
\$mysqli = new mysqli(\$host, \$usuario, \$clave, \$base);
if (\$mysqli->connect_errno) {
    die("Error de conexión: " . \$mysqli->connect_error);
}
\$mysqli->set_charset("utf8");
PHP;
        } elseif ($variant === 'function') {
            $content .= <<<PHP
// database.php - function db()
function db() {
    \$host = "{$host}";
    \$usuario = "{$user}";
    \$clave = "{$pass}";
    \$base = "{$base}";
    \$mysqli = new mysqli(\$host, \$usuario, \$clave, \$base);
    if (\$mysqli->connect_errno) {
        die("Error de conexión: " . \$mysqli->connect_error);
    }
    \$mysqli->set_charset("utf8");
    return \$mysqli;
}
PHP;
        } elseif ($variant === 'class') {
            $content .= <<<PHP
// database.php - DB class
class DB {
    public static function conectar() {
        \$host = "{$host}";
        \$usuario = "{$user}";
        \$clave = "{$pass}";
        \$base = "{$base}";
        \$cn = new mysqli(\$host, \$usuario, \$clave, \$base);
        if (\$cn->connect_errno) {
            die("Error DB: " . \$cn->connect_error);
        }
        \$cn->set_charset("utf8");
        return \$cn;
    }
}
PHP;
        } else { // silent
            $content .= <<<PHP
// database.php - silent connect
\$mysqli = @new mysqli("{$host}", "{$user}", "{$pass}", "{$base}");
@\$mysqli->set_charset("utf8");
PHP;
        }

        // write file
        $written = @file_put_contents($target, $content);
        if ($written === false) { echo json_encode(['success'=>false,'message'=>'No se pudo escribir el archivo: '.$target]); exit; }
        echo json_encode(['success'=>true,'path'=>str_replace(MG_BASE_DIR.'/', '', $target)]); exit;
    }

    // ---- Manejador de consultas (simple wrapper de sql_execute) ----
    // Use existing sql_execute endpoint for arbitrary query execution.

    // default fallback
    echo json_encode(['success'=>false,'message'=>'Acción no reconocida']);
    exit;
}

// ---------------- AUTH UI ----------------
if (isset($_GET['logout'])) {
    $mg->log('LOGOUT'); session_destroy(); header('Location: ' . $_SERVER['PHP_SELF']); exit;
}
if (!isset($_SESSION['mg_auth'])) {
    if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['mg_password'])) {
        if ($_POST['mg_password'] === MG_PASSWORD) {
            $_SESSION['mg_auth'] = true; $_SESSION['mg_login_time'] = time(); $_SESSION['mg_user'] = 'admin';
            $mg->log('LOGIN','Acceso correcto'); header('Location: ' . $_SERVER['PHP_SELF']); exit;
        } else { $loginError = 'Contraseña incorrecta'; }
    }
    ?>
    <!DOCTYPE html><html lang="es"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Login</title>
    <style>body{font-family:Arial;background:#000;color:#0f0;display:flex;align-items:center;justify-content:center;height:100vh;margin:0} .login{background:#010;border:2px solid #0f0;padding:30px;border-radius:8px;width:360px;text-align:center} input{width:100%;padding:12px;margin:8px 0;background:#000;border:1px solid #0f0;color:#0f0} button{padding:12px 20px;background:#0f0;color:#000;border:none;cursor:pointer;font-weight:bold}</style>
    </head><body>
    <div class="login"><h1>MASSOLAGUARD</h1><?php if(!empty($loginError)):?><div style="color:#f66;margin:8px 0"><?=htmlspecialchars($loginError)?></div><?php endif;?>
    <form method="post"><input type="password" name="mg_password" placeholder="Contraseña Maestra" required autofocus><button type="submit">ACCEDER</button></form>
    <div style="margin-top:10px;opacity:0.6">v<?=MG_VERSION?></div></div></body></html>
    <?php exit;
}

// ---------------- UI HTML (main) ----------------
?><!DOCTYPE html>
<html lang="es">
<head>
<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>Massolaguard - Centro</title>
<style>
:root{--bg:#000;--panel:#0a0a0a;--accent:#0f0;--dim:#0a0}
body{margin:0;font-family:Arial,monospace;background:var(--bg);color:var(--accent);height:100vh;display:flex;flex-direction:column}
.topbar{padding:10px 20px;background:var(--panel);border-bottom:2px solid var(--dim);display:flex;justify-content:space-between;align-items:center}
.main{flex:1;display:flex;overflow:hidden}
.sidebar{width:320px;background:var(--panel);border-right:2px solid var(--dim);padding:12px;overflow:auto}
.content{flex:1;padding:12px;overflow:auto}
.btn{background:var(--accent);color:#000;padding:8px 12px;border:none;cursor:pointer;margin:4px}
.tree-item{padding:6px 8px;border-radius:4px;cursor:pointer}
.tree-item:hover{background:rgba(0,255,0,0.06)}
.folder{font-weight:bold}
.editor-tabs{display:flex;gap:6px;margin-bottom:8px;flex-wrap:wrap}
.tab{background:var(--panel);padding:6px 10px;border:1px solid var(--dim);cursor:pointer}
.tab.active{background:rgba(0,255,0,0.14);border-left:4px solid var(--accent)}
textarea.editor{width:100%;height:360px;background:#000;color:var(--accent);border:1px solid var(--dim);padding:12px;resize:vertical}
.info{font-size:0.9em;opacity:0.8}
.status{padding:8px 12px;background:var(--panel);border-top:2px solid var(--dim);font-size:0.9em}
.search{width:100%;padding:8px;margin-bottom:8px;background:#000;border:1px solid var(--dim);color:var(--accent)}
.panel{display:none}
.panel.active{display:block}
.table{width:100%;border-collapse:collapse;margin:10px 0}
.table th,.table td{border:1px solid var(--dim);padding:6px;text-align:left;color:var(--accent)}
.table th{background:rgba(0,255,0,0.08)}
.input-small{padding:6px;border:1px solid var(--dim);background:#000;color:var(--accent)}
.card{background:var(--panel);padding:12px;border:1px solid var(--dim);margin-bottom:12px}
.snippet{background:#050;border:1px solid #0a0;padding:8px;margin:6px 0;color:#000;white-space:pre-wrap;font-family:monospace}
</style>
</head>
<body>
    <div class="topbar">
        <div><strong>MASSOLAGUARD</strong> <span class="info">v<?=MG_VERSION?> — Usuario: <?=htmlspecialchars($_SESSION['mg_user'])?></span></div>
        <div>
            <button class="btn" onclick="showPanel('filespanel')">📁 Archivos</button>
            <button class="btn" onclick="showPanel('dbpanel')">🗄️ Base de Datos</button>
            <button class="btn" onclick="showPanel('sqlpanel')">🧾 SQL Console</button>
            <button class="btn" onclick="showPanel('createproject')">🛠 Crear Proyecto</button>
            <button class="btn" onclick="showPanel('dbconfig')">⚙️ DB Config</button>
            <button class="btn" onclick="showPanel('help')">❔ Guía</button>
            <button class="btn" onclick="location.href='?logout=1'">Salir</button>
        </div>
    </div>

    <div class="main">
        <div class="sidebar">
            <div class="card">
                <input id="pathInput" class="search" placeholder="Ruta (vacío = base)"/>
                <div style="margin-top:8px">
                    <button class="btn" onclick="loadTree()">Recargar</button>
                    <button class="btn" onclick="createFilePrompt()">Crear archivo</button>
                </div>
            </div>
            <div id="fileTree" class="card"></div>
        </div>

        <div class="content">
            <!-- FILES -->
            <div id="filespanel" class="panel active">
                <div style="display:flex;justify-content:space-between;align-items:center">
                    <div class="editor-tabs" id="editorTabs"></div>
                    <div>
                        <button class="btn" onclick="saveCurrent()">Guardar (Ctrl+S)</button>
                        <button class="btn" onclick="closeCurrent()">Cerrar</button>
                    </div>
                </div>
                <div id="editorContainer" class="card"><textarea id="editor" class="editor" placeholder="Selecciona un archivo..."></textarea></div>
                <div id="fileInfo" class="card info"></div>
            </div>

            <!-- DB panel (tables, forms) -->
            <div id="dbpanel" class="panel">
                <div class="card">
                    <div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap">
                        <select id="dbSelect" onchange="onDbChange(this.value)"><option value="">Cargando...</option></select>
                        <select id="tableSelect" onchange="onTableChange(this.value)"><option value="">Selecciona tabla</option></select>
                        <input id="dbHost" class="input-small" placeholder="host (opcional)"/>
                        <input id="dbUser" class="input-small" placeholder="user (opcional)"/>
                        <input id="dbPass" class="input-small" placeholder="pass (opcional)"/>
                        <button class="btn" onclick="loadDatabases()">Recargar BD</button>
                    </div>
                </div>
                <div id="dbInfo" class="card"></div>
                <div id="dbFormsContainer" class="card" style="display:grid;grid-template-columns:1fr 1fr;gap:12px">
                    <div>
                        <h3 style="color:#6f6">Formulario INSERT</h3>
                        <div id="insertFormContainer" class="card" style="background:#021;padding:12px"></div>
                        <h3 style="color:#6f6;margin-top:8px">Formulario UPDATE (cargar por PK)</h3>
                        <div style="display:flex;gap:8px;margin-bottom:8px">
                            <input id="updatePkField" class="input-small" placeholder="PK field (ej: id)">
                            <input id="updatePkValue" class="input-small" placeholder="PK value">
                            <button class="btn" onclick="loadRowForUpdate()">Cargar fila</button>
                        </div>
                        <div id="updateFormContainer" class="card" style="background:#120;padding:12px"></div>
                    </div>
                    <div>
                        <h3 style="color:#f66">Eliminar fila</h3>
                        <div style="display:flex;gap:8px;align-items:center">
                            <input id="deletePkField" class="input-small" placeholder="PK field">
                            <input id="deletePkValue" class="input-small" placeholder="PK value">
                            <button class="btn" onclick="deleteRowFromForm()">Eliminar</button>
                        </div>
                        <h3 style="color:#6cf;margin-top:12px">Filas (vista rápida)</h3>
                        <div style="margin-bottom:8px">
                            <button class="btn" onclick="loadTableRows(20,0)">Cargar 20</button>
                            <button class="btn" onclick="loadTableRows(100,0)">Cargar 100</button>
                        </div>
                        <div id="tableRowsContainer" class="card" style="background:#011;padding:6px"></div>
                    </div>
                </div>
            </div>

            <!-- SQL Console -->
            <div id="sqlpanel" class="panel">
                <div class="card">
                    <div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap">
                        <select id="sqlDbSelect"><option value="">(usar BD seleccionada)</option></select>
                        <button class="btn" onclick="runSQL()">Ejecutar SQL</button>
                        <button class="btn" onclick="loadHistory()">Historial</button>
                        <button class="btn" onclick="loadErrors()">Logs Errores</button>
                    </div>
                </div>
                <div class="card">
                    <textarea id="sqlInput" class="editor" style="height:160px" placeholder="Escribe una consulta SQL aquí..."></textarea>
                    <div style="margin-top:8px">
                        <button class="btn" onclick="runSQL()">Ejecutar</button>
                        <button class="btn" onclick="saveSQLToHistory()">Ejecutar y Guardar</button>
                    </div>
                </div>
                <div id="sqlResult" class="card"></div>
                <div id="sqlHistory" class="card"></div>
                <div id="sqlErrors" class="card"></div>
            </div>

            <!-- Create Project -->
            <div id="createproject" class="panel">
                <div class="card">
                    <h2>🛠 Crear estructura de proyecto</h2>
                    <div id="createProjectResult"></div>
                    <label>Nombre del proyecto:</label><input id="cp_proyecto" class="input-small" placeholder="miweb">
                    <label>Carpetas (una por línea):</label><textarea id="cp_carpetas" rows="6" class="input-small" placeholder="assets
assets/css
includes"></textarea>
                    <label>Archivos (una por línea):</label><textarea id="cp_archivos" rows="6" class="input-small" placeholder="index.php
includes/config.php"></textarea>
                    <div style="margin-top:8px"><button class="btn" onclick="createProjectSubmit()">Crear proyecto</button> <button class="btn" onclick="clearCreateProjectForm()">Limpiar</button></div>
                </div>
            </div>

            <!-- DB Config generator -->
            <div id="dbconfig" class="panel">
                <div class="card">
                    <h2>⚙️ Generador database.php</h2>
                    <label>Host</label><input id="gen_host" class="input-small" value="localhost">
                    <label>Usuario</label><input id="gen_user" class="input-small" value="">
                    <label>Contraseña</label><input id="gen_pass" class="input-small" value="">
                    <label>Base</label><input id="gen_base" class="input-small" value="">
                    <label>Puerto</label><input id="gen_port" class="input-small" value="<?=MG_DB_PORT?>">
                    <label>Variant</label>
                    <select id="gen_variant" class="input-small">
                        <option value="simple">Simple (mysqli)</option>
                        <option value="function">Function db()</option>
                        <option value="class">Class DB::conectar()</option>
                        <option value="silent">Silent (production)</option>
                    </select>
                    <label>Nombre archivo a generar (relative to /generated)</label><input id="gen_filename" class="input-small" value="database.php">
                    <div style="margin-top:8px">
                        <button class="btn" onclick="testDbConnection()">Probar Conexión</button>
                        <button class="btn" onclick="generateDbFile()">Generar archivo</button>
                    </div>
                    <div id="gen_result" style="margin-top:12px"></div>
                </div>
            </div>

            <!-- Help / Snippets -->
            <div id="help" class="panel">
                <h2>Guía y Snippets</h2>
                <div class="card">
                    <strong>Snippets SQL esenciales</strong>
                    <pre class="snippet">
-- Crear DB
CREATE DATABASE nombre_basedatos;
-- Usar DB
USE nombre_basedatos;
-- Crear tabla ejemplo
CREATE TABLE usuarios (
  id INT AUTO_INCREMENT PRIMARY KEY,
  nombre VARCHAR(100),
  correo VARCHAR(150),
  fecha_registro DATETIME
);
-- Insertar
INSERT INTO usuarios (nombre, correo, fecha_registro) VALUES ('Alexis', 'alexis@mail.com', NOW());
-- Update/Delete/Drop/Alter ... (ver la guía en la UI)
                    </pre>
                </div>

                <div class="card">
                    <h3>Áreas y módulos (visión)</h3>
                    <ul>
                        <li>Inteligencia del proyecto: análisis de archivos, seguridad, rendimiento</li>
                        <li>Automatización: generador de proyectos, módulos y CRUD</li>
                        <li>Gestión de archivos: búsqueda, diff, historial</li>
                        <li>Base de datos avanzada: diseñador visual, ER diagram, export/import</li>
                        <li>Monitoreo y logs, seguridad empresarial, pruebas y más</li>
                    </ul>
                    <div style="opacity:0.8">Esta herramienta es extensible: añade módulos en el código server-side y paneles en la UI.</div>
                </div>
            </div>
        </div>
    </div>

    <div class="status"><span id="statusText">Listo</span></div>

<script>
// ---------------- UTIL ----------------
function escHtml(s){ return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;'); }
function setStatus(t){ document.getElementById('statusText').textContent = t; }
async function apiCall(action, data={}) {
    const form = new FormData();
    form.append('action', action);
    for (let k in data) form.append(k, data[k]);
    const res = await fetch('', {method:'POST', body: form});
    try { return await res.json(); } catch(e) { return { success:false, message:'Invalid JSON response' };}
}
function showPanel(id) {
    document.querySelectorAll('.panel').forEach(p=>p.classList.remove('active'));
    const el = document.getElementById(id); if (el) el.classList.add('active');
    if (id === 'dbpanel') loadDatabases();
    if (id === 'sqlpanel') { loadDatabases(); loadSqlDbSelect(); }
    if (id === 'createproject') { document.getElementById('createProjectResult').innerHTML=''; }
}

// ---------------- FILES UI ----------------
let openEditors = [], currentPath = null;
const MG_MAX_EDITORS = <?=MG_MAX_EDITORS?>;

async function loadTree() {
    const p = document.getElementById('pathInput').value.trim();
    const res = await apiCall('files_scan', {path: p, maxDepth: 4});
    if (!res.success) { alert(res.message||'Error'); return; }
    document.getElementById('fileTree').innerHTML = renderTree(res.files || res);
}
function renderTree(items, depth=0) {
    if (!items) return '';
    let html=''; items.forEach(it=>{
        const indent='&nbsp;'.repeat(depth*2);
        if (it.type==='folder') {
            html+=`<div class="tree-item folder" onclick="toggleFolder(this)">${indent}📁 ${escHtml(it.name)}</div>`;
            if (it.children && it.children.length) html+=`<div class="tree-children" style="display:none;margin-left:8px">${renderTree(it.children, depth+1)}</div>`;
        } else {
            html+=`<div class="tree-item" onclick="openFile('${encodeURIComponent(it.path)}')">${indent}📄 ${escHtml(it.name)}</div>`;
        }
    });
    return html;
}
function toggleFolder(el){ const n=el.nextElementSibling; if(n) n.style.display = n.style.display==='none'?'block':'none'; }
async function openFile(encodedPath) {
    const path = decodeURIComponent(encodedPath);
    const res = await apiCall('files_read',{filepath:path});
    if (!res.success) return alert(res.message||'Error al leer archivo');
    currentPath = path;
    if (!openEditors.find(e=>e.path===path)) {
        if (openEditors.length>=MG_MAX_EDITORS) return alert('Máximo editores abiertos');
        openEditors.push({path:path,title:res.filename,content:res.content});
    }
    renderTabs(); setEditorContent(path, res.content); updateFileInfo(res); setStatus('Archivo abierto: '+path);
}
function renderTabs(){ const c=document.getElementById('editorTabs'); c.innerHTML=''; openEditors.forEach(e=>{ const d=document.createElement('div'); d.className='tab'+(e.path===currentPath?' active':''); d.textContent=e.title; d.onclick=()=>{ setEditorContent(e.path,e.content); currentPath=e.path; renderTabs(); }; c.appendChild(d); });}
function setEditorContent(path, content){ currentPath=path; document.getElementById('editor').value = content; renderTabs(); }
async function saveCurrent(){ if(!currentPath){ alert('No hay archivo abierto'); return;} const content=document.getElementById('editor').value; const res=await apiCall('files_save',{filepath: currentPath, content}); if(res.success){ const ed=openEditors.find(e=>e.path===currentPath); if(ed) ed.content=content; setStatus('Guardado correctamente'); } else { setStatus('Error al guardar: '+(res.message||'')); alert(res.message||'Error'); } }
function closeCurrent(){ if(!currentPath) return; const i=openEditors.findIndex(e=>e.path===currentPath); if(i!==-1) openEditors.splice(i,1); if(openEditors.length){ currentPath=openEditors[openEditors.length-1].path; setEditorContent(currentPath, openEditors[openEditors.length-1].content); } else { currentPath=null; document.getElementById('editor').value=''; } renderTabs(); }
function updateFileInfo(info){ document.getElementById('fileInfo').innerHTML = `<div class="info"><strong>${escHtml(info.filename)}</strong> — ${info.size} bytes — Mod: ${info.modified} — <em>${info.writable? 'Editable':'No editable'}</em></div>`;}
function createFilePrompt(){ const name=prompt('Nombre del nuevo archivo (ruta relativa):'); if(!name) return; apiCall('files_save',{filepath:name, content:''}).then(res=>{ if(res.success){ alert('Archivo creado'); loadTree(); openFile(encodeURIComponent(name)); } else alert('No se pudo crear: '+(res.message||'')); }); }
document.addEventListener('keydown', e=>{ if((e.ctrlKey||e.metaKey) && e.key.toLowerCase()==='s'){ e.preventDefault(); saveCurrent(); } });

// init
loadTree();

// ---------------- DB UI & Forms ----------------
async function loadDatabases(){
    const res = await apiCall('db_list'); if (!res.success) return; const sel=document.getElementById('dbSelect'); sel.innerHTML='<option value="">-- Seleccione BD --</option>'; res.databases.forEach(db=>{ const o=document.createElement('option'); o.value=db; o.textContent=db; sel.appendChild(o); }); const s2=document.getElementById('sqlDbSelect'); if(s2){ s2.innerHTML = '<option value="">(usar BD seleccionada)</option>'; res.databases.forEach(db=>{ const o=document.createElement('option'); o.value=db; o.textContent=db; s2.appendChild(o); }); } }
function onDbChange(db){ if(db) loadTablesForDB(db); else { document.getElementById('tableSelect').innerHTML='<option value="">Selecciona tabla</option>'; document.getElementById('tableRowsContainer').innerHTML=''; } }
async function loadTablesForDB(db){ if(!db) return; const res = await apiCall('db_tables',{database:db}); if(!res.success) return alert(res.message||'Error'); const sel=document.getElementById('tableSelect'); sel.innerHTML='<option value="">-- Seleccione Tabla --</option>'; res.tables.forEach(t=>{ const o=document.createElement('option'); o.value=t; o.textContent=t; sel.appendChild(o); }); document.getElementById('tableRowsContainer').innerHTML=''; }

function mapColumnToField(col) {
    const type = col.Type.toLowerCase(); let inputType='text', step=null, isTextarea=false, isCheckbox=false, options=null;
    if (type.match(/\bint\b/) || type.match(/tinyint/)) { if (/tinyint\(1\)/.test(type)) { isCheckbox=true; inputType='checkbox'; } else inputType='number'; }
    else if (/decimal|float|double/.test(type)) { inputType='number'; step='0.01'; }
    else if (/varchar|char/.test(type)) inputType='text';
    else if (/text|blob/.test(type)) isTextarea=true;
    else if (/datetime|timestamp/.test(type)) inputType='datetime-local';
    else if (/date/.test(type)) inputType='date';
    else if (/time/.test(type)) inputType='time';
    else if (/^enum\((.+)\)$/i.test(type)) { const m=type.match(/^enum\((.+)\)$/i); options = m[1].split(',').map(s=>s.trim().replace(/^'|'$/g,'')); }
    return {inputType, step, isTextarea, isCheckbox, options};
}

function buildFormFromDescribe(describe, containerId, mode='insert', prefill={}) {
    const container = document.getElementById(containerId); container.innerHTML=''; const form=document.createElement('div'); form.style.display='grid'; form.style.gap='8px';
    describe.forEach(col=>{
        const name=col.Field; const meta=mapColumnToField(col);
        const label=document.createElement('label'); label.style.display='block'; label.style.fontSize='0.9em';
        label.innerHTML=`<strong style="color:#afa">${name}</strong> <span style="color:#777;font-size:0.85em"> ${col.Type} ${col.Null==='NO' ? '(NOT NULL)':''} ${col.Key==='PRI' ? '[PK]':''}</span>`;
        if (meta.options) {
            const sel=document.createElement('select'); sel.className='input-small'; sel.dataset.col=name; const empty=document.createElement('option'); empty.value=''; empty.textContent='(vacío)'; sel.appendChild(empty);
            meta.options.forEach(op=>{ const o=document.createElement('option'); o.value=op; o.textContent=op; sel.appendChild(o); });
            if(prefill[name]!==undefined) sel.value=prefill[name];
            form.appendChild(label); form.appendChild(sel);
        } else if (meta.isTextarea) {
            const ta=document.createElement('textarea'); ta.className='input-small'; ta.dataset.col=name; ta.rows=4; ta.value=prefill[name] ?? (col.Default ?? ''); form.appendChild(label); form.appendChild(ta);
        } else if (meta.isCheckbox) {
            const chk=document.createElement('input'); chk.type='checkbox'; chk.dataset.col=name; chk.checked = !!(prefill[name] ?? (col.Default == '1')); form.appendChild(label); form.appendChild(chk);
        } else {
            const inp=document.createElement('input'); inp.type=meta.inputType || 'text'; if(meta.step) inp.step=meta.step; inp.className='input-small'; inp.dataset.col=name; inp.value=prefill[name] ?? (col.Default ?? '');
            if (inp.type==='datetime-local' && inp.value) inp.value = inp.value.replace(' ','T').slice(0,16);
            form.appendChild(label); form.appendChild(inp);
        }
    });
    const btnWrap=document.createElement('div'); btnWrap.style.marginTop='8px'; btnWrap.style.display='flex'; btnWrap.style.gap='8px';
    const primary=document.createElement('button'); primary.className='btn'; primary.textContent = mode==='insert' ? 'Insertar fila' : 'Actualizar fila';
    primary.onclick = async ()=> {
        const db = document.getElementById('dbSelect').value; const table=document.getElementById('tableSelect').value;
        if(!db||!table) return alert('Selecciona BD y tabla');
        const fields=form.querySelectorAll('[data-col]'); const data={}; fields.forEach(f=>{ const col=f.dataset.col; if(f.type==='checkbox') data[col]=f.checked?1:0; else data[col]=f.value===''?null:f.value; });
        if(mode==='insert'){ const res=await apiCall('db_insert_row',{database:db,table:table,data:JSON.stringify(data)}); if(res.success){ alert('Insertado id: '+res.insert_id); loadTableRows(); } else alert('Error: '+(res.message||'')); }
        else { const pkField=document.getElementById('updatePkField').value.trim(); const pkValue=document.getElementById('updatePkValue').value.trim(); if(!pkField||pkValue==='') return alert('PK field y value requeridos'); delete data[pkField]; const res=await apiCall('db_update_row',{database:db,table:table,pk:pkField,pk_value:pkValue,data:JSON.stringify(data)}); if(res.success){ alert('Actualizado. Affected: '+res.affected); loadTableRows(); } else alert('Error: '+(res.message||'')); }
    };
    btnWrap.appendChild(primary);
    if(mode==='insert'){ const clear=document.createElement('button'); clear.className='btn'; clear.textContent='Limpiar'; clear.onclick=()=>{ form.querySelectorAll('[data-col]').forEach(f=>{ if(f.type==='checkbox') f.checked=false; else f.value=''; }); }; btnWrap.appendChild(clear); }
    form.appendChild(btnWrap); container.appendChild(form);
}

async function onTableChange(table) {
    if(!table) return;
    const db=document.getElementById('dbSelect').value; if(!db) return alert('Selecciona BD primero');
    const descRes = await apiCall('db_describe',{database:db,table:table}); if(!descRes.success) return alert(descRes.message||'Error'); const describe = descRes.describe||[];
    buildFormFromDescribe(describe,'insertFormContainer','insert',{}); buildFormFromDescribe(describe,'updateFormContainer','update',{});
    const pkCol = describe.find(c=>c.Key==='PRI'); if(pkCol){ document.getElementById('updatePkField').value=pkCol.Field; document.getElementById('deletePkField').value=pkCol.Field; }
    loadTableRows(20,0);
}

async function loadRowForUpdate() {
    const db=document.getElementById('dbSelect').value; const table=document.getElementById('tableSelect').value;
    const pkField=document.getElementById('updatePkField').value.trim(); const pkValue=document.getElementById('updatePkValue').value.trim();
    if(!db||!table||!pkField||pkValue==='') return alert('Selecciona BD, tabla y PK');
    const res = await apiCall('db_get_row',{database:db,table:table,pk:pkField,pk_value:pkValue});
    if(!res.success) return alert(res.message||'No encontrada'); const row=res.row||{};
    const descRes = await apiCall('db_describe',{database:db,table:table}); const describe=descRes.describe||[];
    buildFormFromDescribe(describe,'updateFormContainer','update',row); setStatus('Fila cargada para editar (PK='+pkValue+')');
}

async function deleteRowFromForm() {
    const db=document.getElementById('dbSelect').value; const table=document.getElementById('tableSelect').value;
    const pkField=document.getElementById('deletePkField').value.trim(); const pkValue=document.getElementById('deletePkValue').value.trim();
    if(!db||!table||!pkField||pkValue==='') return alert('Selecciona BD, tabla y PK');
    if(!confirm('Confirmar eliminación?')) return;
    const res = await apiCall('db_delete_row',{database:db,table:table,pk:pkField,pk_value:pkValue});
    if(res.success){ alert('Eliminado'); loadTableRows(); } else alert('Error: '+(res.message||''));
}

async function loadTableRows(limit=50, offset=0) {
    const db=document.getElementById('dbSelect').value; const table=document.getElementById('tableSelect').value;
    if(!db||!table) return alert('Selecciona BD y tabla');
    const descRes = await apiCall('db_describe',{database:db,table:table}); const describe = descRes.describe || [];
    let pkField = null; describe.forEach(c=>{ if(c.Key==='PRI') pkField=c.Field; });
    const rowsRes = await apiCall('db_select',{database:db,table:table,limit:limit,offset:offset});
    if(!rowsRes.success) return alert(rowsRes.message||'Error'); const rows = rowsRes.rows||[];
    if(rows.length===0){ document.getElementById('tableRowsContainer').innerHTML='<div>No hay filas</div>'; return; }
    let html='<table class="table"><thead><tr>'; const cols=Object.keys(rows[0]); cols.forEach(c=> html+=`<th>${escHtml(c)}</th>`); html+='<th>Acciones</th></tr></thead><tbody>';
    rows.forEach((r,idx)=>{ html+='<tr>'; cols.forEach(c=> html+=`<td>${escHtml(r[c]===null?'':String(r[c]))}</td>`); const pkVal = pkField?String(r[pkField]).replace(/'/g,"\\'") : ''; html+=`<td>${pkField?`<button class="btn" onclick="document.getElementById('updatePkField').value='${pkField}';document.getElementById('updatePkValue').value='${pkVal}';loadRowForUpdate();">Editar</button>`:''}${pkField?` <button class="btn" onclick="if(confirm('Eliminar?')) apiCall('db_delete_row',{database:'${db}',table:'${table}',pk:'${pkField}',pk_value:'${pkVal}'}).then(r=>{if(r.success){alert('Eliminado');loadTableRows();}else alert(r.message)})">Eliminar</button>`:''}</td>`; html+='</tr>'; });
    html+='</tbody></table>'; document.getElementById('tableRowsContainer').innerHTML=html;
}

// ---------------- SQL Console ----------------
async function loadSqlDbSelect(){ const sel=document.getElementById('sqlDbSelect'); if(!sel) return; const res = await apiCall('db_list'); if(!res.success) return; sel.innerHTML='<option value="">(usar BD seleccionada)</option>'; res.databases.forEach(db=>{ const o=document.createElement('option'); o.value=db; o.textContent=db; sel.appendChild(o); }); }
async function runSQL(){ const raw=document.getElementById('sqlInput').value.trim(); if(!raw) return alert('SQL vacío'); const dbSel=document.getElementById('sqlDbSelect').value || document.getElementById('dbSelect').value || ''; const res=await apiCall('sql_execute',{database:dbSel,sql:raw}); if(!res.success) return alert('Error: '+(res.message||'')); const out=document.getElementById('sqlResult'); out.innerHTML=''; if(res.type==='select'){ const rows=res.rows||[]; if(rows.length===0){ out.innerHTML='<div>No rows</div>'; return; } let html='<table class="table"><thead><tr>'; Object.keys(rows[0]).forEach(c=> html+=`<th>${escHtml(c)}</th>`); html+='</tr></thead><tbody>'; rows.forEach(r=>{ html+='<tr>'; Object.values(r).forEach(v=> html+=`<td>${escHtml(v===null?'':String(v))}</td>`); html+='</tr>'; }); html+='</tbody></table>'; out.innerHTML=html; } else { out.innerHTML='<div>Consulta ejecutada. Affected: '+(res.affected||0)+'</div>'; } loadHistory(); }
async function saveSQLToHistory(){ const sql=document.getElementById('sqlInput').value.trim(); if(!sql) return alert('SQL vacío'); const res=await apiCall('sql_execute',{sql:sql}); if(!res.success) return alert('Error: '+(res.message||'')); alert('Consulta ejecutada y registrada'); loadHistory(); }
async function loadHistory(){ const res=await apiCall('sql_history_read'); if(!res.success) return alert('No history'); document.getElementById('sqlHistory').innerHTML='<strong>Historial (últimas)</strong><div style="white-space:pre-wrap;background:#020;padding:8px;margin-top:6px;color:#0f0">'+ escHtml((res.history||[]).join("\n")) +'</div>'; }
async function loadErrors(){ const res=await apiCall('sql_errors_read'); if(!res.success) return alert('No logs'); document.getElementById('sqlErrors').innerHTML='<strong>Errores SQL</strong><div style="white-space:pre-wrap;background:#200;padding:8px;margin-top:6px;color:#f88">'+ escHtml((res.errors||[]).join("\n")) +'</div>'; }

// ---------------- Create Project JS ----------------
function clearCreateProjectForm(){ document.getElementById('cp_proyecto').value=''; document.getElementById('cp_carpetas').value=''; document.getElementById('cp_archivos').value=''; document.getElementById('createProjectResult').innerHTML=''; }
async function createProjectSubmit(){ const proyecto=document.getElementById('cp_proyecto').value.trim(); const carpetas=document.getElementById('cp_carpetas').value; const archivos=document.getElementById('cp_archivos').value; if(!proyecto) return alert('Ingrese nombre del proyecto'); setStatus('Creando proyecto...'); const res=await apiCall('create_project',{proyecto:proyecto,carpetas:carpetas,archivos:archivos}); setStatus('Listo'); const out=document.getElementById('createProjectResult'); out.innerHTML=''; if(!res.success){ out.innerHTML='<div style="background:#200;padding:8px;color:#f88">Error: '+escHtml(res.message||'')+'</div>'; return; } const r=res.result||{}; let html=''; if(r.created_dirs && r.created_dirs.length){ html += '<div style="background:#052;padding:8px;color:#0f0"><strong>Carpetas creadas:</strong><ul>'; r.created_dirs.forEach(d=> html += '<li>'+escHtml(d)+'</li>'); html += '</ul></div>'; } if(r.created_files && r.created_files.length){ html += '<div style="background:#052;padding:8px;color:#0f0"><strong>Archivos creados:</strong><ul>'; r.created_files.forEach(f=> html += '<li>'+escHtml(f)+'</li>'); html += '</ul></div>'; } if(r.errors && r.errors.length){ html += '<div style="background:#200;padding:8px;color:#f88"><strong>Errores:</strong><ul>'; r.errors.forEach(e=> html += '<li>'+escHtml(e)+'</li>'); html += '</ul></div>'; } out.innerHTML = html; loadTree(); showPanel('filespanel'); }

// ---------------- DB Config generator JS ----------------
async function testDbConnection(){ const host=document.getElementById('gen_host').value; const user=document.getElementById('gen_user').value; const pass=document.getElementById('gen_pass').value; const base=document.getElementById('gen_base').value; const port=document.getElementById('gen_port').value || <?=MG_DB_PORT?>; const out=document.getElementById('gen_result'); out.innerHTML=''; setStatus('Probando conexión...'); const res=await apiCall('test_db_connection',{host:host,user:user,pass:pass,base:base,port:port}); setStatus('Listo'); if(res.success){ out.innerHTML='<div style="background:#052;padding:8px;color:#0f0">Conexión OK</div>'; } else { out.innerHTML='<div style="background:#200;padding:8px;color:#f88">Error: '+escHtml(res.message||'')+'</div>'; } }
async function generateDbFile(){ const host=document.getElementById('gen_host').value; const user=document.getElementById('gen_user').value; const pass=document.getElementById('gen_pass').value; const base=document.getElementById('gen_base').value; const variant=document.getElementById('gen_variant').value; const filename=document.getElementById('gen_filename').value||'database.php'; setStatus('Generando archivo...'); const res=await apiCall('generate_db_file',{variant:variant,host:host,user:user,pass:pass,base:base,filename:filename}); setStatus('Listo'); const out=document.getElementById('gen_result'); if(!res.success) out.innerHTML='<div style="background:#200;padding:8px;color:#f88">Error: '+escHtml(res.message||'')+'</div>'; else out.innerHTML='<div style="background:#052;padding:8px;color:#0f0">Archivo generado: '+escHtml(res.path)+'</div>'; loadTree(); }

// ---------------- Hash generator ----------------
async function generateHash(){ const text=document.getElementById('hashText').value||''; const type=document.getElementById('hashType').value; if(text==='') return alert('Introduce texto'); const res=await apiCall('hash_generate',{text:text,type:type}); if(!res.success) return alert('Error: '+(res.message||'')); document.getElementById('hashOutput').innerHTML = `<div><strong>${escHtml(type)}:</strong> <span style="color:#000;background:#0f0;padding:4px;">${escHtml(res.hash)}</span></div>`; }

// init helpers
loadDatabases(); loadSqlDbSelect();
</script>
</body>
</html>