/**
 * Graph Queries for Wiki Generation
 *
 * Encapsulated Cypher queries against the CodraGraph knowledge graph.
 * Uses the MCP-style pooled cgdb-adapter for connection management.
 */
import { initCgdb, executeQuery, closeCgdb, touchRepo } from '../cgdb/pool-adapter.js';
const REPO_ID = '__wiki__';
/**
 * Touch the wiki DB connection to prevent idle timeout during long LLM calls.
 */
export function touchWikiDb() {
    touchRepo(REPO_ID);
}
/**
 * Initialize the LadybugDB connection for wiki generation.
 */
export async function initWikiDb(cgdbPath) {
    await initCgdb(REPO_ID, cgdbPath);
}
/**
 * Close the LadybugDB connection.
 */
export async function closeWikiDb() {
    await closeCgdb(REPO_ID);
}
/**
 * Get all source files with their exported symbol names and types.
 */
export async function getFilesWithExports() {
    const rows = await executeQuery(REPO_ID, `
    MATCH (f:File)-[:CodeRelation {type: 'DEFINES'}]->(n)
    WHERE n.isExported = true
    RETURN f.filePath AS filePath, n.name AS name, labels(n)[0] AS type
    ORDER BY f.filePath
  `);
    const fileMap = new Map();
    for (const row of rows) {
        const fp = row.filePath || row[0];
        const name = row.name || row[1];
        const type = row.type || row[2];
        let entry = fileMap.get(fp);
        if (!entry) {
            entry = { filePath: fp, symbols: [] };
            fileMap.set(fp, entry);
        }
        entry.symbols.push({ name, type });
    }
    return Array.from(fileMap.values());
}
/**
 * Get all files tracked in the graph (including those with no exports).
 */
export async function getAllFiles() {
    const rows = await executeQuery(REPO_ID, `
    MATCH (f:File)
    RETURN f.filePath AS filePath
    ORDER BY f.filePath
  `);
    return rows.map((r) => r.filePath || r[0]);
}
/**
 * Get inter-file call edges (calls between different files).
 */
export async function getInterFileCallEdges() {
    const rows = await executeQuery(REPO_ID, `
    MATCH (a)-[:CodeRelation {type: 'CALLS'}]->(b)
    WHERE a.filePath <> b.filePath
    RETURN DISTINCT a.filePath AS fromFile, a.name AS fromName,
           b.filePath AS toFile, b.name AS toName
  `);
    return rows.map((r) => ({
        fromFile: r.fromFile || r[0],
        fromName: r.fromName || r[1],
        toFile: r.toFile || r[2],
        toName: r.toName || r[3],
    }));
}
/**
 * Get call edges between files within a specific set (intra-module).
 */
export async function getIntraModuleCallEdges(filePaths) {
    if (filePaths.length === 0)
        return [];
    const fileList = filePaths.map((f) => `'${f.replace(/'/g, "''")}'`).join(', ');
    const rows = await executeQuery(REPO_ID, `
    MATCH (a)-[:CodeRelation {type: 'CALLS'}]->(b)
    WHERE a.filePath IN [${fileList}] AND b.filePath IN [${fileList}]
    RETURN DISTINCT a.filePath AS fromFile, a.name AS fromName,
           b.filePath AS toFile, b.name AS toName
  `);
    return rows.map((r) => ({
        fromFile: r.fromFile || r[0],
        fromName: r.fromName || r[1],
        toFile: r.toFile || r[2],
        toName: r.toName || r[3],
    }));
}
/**
 * Get call edges crossing module boundaries (external calls from/to module files).
 */
export async function getInterModuleCallEdges(filePaths) {
    if (filePaths.length === 0)
        return { outgoing: [], incoming: [] };
    const fileList = filePaths.map((f) => `'${f.replace(/'/g, "''")}'`).join(', ');
    const outRows = await executeQuery(REPO_ID, `
    MATCH (a)-[:CodeRelation {type: 'CALLS'}]->(b)
    WHERE a.filePath IN [${fileList}] AND NOT b.filePath IN [${fileList}]
    RETURN DISTINCT a.filePath AS fromFile, a.name AS fromName,
           b.filePath AS toFile, b.name AS toName
    LIMIT 30
  `);
    const inRows = await executeQuery(REPO_ID, `
    MATCH (a)-[:CodeRelation {type: 'CALLS'}]->(b)
    WHERE NOT a.filePath IN [${fileList}] AND b.filePath IN [${fileList}]
    RETURN DISTINCT a.filePath AS fromFile, a.name AS fromName,
           b.filePath AS toFile, b.name AS toName
    LIMIT 30
  `);
    return {
        outgoing: outRows.map((r) => ({
            fromFile: r.fromFile || r[0],
            fromName: r.fromName || r[1],
            toFile: r.toFile || r[2],
            toName: r.toName || r[3],
        })),
        incoming: inRows.map((r) => ({
            fromFile: r.fromFile || r[0],
            fromName: r.fromName || r[1],
            toFile: r.toFile || r[2],
            toName: r.toName || r[3],
        })),
    };
}
/**
 * Get processes (execution flows) that pass through a set of files.
 * Returns top N by step count.
 */
