import { DatabaseSync } from 'node:sqlite';
import { join } from 'node:path';
import { v4 as uuid } from 'uuid';
import { getDataDir, ensureDir, readJsonFile, writeJsonFile, deleteFile } from './json-store.js';
import { checkSql, validateDbName, bindArgs, MAX_ROWS } from './sql-safety.js';
const META_FILE = join(getDataDir(), 'sqlite', 'databases.json');
const DB_DIR = join(getDataDir(), 'sqlite');
const FIELD_META_TABLE = '__sqlite_field_meta__';
const POOL = new Map();
let metaCache = null;
function loadMeta() {
    if (metaCache)
        return metaCache;
    metaCache = readJsonFile(META_FILE) ?? [];
    return metaCache;
}
function saveMeta(list) {
    metaCache = list;
    writeJsonFile(META_FILE, list);
}
function dbPath(id) {
    ensureDir(DB_DIR);
    return join(DB_DIR, `${id}.sqlite`);
}
function openDb(id) {
    const cached = POOL.get(id);
    if (cached)
        return cached;
    const db = new DatabaseSync(dbPath(id));
    db.exec('PRAGMA journal_mode = WAL; PRAGMA busy_timeout = 5000');
    POOL.set(id, db);
    return db;
}
function now() { return Date.now(); }
export function listDatabases(workflowId) {
    const all = loadMeta();
    if (!workflowId)
        return [...all].sort((a, b) => a.createdAt - b.createdAt);
    return all.filter((d) => d.workflowIds.includes(workflowId)).sort((a, b) => a.createdAt - b.createdAt);
}
export function getDatabase(id) {
    return loadMeta().find((d) => d.id === id) ?? null;
}
export function createDatabase(input) {
    validateDbName(input.name);
    const list = loadMeta();
    const meta = {
        id: uuid(), name: input.name.trim(), description: input.description ?? '',
        workflowIds: input.workflowIds ?? [], createdAt: now(), updatedAt: now(),
    };
    list.push(meta);
    saveMeta(list);
    openDb(meta.id);
    return meta;
}
export function updateDatabase(id, updates) {
    const list = loadMeta();
    const idx = list.findIndex((d) => d.id === id);
    if (idx === -1)
        return null;
    if (updates.name !== undefined)
        validateDbName(updates.name);
    list[idx] = { ...list[idx], ...updates, updatedAt: now() };
    saveMeta(list);
    return list[idx];
}
export function setWorkflowAssociations(id, workflowIds) {
    return updateDatabase(id, { workflowIds });
}
export function deleteDatabase(id) {
    const list = loadMeta();
    const idx = list.findIndex((d) => d.id === id);
    if (idx === -1)
        return false;
    const db = POOL.get(id);
    if (db) {
        try {
            db.close();
        }
        catch { /* noop */ }
        POOL.delete(id);
    }
    deleteFile(dbPath(id));
    list.splice(idx, 1);
    saveMeta(list);
    return true;
}
export function listTables(id) {
    const db = openDb(id);
    const tables = db.prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name NOT LIKE 'sqlite_%' ORDER BY name").all();
    return tables.map((t) => {
        const c = db.prepare(`SELECT COUNT(*) AS n FROM "${t.name}"`).get();
        return { name: t.name, rowCount: c.n };
    });
}
export function describeTable(id, table) {
    const db = openDb(id);
    const cols = db.prepare(`PRAGMA table_info(${table})`).all();
    let descriptions = new Map();
    const metaExists = db.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = ?").get(FIELD_META_TABLE);
    if (metaExists) {
        const rows = db.prepare(`SELECT "column", "description" FROM "${FIELD_META_TABLE}" WHERE "table" = ?`).all(table);
        descriptions = new Map(rows.map((r) => [r.column, r.description ?? '']));
    }
    const indexedColumns = new Set();
    const indexes = db.prepare(`PRAGMA index_list("${table}")`).all();
    for (const index of indexes) {
        const rows = db.prepare(`PRAGMA index_info("${index.name}")`).all();
        for (const row of rows) {
            if (row.name)
                indexedColumns.add(row.name);
        }
    }
    return cols.map((c) => ({
        name: c.name,
        type: c.type,
        notNull: !!c.notnull,
        pk: !!c.pk,
        defaultValue: c.dflt_value,
        description: descriptions.get(c.name) ?? '',
        indexed: indexedColumns.has(c.name),
    }));
}
export function query(id, sql, params) {
    checkSql(sql);
    const db = openDb(id);
    const stmt = db.prepare(sql);
    const rows = stmt.all(...bindArgs(params));
    const columns = 'columns' in stmt && typeof stmt.columns === 'function'
        ? stmt.columns().map((c) => c.name)
        : (rows[0] ? Object.keys(rows[0]) : []);
    const truncated = rows.length > MAX_ROWS;
    return { columns, rows: truncated ? rows.slice(0, MAX_ROWS) : rows, rowCount: rows.length, truncated };
}
export function exec(id, sql, params) {
    checkSql(sql);
    const db = openDb(id);
    const r = db.prepare(sql).run(...bindArgs(params));
    return { changes: r.changes, lastInsertRowid: r.lastInsertRowid == null ? null : Number(r.lastInsertRowid) };
}
export function closeAllDbs() {
    for (const [, db] of POOL) {
        try {
            db.close();
        }
        catch { /* noop */ }
    }
    POOL.clear();
}
//# sourceMappingURL=sqlite-store.js.map