import { parse } from "@babel/parser";
import * as t from "@babel/types";
import { extractIntegrationIdsFromSource as extractSdkApiIntegrationIds } from "../../../../extract-api-integrations.js";
import { generate } from "../../../../generate.js";
import { traverse } from "../../../../traverse.js";
import { datasourceSdkClassByType } from "../../../const.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",
    "SnowflakePostgres",
];
/**
 * 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",
    "SnowflakeCortex",
    "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 SDK_PLUGIN_TO_CLASS = new Map([
    ...Object.entries(datasourceSdkClassByType),
    // SDK declaration functions use camelCase names that differ from pluginId keys.
    // Map them to the same class names so AST detection finds them.
    ["restApiIntegration", "RestApi"],
    ["googleSheets", "GoogleSheets"],
    ["googleAnalytics", "GoogleAnalytics"],
    ["googleDrive", "GoogleDrive"],
    ["openai", "OpenAI"],
    ["snowflakeCortex", "SnowflakeCortex"],
    ["snowflakePostgres", "SnowflakePostgres"],
    ["launchDarkly", "LaunchDarkly"],
    ["pagerDuty", "PagerDuty"],
    ["sendGrid", "SendGrid"],
    ["stabilityAI", "StabilityAI"],
    ["superblocksOcr", "OCR"],
]);
const SDK_SAFE_PLUGIN_IDS = new Set([
    ...AI_PROVIDER_TYPES.flatMap((cls) => Object.entries(datasourceSdkClassByType)
        .filter(([, v]) => v === cls)
        .map(([k]) => k)),
    "javascript",
    "python",
    // camelCase aliases for safe plugins
    "openai",
    "snowflakeCortex",
    "stabilityAI",
    "superblocksOcr",
]);
const SDK_DYNAMODB_MUTATION_METHODS = [
    "putItem",
    "updateItem",
    "deleteItem",
    "batchWriteItem",
    "createTable",
    "updateTable",
    "deleteTable",
];
const SDK_OBJECT_STORAGE_MUTATION_METHODS = [
    "deleteObject",
    "uploadObject",
    "uploadMultipleObjects",
    "createBucket",
];
const SDK_SALESFORCE_MUTATION_METHODS = [
    "create",
    "update",
    "remove",
    "bulkCreate",
    "bulkUpdate",
    "bulkDelete",
    "bulkUpsert",
];
const SDK_COUCHBASE_MUTATION_METHODS = ["insert", "remove"];
const SDK_COSMOSDB_MUTATION_METHODS = [
    "create",
    "replaceItem",
    "upsert",
    "deleteItem",
];
const GSHEETS_MUTATION_ACTIONS = [
    "APPEND_SPREADSHEET",
    "CLEAR_SPREADSHEET",
    "CREATE_SPREADSHEET_ROWS",
    "CREATE_WORKSHEET",
];
const REDIS_WRITE_COMMANDS = [
    "APPEND",
    "COPY",
    "DECR",
    "DECRBY",
    "DEL",
    "EXPIRE",
    "EXPIREAT",
    "FLUSHALL",
    "FLUSHDB",
    "HDEL",
    "HSET",
    "HSETNX",
    "INCR",
    "INCRBY",
    "INCRBYFLOAT",
    "LINSERT",
    "LPOP",
    "LPUSH",
    "LREM",
    "LSET",
    "LTRIM",
    "MSET",
    "MSETNX",
    "PERSIST",
    "PEXPIRE",
    "PEXPIREAT",
    "RENAME",
    "RENAMENX",
    "RPOP",
    "RPUSH",
    "SADD",
    "SET",
    "SETEX",
    "SETNX",
    "SMOVE",
    "SPOP",
    "SREM",
    "UNLINK",
    "XADD",
    "XDEL",
    "XTRIM",
    "ZADD",
    "ZINCRBY",
    "ZPOPMAX",
    "ZPOPMIN",
    "ZREM",
];
const integrationConstructorRegex = /new\s+[\w$.]+\s*\(\s*(["'`])(?:\\.|(?!\1).)*?\1\s*,\s*(["'`])((?:\\.|(?!\2).)*?)\2/g;
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);
        }
    }
    for (const id of extractSdkApiIntegrationIds(source)) {
        ids.add(id);
    }
    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) &&
        !t.isObjectMethod(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() and new Set<string>() which are not SQL
    // To avoid false positives from SQL keywords appearing as data values inside
    // single-quoted strings in SQL WHERE clauses (e.g. action_type IN ('create',
    // 'update')), we skip matches where the keyword is a complete single-quoted
    // value — both preceded AND followed by a single quote (the 'keyword' pattern).
    // This correctly distinguishes SQL data values ('update') from JS string
    // literals containing SQL statements ('INSERT INTO ...' or "DELETE FROM ...").
    const hasMutation = SQL_WRITE_KEYWORDS.some((keyword) => {
        const regex = new RegExp(`\\b${keyword}\\b(?!\\s*[<(])`, "gi");
        let match;
        while ((match = regex.exec(functionBodyWithoutComments)) !== null) {
            const idx = match.index;
            const precedingChar = idx > 0 ? functionBodyWithoutComments[idx - 1] : "";
            const followingChar = idx + keyword.length < functionBodyWithoutComments.length
                ? functionBodyWithoutComments[idx + keyword.length]
                : "";
            // Skip if keyword is a single-quoted SQL data value: 'keyword'
            if (precedingChar === "'" && followingChar === "'")
                continue;
            return true;
        }
        return false;
    });
    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 detectSdkMutation = (className, runFn, source) => {
    if (className === "SMTP" || className === "Email") {
        return `${className} send mutation`;
    }
    if (!runFn.start || !runFn.end) {
        return `SDK API integration (${className}) may perform mutations`;
    }
    if (SQL_INTEGRATION_TYPES.includes(className)) {
        return scanJavaScriptForSqlMutations(runFn, source)
            ? "SQL mutation detected"
            : null;
    }
    const runBody = source.slice(runFn.start, runFn.end);
    const cleanBody = removeComments(runBody);
    // GoogleSheets is in REST_INTEGRATION_TYPES but uses action-based detection,
    // not HTTP method detection. This handler MUST come before the REST fallback.
    if (className === "GoogleSheets") {
        return GSHEETS_MUTATION_ACTIONS.some((a) => new RegExp(`["']${a}["']`).test(cleanBody))
            ? "Google Sheets mutation detected"
            : null;
    }
    if (className === "GraphQL" || className === "GraphQLIntegration") {
        return /\.mutation\s*\(/.test(cleanBody)
            ? "GraphQL mutation detected"
            : null;
    }
    if (className === "MongoDB") {
        return MONGODB_MUTATION_OPERATIONS.some((a) => new RegExp(`["']${a}["']`).test(cleanBody))
            ? "MongoDB mutation detected"
            : null;
    }
    if (className === "DynamoDb") {
        if (SDK_DYNAMODB_MUTATION_METHODS.some((m) => new RegExp(`\\.${m}\\s*\\(`).test(cleanBody))) {
            return "DynamoDB mutation detected";
        }
        return scanJavaScriptForSqlMutations(runFn, source)
            ? "DynamoDB PartiQL mutation detected"
            : null;
    }
    if (className === "S3" || className === "GoogleCloudStorage") {
        return SDK_OBJECT_STORAGE_MUTATION_METHODS.some((m) => new RegExp(`\\.${m}\\s*\\(`).test(cleanBody))
            ? `${className === "S3" ? "S3" : "GCS"} mutation detected`
            : null;
    }
    if (className === "Salesforce") {
        return SDK_SALESFORCE_MUTATION_METHODS.some((m) => new RegExp(`\\.${m}\\s*\\(`).test(cleanBody))
            ? "Salesforce mutation detected"
            : null;
    }
    if (className === "Couchbase") {
        if (SDK_COUCHBASE_MUTATION_METHODS.some((m) => new RegExp(`\\.${m}\\s*\\(`).test(cleanBody))) {
            return "Couchbase mutation detected";
        }
        return scanJavaScriptForSqlMutations(runFn, source)
            ? "Couchbase SQL++ mutation detected"
            : null;
    }
    if (className === "CosmosDB") {
        return SDK_COSMOSDB_MUTATION_METHODS.some((m) => new RegExp(`\\.${m}\\s*\\(`).test(cleanBody))
            ? "CosmosDB mutation detected"
            : null;
    }
    if (REST_INTEGRATION_TYPES.includes(className)) {
        return /method:\s*["'`](POST|PUT|DELETE|PATCH)["'`]/i.test(cleanBody)
            ? "HTTP mutation method detected"
            : null;
    }
    if (className === "Redis") {
        return REDIS_WRITE_COMMANDS.some((cmd) => new RegExp(`["'\`]\\s*${cmd}\\b`, "i").test(cleanBody))
            ? "Redis write command detected"
            : null;
    }
    if (className === "Kafka" ||
        className === "Confluent" ||
        className === "Redpanda") {
        return /\.produce\s*\(/.test(cleanBody)
            ? `${className} produce detected`
            : null;
    }
    if (className === "Kinesis") {
        return /\.putRecord\s*\(/.test(cleanBody)
            ? "Kinesis putRecord detected"
            : null;
    }
    if (className === "Workflow") {
        return "Workflow execution (may have side effects)";
    }
    return `SDK API integration (${className}) may perform mutations`;
};
const findRunFunction = (apiConfigNode) => {
    for (const prop of apiConfigNode.properties) {
        if (t.isObjectProperty(prop) &&
            t.isIdentifier(prop.key) &&
            prop.key.name === "run") {
            if (t.isArrowFunctionExpression(prop.value) ||
                t.isFunctionExpression(prop.value)) {
                return prop.value;
            }
            return null;
        }
        if (t.isObjectMethod(prop) &&
            t.isIdentifier(prop.key) &&
            prop.key.name === "run") {
            return prop;
        }
    }
    return null;
};
const handleSdkIntegrationMutation = (className, integrationKey, apiConfigNode, source, mutations) => {
    const runFn = findRunFunction(apiConfigNode);
    if (!runFn) {
        mutations.push({
            type: className,
            name: integrationKey,
            details: `SDK API integration (${className}) may perform mutations`,
        });
        return;
    }
    const details = detectSdkMutation(className, runFn, source);
    if (details) {
        mutations.push({
            type: className,
            name: integrationKey,
            details,
        });
    }
};
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;
};
const buildSdkPluginVariableMap = (ast) => {
    const refs = new Map();
    traverse(ast, {
        VariableDeclarator(path) {
            const id = path.node.id;
            const init = path.node.init;
            if (!t.isIdentifier(id))
                return;
            if (!t.isCallExpression(init))
                return;
            if (!t.isIdentifier(init.callee))
                return;
            const pluginId = init.callee.name;
            const className = SDK_PLUGIN_TO_CLASS.get(pluginId);
            if (!className)
                return;
            refs.set(id.name, { pluginId, className, callNode: init });
        },
    });
    return refs;
};
/**
 * Walk up from a Babel visitor path to find the enclosing `api()` export and
 * return the export identifier:
 *   - `export const X = api(...)` → returns `"X"` (the variable name)
 *   - `export default api(...)`   → returns `"default"`
 *
 * Returns undefined if the path is not inside an `api()` call.
 *
 * NOTE: This assumes `api` is imported under its original name (i.e.
 * `import { api } from "@superblocksteam/sdk-api"`). If `api` is aliased
 * (e.g. `import { api as createApi }`), the function won't match and will
 * return `undefined`, which is the safe fallback (mutations are included).
 * Clark always emits the canonical `import { api }` today.
 */
