import { utimes } from "node:fs/promises";
import path from "node:path";
import yaml from "yaml";
import { extractInputsWithTypesFromApi, } from "../../../../binding-extraction/extract-identifiers.js";
import { getErrorMeta, getLogger, getPrefixedLogger, } from "../../../../util/logger.js";
import { Paths } from "../../../const.js";
import { ApiBuilderTypeScriptError } from "../../../transform/api-builder/shared.js";
import { ApiBuilderToYamlTransformer } from "../../../transform/api-builder/to-yaml-transformer.js";
import { applyFileTransformations, renderPath, } from "../../../transform/shared.js";
import { apiStaticAnalysis } from "../../subagents/apis/static-analysis.js";
import { ToolExecutionError } from "../../tools2/types.js";
/**
 * Integration output type definitions.
 * Source of truth for what each integration returns.
 */
export const INTEGRATION_OUTPUT_TYPES = {
    // SQL Databases - return arrays of rows directly
    postgres: "any[]",
    postgresql: "any[]",
    mysql: "any[]",
    mariadb: "any[]",
    mssql: "any[]",
    snowflake: "any[]",
    redshift: "any[]",
    bigquery: "any[]",
    databricks: "any[]",
    athena: "any[]",
    cockroachdb: "any[]",
    oracledb: "any[]",
    // NoSQL Databases
    mongodb: "any[]",
    dynamodb: "{ Items?: any[]; Count?: number; ScannedCount?: number }",
    cosmosdb: "{ resources: any[]; headers: Record<string, string> }",
    // REST APIs
    restapi: "{ data: any; status: number; headers: Record<string, string> }",
    restapiintegration: "{ data: any; status: number; headers: Record<string, string> }",
    snowflakecortex: "{ data: any; status: number; headers: Record<string, string> }",
    openapi: "{ data: any; status: number; headers: Record<string, string> }",
    salesforce: "{ data: any; status: number; headers: Record<string, string> }",
    // GraphQL
    graphql: "{ data: any; errors?: any[] }",
    graphqlintegration: "{ data: any; errors?: any[] }",
    // JavaScript/Python
    javascript: "any",
    python: "any",
    // Messaging
    kafka: "{ success: boolean; partition?: number; offset?: string }",
    email: "{ success: boolean; messageId?: string }",
    smtp: "{ success: boolean; messageId?: string }",
    // Other
    redis: "any",
    slack: "any",
    s3: "{ ETag?: string; Location?: string; Key?: string; Bucket?: string }",
    gcs: "any", // Returns vary by action: objects list, file content, upload result, presigned URL, etc.
};
/**
 * Get the output type for a specific block.
 * Control flow blocks return their last child's output.
 * @internal Exported for testing only
 */
export function getBlockOutputType(block) {
    // Handle control flow blocks
    if (block.conditional) {
        // Conditional returns the output of whichever branch executes
        // For type purposes, we need to handle all possible branches
        const types = [];
        if (block.conditional.if?.blocks?.length > 0) {
            const lastIfBlock = block.conditional.if.blocks[block.conditional.if.blocks.length - 1];
            types.push(getBlockOutputType(lastIfBlock));
        }
        block.conditional.elseIf?.forEach((elseIfBranch) => {
            if (elseIfBranch.blocks?.length > 0) {
                const lastElseIfBlock = elseIfBranch.blocks[elseIfBranch.blocks.length - 1];
                types.push(getBlockOutputType(lastElseIfBlock));
            }
        });
        if (block.conditional.else?.blocks?.length > 0) {
            const lastElseBlock = block.conditional.else.blocks[block.conditional.else.blocks.length - 1];
            types.push(getBlockOutputType(lastElseBlock));
        }
        // If all branches have the same type, use that; otherwise union
        const uniqueTypes = [...new Set(types)];
        if (uniqueTypes.length === 0)
            return "void";
        if (uniqueTypes.length === 1)
            return uniqueTypes[0] || "void";
        return uniqueTypes.join(" | ");
    }
    if (block.loop) {
        // Loop returns array of outputs from last block in loop body
        if (block.loop.blocks?.length > 0) {
            const lastLoopBlock = block.loop.blocks[block.loop.blocks.length - 1];
            const itemType = getBlockOutputType(lastLoopBlock);
            return `Array<${itemType}>`;
        }
        return "any[]";
    }
    if (block.trycatch) {
        // TryCatch returns output of try block (or catch if try fails)
        const types = [];
        if (block.trycatch.try?.blocks?.length > 0) {
            const lastTryBlock = block.trycatch.try.blocks[block.trycatch.try.blocks.length - 1];
            types.push(getBlockOutputType(lastTryBlock));
        }
        if (block.trycatch.catch?.blocks?.length > 0) {
            const lastCatchBlock = block.trycatch.catch.blocks[block.trycatch.catch.blocks.length - 1];
            types.push(getBlockOutputType(lastCatchBlock));
        }
        const uniqueTypes = [...new Set(types)];
        if (uniqueTypes.length === 0)
            return "void";
        if (uniqueTypes.length === 1)
            return uniqueTypes[0] || "void";
        return uniqueTypes.join(" | ");
    }
    // Handle step blocks (integrations)
    if (block.step) {
        const integration = block.step.integration;
        // Map integration ID to type
        if (integration === "javascript" || integration === "python") {
            // For code blocks, try to infer from return statement if possible
            // For now, default to any
            return "any";
        }
        // Check if it's a database integration ID (UUID format)
        if (/^[0-9a-f-]{36}$/i.test(integration)) {
            // This is likely a database integration
            // We need to check what type it is from the block config
            if (block.step.postgres || block.step.postgresql) {
                return INTEGRATION_OUTPUT_TYPES.postgresql || "any[]";
            }
            if (block.step.mysql) {
                return INTEGRATION_OUTPUT_TYPES.mysql || "any[]";
            }
            if (block.step.snowflake) {
                return INTEGRATION_OUTPUT_TYPES.snowflake || "any[]";
            }
            // Default for unknown database - returns array of rows
            return INTEGRATION_OUTPUT_TYPES[integration.toLowerCase()] || "any[]";
        }
        // Look up in our type map
        return INTEGRATION_OUTPUT_TYPES[integration.toLowerCase()] || "any";
    }
    // Handle other block types
    if (block.return) {
        return "any"; // Return block output is whatever data it returns
    }
    if (block.throw) {
        return "never"; // Throw block never returns normally
    }
    return "void";
}
/**
 * Determine the output type of an API based on its last block.
 * The API response is always the output of the last block executed.
 * @internal Exported for testing only
 */
