/**
 * Structure query functions — read-only DB queries for directory structure,
 * hotspots, and module boundaries.
 *
 * Split from structure.ts to separate query-time concerns (DB reads, sorting,
 * pagination) from build-time concerns (directory insertion, metrics computation,
 * role classification).
 */
import { openReadonlyOrFail, openReadonlyWithNative, testFilterSQL } from '../db/index.js';
import { loadConfig } from '../infrastructure/config.js';
import { isTestFile } from '../infrastructure/test-filter.js';
import { normalizePath } from '../shared/constants.js';
import { paginateResult } from '../shared/paginate.js';
function buildDirectoryEntry(d, filesStmt, subdirsStmt, noTests) {
    let files = filesStmt.all(d.id);
    if (noTests)
        files = files.filter((f) => !isTestFile(f.name));
    const subdirs = subdirsStmt.all(d.id);
    const fileCount = noTests ? files.length : d.file_count || 0;
    return {
        directory: d.name,
        fileCount,
        symbolCount: d.symbol_count || 0,
        fanIn: d.fan_in || 0,
        fanOut: d.fan_out || 0,
        cohesion: d.cohesion,
        density: fileCount > 0 ? (d.symbol_count || 0) / fileCount : 0,
        files: files.map((f) => ({
            file: f.name,
            lineCount: f.line_count || 0,
            symbolCount: f.symbol_count || 0,
            importCount: f.import_count || 0,
            exportCount: f.export_count || 0,
            fanIn: f.fan_in || 0,
            fanOut: f.fan_out || 0,
        })),
        subdirectories: subdirs.map((s) => s.name),
    };
}
function applyFileLimit(result, fileLimit) {
    const totalFiles = result.reduce((sum, d) => sum + d.files.length, 0);
    if (totalFiles <= fileLimit)
        return null;
    let shown = 0;
    for (const d of result) {
        const remaining = fileLimit - shown;
        if (remaining <= 0) {
            d.files = [];
        }
        else if (d.files.length > remaining) {
            d.files = d.files.slice(0, remaining);
            shown = fileLimit;
        }
        else {
            shown += d.files.length;
        }
    }
    const suppressed = totalFiles - fileLimit;
    return {
        directories: result,
        count: result.length,
        suppressed,
        warning: `${suppressed} files omitted (showing ${fileLimit}/${totalFiles}). Use --full to show all files, or narrow with --directory.`,
    };
}
export function structureData(customDbPath, opts = {}) {
    const db = openReadonlyOrFail(customDbPath);
    try {
        const rawDir = opts.directory || null;
        const filterDir = rawDir && normalizePath(rawDir) !== '.' ? rawDir : null;
        const maxDepth = opts.depth || null;
        const sortBy = opts.sort || 'files';
        const noTests = opts.noTests || false;
        const full = opts.full || false;
        const fileLimit = opts.fileLimit || 25;
        // Get all directory nodes with their metrics
        let dirs = db
            .prepare(`
        SELECT n.id, n.name, n.file, nm.symbol_count, nm.fan_in, nm.fan_out, nm.cohesion, nm.file_count
        FROM nodes n
        LEFT JOIN node_metrics nm ON n.id = nm.node_id
        WHERE n.kind = 'directory'
      `)
            .all();
        if (filterDir) {
            const norm = normalizePath(filterDir);
            dirs = dirs.filter((d) => d.name === norm || d.name.startsWith(`${norm}/`));
        }
        if (maxDepth) {
            const baseDepth = filterDir ? normalizePath(filterDir).split('/').length : 0;
            dirs = dirs.filter((d) => {
                const depth = d.name.split('/').length - baseDepth;
                return depth <= maxDepth;
            });
        }
        // Sort
        const sortFn = getSortFn(sortBy);
        dirs.sort(sortFn);
        // Get file metrics for each directory
        const filesStmt = db.prepare(`
      SELECT n.name, nm.line_count, nm.symbol_count, nm.import_count, nm.export_count, nm.fan_in, nm.fan_out
      FROM edges e
      JOIN nodes n ON e.target_id = n.id
      LEFT JOIN node_metrics nm ON n.id = nm.node_id
      WHERE e.source_id = ? AND e.kind = 'contains' AND n.kind = 'file'
    `);
        const subdirsStmt = db.prepare(`
      SELECT n.name
      FROM edges e
      JOIN nodes n ON e.target_id = n.id
      WHERE e.source_id = ? AND e.kind = 'contains' AND n.kind = 'directory'
    `);
        const result = dirs.map((d) => buildDirectoryEntry(d, filesStmt, subdirsStmt, noTests));
        // Apply global file limit unless full mode
        if (!full) {
            const limited = applyFileLimit(result, fileLimit);
            if (limited)
                return limited;
        }
        const base = { directories: result, count: result.length };
        return paginateResult(base, 'directories', { limit: opts.limit, offset: opts.offset });
    }
    finally {
        db.close();
    }
}
/** Compute density from either fileCount/symbolCount or lineCount/symbolCount. */
function computeHotspotDensity(symbolCount, fileCount, lineCount) {
    if ((fileCount ?? 0) > 0)
        return (symbolCount || 0) / (fileCount ?? 1);
    if ((lineCount ?? 0) > 0)
        return (symbolCount || 0) / (lineCount ?? 1);
    return 0;
}
/** Map a native-engine hotspot row (camelCase keys) to the public HotspotEntry shape. */
function mapNativeHotspotRow(r) {
    return {
        name: r.name,
        kind: r.kind,
        lineCount: r.lineCount,
        symbolCount: r.symbolCount,
        importCount: r.importCount,
        exportCount: r.exportCount,
        fanIn: r.fanIn,
        fanOut: r.fanOut,
        cohesion: r.cohesion,
        fileCount: r.fileCount,
        density: computeHotspotDensity(r.symbolCount, r.fileCount, r.lineCount),
        coupling: (r.fanIn || 0) + (r.fanOut || 0),
    };
}
/** Map a JS-path hotspot row (snake_case keys from SQLite) to the public HotspotEntry shape. */
function mapJsHotspotRow(r) {
    return {
        name: r.name,
        kind: r.kind,
        lineCount: r.line_count,
        symbolCount: r.symbol_count,
        importCount: r.import_count,
        exportCount: r.export_count,
        fanIn: r.fan_in,
        fanOut: r.fan_out,
        cohesion: r.cohesion,
        fileCount: r.file_count,
        density: computeHotspotDensity(r.symbol_count, r.file_count, r.line_count),
        coupling: (r.fan_in || 0) + (r.fan_out || 0),
    };
}
/** ORDER BY clause for each ranking dimension (strategy pattern). */
const HOTSPOT_ORDER_BY = {
    'fan-in': 'nm.fan_in DESC NULLS LAST',
    'fan-out': 'nm.fan_out DESC NULLS LAST',
    density: 'nm.symbol_count DESC NULLS LAST',
    coupling: '(COALESCE(nm.fan_in, 0) + COALESCE(nm.fan_out, 0)) DESC NULLS LAST',
};
/** Build the JS-path SQL query for a given metric and test filter. */
function buildHotspotQuery(metric, testFilter) {
    const orderBy = HOTSPOT_ORDER_BY[metric] ?? HOTSPOT_ORDER_BY['fan-in'];
    return `SELECT n.name, n.kind, nm.line_count, nm.symbol_count, nm.import_count, nm.export_count,
                 nm.fan_in, nm.fan_out, nm.cohesion, nm.file_count
          FROM nodes n JOIN node_metrics nm ON n.id = nm.node_id
          WHERE n.kind = ? ${testFilter} ORDER BY ${orderBy} LIMIT ?`;
}
export function hotspotsData(customDbPath, opts = {}) {
    const { db, nativeDb, close } = openReadonlyWithNative(customDbPath);
    try {
        const metric = opts.metric || 'fan-in';
        const level = opts.level || 'file';
        const limit = opts.limit || 10;
        const noTests = opts.noTests || false;
        const kind = level === 'directory' ? 'directory' : 'file';
        // ── Native fast path: single query instead of 4 eagerly prepared ──
        if (nativeDb?.getHotspots) {
            const rows = nativeDb.getHotspots(kind, metric, noTests, limit);
            const hotspots = rows.map(mapNativeHotspotRow);
            const base = { metric, level, limit, hotspots };
            return paginateResult(base, 'hotspots', { limit: opts.limit, offset: opts.offset });
        }
        // ── JS fallback ───────────────────────────────────────────────────
        const testFilter = testFilterSQL('n.name', noTests && kind === 'file');
        const stmt = db.prepare(buildHotspotQuery(metric, testFilter));
        const rows = stmt.all(kind, limit);
        const hotspots = rows.map(mapJsHotspotRow);
        const base = { metric, level, limit, hotspots };
        return paginateResult(base, 'hotspots', { limit: opts.limit, offset: opts.offset });
    }
    finally {
        close();
    }
}
export function moduleBoundariesData(customDbPath, opts = {}) {
    const db = openReadonlyOrFail(customDbPath);
    try {
        const config = opts.config || loadConfig();
        const threshold = opts.threshold ??
            config.structure
                ?.cohesionThreshold ??
            0.3;
        const dirs = db
            .prepare(`
        SELECT n.id, n.name, nm.symbol_count, nm.fan_in, nm.fan_out, nm.cohesion, nm.file_count
        FROM nodes n
        JOIN node_metrics nm ON n.id = nm.node_id
        WHERE n.kind = 'directory' AND nm.cohesion IS NOT NULL AND nm.cohesion >= ?
        ORDER BY nm.cohesion DESC
      `)
            .all(threshold);
        const modules = dirs.map((d) => {
            // Get files inside this directory
            const files = db
                .prepare(`
          SELECT n.name FROM edges e
          JOIN nodes n ON e.target_id = n.id
          WHERE e.source_id = ? AND e.kind = 'contains' AND n.kind = 'file'
        `)
                .all(d.id).map((f) => f.name);
            return {
                directory: d.name,
                cohesion: d.cohesion,
                fileCount: d.file_count || 0,
                symbolCount: d.symbol_count || 0,
                fanIn: d.fan_in || 0,
                fanOut: d.fan_out || 0,
                files,
            };
        });
        return { threshold, modules, count: modules.length };
    }
    finally {
        db.close();
    }
}
// ─── Helpers ──────────────────────────────────────────────────────────
function getSortFn(sortBy) {
    switch (sortBy) {
        case 'cohesion':
            return (a, b) => (b.cohesion ?? -1) - (a.cohesion ?? -1);
        case 'fan-in':
            return (a, b) => (b.fan_in || 0) - (a.fan_in || 0);
        case 'fan-out':
            return (a, b) => (b.fan_out || 0) - (a.fan_out || 0);
        case 'density':
            return (a, b) => {
                const da = (a.file_count ?? 0) > 0 ? (a.symbol_count || 0) / (a.file_count ?? 1) : 0;
                const db_ = (b.file_count ?? 0) > 0 ? (b.symbol_count || 0) / (b.file_count ?? 1) : 0;
                return db_ - da;
            };
        case 'files':
            return (a, b) => (b.file_count || 0) - (a.file_count || 0);
        default:
            return (a, b) => a.name.localeCompare(b.name);
    }
}
//# sourceMappingURL=structure-query.js.map