import { computeHalsteadDerived, computeLOCMetrics, computeMaintainabilityIndex, } from '../metrics.js';
function classifyHalstead(node, hRules, acc) {
    const type = node.type;
    if (hRules.skipTypes.has(type))
        acc.halsteadSkipDepth++;
    if (acc.halsteadSkipDepth > 0)
        return;
    if (hRules.compoundOperators.has(type) && acc.operators) {
        acc.operators.set(type, (acc.operators.get(type) || 0) + 1);
    }
    if (node.childCount === 0) {
        if (hRules.operatorLeafTypes.has(type) && acc.operators) {
            acc.operators.set(type, (acc.operators.get(type) || 0) + 1);
        }
        else if (hRules.operandLeafTypes.has(type) && acc.operands) {
            const text = node.text;
            acc.operands.set(text, (acc.operands.get(text) || 0) + 1);
        }
    }
}
/**
 * Detect whether a branch node is an else-if that the DFS walk would NOT
 * increment nesting for.  Returns true for:
 *  - Pattern A (JS/C#/Rust): if_statement whose parent is else_clause
 *  - Pattern C (Go/Java): if_statement that is the alternative of parent if
 *
 * Pattern B (Python elif_clause) is not an issue because elif_clause is
 * never in nestingNodes.
 */