export function determineApiOutputType(apiJson) {
    if (!apiJson.blocks || apiJson.blocks.length === 0) {
        return "void";
    }
    // Get the last block
    const lastBlock = apiJson.blocks[apiJson.blocks.length - 1];
    return getBlockOutputType(lastBlock);
}
/**
 * Generate TypeScript interface for API inputs with proper types.
 */
function generateInputInterface(apiName, inputs) {
    const capitalizedName = apiName.charAt(0).toUpperCase() + apiName.slice(1);
    if (inputs.length === 0) {
        return `export interface ${capitalizedName}Input {}`;
    }
    const properties = inputs
        .map((input) => {
        const optionalMarker = input.optional ? "?" : "";
        return `  ${input.name}${optionalMarker}: ${input.type};`;
    })
        .join("\n");
    return `export interface ${capitalizedName}Input {\n${properties}\n}`;
}
/**
 * Generate TypeScript type for API response.
 */
function generateResponseType(apiName, outputType) {
    const capitalizedName = apiName.charAt(0).toUpperCase() + apiName.slice(1);
    return `export type ${capitalizedName}Response = ${outputType};`;
}
/**
 * Generate complete types.d.ts file content.
 */
function generateTypesFile(apiName, inputs, outputType) {
    return `// Auto-generated by Superblocks
// Input/output types for ${apiName}

${generateInputInterface(apiName, inputs)}

${generateResponseType(apiName, outputType)}
`;
}
/**
 * Compile an API source file, validate it, and persist the generated YAML artifact.
 *
 * @param params - Build parameters including identifiers and shared services.
 * @returns The generated API YAML artifact and whether the API is newly created.
 * @throws Error when the API source is missing, compilation fails, or persistence is not possible.
 */
