"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.AstDiffer = void 0;
const typescript_1 = __importDefault(require("typescript"));
function visitAll(node, results) {
    results.push(node);
    typescript_1.default.forEachChild(node, (child) => visitAll(child, results));
}
function getModifierFlags(node) {
    const flags = new Set();
    const mods = typescript_1.default.canHaveModifiers(node) ? typescript_1.default.getModifiers(node) : undefined;
    if (mods) {
        for (const m of mods) {
            if (m.kind === typescript_1.default.SyntaxKind.AsyncKeyword)
                flags.add('async');
            if (m.kind === typescript_1.default.SyntaxKind.ExportKeyword)
                flags.add('export');
            if (m.kind === typescript_1.default.SyntaxKind.DefaultKeyword)
                flags.add('default');
            if (m.kind === typescript_1.default.SyntaxKind.DeclareKeyword)
                flags.add('declare');
            if (m.kind === typescript_1.default.SyntaxKind.PublicKeyword)
                flags.add('public');
            if (m.kind === typescript_1.default.SyntaxKind.PrivateKeyword)
                flags.add('private');
            if (m.kind === typescript_1.default.SyntaxKind.ProtectedKeyword)
                flags.add('protected');
            if (m.kind === typescript_1.default.SyntaxKind.ReadonlyKeyword)
                flags.add('readonly');
            if (m.kind === typescript_1.default.SyntaxKind.StaticKeyword)
                flags.add('static');
        }
    }
    return flags;
}
function getTypeAnnotation(node) {
    if (typescript_1.default.isVariableDeclaration(node) && node.type) {
        return node.type.getText();
    }
    if (typescript_1.default.isFunctionDeclaration(node) && node.type) {
        return `returns ${node.type.getText()}`;
    }
    if (typescript_1.default.isMethodDeclaration(node) && node.type) {
        return `returns ${node.type.getText()}`;
    }
    if (typescript_1.default.isPropertyDeclaration(node) && node.type) {
        return node.type.getText();
    }
    return null;
}
function getParameterTypes(node) {
    if (typescript_1.default.isFunctionDeclaration(node) || typescript_1.default.isArrowFunction(node) || typescript_1.default.isMethodDeclaration(node)) {
        const params = node.parameters.map((p) => {
            const name = p.name.getText();
            const type = p.type ? p.type.getText() : 'any';
            return `${name}: ${type}`;
        });
        return params.join(', ');
    }
    return null;
}
function getKeywordKind(node) {
    if (typescript_1.default.isVariableDeclaration(node)) {
        const parent = node.parent;
        if (parent && typescript_1.default.isVariableDeclarationList(parent)) {
            return parent.getFirstToken()?.kind ?? null;
        }
    }
    return null;
}
function getDeclaredName(node) {
    if (typescript_1.default.isFunctionDeclaration(node) && node.name)
        return node.name.text;
    if (typescript_1.default.isVariableDeclaration(node) && typescript_1.default.isIdentifier(node.name))
        return node.name.text;
    if (typescript_1.default.isClassDeclaration(node) && node.name)
        return node.name.text;
    if (typescript_1.default.isInterfaceDeclaration(node) && node.name)
        return node.name.text;
    if (typescript_1.default.isTypeAliasDeclaration(node) && node.name)
        return node.name.text;
    if (typescript_1.default.isEnumDeclaration(node) && node.name)
        return node.name.text;
    if (typescript_1.default.isModuleDeclaration(node) && node.name && typescript_1.default.isIdentifier(node.name))
        return node.name.text;
    return null;
}
class AstDiffer {
    extract(originalCode, editedCode) {
        const results = [];
        if (!originalCode.trim() || !editedCode.trim())
            return results;
        const originalAst = typescript_1.default.createSourceFile('original.ts', originalCode, typescript_1.default.ScriptTarget.Latest, true, typescript_1.default.ScriptKind.TS);
        const editedAst = typescript_1.default.createSourceFile('edited.ts', editedCode, typescript_1.default.ScriptTarget.Latest, true, typescript_1.default.ScriptKind.TS);
        const originalNodes = [];
        const editedNodes = [];
        visitAll(originalAst, originalNodes);
        visitAll(editedAst, editedNodes);
        results.push(...this.detectKeywordChange(originalNodes, editedNodes));
        results.push(...this.detectTypeAnnotationChange(originalNodes, editedNodes));
        results.push(...this.detectModifierChange(originalNodes, editedNodes));
        results.push(...this.detectImportChange(originalAst, editedAst));
        results.push(...this.detectStructuralChange(originalCode, editedCode));
        results.push(...this.detectFunctionStyleChange(originalAst, editedAst));
        results.push(...this.detectTemplateLiteralChange(originalCode, editedCode));
        results.push(...this.detectNullSafetyChange(originalCode, editedCode));
        results.push(...this.detectDestructuringChange(originalCode, editedCode));
        results.push(...this.detectAsyncAwaitChange(originalCode, editedCode));
        return results;
    }
    detectKeywordChange(originalNodes, editedNodes) {
        const results = [];
        const origKeywords = new Map();
        const editKeywords = new Map();
        for (const node of originalNodes) {
            const name = getDeclaredName(node);
            const kind = getKeywordKind(node);
            if (name && kind !== null)
                origKeywords.set(name, kind);
        }
        for (const node of editedNodes) {
            const name = getDeclaredName(node);
            const kind = getKeywordKind(node);
            if (name && kind !== null)
                editKeywords.set(name, kind);
        }
        for (const [name, editKind] of editKeywords) {
            const origKind = origKeywords.get(name);
            if (origKind && origKind !== editKind) {
                const fromKeyword = keywordKindToString(origKind);
                const toKeyword = keywordKindToString(editKind);
                results.push({
                    category: 'variable_declaration',
                    description: `Changed variable declaration from ${fromKeyword} to ${toKeyword}`,
                    from: fromKeyword,
                    to: toKeyword,
                    confidenceDelta: 0.1,
                    conditions: [],
                });
            }
        }
        return results;
    }
    detectTypeAnnotationChange(originalNodes, editedNodes) {
        const results = [];
        const origTypes = new Map();
        const editTypes = new Map();
        const origParamTypes = new Map();
        const editParamTypes = new Map();
        for (const node of originalNodes) {
            const name = getDeclaredName(node);
            if (name)
                origTypes.set(name, getTypeAnnotation(node));
            if (name && (typescript_1.default.isFunctionDeclaration(node) || typescript_1.default.isMethodDeclaration(node))) {
                for (const p of node.parameters) {
                    origParamTypes.set(`${name}::${p.name.getText()}`, p.type ? p.type.getText() : 'any');
                }
            }
        }
        for (const node of editedNodes) {
            const name = getDeclaredName(node);
            if (name)
                editTypes.set(name, getTypeAnnotation(node));
            if (name && (typescript_1.default.isFunctionDeclaration(node) || typescript_1.default.isMethodDeclaration(node))) {
                for (const p of node.parameters) {
                    editParamTypes.set(`${name}::${p.name.getText()}`, p.type ? p.type.getText() : 'any');
                }
            }
        }
        for (const [name, editTypeVal] of editTypes) {
            const origTypeVal = origTypes.get(name);
            if (origTypeVal == null && editTypeVal != null) {
                results.push({
                    category: 'type_annotation',
                    description: `Added type annotation: ${editTypeVal}`,
                    from: 'no_type',
                    to: editTypeVal,
                    confidenceDelta: 0.12,
                    conditions: [],
                });
            }
            if (origTypeVal != null && editTypeVal != null && origTypeVal !== editTypeVal) {
                const origType = origTypeVal;
                const editType = editTypeVal;
                results.push({
                    category: 'type_annotation',
                    description: `Changed type annotation from ${origType} to ${editType}`,
                    from: origType,
                    to: editType,
                    confidenceDelta: 0.08,
                    conditions: [],
                });
            }
        }
        for (const [key, editType] of editParamTypes) {
            const origType = origParamTypes.get(key);
            if (origType && origType !== editType) {
                results.push({
                    category: 'type_annotation',
                    description: `Changed parameter type ${key} from ${origType} to ${editType}`,
                    from: origType,
                    to: editType,
                    confidenceDelta: 0.1,
                    conditions: [],
                });
            }
        }
        return results;
    }
    detectModifierChange(originalNodes, editedNodes) {
        const results = [];
        const origMods = new Map();
        const editMods = new Map();
        for (const node of originalNodes) {
            const name = getDeclaredName(node);
            if (name && (typescript_1.default.isFunctionDeclaration(node) || typescript_1.default.isMethodDeclaration(node) || typescript_1.default.isClassDeclaration(node))) {
                origMods.set(name, getModifierFlags(node));
            }
        }
        for (const node of editedNodes) {
            const name = getDeclaredName(node);
            if (name && (typescript_1.default.isFunctionDeclaration(node) || typescript_1.default.isMethodDeclaration(node) || typescript_1.default.isClassDeclaration(node))) {
                editMods.set(name, getModifierFlags(node));
            }
        }
        for (const [name, editFlags] of editMods) {
            const origFlags = origMods.get(name);
            if (origFlags) {
                const added = [...editFlags].filter((f) => !origFlags.has(f));
                const removed = [...origFlags].filter((f) => !editFlags.has(f));
                if (added.length > 0) {
                    results.push({
                        category: 'modifier_change',
                        description: `Added modifier(s): ${added.join(', ')}`,
                        from: 'without_' + added.join('_'),
                        to: 'with_' + added.join('_'),
                        confidenceDelta: 0.08,
                        conditions: [],
                    });
                }
                if (removed.length > 0) {
                    results.push({
                        category: 'modifier_change',
                        description: `Removed modifier(s): ${removed.join(', ')}`,
                        from: 'with_' + removed.join('_'),
                        to: 'without_' + removed.join('_'),
                        confidenceDelta: 0.08,
                        conditions: [],
                    });
                }
            }
        }
        return results;
    }
    detectImportChange(originalAst, editedAst) {
        const results = [];
        const origImports = getImportTexts(originalAst);
        const editImports = getImportTexts(editedAst);
        const added = editImports.filter((i) => !origImports.includes(i));
        const removed = origImports.filter((i) => !editImports.includes(i));
        if (added.length > 0) {
            results.push({
                category: 'import_change',
                description: `Added ${added.length} import(s)`,
                from: `${origImports.length} imports`,
                to: `${editImports.length} imports`,
                confidenceDelta: 0.05,
                conditions: [],
            });
        }
        if (removed.length > 0) {
            results.push({
                category: 'import_change',
                description: `Removed ${removed.length} import(s)`,
                from: `${origImports.length} imports`,
                to: `${editImports.length} imports`,
                confidenceDelta: 0.05,
                conditions: [],
            });
        }
        if (origImports.length > 0 && editImports.length > 0) {
            const origOrder = origImports.join('\n');
            const editOrder = editImports.join('\n');
            if (origOrder !== editOrder && added.length === 0 && removed.length === 0) {
                results.push({
                    category: 'import_organization',
                    description: 'Reordered import statements',
                    from: 'original_order',
                    to: 'reordered',
                    confidenceDelta: 0.04,
                    conditions: [],
                });
            }
        }
        return results;
    }
    detectStructuralChange(originalCode, editedCode) {
        const results = [];
        const origHasTry = /try\s*\{/.test(originalCode);
        const editHasTry = /try\s*\{/.test(editedCode);
        if (!origHasTry && editHasTry) {
            results.push({
                category: 'error_handling',
                description: 'Wrapped code in try/catch',
                from: 'no_error_handling',
                to: 'try_catch',
                confidenceDelta: 0.1,
                conditions: [],
            });
        }
        if (origHasTry && !editHasTry) {
            results.push({
                category: 'error_handling',
                description: 'Removed try/catch wrapping',
                from: 'try_catch',
                to: 'no_error_handling',
                confidenceDelta: 0.1,
                conditions: [],
            });
        }
        return results;
    }
    detectFunctionStyleChange(originalAst, editedAst) {
        const results = [];
        const origFuncNodes = [];
        const editFuncNodes = [];
        const origArrowNodes = [];
        const editArrowNodes = [];
        const allOrigNodes = [];
        const allEditNodes = [];
        visitAll(originalAst, allOrigNodes);
        visitAll(editedAst, allEditNodes);
        for (const node of allOrigNodes) {
            if (typescript_1.default.isFunctionDeclaration(node) || typescript_1.default.isFunctionExpression(node)) {
                const nm = getDeclaredName(node);
                if (nm)
                    origFuncNodes.push(nm);
            }
            if (typescript_1.default.isArrowFunction(node)) {
                const parent = node.parent;
                const nm = parent && (typescript_1.default.isVariableDeclaration(parent) || typescript_1.default.isPropertyAssignment(parent))
                    ? getDeclaredName(parent) : null;
                if (nm)
                    origArrowNodes.push(nm);
            }
        }
        for (const node of allEditNodes) {
            if (typescript_1.default.isFunctionDeclaration(node) || typescript_1.default.isFunctionExpression(node)) {
                const nm = getDeclaredName(node);
                if (nm)
                    editFuncNodes.push(nm);
            }
            if (typescript_1.default.isArrowFunction(node)) {
                const parent = node.parent;
                const nm = parent && (typescript_1.default.isVariableDeclaration(parent) || typescript_1.default.isPropertyAssignment(parent))
                    ? getDeclaredName(parent) : null;
                if (nm)
                    editArrowNodes.push(nm);
            }
        }
        const changedToArrow = origFuncNodes.filter((n) => editArrowNodes.includes(n));
        for (const name of changedToArrow) {
            results.push({
                category: 'function_style',
                description: `Changed function to arrow function: ${name}`,
                from: 'function_declaration',
                to: 'arrow_function',
                confidenceDelta: 0.15,
                conditions: [],
            });
        }
        const changedToFunc = origArrowNodes.filter((n) => editFuncNodes.includes(n));
        for (const name of changedToFunc) {
            results.push({
                category: 'function_style',
                description: `Changed arrow function to function: ${name}`,
                from: 'arrow_function',
                to: 'function_declaration',
                confidenceDelta: 0.15,
                conditions: [],
            });
        }
        return results;
    }
    detectTemplateLiteralChange(originalCode, editedCode) {
        const results = [];
        const concatPattern = /['"`]\s*\+/;
        const hasOrigConcat = concatPattern.test(originalCode);
        const hasEditConcat = concatPattern.test(editedCode);
        const origHasTemplate = /\$\{/.test(originalCode);
        const editHasTemplate = /\$\{/.test(editedCode);
        if (hasOrigConcat && !hasEditConcat && editHasTemplate) {
            results.push({
                category: 'string_style',
                description: 'Converted string concatenation to template literal',
                from: 'string_concatenation',
                to: 'template_literal',
                confidenceDelta: 0.1,
                conditions: [],
            });
        }
        if (hasOrigConcat && !hasEditConcat && !editHasTemplate) {
            results.push({
                category: 'string_style',
                description: 'Removed string concatenation',
                from: 'string_concatenation',
                to: 'string_literal',
                confidenceDelta: 0.08,
                conditions: [],
            });
        }
        return results;
    }
    detectDestructuringChange(originalCode, editedCode) {
        const results = [];
        const origDestructuring = (originalCode.match(/(?:const|let|var)\s*\{[^}]+\}\s*=/g) || []).length;
        const editDestructuring = (editedCode.match(/(?:const|let|var)\s*\{[^}]+\}\s*=/g) || []).length;
        const origDotAccess = (originalCode.match(/\.\w+\s*[=;]/g) || []).length;
        const editDotAccess = (editedCode.match(/\.\w+\s*[=;]/g) || []).length;
        const res = editDestructuring - origDestructuring;
        if (res > 0) {
            results.push({
                category: 'destructuring',
                description: `Added ${res} destructuring assignment(s)`,
                from: 'dot_access',
                to: 'destructuring',
                confidenceDelta: 0.12,
                conditions: [],
            });
        }
        if (origDotAccess > editDotAccess && res > 0) {
            results.push({
                category: 'destructuring',
                description: 'Replaced property access with destructuring',
                from: 'dot_access',
                to: 'destructuring',
                confidenceDelta: 0.15,
                conditions: [],
            });
        }
        return results;
    }
    detectAsyncAwaitChange(originalCode, editedCode) {
        const results = [];
        const origThenCatch = (originalCode.match(/\.then\s*\(/g) || []).length;
        const editThenCatch = (editedCode.match(/\.then\s*\(/g) || []).length;
        const origTryCatch = (originalCode.match(/try\s*\{/g) || []).length;
        const editTryCatch = (editedCode.match(/try\s*\{/g) || []).length;
        const origAsync = (originalCode.match(/\basync\b/g) || []).length;
        const editAsync = (editedCode.match(/\basync\b/g) || []).length;
        const origAwait = (originalCode.match(/\bawait\b/g) || []).length;
        const editAwait = (editedCode.match(/\bawait\b/g) || []).length;
        if (editAsync > origAsync && editAwait > origAwait && editThenCatch < origThenCatch) {
            results.push({
                category: 'async_await',
                description: 'Converted promise chain to async/await',
                from: 'promise_chain',
                to: 'async_await',
                confidenceDelta: 0.15,
                conditions: [],
            });
        }
        if (editAsync > origAsync) {
            results.push({
                category: 'async_await',
                description: `Added async keyword`,
                from: 'synchronous',
                to: 'async',
                confidenceDelta: 0.08,
                conditions: [],
            });
        }
        return results;
    }
    detectNullSafetyChange(originalCode, editedCode) {
        const results = [];
        const origOptionalChains = (originalCode.match(/\?\.(?![?0-9])/g) || []).length;
        const editOptionalChains = (editedCode.match(/\?\.(?![?0-9])/g) || []).length;
        const origNullCoalesce = (originalCode.match(/\?\?(?!\?)/g) || []).length;
        const editNullCoalesce = (editedCode.match(/\?\?(?!\?)/g) || []).length;
        if (editOptionalChains > origOptionalChains) {
            results.push({
                category: 'null_safety',
                description: `Added ${editOptionalChains - origOptionalChains} optional chaining expression(s)`,
                from: 'and_checks',
                to: 'optional_chaining',
                confidenceDelta: 0.12,
                conditions: [],
            });
        }
        if (editNullCoalesce > origNullCoalesce) {
            results.push({
                category: 'null_safety',
                description: `Added ${editNullCoalesce - origNullCoalesce} nullish coalescing expression(s)`,
                from: 'or_defaults',
                to: 'nullish_coalescing',
                confidenceDelta: 0.1,
                conditions: [],
            });
        }
        return results;
    }
}
exports.AstDiffer = AstDiffer;
function keywordKindToString(kind) {
    switch (kind) {
        case typescript_1.default.SyntaxKind.LetKeyword: return 'let';
        case typescript_1.default.SyntaxKind.ConstKeyword: return 'const';
        case typescript_1.default.SyntaxKind.VarKeyword: return 'var';
        default: return typescript_1.default.SyntaxKind[kind];
    }
}
function getImportTexts(sourceFile) {
    const imports = [];
    for (const stmt of sourceFile.statements) {
        if (typescript_1.default.isImportDeclaration(stmt)) {
            const moduleSpecifier = stmt.moduleSpecifier.getText();
            const namedImports = stmt.importClause?.namedBindings
                ? stmt.importClause.namedBindings.getText() : '';
            imports.push(`${namedImports} from ${moduleSpecifier}`);
        }
    }
    return imports;
}
//# sourceMappingURL=ast-differ.js.map