import fs from 'node:fs';
import path from 'node:path';
import { computeLOCMetrics as _computeLOCMetrics, computeMaintainabilityIndex as _computeMaintainabilityIndex, } from '../ast-analysis/metrics.js';
import { COMPLEXITY_RULES, HALSTEAD_RULES } from '../ast-analysis/rules/index.js';
import { findFunctionNode as _findFunctionNode, buildExtensionSet, buildExtToLangMap, } from '../ast-analysis/shared.js';
import { walkWithVisitors } from '../ast-analysis/visitor.js';
import { createComplexityVisitor } from '../ast-analysis/visitors/complexity-visitor.js';
import { getFunctionNodeId } from '../db/index.js';
import { debug, info } from '../infrastructure/logger.js';
// Re-export rules for backward compatibility
export { COMPLEXITY_RULES, HALSTEAD_RULES };
// Extensions whose language has complexity rules — used to skip needless WASM init
const COMPLEXITY_EXTENSIONS = buildExtensionSet(COMPLEXITY_RULES);
// ─── Halstead Metrics Computation ─────────────────────────────────────────
export function computeHalsteadMetrics(functionNode, language) {
    const rules = HALSTEAD_RULES.get(language);
    if (!rules)
        return null;
    const operators = new Map(); // type -> count
    const operands = new Map(); // text -> count
    function walk(node) {
        if (!node)
            return;
        // Skip type annotation subtrees
        if (rules?.skipTypes.has(node.type))
            return;
        // Compound operators (non-leaf): count the node type as an operator
        if (rules?.compoundOperators.has(node.type)) {
            operators.set(node.type, (operators.get(node.type) || 0) + 1);
        }
        // Leaf nodes: classify as operator or operand
        if (node.childCount === 0) {
            if (rules?.operatorLeafTypes.has(node.type)) {
                operators.set(node.type, (operators.get(node.type) || 0) + 1);
            }
            else if (rules?.operandLeafTypes.has(node.type)) {
                const text = node.text;
                operands.set(text, (operands.get(text) || 0) + 1);
            }
        }
        for (let i = 0; i < node.childCount; i++) {
            walk(node.child(i));
        }
    }
    walk(functionNode);
    const n1 = operators.size; // distinct operators
    const n2 = operands.size; // distinct operands
    let bigN1 = 0; // total operators
    for (const c of operators.values())
        bigN1 += c;
    let bigN2 = 0; // total operands
    for (const c of operands.values())
        bigN2 += c;
    const vocabulary = n1 + n2;
    const length = bigN1 + bigN2;
    // Guard against zero
    const volume = vocabulary > 0 ? length * Math.log2(vocabulary) : 0;
    const difficulty = n2 > 0 ? (n1 / 2) * (bigN2 / n2) : 0;
    const effort = difficulty * volume;
    const bugs = volume / 3000;
    return {
        n1,
        n2,
        bigN1,
        bigN2,
        vocabulary,
        length,
        volume: +volume.toFixed(2),
        difficulty: +difficulty.toFixed(2),
        effort: +effort.toFixed(2),
        bugs: +bugs.toFixed(4),
    };
}
// ─── LOC Metrics Computation ──────────────────────────────────────────────
// Delegated to ast-analysis/metrics.js; re-exported for backward compatibility.
export const computeLOCMetrics = _computeLOCMetrics;
// ─── Maintainability Index ────────────────────────────────────────────────
// Delegated to ast-analysis/metrics.js; re-exported for backward compatibility.
export const computeMaintainabilityIndex = _computeMaintainabilityIndex;
/** Walk all children at the given nesting level. */
function walkChildren(node, nestingLevel, walkFn) {
    for (let i = 0; i < node.childCount; i++) {
        walkFn(node.child(i), nestingLevel, false);
    }
}
/** Handle logical operators in binary expressions. Returns true if handled. */
function handleLogicalOperator(node, type, rules, acc, nestingLevel, walkFn) {
    if (type !== rules.logicalNodeType)
        return false;
    const op = node.child(1)?.type;
    if (!op || !rules.logicalOperators.has(op))
        return false;
    acc.cyclomatic++;
    // Cognitive: +1 only when operator changes from the previous sibling sequence
    const parent = node.parent;
    const sameSequence = parent?.type === rules.logicalNodeType && parent.child(1)?.type === op;
    if (!sameSequence)
        acc.cognitive++;
    walkChildren(node, nestingLevel, walkFn);
    return true;
}
/** Handle else clause wrapping an if (Pattern A: JS/C#/Rust). Returns true if handled. */
function handleElseClause(node, type, rules, acc, nestingLevel, walkFn) {
    if (!rules.elseNodeType || type !== rules.elseNodeType)
        return false;
    const firstChild = node.namedChild(0);
    if (firstChild && firstChild.type === rules.ifNodeType) {
        // else-if: the if_statement child handles its own increment
        walkChildren(node, nestingLevel, walkFn);
        return true;
    }
    // Plain else
    acc.cognitive++;
    walkChildren(node, nestingLevel, walkFn);
    return true;
}
/** Detect and handle else-if patterns (Patterns A, B, C). Returns true if handled. */
function handleElseIf(node, type, rules, acc, nestingLevel, walkFn) {
    // Pattern B: explicit elif node (Python/Ruby/PHP)
    if (rules.elifNodeType && type === rules.elifNodeType) {
        acc.cognitive++;
        acc.cyclomatic++;
        walkChildren(node, nestingLevel, walkFn);
        return true;
    }
    // Detect else-if via Pattern A or C
    if (type === rules.ifNodeType) {
        let isElseIf = false;
        if (rules.elseViaAlternative) {
            // Pattern C (Go/Java): if_statement is the alternative of parent if_statement
            isElseIf =
                node.parent?.type === rules.ifNodeType &&
                    node.parent.childForFieldName('alternative')?.id === node.id;
        }
        else if (rules.elseNodeType) {
            // Pattern A (JS/C#/Rust): if_statement inside else_clause
            isElseIf = node.parent?.type === rules.elseNodeType;
        }
        if (isElseIf) {
            acc.cognitive++;
            acc.cyclomatic++;
            walkChildren(node, nestingLevel, walkFn);
            return true;
        }
    }
    return false;
}
/** Handle branch/control flow nodes. Returns true if handled. */
function handleBranchNode(node, type, rules, acc, nestingLevel, walkFn) {
    if (!rules.branchNodes.has(type) || node.childCount === 0)
        return false;
    if (handleElseClause(node, type, rules, acc, nestingLevel, walkFn))
        return true;
    if (handleElseIf(node, type, rules, acc, nestingLevel, walkFn))
        return true;
    // Regular branch node
    acc.cognitive += 1 + nestingLevel; // structural + nesting
    acc.cyclomatic++;
    // Switch-like nodes don't add cyclomatic themselves (cases do)
    if (rules.switchLikeNodes?.has(type)) {
        acc.cyclomatic--;
    }
    if (rules.nestingNodes.has(type)) {
        walkChildren(node, nestingLevel + 1, walkFn);
        return true;
    }
    return false;
}
/** Handle Pattern C plain else: block is the alternative of an if_statement (Go/Java). */
function handlePatternCElse(node, type, rules, acc, nestingLevel, walkFn) {
    if (!rules.elseViaAlternative ||
        type === rules.ifNodeType ||
        node.parent?.type !== rules.ifNodeType ||
        node.parent.childForFieldName('alternative')?.id !== node.id) {
        return false;
    }
    acc.cognitive++;
    walkChildren(node, nestingLevel, walkFn);
    return true;
}
export function computeFunctionComplexity(functionNode, language) {
    const rules = COMPLEXITY_RULES.get(language);
    if (!rules)
        return null;
    const acc = { cognitive: 0, cyclomatic: 1, maxNesting: 0 };
    function walk(node, nestingLevel, isTopFunction) {
        if (!node)
            return;
        const type = node.type;
        if (nestingLevel > acc.maxNesting)
            acc.maxNesting = nestingLevel;
        if (handleLogicalOperator(node, type, rules, acc, nestingLevel, walk))
            return;
        // Optional chaining (cyclomatic only)
        if (type === rules.optionalChainType)
            acc.cyclomatic++;
        if (handleBranchNode(node, type, rules, acc, nestingLevel, walk))
            return;
        if (handlePatternCElse(node, type, rules, acc, nestingLevel, walk))
            return;
        // Case nodes (cyclomatic only, skip keyword leaves)
        if (rules.caseNodes.has(type) && node.childCount > 0)
            acc.cyclomatic++;
        // Nested function definitions (increase nesting)
        if (!isTopFunction && rules.functionNodes.has(type)) {
            walkChildren(node, nestingLevel + 1, walk);
            return;
        }
        walkChildren(node, nestingLevel, walk);
    }
    walk(functionNode, 0, true);
    return { cognitive: acc.cognitive, cyclomatic: acc.cyclomatic, maxNesting: acc.maxNesting };
}
export function computeAllMetrics(functionNode, langId) {
    const cRules = COMPLEXITY_RULES.get(langId);
    if (!cRules)
        return null;
    const hRules = HALSTEAD_RULES.get(langId);
    const visitor = createComplexityVisitor(cRules, hRules, { langId });
    const nestingNodes = new Set(cRules.nestingNodes);
    // NOTE: do NOT add functionNodes here — in function-level mode the walker
    // walks a single function node, and adding it to nestingNodeTypes would
    // inflate context.nestingLevel by +1 for the entire body.
    const results = walkWithVisitors(functionNode, [visitor], langId, {
        nestingNodeTypes: nestingNodes,
    });
    const rawResult = results.complexity;
    // The visitor's finish() in function-level mode returns the raw metrics
    // but without LOC (needs the functionNode text). Compute LOC + MI here.
    const loc = _computeLOCMetrics(functionNode, langId);
    const volume = rawResult.halstead ? rawResult.halstead.volume : 0;
    const commentRatio = loc.loc > 0 ? loc.commentLines / loc.loc : 0;
    const mi = _computeMaintainabilityIndex(volume, rawResult.cyclomatic, loc.sloc, commentRatio);
    return {
        cognitive: rawResult.cognitive,
        cyclomatic: rawResult.cyclomatic,
        maxNesting: rawResult.maxNesting,
        halstead: rawResult.halstead,
        loc,
        mi,
    };
}
// ─── Build-Time: Compute Metrics for Changed Files ────────────────────────
export { _findFunctionNode as findFunctionNode };
async function initWasmParsersIfNeeded(fileSymbols) {
    for (const [relPath, symbols] of fileSymbols) {
        if (!symbols._tree) {
            const ext = path.extname(relPath).toLowerCase();
            if (!COMPLEXITY_EXTENSIONS.has(ext))
                continue;
            // Only consider definitions with real function bodies (non-dotted names,
            // multi-line span). Interface/type property signatures are extracted as
            // methods but correctly lack complexity data from the native engine.
            const hasPrecomputed = symbols.definitions.every((d) => (d.kind !== 'function' && d.kind !== 'method') ||
                d.complexity ||
                d.name.includes('.') ||
                !d.endLine ||
                d.endLine <= d.line);
            if (!hasPrecomputed) {
                const { createParsers } = await import('../domain/parser.js');
                const parsers = await createParsers();
                const extToLang = buildExtToLangMap();
                return { parsers, extToLang };
            }
        }
    }
    return { parsers: null, extToLang: null };
}
function getTreeForFile(symbols, relPath, rootDir, parsers, extToLang, getParser) {
    let tree = symbols._tree;
    let langId = symbols._langId;
    const allPrecomputed = symbols.definitions.every((d) => (d.kind !== 'function' && d.kind !== 'method') || d.complexity);
    if (!allPrecomputed && !tree) {
        const ext = path.extname(relPath).toLowerCase();
        if (!COMPLEXITY_EXTENSIONS.has(ext))
            return null;
        if (!extToLang)
            return null;
        langId = extToLang.get(ext);
        if (!langId)
            return null;
        const absPath = path.join(rootDir, relPath);
        let code;
        try {
            code = fs.readFileSync(absPath, 'utf-8');
        }
        catch (e) {
            debug(`complexity: cannot read ${relPath}: ${e.message}`);
            return null;
        }
        const parser = getParser(parsers, absPath);
        if (!parser)
            return null;
        try {
            tree = parser.parse(code);
        }
        catch (e) {
            debug(`complexity: parse failed for ${relPath}: ${e.message}`);
            return null;
        }
    }
    return tree && langId ? { tree: tree, langId } : null;
}
function upsertPrecomputedComplexity(db, upsert, def, relPath) {
    const nodeId = getFunctionNodeId(db, def.name, relPath, def.line);
    if (!nodeId)
        return 0;
    const ch = def.complexity?.halstead;
    const cl = def.complexity?.loc;
    upsert.run(nodeId, def.complexity?.cognitive, def.complexity?.cyclomatic, def.complexity?.maxNesting ?? 0, cl ? cl.loc : 0, cl ? cl.sloc : 0, cl ? cl.commentLines : 0, ch ? ch.n1 : 0, ch ? ch.n2 : 0, ch ? ch.bigN1 : 0, ch ? ch.bigN2 : 0, ch ? ch.vocabulary : 0, ch ? ch.length : 0, ch ? ch.volume : 0, ch ? ch.difficulty : 0, ch ? ch.effort : 0, ch ? ch.bugs : 0, def.complexity?.maintainabilityIndex ?? 0);
    return 1;
}
function upsertAstComplexity(db, upsert, def, relPath, tree, langId, rules) {
    if (!tree || !rules || !langId)
        return 0;
    const funcNode = _findFunctionNode(tree.rootNode, def.line, def.endLine ?? def.line, rules);
    if (!funcNode)
        return 0;
    const metrics = computeAllMetrics(funcNode, langId);
    if (!metrics)
        return 0;
    const nodeId = getFunctionNodeId(db, def.name, relPath, def.line);
    if (!nodeId)
        return 0;
    const h = metrics.halstead;
    upsert.run(nodeId, metrics.cognitive, metrics.cyclomatic, metrics.maxNesting, metrics.loc.loc, metrics.loc.sloc, metrics.loc.commentLines, h ? h.n1 : 0, h ? h.n2 : 0, h ? h.bigN1 : 0, h ? h.bigN2 : 0, h ? h.vocabulary : 0, h ? h.length : 0, h ? h.volume : 0, h ? h.difficulty : 0, h ? h.effort : 0, h ? h.bugs : 0, metrics.mi);
    return 1;
}
/** Collect native bulk-insert rows from precomputed complexity data.
 *  Returns the rows array, or null if any definition is missing complexity
 *  (signalling that JS fallback is needed). */
