/**
 * Semantic symbol type for bash scripts.
 */
export var SymbolType;
(function (SymbolType) {
    SymbolType["Variable"] = "Variable";
    SymbolType["Function"] = "Function";
    SymbolType["Command"] = "Command";
    SymbolType["File"] = "File";
    SymbolType["Class"] = "Class";
    SymbolType["Module"] = "Module";
})(SymbolType || (SymbolType = {}));
/**
 * Levenshtein distance utility for fuzzy matching.
 */
export function levenshtein(a, b) {
    if (a.length === 0)
        return b.length;
    if (b.length === 0)
        return a.length;
    const matrix = [];
    for (let i = 0; i <= b.length; i++) {
        matrix[i] = [i];
    }
    for (let j = 0; j <= a.length; j++) {
        matrix[0][j] = j;
    }
    for (let i = 1; i <= b.length; i++) {
        for (let j = 1; j <= a.length; j++) {
            if (b.charAt(i - 1) === a.charAt(j - 1)) {
                matrix[i][j] = matrix[i - 1][j - 1];
            }
            else {
                matrix[i][j] = Math.min(matrix[i - 1][j - 1] + 1, // substitution
                matrix[i][j - 1] + 1, // insertion
                matrix[i - 1][j] + 1);
            }
        }
    }
    return matrix[b.length][a.length];
}
/**
 * Semantic Engine for Ag-Bash.
 *
 * Analyzes ASTs to provide semantic intelligence for shell scripts.
 */
