import { parse } from "@babel/parser";
import * as t from "@babel/types";
import { generate } from "../../../../generate.js";
import { traverse } from "../../../../traverse.js";
const SQL_WRITE_KEYWORDS = [
    "CREATE",
    "INSERT",
    "UPDATE",
    "DELETE",
    "DROP",
    "TRUNCATE",
    "ALTER",
    "REPLACE",
    "MERGE",
    "RENAME",
    "GRANT",
    "REVOKE",
    "COPY",
    "SET",
    "UNSET",
    "VACUUM",
];
const HTTP_MUTATION_METHODS = ["DELETE", "PUT", "PATCH", "POST"];
const SQL_INTEGRATION_TYPES = [
    "Athena",
    "BigQuery",
    "CockroachDB",
    "Snowflake",
    "PostgreSQL",
    "MicrosoftSql",
    "MySQL",
    "Databricks",
    "Lakebase",
    "OracleDB",
    "Redshift",
    "MariaDB",
];
/**
 * AI provider integrations that are inherently read-only.
 * These always use POST for inference/generation requests,
 * but they never modify customer data — they only produce outputs.
 * They should NEVER be flagged as mutations regardless of HTTP method.
 */
const AI_PROVIDER_TYPES = [
    "Anthropic",
    "Cohere",
    "Fireworks",
    "Gemini",
    "Groq",
    "Mistral",
    "OpenAI",
    "Perplexity",
    "StabilityAI",
    "OCR",
];
const REST_INTEGRATION_TYPES = [
    "RestApi",
    "OpenApi",
    "Jira",
    "Airtable",
    "Asana",
    "Bitbucket",
    "Box",
    "CircleCI",
    "Confluence",
    "Datadog",
    "Dropbox",
    "Elasticsearch",
    "Front",
    "GitHub",
    "GoogleAnalytics",
    "GoogleDrive",
    "GoogleSheets",
    "HubSpot",
    "Intercom",
    "LaunchDarkly",
    "Notion",
    "PagerDuty",
    "Segment",
    "SendGrid",
    "Slack",
    "Stripe",
    "Twilio",
    "Zendesk",
    "Zoom",
];
const DYNAMODB_MUTATION_ACTIONS = [
    "updateItem",
    "putItem",
    "batchWriteItem",
    "deleteItem",
    "createTable",
    "updateTable",
    "deleteTable",
];
const SALESFORCE_MUTATION_ACTIONS = [
    "CRUD_ACTION_CREATE",
    "CRUD_ACTION_UPDATE",
    "CRUD_ACTION_DELETE",
    "BULK_ACTION_CREATE",
    "BULK_ACTION_UPDATE",
];
const MONGODB_MUTATION_OPERATIONS = [
    "insertOne",
    "insertMany",
    "updateOne",
    "updateMany",
    "deleteOne",
    "deleteMany",
    "replaceOne",
];
const S3_MUTATION_ACTIONS = [
    "UPLOAD_OBJECT",
    "DELETE_OBJECT",
    "UPLOAD_MULTIPLE_OBJECTS",
    "CREATE_BUCKET",
];
const GCS_MUTATION_ACTIONS = [
    "UPLOAD_OBJECT",
    "DELETE_OBJECT",
    "UPLOAD_MULTIPLE_OBJECTS",
    "CREATE_BUCKET",
];
const COUCHBASE_MUTATION_ACTIONS = ["insert", "remove"];
const COSMOSDB_MUTATION_POINT_ACTIONS = [
    "create",
    "upsert",
    "replace",
    "delete",
];
const integrationConstructorRegex = /new\s+[\w$.]+\s*\(\s*(["'`])(?:\\.|(?!\1).)*?\1\s*,\s*(["'`])((?:\\.|(?!\2).)*?)\2/g;
// extract integration ids from our TS new ClassName("<id>", ...) calls, helps with showing the user metadata
export const extractIntegrationIdsFromSource = (source) => {
    integrationConstructorRegex.lastIndex = 0;
    const ids = new Set();
    let match;
    while ((match = integrationConstructorRegex.exec(source)) !== null) {
        const integrationId = match[3];
        if (integrationId) {
            ids.add(integrationId);
        }
    }
    return Array.from(ids);
};
const isSqlMutation = (sql) => {
    const trimmed = sql.trim();
    // A single SELECT statement is always read-only, even if it contains keywords
    // like REPLACE or RENAME which Snowflake uses as SELECT * column modifiers
    // (e.g., SELECT * REPLACE(expr AS col) RENAME(col AS new_col) FROM t).
    if (/^\s*SELECT\b/i.test(trimmed) && !trimmed.includes(";")) {
        return false;
    }
    return SQL_WRITE_KEYWORDS.some((keyword) => new RegExp(`(^|\\s|;)${keyword}\\s`, "i").test(trimmed));
};
const extractStringValue = (node) => {
    if (!node)
        return null;
    if (t.isStringLiteral(node)) {
        // strip `` if present
        if (node.value.startsWith("`") && node.value.endsWith("`")) {
            return node.value.slice(1, -1);
        }
        return node.value;
    }
    if (t.isTemplateLiteral(node)) {
        return node.quasis.map((q) => q.value.raw).join("");
    }
    if (t.isArrowFunctionExpression(node) || t.isFunctionExpression(node)) {
        const body = node.body;
        if (t.isBlockStatement(body)) {
            // Look for return statements in the block
            for (const statement of body.body) {
                if (t.isReturnStatement(statement) && statement.argument) {
                    const returnValue = extractStringValue(statement.argument);
                    if (returnValue)
                        return returnValue;
                }
            }
            // If no return statement found, check the entire function body text for SQL keywords
            // This is a fallback to catch cases where template literals are built dynamically
            return null;
        }
        else {
            return extractStringValue(body);
        }
    }
    return null;
};
const extractPropertyValue = (configNode, propertyName) => {
    if (!t.isObjectExpression(configNode))
        return null;
    const prop = configNode.properties.find((p) => t.isObjectProperty(p) &&
        t.isIdentifier(p.key) &&
        p.key.name === propertyName);
    if (!prop || !t.isObjectProperty(prop))
        return null;
    return extractStringValue(prop.value);
};
const getIntegrationTypeName = (node) => {
    if (t.isIdentifier(node)) {
        return node.name;
    }
    if (t.isMemberExpression(node)) {
        if (t.isIdentifier(node.property)) {
            return node.property.name;
        }
    }
    return null;
};
const extractFunctionParamNames = (node) => {
    if (!t.isArrowFunctionExpression(node) && !t.isFunctionExpression(node)) {
        return [];
    }
    const params = node.params;
    const paramNames = [];
    for (const param of params) {
        if (t.isObjectPattern(param)) {
            for (const prop of param.properties) {
                if (t.isObjectProperty(prop) && t.isIdentifier(prop.key)) {
                    paramNames.push(prop.key.name);
                }
            }
        }
        else if (t.isIdentifier(param)) {
            paramNames.push(param.name);
        }
    }
    return paramNames;
};
/**
 * Removes JavaScript comments from source code using Babel.
 * This is more accurate than regex as it properly handles comments
 * inside strings and template literals.
 */
const removeComments = (code) => {
    try {
        // Parse the code to get an AST
        const ast = parse(code, {
            sourceType: "module",
            plugins: ["typescript"],
            errorRecovery: true,
        });
        // Regenerate code without comments
        const result = generate(ast, {
            comments: false,
            retainLines: false,
        });
        return result.code;
    }
    catch {
        // If parsing fails, fall back to regex (though this is less accurate)
        // This handles edge cases where the code snippet might not be valid standalone code
        let result = code.replace(/\/\/.*$/gm, "");
        result = result.replace(/\/\*[\s\S]*?\*\//g, "");
        return result;
    }
};
const scanJavaScriptForSqlMutations = (node, source) => {
    if (!t.isArrowFunctionExpression(node) && !t.isFunctionExpression(node)) {
        return false;
    }
    if (!node.start || !node.end) {
        return false;
    }
    const functionBody = source.slice(node.start, node.end);
    // Remove comments before scanning for SQL keywords
    const functionBodyWithoutComments = removeComments(functionBody);
    // Check for SQL keywords in the function body (without comments)
    // Use negative lookahead (?!\s*\() to avoid false positives from:
    // - SQL functions like REPLACE(string, from, to) which are read-only
    // - JavaScript constructors like new Set() which are not SQL
    const hasMutation = SQL_WRITE_KEYWORDS.some((keyword) => new RegExp(`\\b${keyword}\\b(?!\\s*\\()`, "i").test(functionBodyWithoutComments));
    if (hasMutation) {
        return true;
    }
    // Also check if the function body is a template literal with expressions
    // that might contain SQL mutations (e.g., IIFEs with SQL inside)
    if (t.isArrowFunctionExpression(node)) {
        const body = node.body;
        if (t.isTemplateLiteral(body)) {
            // For template literals, check expressions for SQL keywords
            // This handles cases like: `statement: ({  }) => \`\${(() => { return "INSERT..." })()}\``
            for (const expr of body.expressions) {
                if (expr.start && expr.end) {
                    const exprText = source.slice(expr.start, expr.end);
                    if (SQL_WRITE_KEYWORDS.some((keyword) => new RegExp(`\\b${keyword}\\b(?!\\s*\\()`, "i").test(exprText))) {
                        return true;
                    }
                    // Also recursively check if the expression is a function that contains SQL
                    if (t.isArrowFunctionExpression(expr) ||
                        t.isFunctionExpression(expr)) {
                        if (scanJavaScriptForSqlMutations(expr, source)) {
                            return true;
                        }
                    }
                }
            }
        }
    }
    return false;
};
const extractStepNames = (source) => {
    const stepNames = new Set();
    const stepNameRegex = /new\s+(JavaScript|Python)\s*\(\s*["'`]([^"'`]+)["'`]/g;
    let match;
    while ((match = stepNameRegex.exec(source)) !== null) {
        const stepName = match[2];
        if (stepName) {
            stepNames.add(stepName);
        }
    }
    return stepNames;
};
export const extractMutations = (source) => {
    const mutations = [];
    const jsStepNames = extractStepNames(source);
    const jsSteps = new Map();
    let ast;
    try {
        ast = parse(source, {
            sourceType: "module",
            plugins: ["typescript"],
            errorRecovery: true,
        });
    }
    catch {
        throw new Error("Failed to parse source code");
    }
    traverse(ast, {
        NewExpression(path) {
            const typeName = getIntegrationTypeName(path.node.callee);
            if (!typeName)
                return;
            const args = path.node.arguments;
            if (args.length === 0)
                return;
            const name = extractStringValue(args[0]);
            if (!name)
                return;
            if ((typeName === "JavaScript" || typeName === "Python") &&
                jsStepNames.has(name)) {
                const configArg = args[1];
                if (configArg && t.isObjectExpression(configArg)) {
                    const fnProp = configArg.properties.find((p) => t.isObjectProperty(p) &&
                        t.isIdentifier(p.key) &&
                        p.key.name === "fn");
                    if (fnProp && t.isObjectProperty(fnProp)) {
                        jsSteps.set(name, fnProp.value);
                    }
                }
            }
            else if (SQL_INTEGRATION_TYPES.includes(typeName)) {
                const configArg = args[2];
                if (!configArg)
                    return;
                const statementProp = t.isObjectExpression(configArg)
                    ? configArg.properties.find((p) => t.isObjectProperty(p) &&
                        t.isIdentifier(p.key) &&
                        (p.key.name === "statement" || p.key.name === "sqlBody"))
                    : null;
                if (statementProp && t.isObjectProperty(statementProp)) {
                    const statementValue = statementProp.value;
                    const statement = extractStringValue(statementValue);
                    if (statement && isSqlMutation(statement)) {
                        mutations.push({
                            type: typeName,
                            name,
                            details: `SQL mutation detected`,
                        });
                    }
                    else if (t.isArrowFunctionExpression(statementValue) ||
                        t.isFunctionExpression(statementValue)) {
                        // First check if the function body itself contains SQL mutations
                        if (scanJavaScriptForSqlMutations(statementValue, source)) {
                            mutations.push({
                                type: typeName,
                                name,
                                details: `SQL mutation detected`,
                            });
                        }
                        else {
                            // If not, check if it references JavaScript steps that build mutations
                            const paramNames = extractFunctionParamNames(statementValue);
                            for (const paramName of paramNames) {
                                const jsStep = jsSteps.get(paramName);
                                if (jsStep && scanJavaScriptForSqlMutations(jsStep, source)) {
                                    mutations.push({
                                        type: typeName,
                                        name,
                                        details: `SQL mutation detected (references ${paramName} which builds mutation query)`,
                                    });
                                    break;
                                }
                            }
                        }
                    }
                    else if (!statement && t.isTemplateLiteral(statementValue)) {
                        // Handle template literals directly (e.g., when used as statement value)
                        // Scan the template literal for SQL mutations
                        if (statementValue.start && statementValue.end) {
                            const templateContent = source.slice(statementValue.start, statementValue.end);
                            if (isSqlMutation(templateContent)) {
                                mutations.push({
                                    type: typeName,
                                    name,
                                    details: `SQL mutation detected`,
                                });
                            }
                        }
                    }
                }
            }
            else if (AI_PROVIDER_TYPES.includes(typeName)) {
                // AI provider integrations are inherently read-only.
                // They always use POST for inference but never modify customer data.
                // Skip mutation detection entirely.
                return;
            }
            else if (REST_INTEGRATION_TYPES.includes(typeName)) {
                const configArg = args[2] || args[1];
                if (!configArg)
                    return;
                const method = extractPropertyValue(configArg, "method");
                if (method && HTTP_MUTATION_METHODS.includes(method.toUpperCase())) {
                    mutations.push({
                        type: typeName,
                        name,
                        details: `HTTP ${method.toUpperCase()} mutation`,
                    });
                }
            }
            else if (typeName === "DynamoDb") {
                const configArg = args[2];
                if (!configArg)
                    return;
                const action = extractPropertyValue(configArg, "action");
                if (action && DYNAMODB_MUTATION_ACTIONS.includes(action)) {
                    mutations.push({
                        type: typeName,
                        name,
                        details: `DynamoDB ${action} mutation`,
                    });
                }
            }
            else if (typeName === "Salesforce") {
                const configArg = args[2];
                if (!configArg || !t.isObjectExpression(configArg))
                    return;
                const actionProp = configArg.properties.find((p) => t.isObjectProperty(p) &&
                    t.isIdentifier(p.key) &&
                    p.key.name === "action");
                if (actionProp &&
                    t.isObjectProperty(actionProp) &&
                    t.isObjectExpression(actionProp.value)) {
                    const actionField = extractPropertyValue(actionProp.value, "action");
                    if (actionField &&
                        SALESFORCE_MUTATION_ACTIONS.includes(actionField)) {
                        mutations.push({
                            type: typeName,
                            name,
                            details: `Salesforce ${actionField} mutation`,
                        });
                    }
                }
            }
            else if (typeName === "Email") {
                mutations.push({
                    type: typeName,
                    name,
                    details: "Email send mutation",
                });
            }
            else if (typeName === "GraphQL" || typeName === "GraphQLIntegration") {
                const configArg = args[2];
                if (!configArg)
                    return;
                const query = extractPropertyValue(configArg, "query");
                if (query && /^\s*mutation\s/i.test(query)) {
                    mutations.push({
                        type: typeName,
                        name,
                        details: "GraphQL mutation",
                    });
                }
            }
            else if (typeName === "MongoDB") {
                const configArg = args[2];
                if (!configArg)
                    return;
                const operation = extractPropertyValue(configArg, "operation");
                if (operation && MONGODB_MUTATION_OPERATIONS.includes(operation)) {
                    mutations.push({
                        type: typeName,
                        name,
                        details: `MongoDB ${operation} mutation`,
                    });
                }
            }
            else if (typeName === "S3") {
                const configArg = args[2];
                if (!configArg)
                    return;
                const action = extractPropertyValue(configArg, "action");
                if (action && S3_MUTATION_ACTIONS.includes(action)) {
                    mutations.push({
                        type: typeName,
                        name,
                        details: `S3 ${action} mutation`,
                    });
                }
            }
            else if (typeName === "GoogleCloudStorage") {
                const configArg = args[2];
                if (!configArg)
                    return;
                const action = extractPropertyValue(configArg, "action");
                if (action && GCS_MUTATION_ACTIONS.includes(action)) {
                    mutations.push({
                        type: typeName,
                        name,
                        details: `GCS ${action} mutation`,
                    });
                }
            }
            else if (typeName === "SMTP") {
                mutations.push({
                    type: typeName,
                    name,
                    details: "SMTP send mutation",
                });
            }
            else if (typeName === "Couchbase") {
                const configArg = args[2];
                if (!configArg)
                    return;
                const action = extractPropertyValue(configArg, "action");
                if (action && COUCHBASE_MUTATION_ACTIONS.includes(action)) {
                    mutations.push({
                        type: typeName,
                        name,
                        details: `Couchbase ${action} mutation`,
                    });
                }
                else if (action === "runSql") {
                    const sqlBody = extractPropertyValue(configArg, "sqlBody");
                    if (sqlBody && isSqlMutation(sqlBody)) {
                        mutations.push({
                            type: typeName,
                            name,
                            details: `Couchbase SQL++ mutation: ${sqlBody.slice(0, 50)}`,
                        });
                    }
                }
            }
            else if (typeName === "CosmosDB") {
                const configArg = args[2];
                if (!configArg)
                    return;
                const actionType = extractPropertyValue(configArg, "actionType");
                if (actionType === "pointOperation") {
                    const pointAction = extractPropertyValue(configArg, "pointAction");
                    if (pointAction &&
                        COSMOSDB_MUTATION_POINT_ACTIONS.includes(pointAction)) {
                        mutations.push({
                            type: typeName,
                            name,
                            details: `CosmosDB ${pointAction} mutation`,
                        });
                    }
                }
                // actionType === "sql" is read-only (CosmosDB SQL queries are SELECT-only)
            }
        },
    });
    return mutations;
};
export const containsMutations = (source) => {
    try {
        return extractMutations(source).length > 0;
    }
    catch {
        return true;
    }
};
//# sourceMappingURL=analysis.js.map