function collectNativeBulkRows(db, fileSymbols) {
    const rows = [];
    for (const [relPath, symbols] of fileSymbols) {
        const ext = path.extname(relPath).toLowerCase();
        const langId = symbols._langId || '';
        const langSupported = COMPLEXITY_EXTENSIONS.has(ext) || COMPLEXITY_RULES.has(langId);
        for (const def of symbols.definitions) {
            if (def.kind !== 'function' && def.kind !== 'method')
                continue;
            if (!def.line)
                continue;
            // Interface/type property signatures and single-line stubs are extracted
            // as methods but the native engine correctly never assigns complexity.
            // Mirror the leniency in initWasmParsersIfNeeded to avoid bailing out
            // of the native bulk-insert path for every TypeScript codebase (#846).
            if (!def.complexity) {
                if (def.name.includes('.') || !def.endLine || def.endLine <= def.line)
                    continue;
                // Languages without complexity rules will never have data — skip them
                // rather than bailing out of the entire native bulk path.
                if (!langSupported)
                    continue;
                return null; // genuine function body missing complexity — needs JS fallback
            }
            const nodeId = getFunctionNodeId(db, def.name, relPath, def.line);
            if (!nodeId)
                continue;
            const ch = def.complexity.halstead;
            const cl = def.complexity.loc;
            rows.push({
                nodeId,
                cognitive: def.complexity.cognitive ?? 0,
                cyclomatic: def.complexity.cyclomatic ?? 0,
                maxNesting: def.complexity.maxNesting ?? 0,
                loc: cl ? cl.loc : 0,
                sloc: cl ? cl.sloc : 0,
                commentLines: cl ? cl.commentLines : 0,
                halsteadN1: ch ? ch.n1 : 0,
                halsteadN2: ch ? ch.n2 : 0,
                halsteadBigN1: ch ? ch.bigN1 : 0,
                halsteadBigN2: ch ? ch.bigN2 : 0,
                halsteadVocabulary: ch ? ch.vocabulary : 0,
                halsteadLength: ch ? ch.length : 0,
                halsteadVolume: ch ? ch.volume : 0,
                halsteadDifficulty: ch ? ch.difficulty : 0,
                halsteadEffort: ch ? ch.effort : 0,
                halsteadBugs: ch ? ch.bugs : 0,
                maintainabilityIndex: def.complexity.maintainabilityIndex ?? 0,
            });
        }
    }
    return rows;
}
/** Try the native bulk-insert fast path. Returns true if all rows were
 *  inserted successfully (caller can return early). */
