// engine/code-transforms/typescript/add-types.ts — Add types transform (AC-03).
// Infers types from initializers and adds type annotations.
import { getTypeScript, parseSource, getLineNumber, createChange } from './ast-utils.js';
// ---------------------------------------------------------------------------
// Type inference from initializer
// ---------------------------------------------------------------------------
function inferTypeFromInit(ts, init) {
    if (ts.isStringLiteral(init) || ts.isNoSubstitutionTemplateLiteral(init)) {
        return 'string';
    }
    if (ts.isNumericLiteral(init)) {
        return 'number';
    }
    if (init.kind === ts.SyntaxKind.TrueKeyword || init.kind === ts.SyntaxKind.FalseKeyword) {
        return 'boolean';
    }
    if (init.kind === ts.SyntaxKind.NullKeyword) {
        return 'null';
    }
    /* v8 ignore start -- UndefinedKeyword is rarely emitted; TS parses `undefined` as Identifier */
    if (init.kind === ts.SyntaxKind.UndefinedKeyword) {
        return 'undefined';
    }
    /* v8 ignore stop */
    if (ts.isArrayLiteralExpression(init)) {
        return inferArrayType(ts, init);
    }
    if (ts.isObjectLiteralExpression(init)) {
        return 'Record<string, unknown>';
    }
    return undefined;
}
function inferArrayType(ts, arr) {
    if (arr.elements.length === 0) {
        return 'unknown[]';
    }
    const first = arr.elements[0];
    /* v8 ignore start -- defensive: elements[0] always exists when length > 0 */
    if (!first) {
        return 'unknown[]';
    }
    /* v8 ignore stop */
    const elemType = inferTypeFromInit(ts, first);
    return elemType ? `${elemType}[]` : 'unknown[]';
}
// ---------------------------------------------------------------------------
// Collect replacements from variable declarations
// ---------------------------------------------------------------------------
function collectVariableTypes(ts, sourceFile, replacements) {
    function visit(node) {
        if (ts.isVariableDeclaration(node) && !node.type && node.initializer) {
            const inferred = inferTypeFromInit(ts, node.initializer);
            if (inferred) {
                const nameEnd = node.name.getEnd();
                replacements.push({
                    position: nameEnd,
                    length: 0,
                    newText: `: ${inferred}`,
                    line: getLineNumber(sourceFile, node.getStart(sourceFile)),
                    description: `Added type annotation ': ${inferred}' to variable '${node.name.getText(sourceFile)}'`,
                });
            }
        }
        ts.forEachChild(node, visit);
    }
    visit(sourceFile);
}
// ---------------------------------------------------------------------------
// Collect replacements from function return types
// ---------------------------------------------------------------------------
function inferReturnType(ts, body) {
    let foundType;
    for (const stmt of body.statements) {
        if (ts.isReturnStatement(stmt) && stmt.expression) {
            const t = inferTypeFromInit(ts, stmt.expression);
            if (t && !foundType) {
                foundType = t;
            }
            else if (t && foundType && t !== foundType) {
                return undefined; // mixed return types
            }
        }
    }
    return foundType;
}
function findClosingParen(source, startPos) {
    let depth = 0;
    for (let i = startPos; i < source.length; i++) {
        const ch = source[i];
        if (ch === '(') {
            depth++;
        }
        if (ch === ')') {
            depth--;
            if (depth === 0) {
                return i;
            }
        }
    }
    /* v8 ignore next 1 */
    return -1;
}
function collectFunctionReturnTypes(ts, sourceFile, source, replacements) {
    function visit(node) {
        if ((ts.isFunctionDeclaration(node) || ts.isMethodDeclaration(node)) &&
            !node.type &&
            node.body) {
            const retType = inferReturnType(ts, node.body);
            if (retType) {
                const parenPos = findClosingParen(source, node.getStart(sourceFile));
                /* v8 ignore start -- defensive: findClosingParen always succeeds for valid function decls */
                if (parenPos > 0) {
                    /* v8 ignore stop */
                    const insertPos = parenPos + 1;
                    /* v8 ignore start -- FunctionDeclaration without name is extremely rare */
                    const name = node.name ? node.name.getText(sourceFile) : '<anonymous>';
                    /* v8 ignore stop */
                    replacements.push({
                        position: insertPos,
                        length: 0,
                        newText: `: ${retType}`,
                        line: getLineNumber(sourceFile, node.getStart(sourceFile)),
                        description: `Added return type ': ${retType}' to function '${name}'`,
                    });
                }
            }
        }
        ts.forEachChild(node, visit);
    }
    visit(sourceFile);
}
// ---------------------------------------------------------------------------
// Collect replacements from function parameters
// ---------------------------------------------------------------------------
function collectParameterTypes(ts, sourceFile, replacements) {
    function visit(node) {
        if (ts.isFunctionDeclaration(node) || ts.isMethodDeclaration(node)) {
            for (const param of node.parameters) {
                if (!param.type && param.initializer) {
                    const inferred = inferTypeFromInit(ts, param.initializer);
                    if (inferred) {
                        const nameEnd = param.name.getEnd();
                        replacements.push({
                            position: nameEnd,
                            length: 0,
                            newText: `: ${inferred}`,
                            line: getLineNumber(sourceFile, param.getStart(sourceFile)),
                            description: `Added type ': ${inferred}' to parameter '${param.name.getText(sourceFile)}'`,
                        });
                    }
                }
            }
        }
        ts.forEachChild(node, visit);
    }
    visit(sourceFile);
}
// ---------------------------------------------------------------------------
// Main export
// ---------------------------------------------------------------------------
export function addTypes(source, _options, fileName) {
    const ts = getTypeScript();
    const sourceFile = parseSource(source, fileName);
    const replacements = [];
    collectVariableTypes(ts, sourceFile, replacements);
    collectFunctionReturnTypes(ts, sourceFile, source, replacements);
    collectParameterTypes(ts, sourceFile, replacements);
    if (replacements.length === 0) {
        return { transformed: source, changes: [] };
    }
    // Apply in reverse position order
    replacements.sort((a, b) => b.position - a.position);
    let result = source;
    const changes = [];
    for (const rep of replacements) {
        result = result.slice(0, rep.position) + rep.newText + result.slice(rep.position + rep.length);
        changes.push(createChange(rep.line, rep.description));
    }
    changes.sort((a, b) => a.line - b.line);
    return { transformed: result, changes };
}
//# sourceMappingURL=add-types.js.map