function isElseIfNonNesting(node, type, cRules) {
    if (type !== cRules.ifNodeType)
        return false;
    if (cRules.elseViaAlternative) {
        // Pattern C
        return (node.parent?.type === cRules.ifNodeType &&
            node.parent?.childForFieldName('alternative')?.id === node.id);
    }
    if (cRules.elseNodeType) {
        // Pattern A
        return node.parent?.type === cRules.elseNodeType;
    }
    return false;
}
function classifyBranchNode(node, type, nestingLevel, cRules, acc) {
    if (cRules.elseNodeType && type === cRules.elseNodeType) {
        const firstChild = node.namedChild(0);
        if (firstChild && firstChild.type === cRules.ifNodeType) {
            return;
        }
        acc.cognitive++;
        return;
    }
    if (cRules.elifNodeType && type === cRules.elifNodeType) {
        acc.cognitive++;
        acc.cyclomatic++;
        return;
    }
    let isElseIf = false;
    if (type === cRules.ifNodeType) {
        if (cRules.elseViaAlternative) {
            isElseIf =
                node.parent?.type === cRules.ifNodeType &&
                    node.parent?.childForFieldName('alternative')?.id === node.id;
        }
        else if (cRules.elseNodeType) {
            isElseIf = node.parent?.type === cRules.elseNodeType;
        }
    }
    if (isElseIf) {
        acc.cognitive++;
        acc.cyclomatic++;
        return;
    }
    acc.cognitive += 1 + nestingLevel;
    acc.cyclomatic++;
    if (cRules.switchLikeNodes?.has(type)) {
        acc.cyclomatic--;
    }
}
function classifyLogicalOp(node, cRules, acc) {
    const op = node.child(1)?.type;
    if (!op || !cRules.logicalOperators.has(op))
        return;
    acc.cyclomatic++;
    const parent = node.parent;
    const sameSequence = parent != null && parent.type === cRules.logicalNodeType && parent.child(1)?.type === op;
    if (!sameSequence)
        acc.cognitive++;
}
function classifyPlainElse(node, type, cRules, acc) {
    if (cRules.elseViaAlternative &&
        type !== cRules.ifNodeType &&
        node.parent?.type === cRules.ifNodeType &&
        node.parent?.childForFieldName('alternative')?.id === node.id) {
        acc.cognitive++;
    }
}
function collectResult(funcNode, acc, hRules, langId) {
    const halstead = hRules && acc.operators && acc.operands
        ? computeHalsteadDerived(acc.operators, acc.operands)
        : null;
    const loc = computeLOCMetrics(funcNode, langId ?? undefined);
    const volume = halstead ? halstead.volume : 0;
    const commentRatio = loc.loc > 0 ? loc.commentLines / loc.loc : 0;
    const mi = computeMaintainabilityIndex(volume, acc.cyclomatic, loc.sloc, commentRatio);
    return {
        cognitive: acc.cognitive,
        cyclomatic: acc.cyclomatic,
        maxNesting: acc.maxNesting,
        halstead,
        loc,
        mi,
    };
}
function resetAccumulators(hRules) {
    return {
        cognitive: 0,
        cyclomatic: 1,
        maxNesting: 0,
        operators: hRules ? new Map() : null,
        operands: hRules ? new Map() : null,
        halsteadSkipDepth: 0,
    };
}
/** Classify a single node for all complexity metrics (Halstead, branching, logical ops, etc.). */
function classifyNode(node, nestingLevel, cRules, hRules, acc) {
    const type = node.type;
    if (hRules)
        classifyHalstead(node, hRules, acc);
    if (nestingLevel > acc.maxNesting)
        acc.maxNesting = nestingLevel;
    if (type === cRules.logicalNodeType)
        classifyLogicalOp(node, cRules, acc);
    if (type === cRules.optionalChainType)
        acc.cyclomatic++;
    if (cRules.branchNodes.has(type) && node.childCount > 0) {
        classifyBranchNode(node, type, nestingLevel, cRules, acc);
    }
    classifyPlainElse(node, type, cRules, acc);
    if (cRules.caseNodes.has(type) && node.childCount > 0)
        acc.cyclomatic++;
}
export function createComplexityVisitor(cRules, hRules, options = {}) {
    const { fileLevelWalk = false, langId = null } = options;
    let acc = resetAccumulators(hRules);
    let activeFuncNode = null;
    let activeFuncName = null;
    let funcDepth = 0;
    const results = [];
    // The walker increments context.nestingLevel for ALL nodes in nestingNodeTypes
    // (including if_statement). But the DFS engine does NOT increment nesting for
    // else-if if_statement nodes. Track a correction counter so children of else-if
    // nodes see the correct (non-inflated) nesting level.
    let nestingAdjust = 0;
    const adjustNodeIds = new Set();
    return {
        name: 'complexity',
        functionNodeTypes: cRules.functionNodes,
        enterFunction(funcNode, funcName, _context) {
            if (fileLevelWalk && !activeFuncNode) {
                acc = resetAccumulators(hRules);
                activeFuncNode = funcNode;
                activeFuncName = funcName;
                funcDepth = 0;
                nestingAdjust = 0;
                adjustNodeIds.clear();
            }
            else {
                funcDepth++;
            }
        },
        exitFunction(funcNode, _funcName, _context) {
            if (fileLevelWalk && funcNode === activeFuncNode) {
                results.push({
                    funcNode,
                    funcName: activeFuncName,
                    metrics: collectResult(funcNode, acc, hRules, langId),
                });
                activeFuncNode = null;
                activeFuncName = null;
            }
            else {
                funcDepth--;
            }
        },
        enterNode(node, context) {
            if (fileLevelWalk && !activeFuncNode)
                return;
            // In file-level mode, funcDepth starts at 0 for the active function.
            // In function-level mode, funcDepth starts at 1 for the root function
            // (since enterFunction always increments it). Nested functions add +1
            // each level — subtract 1 so the root function contributes 0 nesting
            // and each nested level adds +1, matching the Rust engine's behavior.
            const funcNesting = fileLevelWalk ? funcDepth : Math.max(0, funcDepth - 1);
            const nestingLevel = context.nestingLevel + funcNesting - nestingAdjust;
            classifyNode(node, nestingLevel, cRules, hRules, acc);
            // If this is an else-if if_statement that the walker will treat as a
            // nesting node (incrementing context.nestingLevel for children), but
            // the DFS walk would NOT increment nesting for, compensate by bumping
            // nestingAdjust so children see the correct level.
            if (cRules.nestingNodes.has(node.type) && isElseIfNonNesting(node, node.type, cRules)) {
                nestingAdjust++;
                adjustNodeIds.add(node.id);
            }
        },
        exitNode(node) {
            if (adjustNodeIds.has(node.id)) {
                nestingAdjust--;
                adjustNodeIds.delete(node.id);
            }
            if (hRules?.skipTypes.has(node.type))
                acc.halsteadSkipDepth--;
        },
        finish() {
            if (fileLevelWalk)
                return results;
            return collectResult({ text: '' }, acc, hRules, langId);
        },
    };
}
//# sourceMappingURL=complexity-visitor.js.map