<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>BAI SQL 实验室 - 数据库探勘</title>
    <script src="https://cdn.tailwindcss.com"></script>
    <script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
    <style>
        ::-webkit-scrollbar { width: 6px; height: 6px; }
        ::-webkit-scrollbar-thumb { background: #475569; border-radius: 3px; }
        ::-webkit-scrollbar-track { background: transparent; }
        /* 针对表头拖拽改变宽度的特化样式 */
        .resizable-th { resize: horizontal; overflow: hidden; min-width: 100px; padding-bottom: 2px; }
    </style>
</head>
<body class="bg-slate-900 text-slate-300 h-screen flex flex-col font-mono" x-data="sqlLab()">

    <header class="h-14 bg-slate-950 border-b border-slate-800 flex items-center px-6 justify-between shrink-0 z-10 shadow-sm">
        <div class="flex items-center space-x-4">
            <span class="text-emerald-500 font-bold text-lg tracking-widest">BAI SQL LAB</span>
            
            <div class="flex space-x-3 text-xs bg-slate-900 px-3 py-1.5 rounded-md border border-slate-800 items-center">
                <div class="flex items-center" title="Python 探针网络状态">
                    <span class="w-2 h-2 rounded-full mr-2 transition-colors duration-300" :class="diag.py_network === 'ALIVE' ? 'bg-emerald-500 shadow-[0_0_8px_#10b981]' : (diag.py_network === 'WAIT' ? 'bg-amber-500 animate-pulse' : 'bg-red-500')"></span>
                    <span class="font-bold">PYTHON</span>
                </div>
                <div class="flex items-center ml-4" title="SQLite 核心读取状态">
                    <span class="w-2 h-2 rounded-full mr-2 transition-colors duration-300" :class="diag.sqlite_storage === 'READY' ? 'bg-emerald-500 shadow-[0_0_8px_#10b981]' : (diag.sqlite_storage === 'WAIT' ? 'bg-amber-500 animate-pulse' : 'bg-red-500')"></span>
                    <span class="font-bold">SQLITE <span x-show="diag.sqlite_version" x-text="'v' + diag.sqlite_version" class="text-slate-400 ml-1 font-normal"></span></span>
                </div>
                
                <div class="h-4 border-l border-slate-700 ml-3 mr-1"></div>
                
                <button @click="runDiagnostics()" :disabled="checking" class="text-slate-400 hover:text-emerald-400 transition-colors disabled:opacity-50" title="手动刷新探针状态">
                    <svg class="w-4 h-4" :class="{'animate-spin text-emerald-500': checking}" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"></path>
                    </svg>
                </button>
            </div>
        </div>
        <div class="flex items-center space-x-3">
            <a href="/pb/manage" class="text-xs text-slate-300 hover:text-white border border-slate-700 px-3 py-1 rounded transition-colors">返回首页</a>
            <div class="text-[10px] text-slate-500 max-w-xs truncate" :title="diag.sqlite_detail" x-text="diag.sqlite_detail"></div>
        </div>
    </header>

    <div class="flex-1 flex overflow-hidden">
        
        <aside class="w-[300px] bg-slate-950 border-r border-slate-800 flex flex-col shrink-0">
            <div class="p-3 border-b border-slate-800 flex justify-between items-center bg-slate-900">
                <div class="flex flex-col">
                    <h3 class="text-xs font-bold text-slate-400 uppercase tracking-wider">物理表清单</h3>
                    <span class="text-[10px] text-amber-500 mt-0.5" x-show="totalDbSize">估算总容量: <span x-text="totalDbSize"></span></span>
                </div>
                <button @click="fetchTables()" :disabled="loadingTables" class="text-emerald-500 hover:text-emerald-400" title="刷新表结构">
                    <svg class="w-4 h-4" :class="{'animate-spin': loadingTables}" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"></path></svg>
                </button>
            </div>
            <div class="flex-1 overflow-y-auto p-2 space-y-0.5">
                <div x-show="loadingTables" class="text-xs text-slate-500 p-2 text-center animate-pulse">正在测绘数据库地形...</div>
                <template x-for="tbl in tables" :key="tbl.name">
                    <div 
                        @click="loadTable(tbl.name, 1)"
                        class="flex p-2 rounded cursor-pointer hover:bg-slate-800 border border-transparent hover:border-slate-700 transition-all text-xs"
                        :class="{'bg-slate-800 border-slate-700': navTable === tbl.name}"
                    >
                        <span class="text-emerald-400 truncate w-full" :title="tbl.name" x-text="`${tbl.name} (${tbl.count})(${tbl.size})`"></span>
                    </div>
                </template>
            </div>
        </aside>

        <main class="flex-1 flex flex-col bg-slate-900 overflow-hidden relative">
            <div class="p-4 border-b border-slate-800 bg-[#0d1117] shrink-0 flex flex-col shadow-inner">
                <textarea 
                    x-model="sql" 
                    @input="navTable = ''" 
                    class="w-full h-32 bg-transparent border-none text-emerald-400 text-sm outline-none focus:ring-0 resize-none font-mono" 
                    placeholder="-- 输入标准的 SQLite 语句..."
                    @keydown.ctrl.enter="execute()"
                ></textarea>
                <div class="mt-2 flex justify-between items-center border-t border-slate-800 pt-3">
                    <span class="text-[11px] text-slate-500">快捷键: <kbd class="bg-slate-800 px-1 rounded text-slate-300">Ctrl</kbd> + <kbd class="bg-slate-800 px-1 rounded text-slate-300">Enter</kbd> 执行</span>
                    <button 
                        @click="execute()" 
                        :disabled="loading" 
                        class="bg-emerald-600 hover:bg-emerald-500 text-white px-6 py-1.5 rounded font-bold text-xs disabled:opacity-50 transition-all flex items-center shadow-lg shadow-emerald-900/20"
                    >
                        <span x-show="!loading">▶ 执行 (Run)</span>
                        <span x-show="loading" class="animate-pulse">穿透中...</span>
                    </button>
                </div>
            </div>

            <div class="flex-1 overflow-hidden flex flex-col bg-slate-950 relative">
                <div class="p-4 overflow-auto flex-1 flex flex-col relative">
                    <div x-show="error" class="bg-red-950/50 border border-red-800 text-red-400 p-4 rounded-lg text-sm mb-4 break-words shrink-0" x-text="error"></div>
                    
                    <div x-show="results.length > 0" class="flex flex-col flex-1 min-h-0">
                        <div class="flex justify-between items-end mb-2 shrink-0">
                            <span class="text-xs text-slate-500">本页返回: <span class="text-emerald-500 font-bold" x-text="results.length"></span> 行</span>
                            <button 
                                @click="copyToClipboard" 
                                class="flex items-center gap-1 px-3 py-1 text-xs font-medium text-emerald-400 bg-emerald-900/30 border border-emerald-800/50 rounded hover:bg-emerald-800/50 transition-colors shadow-sm"
                            >
                                <svg xmlns="http://www.w3.org/2000/svg" class="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                                    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" />
                                </svg>
                                复制 Markdown
                            </button>
                        </div>

                        <div class="border border-slate-800 rounded overflow-auto bg-[#0f172a] shadow-inner flex-1 max-h-full">
                            <table class="min-w-full divide-y divide-slate-800 text-xs w-max">
                                <thead class="bg-slate-900 sticky top-0 z-10 shadow">
                                    <tr>
                                        <template x-for="col in Object.keys(results[0] || {})">
                                            <th class="border-r border-slate-800 align-top bg-slate-900">
                                                <div class="resizable-th px-3 py-2 text-left text-slate-400 font-bold uppercase tracking-wider" x-text="col"></div>
                                            </th>
                                        </template>
                                    </tr>
                                </thead>
                                <tbody class="divide-y divide-slate-800/50">
                                    <template x-for="(row, idx) in results" :key="idx">
                                        <tr class="hover:bg-slate-800/70 transition-colors">
                                            <template x-for="val in Object.values(row)">
                                                <td class="px-3 py-1.5 text-slate-300 border-r border-slate-800/50 max-w-[500px] truncate" :title="formatValue(val)" x-text="formatValue(val)" :class="{'text-slate-500 italic': val === null}"></td>
                                            </template>
                                        </tr>
                                    </template>
                                </tbody>
                            </table>
                        </div>
                    </div>

                    <div x-show="!loading && !error && executed && results.length === 0" class="flex items-center justify-center h-full text-slate-600 text-sm italic">
                        SQL 穿透成功，但返回了 0 行数据。
                    </div>
                </div>

                <div x-show="navTable" class="bg-slate-950 border-t border-slate-800 p-2 px-4 flex justify-between items-center shrink-0">
                    <span class="text-xs text-slate-500">当前表: <span class="text-emerald-400 font-bold" x-text="navTable"></span> | 第 <span class="text-white bg-slate-800 px-1.5 rounded" x-text="navPage"></span> 页</span>
                    <div class="flex space-x-2 text-xs">
                        <button @click="loadTable(navTable, navPage - 1)" :disabled="navPage <= 1" class="px-3 py-1 bg-slate-800 hover:bg-slate-700 rounded text-slate-300 disabled:opacity-30 transition-colors">上一页</button>
                        <button @click="loadTable(navTable, navPage + 1)" :disabled="results.length < 50" class="px-3 py-1 bg-slate-800 hover:bg-slate-700 rounded text-slate-300 disabled:opacity-30 transition-colors">下一页</button>
                    </div>
                </div>
            </div>
        </main>

        <aside class="w-56 bg-slate-950 border-l border-slate-800 p-4 flex flex-col space-y-6 overflow-y-auto shrink-0">
            <div>
                <h3 class="text-[11px] font-bold text-slate-500 mb-2 uppercase tracking-wider">结构探测库</h3>
                <div class="space-y-1.5 text-xs">
                    <button @click="runQuery('SELECT name, sql FROM sqlite_master WHERE type=\'table\' AND name NOT LIKE \'sqlite_%\';')" class="w-full text-left px-2 py-1.5 bg-slate-900 hover:bg-slate-800 text-emerald-400 rounded transition-colors border border-slate-800">所有物理表 DDL</button>
                    <button @click="runQuery('PRAGMA database_list;')" class="w-full text-left px-2 py-1.5 bg-slate-900 hover:bg-slate-800 text-blue-400 rounded transition-colors border border-slate-800">数据库绑定列表</button>
                    <button @click="runQuery('PRAGMA foreign_key_list(workspace_chats);')" class="w-full text-left px-2 py-1.5 bg-slate-900 hover:bg-slate-800 text-purple-400 rounded transition-colors border border-slate-800">Chats 外键约束</button>
                    <button @click="runQuery('PRAGMA foreign_key_list(workspace_threads);')" class="w-full text-left px-2 py-1.5 bg-slate-900 hover:bg-slate-800 text-purple-400 rounded transition-colors border border-slate-800">Threads 外键约束</button>
                </div>
            </div>

            <div>
                <h3 class="text-[11px] font-bold text-slate-500 mb-2 uppercase tracking-wider">系统变量</h3>
                <div class="space-y-1.5 text-xs">
                    <button @click="runQuery('PRAGMA page_size;')" class="w-full text-left px-2 py-1.5 bg-slate-900 hover:bg-slate-800 text-amber-400 rounded transition-colors border border-slate-800">页大小 (Page Size)</button>
                    <button @click="runQuery('PRAGMA page_count;')" class="w-full text-left px-2 py-1.5 bg-slate-900 hover:bg-slate-800 text-amber-400 rounded transition-colors border border-slate-800">总页数 (Page Count)</button>
                </div>
            </div>
        </aside>

    </div>

    <script>
        document.addEventListener('alpine:init', () => {
            Alpine.data('sqlLab', () => ({
                diag: { py_network: 'WAIT', sqlite_storage: 'WAIT', sqlite_version: '', sqlite_detail: 'Connecting...' },
                checking: false,
                sql: 'SELECT name FROM sqlite_master WHERE type=\'table\';',
                results: [],
                tables: [],
                totalDbSize: '',
                loading: false,
                loadingTables: false,
                error: '',
                executed: false,
                
                // 分页状态管理
                navTable: '',
                navPage: 1,
                
                get apiBase() { 
                    return (window.location.pathname.startsWith('/pb') ? '/pb' : '') + '/pb-api-v1'; 
                },

                async init() {
                    this.runDiagnostics();
                    this.fetchTables();
                },

                async runDiagnostics() {
                    if (this.checking) return;
                    this.checking = true;
                    this.diag.py_network = 'WAIT';
                    this.diag.sqlite_storage = 'WAIT';
                    
                    try {
                        const res = await fetch(this.apiBase + '/diagnostics');
                        const json = await res.json();
                        this.diag = json;
                    } catch (e) {
                        this.diag.py_network = 'ERROR';
                        this.diag.sqlite_storage = 'ERROR';
                        this.diag.sqlite_detail = '失联: ' + e.message;
                    } finally {
                        setTimeout(() => { this.checking = false; }, 500);
                    }
                },

                // 时间与数据格式化引擎 (UTC+8 毫秒转换)
                formatValue(val) {
                    if (val === null) return 'NULL';
                    
                    // 嗅探 13 位数字 (大概范围从 2001年 到 2049年)
                    if (typeof val === 'number' && val > 1000000000000 && val < 2500000000000) {
                        const d = new Date(val);
                        // 强制换算到 UTC+8 抵消本地宿主机可能的时区干扰
                        const utcTime = d.getTime() + (d.getTimezoneOffset() * 60000);
                        const utc8Time = new Date(utcTime + (8 * 3600000));
                        
                        const pad = (n, len=2) => String(n).padStart(len, '0');
                        return `${utc8Time.getFullYear()}-${pad(utc8Time.getMonth()+1)}-${pad(utc8Time.getDate())} ${pad(utc8Time.getHours())}:${pad(utc8Time.getMinutes())}:${pad(utc8Time.getSeconds())}.${pad(utc8Time.getMilliseconds(), 3)}`;
                    }
                    
                    if (typeof val === 'object') return JSON.stringify(val);
                    return String(val);
                },

                async runRawQuery(querySql) {
                    try {
                        const res = await fetch(this.apiBase + '/sql-ops', {
                            method: 'POST',
                            headers: { 'Content-Type': 'application/json' },
                            body: JSON.stringify({ sql: querySql })
                        });
                        const json = await res.json();
                        return (res.status === 200 && json.code === 200) ? json.data : null;
                    } catch (e) { return null; }
                },

                // 格式化文件大小
                formatSize(bytes) {
                    if (bytes === 0) return '0 B';
                    const k = 1024;
                    const sizes = ['B', 'KB', 'MB', 'GB'];
                    const i = Math.floor(Math.log(bytes) / Math.log(k));
                    return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + sizes[i];
                },

                async fetchTables() {
                    this.loadingTables = true;
                    try {
                        const rawTables = await this.runRawQuery("SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' ORDER BY name;");
                        if (!rawTables || rawTables.length === 0) return;

                        const tableNames = rawTables.map(t => t.name);
                        const countQueries = tableNames.map(name => `SELECT '${name}' AS tableName, COUNT(*) AS rowCount FROM "${name}"`);
                        const masterCountSql = countQueries.join(' UNION ALL ');

                        const countsData = await this.runRawQuery(masterCountSql);
                        
                        if (countsData) {
                            let totalBytes = 0;
                            this.tables = countsData.map(row => {
                                const count = row.rowCount;
                                const estBytes = count * 512; 
                                totalBytes += estBytes;
                                return { name: row.tableName, count: count, size: this.formatSize(estBytes) };
                            });
                            this.totalDbSize = this.formatSize(totalBytes);
                        }
                    } catch (e) {
                        console.error("加载表结构失败", e);
                    } finally {
                        this.loadingTables = false;
                    }
                },

                // 点击左侧表名的单表分页加载器
                loadTable(tableName, page = 1) {
                    this.navTable = tableName;
                    this.navPage = page;
                    const offset = (page - 1) * 50;
                    this.sql = `SELECT * FROM "${tableName}" ORDER BY id DESC LIMIT 50 OFFSET ${offset};`;
                    this.execute(true); // true 代表内部调用，不清理分页状态
                },

                runQuery(query) {
                    this.navTable = ''; // 手动执行其他按钮清理分页状态
                    this.sql = query;
                    this.execute();
                },

                async execute(isFromNav = false) {
                    if (!this.sql.trim()) return;
                    // 如果是编辑器手动按Ctrl+Enter或者点击执行按钮，清理当前分页绑定
                    if (!isFromNav) this.navTable = '';
                    
                    this.loading = true; this.error = ''; this.results = []; this.executed = true;
                    
                    try {
                        const res = await fetch(this.apiBase + '/sql-ops', {
                            method: 'POST',
                            headers: { 'Content-Type': 'application/json' },
                            body: JSON.stringify({ sql: this.sql })
                        });
                        const json = await res.json();
                        
                        if (res.status === 200 && json.code === 200) {
                            this.results = json.data;
                        } else {
                            this.error = `[探针阻断 HTTP ${res.status}] ${json.message}`;
                        }
                    } catch (e) {
                        this.error = "网络异常: " + e.message;
                    } finally {
                        this.loading = false;
                    }
                },

                copyToClipboard() {
                    if (!this.results || this.results.length === 0) return;
                    const headers = Object.keys(this.results[0]);
                    let mdTable = '| ' + headers.join(' | ') + ' |\n';
                    mdTable += '| ' + headers.map(() => '---').join(' | ') + ' |\n';
                    
                    this.results.forEach(row => {
                        const rowData = headers.map(header => {
                            let cellData = row[header];
                            if (cellData === null) return '`NULL`';
                            if (typeof cellData === 'object') cellData = JSON.stringify(cellData);
                            return String(cellData).replace(/\|/g, '\\|').replace(/\n/g, '<br>');
                        });
                        mdTable += '| ' + rowData.join(' | ') + ' |\n';
                    });

                    navigator.clipboard.writeText(mdTable).then(() => {
                        const btn = document.activeElement;
                        const originalText = btn.innerHTML;
                        btn.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" class="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" /></svg> 复制成功`;
                        btn.classList.add('text-white', 'bg-emerald-600');
                        setTimeout(() => { btn.innerHTML = originalText; btn.classList.remove('text-white', 'bg-emerald-600'); }, 1500);
                    });
                }
            }));
        });
    </script>
</body>
</html>