const getEnclosingApiExportName = (path) => {
    let current = path.parentPath;
    while (current) {
        // Named export: export const X = api(...)
        // Verify the full chain: VariableDeclarator → VariableDeclaration → ExportNamedDeclaration
        // to avoid matching non-exported `const X = api(...)`.
        if (t.isVariableDeclarator(current.node) &&
            t.isIdentifier(current.node.id) &&
            t.isCallExpression(current.node.init) &&
            t.isIdentifier(current.node.init.callee) &&
            current.node.init.callee.name === "api" &&
            current.parentPath &&
            t.isVariableDeclaration(current.parentPath.node) &&
            current.parentPath.parentPath &&
            t.isExportNamedDeclaration(current.parentPath.parentPath.node)) {
            return current.node.id.name;
        }
        // Default export: export default api(...)
        if (t.isExportDefaultDeclaration(current.node) &&
            t.isCallExpression(current.node.declaration) &&
            t.isIdentifier(current.node.declaration.callee) &&
            current.node.declaration.callee.name === "api") {
            return "default";
        }
        current = current.parentPath;
    }
    return undefined;
};
export const extractMutations = (source, exportName) => {
    const mutations = [];
    const jsStepNames = extractStepNames(source);
    const jsSteps = new Map();
    let foundTargetExport = false;
    let ast;
    try {
        ast = parse(source, {
            sourceType: "module",
            plugins: ["typescript"],
            errorRecovery: true,
        });
    }
    catch {
        throw new Error("Failed to parse source code");
    }
    const sdkPluginVars = buildSdkPluginVariableMap(ast);
    // Pre-check: detect if the target export exists in the file even if it has
    // no integrations (an integration-less API cannot have mutations, so we must
    // not fall back to unscoped analysis just because no integration handler ran).
    if (exportName) {
        for (const node of ast.program.body) {
            if (exportName === "default") {
                if (t.isExportDefaultDeclaration(node) &&
                    t.isCallExpression(node.declaration) &&
                    t.isIdentifier(node.declaration.callee) &&
                    node.declaration.callee.name === "api") {
                    foundTargetExport = true;
                    break;
                }
            }
            else if (t.isExportNamedDeclaration(node)) {
                const decl = node.declaration;
                if (t.isVariableDeclaration(decl)) {
                    for (const declarator of decl.declarations) {
                        if (t.isIdentifier(declarator.id) &&
                            declarator.id.name === exportName &&
                            t.isCallExpression(declarator.init) &&
                            t.isIdentifier(declarator.init.callee) &&
                            declarator.init.callee.name === "api") {
                            foundTargetExport = true;
                            break;
                        }
                    }
                }
            }
            if (foundTargetExport)
                break;
        }
    }
    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)
            }
        },
        CallExpression(path) {
            if (!t.isIdentifier(path.node.callee))
                return;
            const fnName = path.node.callee.name;
            const className = SDK_PLUGIN_TO_CLASS.get(fnName);
            if (!className)
                return;
            if (SDK_SAFE_PLUGIN_IDS.has(fnName))
                return;
            const parent = path.parent;
            if (!t.isObjectProperty(parent))
                return;
            const grandparent = path.parentPath?.parent;
            if (!t.isObjectExpression(grandparent))
                return;
            const greatGrandparent = path.parentPath?.parentPath?.parent;
            if (!t.isObjectProperty(greatGrandparent) ||
                !t.isIdentifier(greatGrandparent.key) ||
                greatGrandparent.key.name !== "integrations")
                return;
            const integrationKey = t.isIdentifier(parent.key)
                ? parent.key.name
                : t.isStringLiteral(parent.key)
                    ? parent.key.value
                    : fnName;
            const apiConfigNode = path.parentPath?.parentPath?.parentPath?.parent;
            if (!t.isObjectExpression(apiConfigNode))
                return;
            const enclosingExport = getEnclosingApiExportName(path);
            if (exportName && enclosingExport === exportName)
                foundTargetExport = true;
            if (exportName &&
                enclosingExport !== undefined &&
                enclosingExport !== exportName)
                return;
            handleSdkIntegrationMutation(className, integrationKey, apiConfigNode, source, mutations);
        },
        Identifier(path) {
            const varInfo = sdkPluginVars.get(path.node.name);
            if (!varInfo)
                return;
            if (SDK_SAFE_PLUGIN_IDS.has(varInfo.pluginId))
                return;
            const parent = path.parent;
            if (!t.isObjectProperty(parent) || parent.value !== path.node)
                return;
            const grandparent = path.parentPath?.parent;
            if (!t.isObjectExpression(grandparent))
                return;
            const greatGrandparent = path.parentPath?.parentPath?.parent;
            if (!t.isObjectProperty(greatGrandparent) ||
                !t.isIdentifier(greatGrandparent.key) ||
                greatGrandparent.key.name !== "integrations")
                return;
            const integrationKey = t.isIdentifier(parent.key)
                ? parent.key.name
                : t.isStringLiteral(parent.key)
                    ? parent.key.value
                    : path.node.name;
            const apiConfigNode = path.parentPath?.parentPath?.parentPath?.parent;
            if (!t.isObjectExpression(apiConfigNode))
                return;
            const enclosingExport = getEnclosingApiExportName(path);
            if (exportName && enclosingExport === exportName)
                foundTargetExport = true;
            if (exportName &&
                enclosingExport !== undefined &&
                enclosingExport !== exportName)
                return;
            handleSdkIntegrationMutation(varInfo.className, integrationKey, apiConfigNode, source, mutations);
        },
    });
    // If exportName was requested but not found in the file (e.g. stale registry),
    // fall back to returning ALL mutations as a safe default rather than returning
    // an empty list that would auto-approve the API.
    if (exportName && !foundTargetExport) {
        return extractMutations(source);
    }
    return mutations;
};
export const containsMutations = (source, exportName) => {
    try {
        return extractMutations(source, exportName).length > 0;
    }
    catch {
        return true;
    }
};
//# sourceMappingURL=analysis.js.map