export async function getProcessesForFiles(filePaths, limit = 5) {
    if (filePaths.length === 0)
        return [];
    const fileList = filePaths.map((f) => `'${f.replace(/'/g, "''")}'`).join(', ');
    // Find processes that have steps in the given files
    const procRows = await executeQuery(REPO_ID, `
    MATCH (s)-[r:CodeRelation {type: 'STEP_IN_PROCESS'}]->(p:Process)
    WHERE s.filePath IN [${fileList}]
    RETURN DISTINCT p.id AS id, p.heuristicLabel AS label,
           p.processType AS type, p.stepCount AS stepCount
    ORDER BY stepCount DESC
    LIMIT ${limit}
  `);
    const processes = [];
    for (const row of procRows) {
        const procId = row.id || row[0];
        const label = row.label || row[1] || procId;
        const type = row.type || row[2] || 'unknown';
        const stepCount = row.stepCount || row[3] || 0;
        // Get the full step trace for this process
        const stepRows = await executeQuery(REPO_ID, `
      MATCH (s)-[r:CodeRelation {type: 'STEP_IN_PROCESS'}]->(p:Process {id: '${procId.replace(/'/g, "''")}'})
      RETURN s.name AS name, s.filePath AS filePath, labels(s)[0] AS type, r.step AS step
      ORDER BY r.step
    `);
        processes.push({
            id: procId,
            label,
            type,
            stepCount,
            steps: stepRows.map((s) => ({
                step: s.step || s[3] || 0,
                name: s.name || s[0],
                filePath: s.filePath || s[1],
                type: s.type || s[2],
            })),
        });
    }
    return processes;
}
/**
 * Get all processes in the graph (for overview page).
 */
export async function getAllProcesses(limit = 20) {
    const procRows = await executeQuery(REPO_ID, `
    MATCH (p:Process)
    RETURN p.id AS id, p.heuristicLabel AS label,
           p.processType AS type, p.stepCount AS stepCount
    ORDER BY stepCount DESC
    LIMIT ${limit}
  `);
    const processes = [];
    for (const row of procRows) {
        const procId = row.id || row[0];
        const label = row.label || row[1] || procId;
        const type = row.type || row[2] || 'unknown';
        const stepCount = row.stepCount || row[3] || 0;
        const stepRows = await executeQuery(REPO_ID, `
      MATCH (s)-[r:CodeRelation {type: 'STEP_IN_PROCESS'}]->(p:Process {id: '${procId.replace(/'/g, "''")}'})
      RETURN s.name AS name, s.filePath AS filePath, labels(s)[0] AS type, r.step AS step
      ORDER BY r.step
    `);
        processes.push({
            id: procId,
            label,
            type,
            stepCount,
            steps: stepRows.map((s) => ({
                step: s.step || s[3] || 0,
                name: s.name || s[0],
                filePath: s.filePath || s[1],
                type: s.type || s[2],
            })),
        });
    }
    return processes;
}
/**
 * Get inter-module edges for overview architecture diagram.
 * Groups call edges by source/target module.
 */
export async function getInterModuleEdgesForOverview(moduleFiles) {
    // Build file-to-module lookup
    const fileToModule = new Map();
    for (const [mod, files] of Object.entries(moduleFiles)) {
        for (const f of files) {
            fileToModule.set(f, mod);
        }
    }
    const allEdges = await getInterFileCallEdges();
    const moduleEdgeCounts = new Map();
    for (const edge of allEdges) {
        const fromMod = fileToModule.get(edge.fromFile);
        const toMod = fileToModule.get(edge.toFile);
        if (fromMod && toMod && fromMod !== toMod) {
            const key = `${fromMod}|||${toMod}`;
            moduleEdgeCounts.set(key, (moduleEdgeCounts.get(key) || 0) + 1);
        }
    }
    return Array.from(moduleEdgeCounts.entries())
        .map(([key, count]) => {
        const [from, to] = key.split('|||');
        return { from, to, count };
    })
        .sort((a, b) => b.count - a.count);
}