export const buildApiArtifact = async (params) => {
    const { apiName, services, scopeUpdateQueue } = params;
    const logger = params.logger ?? getPrefixedLogger(`[buildApiArtifact-${apiName}]`);
    const apiTsPath = `apis/${apiName}.ts`;
    let apiSourceContent;
    try {
        apiSourceContent = await services.appShell.readFile(apiTsPath);
    }
    catch {
        throw new ToolExecutionError(`API source for ${apiName} not found. Ensure the API has been created and source code is available.`, { toolName: "", input: {}, context: {}, severity: "warn" });
    }
    const apiSource = {
        type: "file",
        filePath: apiTsPath,
        content: apiSourceContent,
    };
    const isNewApi = !(await services.appShell.pathExists(renderPath(Paths.ApiYamls, { apiName })));
    let apiYaml;
    try {
        [apiYaml] = await applyFileTransformations([apiSource], [
            new ApiBuilderToYamlTransformer({
                templateRenderer: services.templateRenderer,
                skipCleanup: true,
                pluginExecutionVersions: services.integrationStore.pluginExecutionVersions,
            }),
        ]);
    }
    catch (error) {
        if (error instanceof ApiBuilderTypeScriptError) {
            logger.info(`TypeScript compilation failed: ${error.message} (source preview: ${apiSource.content.slice(0, 200)}...)`);
            throw new ToolExecutionError(`Unable to generate valid API code. This usually means there are syntax errors or issues with integration types. Review the API structure and ensure correct usage of integration methods. Consider checking integration metadata if you haven't already. Error: TypeScript compilation failed with message: ${error.message}`, {
                toolName: "buildApi",
                input: { apiName },
                context: {},
                severity: "info",
            });
        }
        throw error;
    }
    const analysisWarnings = apiStaticAnalysis(apiSource.content);
    if (analysisWarnings.length > 0) {
        logger.info("Static analysis warnings", {
            warnings: analysisWarnings,
            apiSource: apiSource.content.slice(0, 300),
        });
        throw new ToolExecutionError(`The API code has semantic issues that need to be fixed:\n\n${analysisWarnings.join("\n\n")}`, {
            toolName: "buildApi",
            input: { apiName },
            context: {},
            severity: "info",
        });
    }
    if (apiYaml === undefined) {
        throw new Error("Failed to generate API YAML. Try again.");
    }
    // Parse the YAML to extract inputs and generate types
    let inputInterface;
    let responseType;
    try {
        // Parse the YAML content
        const apiData = yaml.parse(apiYaml.content);
        // Validate that step blocks use the right plugin for the integration
        validateBlockFieldNames(apiData, services.integrationStore);
        // Convert to format expected by extractInputsWithTypesFromApi
        const apiPb = { apiPb: apiData };
        // Extract inputs with type information using the robust AST-based function
        // Pass the original TypeScript source so it can extract type annotations
        const inputs = await extractInputsWithTypesFromApi(apiPb, apiSource.content);
        // Determine output type
        const outputType = determineApiOutputType(apiData);
        // Generate TypeScript interface strings
        inputInterface = generateInputInterface(apiName, inputs);
        responseType = generateResponseType(apiName, outputType);
        // Generate complete types.d.ts content
        const typesContent = generateTypesFile(apiName, inputs, outputType);
        // Save files
        await scopeUpdateQueue.enqueue(async () => {
            // Save the API YAML
            await services.draftInterface.createDraftFile(apiYaml.filePath, apiYaml.content);
            // Save the types.d.ts file in the location expected by vite-plugin-yaml-types
            const typesPath = renderPath(Paths.ApiYamls, { apiName }).replace("api.yaml", "types.d.ts");
            await services.draftInterface.createDraftFile(typesPath, typesContent);
            // Touch the scratch file to keep it "fresh" relative to the newly written YAML.
            // This prevents the timestamp-based freshness check from incorrectly marking
            // the scratch as stale (since the YAML was just generated from this scratch content).
            const scratchPath = renderPath(Paths.Scratch, {
                path: `apis/${apiName}.ts`,
            });
            const fullScratchPath = path.join(services.appShell.appRootDirPath, scratchPath);
            try {
                const now = new Date();
                await utimes(fullScratchPath, now, now);
            }
            catch {
                // Scratch file may not exist (e.g., if API was created from YAML directly)
                // This is fine - the freshness check will regenerate from YAML as needed
            }
        });
    }
    catch (error) {
        logger.error("Failed to build API or generate types", getErrorMeta(error));
        throw new Error(`Failed to build API: ${error instanceof Error ? error.message : String(error)}`);
    }
    return {
        apiYaml,
        isNewApi,
        inputInterface,
        responseType,
    };
};
/**
 * Validate that blocks use the correct plugin field name matching the integration's plugin.id.
 */
export function validateBlockFieldNames(api, integrationStore) {
    const logger = getLogger();
    for (const block of api.blocks || []) {
        if (!block.step)
            continue;
        const integrationId = block.step.integration;
        if (!integrationId)
            continue;
        // Skip built-in plugins that don't have integrations in the store
        if (integrationId === "javascript" || integrationId === "python") {
            continue;
        }
        // Get the integration header to find the actual plugin type
        const integrationHeader = integrationStore.getHeader(integrationId);
        if (!integrationHeader) {
            logger.warn(`Integration "${integrationId}" not found in store for block "${block.name}"`);
            continue;
        }
        const expectedPluginId = integrationHeader.plugin.id;
        // Check if the correct field name is used
        if (!(expectedPluginId in block.step)) {
            // Find what field they're actually using
            const usedFields = Object.keys(block.step).filter((key) => key !== "integration" && typeof block.step[key] === "object");
            throw new ToolExecutionError(`Block "${block.name}" uses incorrect field name. ` +
                `The field name must match the integration's plugin ID.\n\n` +
                `Expected field name: "${expectedPluginId}"\n` +
                (usedFields.length > 0
                    ? `Used field name: "${usedFields[0]}"\n\n`
                    : `No plugin configuration field found\n\n`) +
                `The integration "${integrationId}" is of type "${expectedPluginId}", ` +
                `so your step configuration must use:\n` +
                `${expectedPluginId}: { ... }\n\n` +
                `This ensures proper routing and validation of your API calls.`, {
                toolName: "buildApi",
                input: {},
                context: {},
                severity: "warn",
            });
        }
    }
}
//# sourceMappingURL=build-api-artifact.js.map