/**
 * TypeScript/JavaScript AST Transforms
 *
 * Uses ts-morph for AST manipulation to fix security vulnerabilities.
 *
 * @module autofix/ast/typescript
 */
import { Project, SyntaxKind, Node } from "ts-morph";
import { readFile, writeFile } from "fs/promises";
const project = new Project({
    useInMemoryFileSystem: false,
    skipFileDependencyResolution: true,
});
export async function transformSQLInjection(context) {
    const { filePath, finding } = context;
    const changes = [];
    try {
        const originalCode = await readFile(filePath, "utf-8");
        const sourceFile = project.createSourceFile(`temp_${Date.now()}.ts`, originalCode, { overwrite: true });
        const targetLine = finding.line;
        sourceFile.forEachDescendant((node) => {
            const startLine = node.getStartLineNumber();
            if (Math.abs(startLine - targetLine) > 3)
                return;
            if (Node.isCallExpression(node)) {
                const expression = node.getExpression();
                const expressionText = expression.getText();
                if (expressionText.includes("query") ||
                    expressionText.includes("execute") ||
                    expressionText.includes("raw")) {
                    const args = node.getArguments();
                    if (args.length > 0) {
                        const firstArg = args[0];
                        if (Node.isTemplateExpression(firstArg)) {
                            const result = transformTemplateToParameterized(node, firstArg);
                            if (result) {
                                changes.push({
                                    type: "replace",
                                    startLine: node.getStartLineNumber(),
                                    endLine: node.getEndLineNumber(),
                                    originalText: node.getText(),
                                    newText: result.newCode,
                                    description: "Convert template literal SQL to parameterized query",
                                });
                                node.replaceWithText(result.newCode);
                            }
                        }
                        else if (Node.isBinaryExpression(firstArg)) {
                            const result = transformConcatToParameterized(node, firstArg);
                            if (result) {
                                changes.push({
                                    type: "replace",
                                    startLine: node.getStartLineNumber(),
                                    endLine: node.getEndLineNumber(),
                                    originalText: node.getText(),
                                    newText: result.newCode,
                                    description: "Convert string concatenation SQL to parameterized query",
                                });
                                node.replaceWithText(result.newCode);
                            }
                        }
                    }
                }
            }
        });
        const transformedCode = sourceFile.getFullText();
        sourceFile.delete();
        if (changes.length > 0) {
            await writeFile(filePath, transformedCode, "utf-8");
        }
        return {
            success: changes.length > 0,
            filePath,
            originalCode,
            transformedCode: changes.length > 0 ? transformedCode : originalCode,
            changesApplied: changes,
            error: changes.length === 0 ? "No SQL injection patterns found to fix" : undefined,
        };
    }
    catch (error) {
        return {
            success: false,
            filePath,
            originalCode: "",
            transformedCode: "",
            changesApplied: [],
            error: error instanceof Error ? error.message : "Unknown error",
        };
    }
}
function transformTemplateToParameterized(callExpr, templateExpr) {
    const params = [];
    let paramIndex = 1;
    let sqlParts = [];
    const head = templateExpr.getHead().getLiteralText();
    sqlParts.push(head);
    for (const span of templateExpr.getTemplateSpans()) {
        const expr = span.getExpression();
        params.push(expr.getText());
        sqlParts.push(`$${paramIndex++}`);
        sqlParts.push(span.getLiteral().getLiteralText());
    }
    const sql = sqlParts.join("");
    const expression = callExpr.getExpression().getText();
    const newCode = `${expression}(\`${sql}\`, [${params.join(", ")}])`;
    return { newCode, params };
}
function transformConcatToParameterized(callExpr, binaryExpr) {
    const params = [];
    const sqlParts = [];
    let paramIndex = 1;
    function extractParts(node) {
        if (Node.isBinaryExpression(node)) {
            extractParts(node.getLeft());
            extractParts(node.getRight());
        }
        else if (Node.isStringLiteral(node)) {
            sqlParts.push(node.getLiteralText());
        }
        else {
            params.push(node.getText());
            sqlParts.push(`$${paramIndex++}`);
        }
    }
    extractParts(binaryExpr);
    const sql = sqlParts.join("");
    const expression = callExpr.getExpression().getText();
    const newCode = `${expression}(\`${sql}\`, [${params.join(", ")}])`;
    return { newCode, params };
}
export async function transformXSS(context) {
    const { filePath, finding } = context;
    const changes = [];
    try {
        const originalCode = await readFile(filePath, "utf-8");
        const sourceFile = project.createSourceFile(`temp_${Date.now()}.ts`, originalCode, { overwrite: true });
        const targetLine = finding.line;
        sourceFile.forEachDescendant((node) => {
            const startLine = node.getStartLineNumber();
            if (Math.abs(startLine - targetLine) > 3)
                return;
            if (Node.isBinaryExpression(node)) {
                const left = node.getLeft();
                if (Node.isPropertyAccessExpression(left)) {
                    const propName = left.getName();
                    if (propName === "innerHTML" || propName === "outerHTML") {
                        const parent = node.getParent();
                        if (parent) {
                            const leftText = left.getExpression().getText();
                            const rightText = node.getRight().getText();
                            const newCode = `${leftText}.textContent = ${rightText}`;
                            changes.push({
                                type: "replace",
                                startLine: node.getStartLineNumber(),
                                endLine: node.getEndLineNumber(),
                                originalText: node.getText(),
                                newText: newCode,
                                description: `Replace ${propName} with textContent to prevent XSS`,
                            });
                            node.replaceWithText(newCode);
                        }
                    }
                }
            }
            if (Node.isCallExpression(node)) {
                const expression = node.getExpression();
                if (Node.isPropertyAccessExpression(expression)) {
                    const methodName = expression.getName();
                    if (methodName === "insertAdjacentHTML") {
                        const args = node.getArguments();
                        if (args.length >= 2) {
                            const objText = expression.getExpression().getText();
                            const position = args[0].getText();
                            const content = args[1].getText();
                            const newCode = position.includes("beforeend") || position.includes("afterbegin")
                                ? `${objText}.append(document.createTextNode(${content}))`
                                : `${objText}.insertAdjacentText(${position}, ${content})`;
                            changes.push({
                                type: "replace",
                                startLine: node.getStartLineNumber(),
                                endLine: node.getEndLineNumber(),
                                originalText: node.getText(),
                                newText: newCode,
                                description: "Replace insertAdjacentHTML with safe text insertion",
                            });
                            node.replaceWithText(newCode);
                        }
                    }
                    if (methodName === "write" || methodName === "writeln") {
                        const objText = expression.getExpression().getText();
                        if (objText === "document") {
                            changes.push({
                                type: "replace",
                                startLine: node.getStartLineNumber(),
                                endLine: node.getEndLineNumber(),
                                originalText: node.getText(),
                                newText: `/* UNSAFE: ${node.getText()} - Use DOM methods instead */`,
                                description: "Comment out document.write (requires manual fix)",
                            });
                        }
                    }
                }
            }
        });
        const transformedCode = sourceFile.getFullText();
        sourceFile.delete();
        if (changes.length > 0) {
            await writeFile(filePath, transformedCode, "utf-8");
        }
        return {
            success: changes.length > 0,
            filePath,
            originalCode,
            transformedCode: changes.length > 0 ? transformedCode : originalCode,
            changesApplied: changes,
            error: changes.length === 0 ? "No XSS patterns found to fix" : undefined,
        };
    }
    catch (error) {
        return {
            success: false,
            filePath,
            originalCode: "",
            transformedCode: "",
            changesApplied: [],
            error: error instanceof Error ? error.message : "Unknown error",
        };
    }
}
export async function transformHardcodedSecrets(context) {
    const { filePath, finding } = context;
    const changes = [];
    try {
        const originalCode = await readFile(filePath, "utf-8");
        const sourceFile = project.createSourceFile(`temp_${Date.now()}.ts`, originalCode, { overwrite: true });
        const targetLine = finding.line;
        const secretPatterns = [
            /api[_-]?key/i,
            /secret[_-]?key/i,
            /password/i,
            /auth[_-]?token/i,
            /access[_-]?token/i,
            /private[_-]?key/i,
            /bearer/i,
            /credential/i,
        ];
        sourceFile.forEachDescendant((node) => {
            const startLine = node.getStartLineNumber();
            if (Math.abs(startLine - targetLine) > 3)
                return;
            if (Node.isVariableDeclaration(node)) {
                const name = node.getName();
                const initializer = node.getInitializer();
                if (initializer && Node.isStringLiteral(initializer)) {
                    const value = initializer.getLiteralText();
                    const isSecretName = secretPatterns.some((p) => p.test(name));
                    const looksLikeSecret = value.length > 10 && /[a-zA-Z0-9+/=_-]{10,}/.test(value);
                    if (isSecretName || looksLikeSecret) {
                        const envVarName = toEnvVarName(name);
                        const newCode = `process.env.${envVarName}`;
                        changes.push({
                            type: "replace",
                            startLine: initializer.getStartLineNumber(),
                            endLine: initializer.getEndLineNumber(),
                            originalText: initializer.getText(),
                            newText: newCode,
                            description: `Replace hardcoded secret with environment variable ${envVarName}`,
                        });
                        initializer.replaceWithText(newCode);
                    }
                }
            }
            if (Node.isPropertyAssignment(node)) {
                const name = node.getName();
                const initializer = node.getInitializer();
                if (initializer && Node.isStringLiteral(initializer)) {
                    const value = initializer.getLiteralText();
                    const isSecretName = secretPatterns.some((p) => p.test(name));
                    const looksLikeSecret = value.length > 10 && /[a-zA-Z0-9+/=_-]{10,}/.test(value);
                    if (isSecretName || looksLikeSecret) {
                        const envVarName = toEnvVarName(name);
                        const newCode = `process.env.${envVarName}`;
                        changes.push({
                            type: "replace",
                            startLine: initializer.getStartLineNumber(),
                            endLine: initializer.getEndLineNumber(),
                            originalText: initializer.getText(),
                            newText: newCode,
                            description: `Replace hardcoded secret with environment variable ${envVarName}`,
                        });
                        initializer.replaceWithText(newCode);
                    }
                }
            }
        });
        const transformedCode = sourceFile.getFullText();
        sourceFile.delete();
        if (changes.length > 0) {
            await writeFile(filePath, transformedCode, "utf-8");
        }
        return {
            success: changes.length > 0,
            filePath,
            originalCode,
            transformedCode: changes.length > 0 ? transformedCode : originalCode,
            changesApplied: changes,
            error: changes.length === 0 ? "No hardcoded secrets found to fix" : undefined,
        };
    }
    catch (error) {
        return {
            success: false,
            filePath,
            originalCode: "",
            transformedCode: "",
            changesApplied: [],
            error: error instanceof Error ? error.message : "Unknown error",
        };
    }
}
function toEnvVarName(name) {
    return name
        .replace(/([a-z])([A-Z])/g, "$1_$2")
        .replace(/[-\s]/g, "_")
        .toUpperCase();
}
export async function transformIDOR(context) {
    const { filePath, finding } = context;
    const changes = [];
    try {
        const originalCode = await readFile(filePath, "utf-8");
        const sourceFile = project.createSourceFile(`temp_${Date.now()}.ts`, originalCode, { overwrite: true });
        const targetLine = finding.line;
        sourceFile.forEachDescendant((node) => {
            const startLine = node.getStartLineNumber();
            if (Math.abs(startLine - targetLine) > 5)
                return;
            if (Node.isCallExpression(node)) {
                const expression = node.getExpression();
                const expressionText = expression.getText();
                const isDbAccess = expressionText.includes("findById") ||
                    expressionText.includes("findOne") ||
                    expressionText.includes("findUnique") ||
                    expressionText.includes("where");
                if (isDbAccess) {
                    const args = node.getArguments();
                    if (args.length > 0) {
                        const idArg = args[0].getText();
                        if (idArg.includes("req.params") ||
                            idArg.includes("req.query") ||
                            idArg.includes("req.body")) {
                            const ownershipCheck = `// IDOR Protection: Verify ownership before accessing resource
  const resource = await ${node.getText()};
  if (!resource || resource.userId !== req.user?.id) {
    throw new Error("Access denied: Resource not found or not owned by user");
  }`;
                            const statement = node.getFirstAncestorByKind(SyntaxKind.VariableStatement);
                            if (statement) {
                                changes.push({
                                    type: "replace",
                                    startLine: statement.getStartLineNumber(),
                                    endLine: statement.getEndLineNumber(),
                                    originalText: statement.getText(),
                                    newText: ownershipCheck.trim(),
                                    description: "Add ownership verification to prevent IDOR",
                                });
                                statement.replaceWithText(ownershipCheck.trim());
                            }
                        }
                    }
                }
            }
        });
        const transformedCode = sourceFile.getFullText();
        sourceFile.delete();
        if (changes.length > 0) {
            await writeFile(filePath, transformedCode, "utf-8");
        }
        return {
            success: changes.length > 0,
            filePath,
            originalCode,
            transformedCode: changes.length > 0 ? transformedCode : originalCode,
            changesApplied: changes,
            error: changes.length === 0 ? "No IDOR patterns found to fix" : undefined,
        };
    }
    catch (error) {
        return {
            success: false,
            filePath,
            originalCode: "",
            transformedCode: "",
            changesApplied: [],
            error: error instanceof Error ? error.message : "Unknown error",
        };
    }
}
export const AST_TRANSFORMS = {
    "sql-injection": transformSQLInjection,
    "xss": transformXSS,
    "hardcoded-secrets": transformHardcodedSecrets,
    "secrets": transformHardcodedSecrets,
    "idor": transformIDOR,
    "authorization": transformIDOR,
};
export async function applyASTTransform(category, context) {
    const transform = AST_TRANSFORMS[category.toLowerCase()];
    if (!transform) {
        return {
            success: false,
            filePath: context.filePath,
            originalCode: "",
            transformedCode: "",
            changesApplied: [],
            error: `No AST transform available for category: ${category}`,
        };
    }
    return transform(context);
}
export function hasASTTransform(category) {
    return category.toLowerCase() in AST_TRANSFORMS;
}
export function listASTTransforms() {
    return Object.keys(AST_TRANSFORMS);
}
//# sourceMappingURL=typescript.js.map