"use strict";
// src/orchestrator/graph/GraphStore.ts
// v1.14.0 — SQLite-backed symbol + dependency store. Lives in the same
// codebase.db file as the RAG index (different tables).
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.GraphStore = void 0;
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const SqliteVectorStore_1 = require("../rag/SqliteVectorStore");
function dbPath() {
    return path.join((0, SqliteVectorStore_1.defaultIndexDir)(), 'codebase.db');
}
function ensureDir(p) {
    if (!fs.existsSync(p))
        fs.mkdirSync(p, { recursive: true });
}
class GraphStore {
    db;
    constructor(opts) {
        const fullPath = opts?.dbPath || dbPath();
        ensureDir(path.dirname(fullPath));
        const BetterSqlite3 = require('better-sqlite3');
        this.db = new BetterSqlite3(fullPath);
        try {
            fs.chmodSync(fullPath, 0o600);
        }
        catch { /* best effort */ }
        this.db.pragma('journal_mode = WAL');
        this.db.exec(`
      CREATE TABLE IF NOT EXISTS symbols (
        id         TEXT PRIMARY KEY,
        name       TEXT NOT NULL,
        kind       TEXT NOT NULL,
        file       TEXT NOT NULL,
        start_line INTEGER NOT NULL,
        end_line   INTEGER NOT NULL,
        exported   INTEGER NOT NULL,
        language   TEXT
      );
      CREATE INDEX IF NOT EXISTS idx_symbols_name ON symbols(name);
      CREATE INDEX IF NOT EXISTS idx_symbols_file ON symbols(file);
      CREATE TABLE IF NOT EXISTS edges (
        from_file     TEXT NOT NULL,
        to_module     TEXT NOT NULL,
        resolved_file TEXT,
        kind          TEXT NOT NULL,
        language      TEXT
      );
      CREATE INDEX IF NOT EXISTS idx_edges_from     ON edges(from_file);
      CREATE INDEX IF NOT EXISTS idx_edges_resolved ON edges(resolved_file);
    `);
    }
    clearFile(file) {
        this.db.prepare(`DELETE FROM symbols WHERE file = ?`).run(file);
        this.db.prepare(`DELETE FROM edges WHERE from_file = ?`).run(file);
    }
    putSymbols(symbols) {
        if (symbols.length === 0)
            return;
        const stmt = this.db.prepare(`
      INSERT INTO symbols (id, name, kind, file, start_line, end_line, exported, language)
      VALUES (@id, @name, @kind, @file, @startLine, @endLine, @exported, @language)
      ON CONFLICT(id) DO UPDATE SET
        name=excluded.name, kind=excluded.kind, file=excluded.file,
        start_line=excluded.start_line, end_line=excluded.end_line,
        exported=excluded.exported, language=excluded.language
    `);
        const tx = this.db.transaction((items) => {
            for (const s of items) {
                stmt.run({
                    id: `${s.file}::${s.name}::${s.startLine}`,
                    name: s.name,
                    kind: s.kind,
                    file: s.file,
                    startLine: s.startLine,
                    endLine: s.endLine,
                    exported: s.exported ? 1 : 0,
                    language: s.language,
                });
            }
        });
        tx(symbols);
    }
    putEdges(edges) {
        if (edges.length === 0)
            return;
        const stmt = this.db.prepare(`
      INSERT INTO edges (from_file, to_module, resolved_file, kind, language)
      VALUES (@from, @toModule, @resolvedFile, @kind, @language)
    `);
        const tx = this.db.transaction((items) => {
            for (const e of items) {
                stmt.run({
                    from: e.from,
                    toModule: e.toModule,
                    resolvedFile: e.resolvedFile ?? null,
                    kind: e.kind,
                    language: e.language,
                });
            }
        });
        tx(edges);
    }
    findSymbol(name) {
        return this.db.prepare(`
      SELECT name, kind, file, start_line AS startLine, end_line AS endLine, exported, language
      FROM symbols WHERE name = ? OR name LIKE ?
    `).all(name, `%.${name}`).map((r) => {
            const row = r;
            return { ...row, exported: row.exported === 1 };
        });
    }
    findCallers(symbolName) {
        // Best-effort: caller = any symbol whose source file imports the defining file.
        const defs = this.findSymbol(symbolName);
        if (defs.length === 0)
            return [];
        const callerFiles = new Set();
        for (const def of defs) {
            const rows = this.db.prepare(`SELECT from_file FROM edges WHERE resolved_file = ?`).all(def.file);
            for (const r of rows)
                callerFiles.add(r.from_file);
        }
        if (callerFiles.size === 0)
            return [];
        const placeholders = Array.from(callerFiles).map(() => '?').join(',');
        const rows = this.db.prepare(`
      SELECT name, kind, file, start_line AS startLine, end_line AS endLine, exported, language
      FROM symbols WHERE file IN (${placeholders})
    `).all(...Array.from(callerFiles));
        return rows.map((r) => {
            const row = r;
            return { ...row, exported: row.exported === 1 };
        });
    }
    findDependencies(file) {
        return this.db.prepare(`
      SELECT from_file AS "from", to_module AS toModule, resolved_file AS resolvedFile, kind, language
      FROM edges WHERE from_file = ?
    `).all(file);
    }
    /**
     * v1.14.1 — Find files in the graph whose stored absolute path ends with
     * the given suffix. Used by GraphCommand to accept relative or bare paths.
     * Returns up to `limit` matches.
     */
    findFilesBySuffix(suffix, limit = 20) {
        const normalized = suffix.replace(/^\.\/+/, '').replace(/^\/+/, '');
        const rows = this.db.prepare(`
      SELECT DISTINCT file FROM symbols WHERE file LIKE ?
      UNION
      SELECT DISTINCT from_file AS file FROM edges WHERE from_file LIKE ?
      LIMIT ?
    `).all(`%/${normalized}`, `%/${normalized}`, limit);
        return rows.map(r => r.file);
    }
    findUsages(name) {
        return this.db.prepare(`
      SELECT name, kind, file, start_line AS startLine, end_line AS endLine, exported, language
      FROM symbols WHERE name LIKE ?
    `).all(`%${name}%`).map((r) => {
            const row = r;
            return { ...row, exported: row.exported === 1 };
        });
    }
    size() {
        const s = this.db.prepare(`SELECT COUNT(*) AS n FROM symbols`).get();
        const e = this.db.prepare(`SELECT COUNT(*) AS n FROM edges`).get();
        return { symbols: s.n, edges: e.n };
    }
    close() {
        this.db.close();
    }
}
exports.GraphStore = GraphStore;