export class SemanticEngine {
    ast;
    symbols = [];
    occurrences = [];
    constructor(ast = undefined) {
        this.ast = ast;
        if (this.ast) {
            this.analyze();
        }
    }
    /**
     * Refreshes the semantic symbol table by traversing the AST.
     */
    analyze(path) {
        if (!this.ast)
            return;
        this.symbols = [];
        this.occurrences = [];
        this.traverse(this.ast, "global", path);
    }
    traverse(node, currentScope, path) {
        if (!node)
            return;
        switch (node.type) {
            case "Script":
                node.statements.forEach((s) => this.traverse(s, currentScope, path));
                break;
            case "Statement":
                node.pipelines.forEach((p) => p.commands.forEach((c) => this.traverse(c, currentScope, path)));
                break;
            case "Pipeline":
                node.commands.forEach((c) => this.traverse(c, currentScope, path));
                break;
            case "FunctionDef": {
                const fnNode = node;
                const occ = {
                    name: fnNode.name,
                    type: SymbolType.Function,
                    line: fnNode.line ?? 0,
                    column: 0,
                    isDefinition: true,
                    scope: "global",
                    path,
                };
                this.occurrences.push(occ);
                if (!this.symbols.some((s) => s.name === fnNode.name &&
                    s.type === SymbolType.Function &&
                    s.path === path)) {
                    this.symbols.push({
                        name: fnNode.name,
                        type: SymbolType.Function,
                        line: fnNode.line ?? 0,
                        column: 0,
                        scope: "global",
                        path,
                    });
                }
                this.traverse(fnNode.body, fnNode.name, path);
                break;
            }
            case "SimpleCommand": {
                const cmdNode = node;
                cmdNode.assignments.forEach((a) => this.traverse(a, currentScope, path));
                // Track function calls as references
                if (cmdNode.name &&
                    cmdNode.name.parts.length === 1 &&
                    cmdNode.name.parts[0].type === "Literal") {
                    const name = cmdNode.name.parts[0].value;
                    this.occurrences.push({
                        name,
                        type: SymbolType.Function,
                        line: cmdNode.line ?? 0,
                        column: 0,
                        isDefinition: false,
                        scope: currentScope,
                        path,
                    });
                }
                cmdNode.args.forEach((arg) => this.traverse(arg, currentScope, path));
                break;
            }
            case "Assignment": {
                const assignNode = node;
                const occ = {
                    name: assignNode.name,
                    type: SymbolType.Variable,
                    line: assignNode.line ?? 0,
                    column: 0,
                    isDefinition: true,
                    scope: currentScope,
                    path,
                };
                this.occurrences.push(occ);
                if (!this.symbols.some((s) => s.name === assignNode.name &&
                    s.type === SymbolType.Variable &&
                    s.scope === currentScope &&
                    s.path === path)) {
                    this.symbols.push({
                        name: assignNode.name,
                        type: SymbolType.Variable,
                        line: assignNode.line ?? 0,
                        column: 0,
                        scope: currentScope,
                        path,
                    });
                }
                if (assignNode.value) {
                    this.traverse(assignNode.value, currentScope, path);
                }
                break;
            }
            case "Word": {
                const wordNode = node;
                wordNode.parts.forEach((p) => this.traverse(p, currentScope, path));
                break;
            }
            case "ParameterExpansion": {
                const peNode = node;
                this.occurrences.push({
                    name: peNode.parameter,
                    type: SymbolType.Variable,
                    line: peNode.line ?? 0,
                    column: 0,
                    isDefinition: false,
                    scope: currentScope,
                    path,
                });
                break;
            }
            case "DoubleQuoted": {
                node.parts.forEach((p) => this.traverse(p, currentScope, path));
                break;
            }
            case "Group":
                node.body.forEach((s) => this.traverse(s, currentScope, path));
                break;
            case "Subshell":
                node.body.forEach((s) => this.traverse(s, currentScope, path));
                break;
            case "If":
                node.clauses.forEach((c) => {
                    c.condition.forEach((s) => this.traverse(s, currentScope, path));
                    c.body.forEach((s) => this.traverse(s, currentScope, path));
                });
                if (node.elseBody) {
                    node.elseBody.forEach((s) => this.traverse(s, currentScope, path));
                }
                break;
            case "While":
            case "Until":
                node.condition.forEach((s) => this.traverse(s, currentScope, path));
                node.body.forEach((s) => this.traverse(s, currentScope, path));
                break;
            case "For": {
                const forNode = node;
                // The for loop variable is a definition
                this.occurrences.push({
                    name: forNode.variable,
                    type: SymbolType.Variable,
                    line: forNode.line ?? 0,
                    column: 0,
                    isDefinition: true,
                    scope: currentScope,
                    path,
                });
                if (forNode.words) {
                    forNode.words.forEach((w) => this.traverse(w, currentScope, path));
                }
                forNode.body.forEach((s) => this.traverse(s, currentScope, path));
                break;
            }
        }
    }
    /**
     * Traverses a Tree-sitter CST for generic symbol extraction.
     */
    traverseTreeSitter(node, currentScope, path, language) {
        if (!node)
            return;
        const type = node.type;
        let name;
        let symbolType;
        let nextScope = currentScope;
        // Language-specific mappings
        if (language === "python") {
            if (type === "function_definition") {
                name = node.childForFieldName("name")?.text;
                symbolType = SymbolType.Function;
                nextScope = name || currentScope;
            }
            else if (type === "class_definition") {
                name = node.childForFieldName("name")?.text;
                symbolType = SymbolType.Class;
                nextScope = name || currentScope;
            }
            else if (type === "assignment") {
                name = node.childForFieldName("left")?.text;
                symbolType = SymbolType.Variable;
            }
        }
        else if (language === "javascript" || language === "typescript") {
            if (type === "function_declaration" || type === "function") {
                name = node.childForFieldName("name")?.text;
                symbolType = SymbolType.Function;
                nextScope = name || currentScope;
            }
            else if (type === "class_declaration" || type === "class") {
                name = node.childForFieldName("name")?.text;
                symbolType = SymbolType.Class;
                nextScope = name || currentScope;
            }
            else if (type === "variable_declarator") {
                name = node.childForFieldName("id")?.text;
                symbolType = SymbolType.Variable;
            }
        }
        if (name && symbolType) {
            const line = node.startPosition.row + 1;
            const column = node.startPosition.column;
            this.occurrences.push({
                name,
                type: symbolType,
                line,
                column,
                isDefinition: true,
                scope: currentScope,
                path,
            });
            if (!this.symbols.some((s) => s.name === name &&
                s.type === symbolType &&
                s.path === path &&
                s.scope === currentScope)) {
                this.symbols.push({
                    name,
                    type: symbolType,
                    line,
                    column,
                    scope: currentScope,
                    path,
                });
            }
        }
        // Recurse into children
        for (const child of node.namedChildren) {
            this.traverseTreeSitter(child, nextScope, path, language);
        }
    }
    /**
     * Returns all symbols visible from a specific scope.
     */
    getVisibleSymbols(scope = "global") {
        return this.symbols.filter((s) => s.scope === "global" || s.scope === scope);
    }
    /**
     * Returns all indexed symbols.
     */
    getAllSymbols() {
        return this.symbols;
    }
    /**
     * Returns all occurrences of a symbol.
     */
    getOccurrences(name) {
        return this.occurrences.filter((o) => o.name === name);
    }
    /**
     * Resolves a symbol's definition.
     */
    findDefinition(name, scope = "global") {
        return (this.symbols.find((s) => s.name === name && s.scope === scope) ||
            this.symbols.find((s) => s.name === name && s.scope === "global"));
    }
    /**
     * Incremental indexer called by the interpreter or workspace indexer.
     */
    indexNode(node, path, language = "bash") {
        if (language === "bash" && node.type) {
            this.traverse(node, "global", path);
        }
        else {
            this.traverseTreeSitter(node, "global", path, language);
        }
    }
    async indexStatement(node, path) {
        this.traverse(node, "global", path);
    }
    async indexScript(node, path) {
        this.traverse(node, "global", path);
    }
    /**
     * Fuzzy search for symbols similar to the query.
     */
    fuzzySearchSymbols(query, type, maxDistance = 2) {
        return this.symbols
            .filter((s) => !type || s.type === type)
            .map((s) => ({ symbol: s, distance: levenshtein(query, s.name) }))
            .filter((res) => res.distance <= maxDistance)
            .sort((a, b) => a.distance - b.distance)
            .map((res) => res.symbol);
    }
    /**
     * Serializes the current symbol table and occurrences.
     */
    serialize() {
        return JSON.stringify({
            symbols: this.symbols,
            occurrences: this.occurrences,
        });
    }
    /**
     * Deserializes symbols and occurrences into the engine.
     */
    deserialize(data) {
        const parsed = typeof data === "string" ? JSON.parse(data) : data;
        if (parsed.symbols)
            this.symbols = parsed.symbols;
        if (parsed.occurrences)
            this.occurrences = parsed.occurrences;
    }
    /**
     * Merges another set of symbols into the current index.
     * Useful for incremental workspace indexing.
     */
    merge(other) {
        // Basic deduplication based on name, type, scope and path
        for (const s of other.symbols) {
            if (!this.symbols.some((existing) => existing.name === s.name &&
                existing.type === s.type &&
                existing.scope === s.scope &&
                existing.path === s.path)) {
                this.symbols.push(s);
            }
        }
        // Occurrences are generally unique by location
        for (const o of other.occurrences) {
            if (!this.occurrences.some((existing) => existing.name === o.name &&
                existing.line === o.line &&
                existing.column === o.column &&
                existing.path === o.path)) {
                this.occurrences.push(o);
            }
        }
    }
    /**
     * Removes all symbols associated with a specific path.
     * Used for incremental updates when a file is modified.
     */
    clearPath(path) {
        this.symbols = this.symbols.filter((s) => s.path !== path);
        this.occurrences = this.occurrences.filter((o) => o.path !== path);
    }
}
