/**
 * NativeRepository — delegates all Repository read methods to NativeDatabase (rusqlite via napi-rs).
 *
 * Phase 6.14: every query runs via rusqlite when the native engine is available.
 * Falls back to SqliteRepository (better-sqlite3) when native is unavailable.
 *
 * napi-rs converts Rust snake_case fields to JS camelCase. This class maps them
 * back to the snake_case field names that the Repository interface expects.
 */
import Database from 'better-sqlite3';
import { ConfigError } from '../../shared/errors.js';
import { Repository, } from './base.js';
// ── Row converters (napi camelCase → Repository snake_case) ─────────────
function toNodeRow(r) {
    return {
        id: r.id,
        name: r.name,
        kind: r.kind,
        file: r.file,
        line: r.line ?? 0,
        end_line: r.endLine ?? null,
        parent_id: r.parentId ?? null,
        exported: (r.exported ?? null),
        qualified_name: r.qualifiedName ?? null,
        scope: r.scope ?? null,
        visibility: (r.visibility ?? null),
        role: (r.role ?? null),
    };
}
function toNodeRowWithFanIn(r) {
    return { ...toNodeRow(r), fan_in: r.fanIn };
}
function toTriageNodeRow(r) {
    return {
        ...toNodeRow(r),
        fan_in: r.fanIn,
        cognitive: r.cognitive,
        mi: r.mi,
        cyclomatic: r.cyclomatic,
        max_nesting: r.maxNesting,
        churn: r.churn,
    };
}
function toNodeIdRow(r) {
    return { id: r.id, name: r.name, kind: r.kind, line: r.line ?? 0 };
}
function toChildNodeRow(r) {
    return {
        name: r.name,
        kind: r.kind,
        line: r.line ?? 0,
        end_line: r.endLine ?? null,
        qualified_name: r.qualifiedName ?? null,
        scope: r.scope ?? null,
        visibility: (r.visibility ?? null),
    };
}
function toRelatedNodeRow(r) {
    return {
        id: r.id,
        name: r.name,
        kind: r.kind,
        file: r.file,
        line: r.line ?? 0,
        end_line: r.endLine,
    };
}
function toAdjacentEdgeRow(r) {
    return {
        name: r.name,
        kind: r.kind,
        file: r.file,
        line: r.line ?? 0,
        edge_kind: r.edgeKind,
    };
}
function toImportEdgeRow(r) {
    return { file: r.file, edge_kind: r.edgeKind };
}
function toIntraFileCallEdge(r) {
    return { caller_name: r.callerName, callee_name: r.calleeName };
}
function toCallableNodeRow(r) {
    return { id: r.id, name: r.name, kind: r.kind, file: r.file };
}
function toCallEdgeRow(r) {
    return {
        source_id: r.sourceId,
        target_id: r.targetId,
        confidence: r.confidence,
    };
}
function toFileNodeRow(r) {
    return { id: r.id, name: r.name, file: r.file };
}
function toImportGraphEdgeRow(r) {
    return { source_id: r.sourceId, target_id: r.targetId };
}
function toComplexityMetrics(r) {
    return {
        cognitive: r.cognitive,
        cyclomatic: r.cyclomatic,
        max_nesting: r.maxNesting,
        maintainability_index: r.maintainabilityIndex ?? null,
        halstead_volume: r.halsteadVolume ?? null,
    };
}
// ── NativeRepository ────────────────────────────────────────────────────
export class NativeRepository extends Repository {
    #ndb;
    #dbPath;
    #fallbackDb;
    constructor(ndb, dbPath) {
        super();
        this.#ndb = ndb;
        this.#dbPath = dbPath;
    }
    /** Lazy better-sqlite3 handle for methods not yet ported to Rust. */
    #getFallbackDb() {
        if (this.#fallbackDb)
            return this.#fallbackDb;
        if (!this.#dbPath)
            return undefined;
        try {
            this.#fallbackDb = new Database(this.#dbPath, { readonly: true });
            return this.#fallbackDb;
        }
        catch {
            return undefined;
        }
    }
    /** Close the lazy fallback connection if it was opened. */
    closeFallback() {
        if (this.#fallbackDb) {
            this.#fallbackDb.close();
            this.#fallbackDb = undefined;
        }
    }
    // ── Node lookups ──────────────────────────────────────────────────
    findNodeById(id) {
        const r = this.#ndb.findNodeById(id);
        return r ? toNodeRow(r) : undefined;
    }
    findNodesByFile(file) {
        return this.#ndb.findNodesByFile(file).map(toNodeRow);
    }
    findFileNodes(fileLike) {
        return this.#ndb.findFileNodes(fileLike).map(toNodeRow);
    }
    findNodesWithFanIn(namePattern, opts = {}) {
        return this.#ndb
            .findNodesWithFanIn(namePattern, opts.kinds ?? null, opts.file ?? null)
            .map(toNodeRowWithFanIn);
    }
    countNodes() {
        return this.#ndb.countNodes();
    }
    countEdges() {
        return this.#ndb.countEdges();
    }
    countFiles() {
        return this.#ndb.countFiles();
    }
    getNodeId(name, kind, file, line) {
        return this.#ndb.getNodeId(name, kind, file, line) ?? undefined;
    }
    getFunctionNodeId(name, file, line) {
        return this.#ndb.getFunctionNodeId(name, file, line) ?? undefined;
    }
    bulkNodeIdsByFile(file) {
        return this.#ndb.bulkNodeIdsByFile(file).map(toNodeIdRow);
    }
    findNodeChildren(parentId) {
        return this.#ndb.findNodeChildren(parentId).map(toChildNodeRow);
    }
    findNodesByScope(scopeName, opts = {}) {
        return this.#ndb
            .findNodesByScope(scopeName, opts.kind ?? null, opts.file ?? null)
            .map(toNodeRow);
    }
    findNodeByQualifiedName(qualifiedName, opts = {}) {
        return this.#ndb.findNodeByQualifiedName(qualifiedName, opts.file ?? null).map(toNodeRow);
    }
    listFunctionNodes(opts = {}) {
        return this.#ndb
            .listFunctionNodes(opts.file ?? null, opts.pattern ?? null, opts.noTests ?? null)
            .map(toNodeRow);
    }
    iterateFunctionNodes(opts = {}) {
        const rows = this.#ndb
            .iterateFunctionNodes(opts.file ?? null, opts.pattern ?? null, opts.noTests ?? null)
            .map(toNodeRow);
        return rows[Symbol.iterator]();
    }
    findNodesForTriage(opts = {}) {
        try {
            return this.#ndb
                .findNodesForTriage(opts.kind ?? null, opts.role ?? null, opts.file ?? null, opts.noTests ?? null)
                .map(toTriageNodeRow);
        }
        catch (e) {
            const msg = e.message;
            if (msg.startsWith('Invalid kind:') || msg.startsWith('Invalid role:')) {
                throw new ConfigError(msg);
            }
            throw e;
        }
    }
    // ── Edge queries ──────────────────────────────────────────────────
    findCallees(nodeId) {
        return this.#ndb.findCallees(nodeId).map(toRelatedNodeRow);
    }
    findCallers(nodeId) {
        return this.#ndb.findCallers(nodeId).map(toRelatedNodeRow);
    }
    findCallersBatch(nodeIds) {
        if (nodeIds.length === 0)
            return new Map();
        const placeholders = nodeIds.map(() => '?').join(',');
        const rows = this.#ndb.queryAll(`SELECT e.target_id AS queried_id, n.id, n.name, n.kind, n.file, n.line, n.end_line
       FROM edges e JOIN nodes n ON e.source_id = n.id
       WHERE e.target_id IN (${placeholders}) AND e.kind = 'calls'`, nodeIds);
        const result = new Map();
        for (const row of rows) {
            const qid = row.queried_id;
            if (!result.has(qid))
                result.set(qid, []);
            result.get(qid).push({
                id: row.id,
                name: row.name,
                kind: row.kind,
                file: row.file,
                line: row.line,
                end_line: row.end_line ?? null,
            });
        }
        return result;
    }
    findDistinctCallers(nodeId) {
        return this.#ndb.findDistinctCallers(nodeId).map(toRelatedNodeRow);
    }
    findAllOutgoingEdges(nodeId) {
        return this.#ndb.findAllOutgoingEdges(nodeId).map(toAdjacentEdgeRow);
    }
    findAllIncomingEdges(nodeId) {
        return this.#ndb.findAllIncomingEdges(nodeId).map(toAdjacentEdgeRow);
    }
    findCalleeNames(nodeId) {
        return this.#ndb.findCalleeNames(nodeId);
    }
    findCallerNames(nodeId) {
        return this.#ndb.findCallerNames(nodeId);
    }
    findImportTargets(nodeId) {
        return this.#ndb.findImportTargets(nodeId).map(toImportEdgeRow);
    }
    findImportSources(nodeId) {
        return this.#ndb.findImportSources(nodeId).map(toImportEdgeRow);
    }
    findImportDependents(nodeId) {
        return this.#ndb.findImportDependents(nodeId).map(toNodeRow);
    }
    findCrossFileCallTargets(file) {
        return new Set(this.#ndb.findCrossFileCallTargets(file));
    }
    countCrossFileCallers(nodeId, file) {
        return this.#ndb.countCrossFileCallers(nodeId, file);
    }
    getClassHierarchy(classNodeId) {
        return new Set(this.#ndb.getClassHierarchy(classNodeId));
    }
    findImplementors(nodeId) {
        return this.#ndb.findImplementors(nodeId).map(toRelatedNodeRow);
    }
    findInterfaces(nodeId) {
        return this.#ndb.findInterfaces(nodeId).map(toRelatedNodeRow);
    }
    findIntraFileCallEdges(file) {
        return this.#ndb.findIntraFileCallEdges(file).map(toIntraFileCallEdge);
    }
    // ── Graph-read queries ────────────────────────────────────────────
    getCallableNodes() {
        return this.#ndb.getCallableNodes().map(toCallableNodeRow);
    }
    getCallEdges() {
        return this.#ndb.getCallEdges().map(toCallEdgeRow);
    }
    getFileNodesAll() {
        return this.#ndb.getFileNodesAll().map(toFileNodeRow);
    }
    getImportEdges() {
        return this.#ndb.getImportEdges().map(toImportGraphEdgeRow);
    }
    // ── Optional table checks ─────────────────────────────────────────
    hasCfgTables() {
        return this.#ndb.hasCfgTables();
    }
    hasEmbeddings() {
        return this.#ndb.hasEmbeddings();
    }
    hasDataflowTable() {
        return this.#ndb.hasDataflowTable();
    }
    getComplexityForNode(nodeId) {
        const r = this.#ndb.getComplexityForNode(nodeId);
        return r ? toComplexityMetrics(r) : undefined;
    }
    // ── Convenience queries ────────────────────────────────────────────
    getFileHash(file) {
        if (typeof this.#ndb.getFileHash === 'function')
            return this.#ndb.getFileHash(file);
        // Fallback to better-sqlite3 until Rust implements getFileHash
        const db = this.#getFallbackDb();
        if (db) {
            const row = db.prepare('SELECT hash FROM file_hashes WHERE file = ?').get(file);
            return row?.hash ?? null;
        }
        return null;
    }
    #implementsEdgesCache;
    hasImplementsEdges() {
        if (this.#implementsEdgesCache !== undefined)
            return this.#implementsEdgesCache;
        if (typeof this.#ndb.hasImplementsEdges === 'function') {
            this.#implementsEdgesCache = this.#ndb.hasImplementsEdges();
            return this.#implementsEdgesCache;
        }
        // Fallback to better-sqlite3
        const db = this.#getFallbackDb();
        if (db) {
            this.#implementsEdgesCache = !!db
                .prepare("SELECT 1 FROM edges WHERE kind = 'implements' LIMIT 1")
                .get();
            return this.#implementsEdgesCache;
        }
        return true; // conservative: assume yes when no fallback available
    }
    #coChangesTableCache;
    hasCoChangesTable() {
        if (this.#coChangesTableCache !== undefined)
            return this.#coChangesTableCache;
        if (typeof this.#ndb.hasCoChangesTable === 'function') {
            this.#coChangesTableCache = this.#ndb.hasCoChangesTable();
            return this.#coChangesTableCache;
        }
        // Fallback to better-sqlite3
        const db = this.#getFallbackDb();
        if (db) {
            try {
                this.#coChangesTableCache = !!db.prepare('SELECT 1 FROM co_changes LIMIT 1').get();
            }
            catch {
                this.#coChangesTableCache = false;
            }
            return this.#coChangesTableCache;
        }
        return false;
    }
    // ── Composite queries ──────────────────────────────────────────────
    fnDeps(name, opts) {
        if (typeof this.#ndb.fnDeps !== 'function')
            return null;
        const raw = this.#ndb.fnDeps(name, opts?.depth ?? undefined, opts?.noTests ?? undefined, opts?.file ?? undefined, opts?.kind ?? undefined);
        // Convert from native format (transitive_callers as array of groups)
        // to JS format (transitiveCallers as Record<number, Array>)
        return {
            name: raw.name,
            results: raw.results.map((entry) => {
                const transitiveCallers = {};
                for (const group of entry.transitiveCallers ?? []) {
                    transitiveCallers[group.depth] = (group.callers ?? []).map((c) => ({
                        name: c.name,
                        kind: c.kind,
                        file: c.file,
                        line: c.line ?? null,
                    }));
                }
                return {
                    name: entry.name,
                    kind: entry.kind,
                    file: entry.file,
                    line: entry.line ?? null,
                    endLine: entry.endLine ?? entry.end_line ?? null,
                    role: entry.role ?? null,
                    fileHash: entry.fileHash ?? entry.file_hash ?? null,
                    callees: (entry.callees ?? []).map((c) => ({
                        name: c.name,
                        kind: c.kind,
                        file: c.file,
                        line: c.line ?? null,
                    })),
                    callers: (entry.callers ?? []).map((c) => ({
                        name: c.name,
                        kind: c.kind,
                        file: c.file,
                        line: c.line ?? null,
                        viaHierarchy: c.viaHierarchy ?? c.via_hierarchy ?? undefined,
                    })),
                    transitiveCallers,
                };
            }),
        };
    }
}
//# sourceMappingURL=native-repository.js.map