function tryNativeBulkInsert(db, fileSymbols, engineOpts) {
    const nativeDb = engineOpts?.nativeDb;
    if (!nativeDb?.bulkInsertComplexity)
        return false;
    const rows = collectNativeBulkRows(db, fileSymbols);
    if (rows === null)
        return false; // missing complexity — needs JS fallback
    if (rows.length === 0)
        return true; // nothing to insert — native path done
    let inserted;
    try {
        engineOpts?.suspendJsDb?.();
        inserted = nativeDb.bulkInsertComplexity(rows);
    }
    finally {
        engineOpts?.resumeJsDb?.();
    }
    if (inserted === rows.length) {
        info(`Complexity (native bulk): ${inserted} functions analyzed`);
        return true;
    }
    debug(`Native bulkInsertComplexity partial: ${inserted}/${rows.length} — falling back to JS`);
    return false;
}
/** JS/WASM fallback: parse files and compute metrics via AST traversal. */
async function computeJsFallbackMetrics(db, fileSymbols, rootDir) {
    const { parsers, extToLang } = await initWasmParsersIfNeeded(fileSymbols);
    const { getParser } = await import('../domain/parser.js');
    const upsert = db.prepare(`INSERT OR REPLACE INTO function_complexity
     (node_id, cognitive, cyclomatic, max_nesting,
      loc, sloc, comment_lines,
      halstead_n1, halstead_n2, halstead_big_n1, halstead_big_n2,
      halstead_vocabulary, halstead_length, halstead_volume,
      halstead_difficulty, halstead_effort, halstead_bugs,
      maintainability_index)
     VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);
    let analyzed = 0;
    const tx = db.transaction(() => {
        for (const [relPath, symbols] of fileSymbols) {
            const result = getTreeForFile(symbols, relPath, rootDir, parsers, extToLang, getParser);
            const tree = result ? result.tree : null;
            const langId = result ? result.langId : null;
            const rules = langId
                ? COMPLEXITY_RULES.get(langId)
                : undefined;
            for (const def of symbols.definitions) {
                if (def.kind !== 'function' && def.kind !== 'method')
                    continue;
                if (!def.line)
                    continue;
                if (def.complexity) {
                    analyzed += upsertPrecomputedComplexity(db, upsert, def, relPath);
                }
                else {
                    analyzed += upsertAstComplexity(db, upsert, def, relPath, tree, langId, rules);
                }
            }
        }
    });
    tx();
    if (analyzed > 0) {
        info(`Complexity: ${analyzed} functions analyzed`);
    }
}
export async function buildComplexityMetrics(db, fileSymbols, rootDir, engineOpts) {
    if (tryNativeBulkInsert(db, fileSymbols, engineOpts))
        return;
    await computeJsFallbackMetrics(db, fileSymbols, rootDir);
}
// ─── Query-Time Functions (re-exported from complexity-query.ts) ──────────
// Split to separate query-time concerns (DB reads, filtering, pagination)
// from compute-time concerns (AST traversal, metric algorithms).
export { complexityData, iterComplexity } from './complexity-query.js';
//# sourceMappingURL=complexity.js.map