"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.collectInvalidInputTestCases = void 0;
const ts_morph_1 = require("ts-morph");
const extractValidInputSchemaForField_1 = require("./extractValidInputSchemaForField");
const renderInvalidInputTest_1 = require("./renderInvalidInputTest");
async function collectInvalidInputTestCases(args) {
    const describeInfo = findPrimaryDescribeInfo(args.sourceFile);
    const testCalls = findTestCalls(args.sourceFile);
    const generated = [];
    for (const testCall of testCalls) {
        const originTitle = getStringLiteralArg(testCall, 0) ?? 'Unnamed test';
        const callback = testCall.getArguments()[1];
        if (!callback || !ts_morph_1.Node.isArrowFunction(callback))
            continue;
        const fillCalls = callback
            .getDescendantsOfKind(ts_morph_1.SyntaxKind.CallExpression)
            .filter((call) => isFillMethodCall(call));
        for (const fillCall of fillCalls) {
            const propAccess = fillCall.getExpressionIfKindOrThrow(ts_morph_1.SyntaxKind.PropertyAccessExpression);
            const pageObjectExpr = propAccess.getExpression();
            const pageObjectName = pageObjectExpr.getText();
            const fillMethodName = propAccess.getName();
            const inputFieldName = inferInputFieldNameFromFillMethod(fillMethodName);
            if (!inputFieldName)
                continue;
            const schema = (0, extractValidInputSchemaForField_1.extractValidInputSchemaForField)({
                project: args.project,
                typeAtNode: pageObjectExpr,
                fieldName: inputFieldName,
            });
            if (!schema)
                continue;
            const invalidMin = makeInvalidString(schema._schema.minlength - 1);
            const invalidMax = makeInvalidString(schema._schema.maxlength + 1);
            const prefixStatements = computePrefixStatements({
                testCall,
                callback,
                targetFillCall: fillCall,
                replacementArgLiteral: toSingleQuotedStringLiteral(invalidMin),
            });
            const minParamsText = buildMinimalTestParams({
                callback,
                pageObjectName,
                referencedText: prefixStatements,
            });
            const minTest = (0, renderInvalidInputTest_1.renderInvalidInputTest)({
                testTitle: `${originTitle} - invalid ${inputFieldName} minlength`,
                testParams: minParamsText,
                prefixStatements,
                pageObjectName,
                inputFieldName,
                violationEffect: schema._schema.violationEffect,
                schema,
                kind: 'minlength',
                invalidValue: invalidMin,
                originalCallback: callback,
                targetFillCall: fillCall,
            });
            const prefixStatementsMax = computePrefixStatements({
                testCall,
                callback,
                targetFillCall: fillCall,
                replacementArgLiteral: toSingleQuotedStringLiteral(invalidMax),
            });
            const maxParamsText = buildMinimalTestParams({
                callback,
                pageObjectName,
                referencedText: prefixStatementsMax,
            });
            const maxTest = (0, renderInvalidInputTest_1.renderInvalidInputTest)({
                testTitle: `${originTitle} - invalid ${inputFieldName} maxlength`,
                testParams: maxParamsText,
                prefixStatements: prefixStatementsMax,
                pageObjectName,
                inputFieldName,
                violationEffect: schema._schema.violationEffect,
                schema,
                kind: 'maxlength',
                invalidValue: invalidMax,
                originalCallback: callback,
                targetFillCall: fillCall,
            });
            generated.push({
                originTestTitle: originTitle,
                originDescribeTitle: describeInfo?.title,
                beforeEachText: describeInfo?.beforeEachText,
                afterEachText: describeInfo?.afterEachText,
                renderedTestText: minTest,
            });
            generated.push({
                originTestTitle: originTitle,
                originDescribeTitle: describeInfo?.title,
                beforeEachText: describeInfo?.beforeEachText,
                afterEachText: describeInfo?.afterEachText,
                renderedTestText: maxTest,
            });
        }
    }
    return generated;
}
exports.collectInvalidInputTestCases = collectInvalidInputTestCases;
function findTestCalls(sourceFile) {
    return sourceFile
        .getDescendantsOfKind(ts_morph_1.SyntaxKind.CallExpression)
        .filter((call) => {
        const expr = call.getExpression();
        return ts_morph_1.Node.isIdentifier(expr) && expr.getText() === 'test';
    })
        .filter((call) => call.getArguments().length >= 2);
}
function isFillMethodCall(call) {
    const expr = call.getExpression();
    if (!ts_morph_1.Node.isPropertyAccessExpression(expr))
        return false;
    const name = expr.getName();
    return name.startsWith('fill') && name.length > 'fill'.length;
}
function inferInputFieldNameFromFillMethod(fillMethodName) {
    if (!fillMethodName.startsWith('fill'))
        return null;
    const suffix = fillMethodName.slice('fill'.length);
    if (suffix.length === 0)
        return null;
    const camel = suffix[0].toLowerCase() + suffix.slice(1);
    return `${camel}Input`;
}
function getStringLiteralArg(call, index) {
    const arg = call.getArguments()[index];
    if (!arg)
        return null;
    const literal = arg.asKind(ts_morph_1.SyntaxKind.StringLiteral);
    return literal ? literal.getLiteralText() : null;
}
function findPrimaryDescribeInfo(sourceFile) {
    const describeCalls = sourceFile
        .getDescendantsOfKind(ts_morph_1.SyntaxKind.CallExpression)
        .filter((call) => {
        const expr = call.getExpression();
        if (!ts_morph_1.Node.isPropertyAccessExpression(expr))
            return false;
        return (expr.getExpression().getText() === 'test' &&
            expr.getName() === 'describe');
    });
    const primary = describeCalls[0];
    if (!primary)
        return null;
    const title = getStringLiteralArg(primary, 0) ?? 'Generated';
    const callback = primary.getArguments()[1];
    if (!callback || !ts_morph_1.Node.isArrowFunction(callback)) {
        return { title };
    }
    const block = callback.getBody().asKind(ts_morph_1.SyntaxKind.Block);
    if (!block)
        return { title };
    const statements = block.getStatements();
    const beforeEach = statements.find((s) => s.getText().trimStart().startsWith('test.beforeEach'));
    const afterEach = statements.find((s) => s.getText().trimStart().startsWith('test.afterEach'));
    return {
        title,
        beforeEachText: beforeEach?.getText(),
        afterEachText: afterEach?.getText(),
    };
}
function computePrefixStatements(args) {
    if (!ts_morph_1.Node.isArrowFunction(args.callback))
        return '';
    const body = args.callback.getBody();
    const block = body.asKind(ts_morph_1.SyntaxKind.Block);
    if (!block)
        return '';
    const statements = block.getStatements();
    const targetStatement = statements.find((stmt) => stmt
        .getDescendantsOfKind(ts_morph_1.SyntaxKind.CallExpression)
        .some((c) => c === args.targetFillCall));
    if (!targetStatement)
        return '';
    const idx = statements.indexOf(targetStatement);
    const prefix = statements.slice(0, idx + 1);
    const rendered = prefix.map((stmt) => {
        if (stmt !== targetStatement)
            return stmt.getText();
        // Replace only the argument list for the targeted fill call.
        return replaceCallArgInStatement({
            statementText: stmt.getText(),
            call: args.targetFillCall,
            newArg: args.replacementArgLiteral,
        });
    });
    return rendered.join('\n');
}
function replaceCallArgInStatement(args) {
    const expr = args.call.getExpression();
    if (!ts_morph_1.Node.isPropertyAccessExpression(expr))
        return args.statementText;
    const methodName = expr.getName();
    // Naive but stable enough for our conventions.
    const pattern = new RegExp(`${escapeRegExp(methodName)}\\s*\\(([^)]*)\\)`, 'm');
    return args.statementText.replace(pattern, `${methodName}(${args.newArg})`);
}
function escapeRegExp(value) {
    return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
function buildMinimalTestParams(args) {
    const params = args.callback.getParameters();
    if (params.length === 0)
        return '{}';
    const first = params[0];
    const nameNode = first.getNameNode();
    // Common convention: async ({loginPage, homePage}, testInfo) => ...
    if (nameNode && ts_morph_1.Node.isObjectBindingPattern(nameNode)) {
        const available = nameNode.getElements().map((el) => el.getName());
        const required = new Set([args.pageObjectName]);
        for (const candidate of available) {
            if (candidate &&
                isIdentifierReferenced({
                    text: args.referencedText,
                    identifier: candidate,
                })) {
                required.add(candidate);
            }
        }
        const firstParamText = `{${Array.from(required).sort().join(', ')}}`;
        const rest = params.slice(1).map((p) => p.getText());
        return [firstParamText, ...rest].join(', ');
    }
    // Fallback: keep original parameters.
    return params.map((p) => p.getText()).join(', ');
}
function isIdentifierReferenced(args) {
    const escaped = escapeRegExp(args.identifier);
    const re = new RegExp(`\\b${escaped}\\b`, 'm');
    return re.test(args.text);
}
function makeInvalidString(length) {
    if (length <= 0)
        return '';
    return 'a'.repeat(length);
}
function toSingleQuotedStringLiteral(value) {
    // Keep generated output consistent with the repo's single-quote style.
    return `'${value.replaceAll('\\', '\\\\').replaceAll("'", "\\'")}'`;
}
