"use strict";
// ---------------------------------------------------------------------------
// SimpleSteps TypeScript Transformer
//
// A ts.TransformerFactory that finds inline workflow usage in CDK stacks:
//
//   new SimpleStepsStateMachine(this, 'Id', {
//     workflow: Steps.createFunction(async (ctx, input) => { ... }),
//   })
//
// The transformer:
// 1. Extracts the workflow function
// 2. Analyzes free variables (service bindings, constants, runtime expressions)
// 3. Synthesizes a virtual workflow file
// 4. Compiles it to ASL using the core compiler
// 5. Replaces the original props with { __compiledAsl, __runtimeBindings }
// ---------------------------------------------------------------------------
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.createSimpleStepsTransformer = createSimpleStepsTransformer;
const typescript_1 = __importDefault(require("typescript"));
const dataFlow_js_1 = require("./dataFlow.js");
const virtualCompiler_js_1 = require("./virtualCompiler.js");
const serviceDiscovery_js_1 = require("../compiler/discovery/serviceDiscovery.js");
const compilerContext_js_1 = require("../compiler/compilerContext.js");
// ---------------------------------------------------------------------------
// Public API
// ---------------------------------------------------------------------------
/**
 * Create the SimpleSteps TypeScript transformer factory.
 *
 * Register via ts-patch, ttypescript, or programmatic ts.CustomTransformers:
 *
 * ```json
 * // tsconfig.json (ts-patch)
 * {
 *   "compilerOptions": {
 *     "plugins": [{ "transform": "@simplesteps/core/transformer" }]
 *   }
 * }
 * ```
 *
 * Or programmatically:
 * ```typescript
 * const result = ts.emit(program, undefined, undefined, false, {
 *   before: [createSimpleStepsTransformer(program)],
 * });
 * ```
 */
function createSimpleStepsTransformer(program) {
    return (transformationContext) => {
        const checker = program.getTypeChecker();
        // Build the service registry once for the whole program
        const compilerContext = new compilerContext_js_1.CompilerContext(program);
        const serviceRegistry = (0, serviceDiscovery_js_1.discoverServices)(compilerContext);
        return (sourceFile) => {
            const visitor = createVisitor(checker, transformationContext, serviceRegistry);
            return typescript_1.default.visitNode(sourceFile, visitor);
        };
    };
}
// Also export as default for ts-patch plugin convention
exports.default = createSimpleStepsTransformer;
// ---------------------------------------------------------------------------
// AST Visitor
// ---------------------------------------------------------------------------
function createVisitor(checker, ctx, serviceRegistry) {
    const visitor = (node) => {
        // Look for: new SimpleStepsStateMachine(scope, id, { workflow: Steps.createFunction(fn) })
        if (typescript_1.default.isNewExpression(node) && isSimpleStepsStateMachineNew(checker, node)) {
            const transformed = tryTransformConstruct(checker, node, serviceRegistry);
            if (transformed)
                return transformed;
        }
        return typescript_1.default.visitEachChild(node, visitor, ctx);
    };
    return visitor;
}
// ---------------------------------------------------------------------------
// Detection
// ---------------------------------------------------------------------------
/**
 * Check if a `new` expression is `new SimpleStepsStateMachine(...)`.
 */
function isSimpleStepsStateMachineNew(checker, node) {
    if (!typescript_1.default.isIdentifier(node.expression))
        return false;
    if (node.expression.text !== 'SimpleStepsStateMachine')
        return false;
    // Verify via the type checker that this actually resolves to our construct
    const sym = checker.getSymbolAtLocation(node.expression);
    if (!sym)
        return false;
    // We accept any class named SimpleStepsStateMachine — the construct import
    // could be from @simplesteps/cdk or a local re-export
    return true;
}
// ---------------------------------------------------------------------------
// Transformation
// ---------------------------------------------------------------------------
/**
 * Try to transform a `new SimpleStepsStateMachine(scope, id, props)` call.
 * Returns the rewritten node, or null if this isn't an inline workflow usage.
 */
function tryTransformConstruct(checker, node, serviceRegistry) {
    const args = node.arguments;
    if (!args || args.length < 3)
        return null;
    const propsArg = args[2];
    if (!typescript_1.default.isObjectLiteralExpression(propsArg))
        return null;
    // Find the `workflow` property
    const workflowProp = propsArg.properties.find(p => typescript_1.default.isPropertyAssignment(p) &&
        typescript_1.default.isIdentifier(p.name) &&
        p.name.text === 'workflow');
    if (!workflowProp || !typescript_1.default.isPropertyAssignment(workflowProp))
        return null;
    // The workflow value should be Steps.createFunction(fn)
    const workflowExpr = workflowProp.initializer;
    if (!typescript_1.default.isCallExpression(workflowExpr))
        return null;
    // Verify it's Steps.createFunction
    if (!isStepsCreateFunction(checker, workflowExpr))
        return null;
    // Extract the factory function
    const factoryArg = workflowExpr.arguments[0];
    if (!factoryArg)
        return null;
    let workflowFn;
    if (typescript_1.default.isArrowFunction(factoryArg) || typescript_1.default.isFunctionExpression(factoryArg)) {
        workflowFn = factoryArg;
    }
    if (!workflowFn)
        return null;
    // Analyze free variables
    const freeVars = (0, dataFlow_js_1.analyzeFreeVariables)(checker, workflowFn, serviceRegistry);
    // Build the synthetic workflow source
    const { source, substitutions, runtimeBindingExprs } = synthesizeWorkflowSource(checker, workflowFn, freeVars, workflowExpr);
    // Compile to ASL
    const result = (0, virtualCompiler_js_1.compileVirtualWorkflow)(source, substitutions);
    if (result.errors.length > 0) {
        // If compilation fails, emit a diagnostic comment but don't crash
        // the build — fall through to runtime error handling in the CDK construct
        const errorMessages = result.errors.map(e => e.message).join('; ');
        console.warn(`[SimpleSteps transformer] Compilation warning: ${errorMessages}`);
    }
    // Build the replacement props object
    const newProps = buildTransformedProps(propsArg, result.asl, runtimeBindingExprs);
    // Create the new `new SimpleStepsStateMachine(scope, id, newProps)` expression
    return typescript_1.default.factory.createNewExpression(node.expression, node.typeArguments, [args[0], args[1], newProps, ...args.slice(3)]);
}
/**
 * Check if a call expression is `Steps.createFunction(...)`.
 */
function isStepsCreateFunction(checker, call) {
    if (!typescript_1.default.isPropertyAccessExpression(call.expression))
        return false;
    const propAccess = call.expression;
    if (!typescript_1.default.isIdentifier(propAccess.expression))
        return false;
    if (propAccess.name.text !== 'createFunction')
        return false;
    // Verify "Steps" resolves to the runtime Steps class
    const sym = checker.getSymbolAtLocation(propAccess.expression);
    if (!sym)
        return false;
    // Accept any class/namespace named "Steps"
    return propAccess.expression.text === 'Steps';
}
/**
 * Synthesize a virtual workflow file from the extracted workflow function
 * and its free variables.
 */
function synthesizeWorkflowSource(checker, workflowFn, freeVars, originalCall) {
    const printer = typescript_1.default.createPrinter({ newLine: typescript_1.default.NewLineKind.LineFeed });
    const lines = [];
    const substitutions = {};
    const runtimeBindingExprs = [];
    let bindingIndex = 0;
    // Imports
    lines.push("import { Steps, SimpleStepContext } from './runtime/index';");
    // Collect which service modules we need to import
    const serviceImports = new Set();
    for (const fv of freeVars) {
        if (fv.classification === 'service-binding' && fv.serviceInfo) {
            serviceImports.add(fv.serviceInfo.service);
        }
    }
    if (serviceImports.size > 0) {
        const names = [...serviceImports].join(', ');
        lines.push(`import { ${names} } from './runtime/services/index';`);
    }
    lines.push('');
    // Emit declarations for each free variable
    for (const fv of freeVars) {
        const varName = fv.symbol.getName();
        switch (fv.classification) {
            case 'service-binding': {
                // For service bindings, we need a declare const for the resource,
                // then construct the binding using the placeholder.
                const bindingName = `__binding_${bindingIndex}__`;
                const placeholder = `$$${bindingIndex}$$`;
                substitutions[bindingName] = placeholder;
                lines.push(`declare const ${bindingName}: string;`);
                if (fv.serviceInfo) {
                    // Reconstruct the service binding with the placeholder variable
                    // e.g.: const validateOrder = Lambda<Req, Res>(__binding_0__);
                    const typeArgs = extractServiceTypeArgs(checker, fv.symbol);
                    const typeArgStr = typeArgs ? `<${typeArgs}>` : '';
                    // Determine if it's a call-style (Lambda) or new-style (DynamoDB) binding
                    const initExpr = getInitializerExpression(fv.symbol);
                    if (initExpr && typescript_1.default.isNewExpression(initExpr)) {
                        lines.push(`const ${varName} = new ${fv.serviceInfo.service}${typeArgStr}(${bindingName});`);
                    }
                    else {
                        lines.push(`const ${varName} = ${fv.serviceInfo.service}${typeArgStr}(${bindingName});`);
                    }
                    // Track the original CDK expression for runtime binding substitution
                    if (fv.serviceInfo.resourceExpr) {
                        runtimeBindingExprs.push(fv.serviceInfo.resourceExpr);
                    }
                    else {
                        // Service without a resource arg (e.g., SecretsManager with no name)
                        runtimeBindingExprs.push(typescript_1.default.factory.createStringLiteral(''));
                    }
                }
                bindingIndex++;
                break;
            }
            case 'constant': {
                // Inline the constant value
                const value = fv.constantValue;
                lines.push(`const ${varName} = ${JSON.stringify(value)};`);
                break;
            }
            case 'cdk-synth': {
                // CDK synth-time expression (e.g., myLambda.functionArn) — treat like runtime
                // but emit SS705 warning so users know this was auto-detected
                const cdkBindingName = `__binding_${bindingIndex}__`;
                const cdkPlaceholder = `$$${bindingIndex}$$`;
                substitutions[cdkBindingName] = cdkPlaceholder;
                lines.push(`declare const ${cdkBindingName}: string;`);
                lines.push(`const ${varName} = ${cdkBindingName};`);
                if (fv.runtimeExpr) {
                    runtimeBindingExprs.push(fv.runtimeExpr);
                }
                else {
                    runtimeBindingExprs.push(typescript_1.default.factory.createIdentifier(varName));
                }
                const exprText = fv.runtimeExpr
                    ? printNode(typescript_1.default.createPrinter(), fv.runtimeExpr)
                    : varName;
                // SS705 diagnostic is emitted by the compiler — no need to also console.warn
                bindingIndex++;
                break;
            }
            case 'runtime': {
                // Runtime expressions become declare const with placeholder
                const bindingName = `__binding_${bindingIndex}__`;
                const placeholder = `$$${bindingIndex}$$`;
                substitutions[bindingName] = placeholder;
                lines.push(`declare const ${bindingName}: string;`);
                lines.push(`const ${varName} = ${bindingName};`);
                if (fv.runtimeExpr) {
                    runtimeBindingExprs.push(fv.runtimeExpr);
                }
                else {
                    runtimeBindingExprs.push(typescript_1.default.factory.createIdentifier(varName));
                }
                bindingIndex++;
                break;
            }
        }
    }
    lines.push('');
    // Print the workflow function body
    // We need to reconstruct: export const __workflow = Steps.createFunction(fn);
    const fnText = printNode(printer, workflowFn);
    // Reconstruct the Steps.createFunction call with type arguments if present
    const typeArgs = originalCall.typeArguments;
    let typeArgStr = '';
    if (typeArgs && typeArgs.length > 0) {
        const typeTexts = typeArgs.map(t => printNode(printer, t));
        typeArgStr = `<${typeTexts.join(', ')}>`;
    }
    lines.push(`export const __workflow = Steps.createFunction${typeArgStr}(${fnText});`);
    return {
        source: lines.join('\n'),
        substitutions,
        runtimeBindingExprs,
    };
}
/**
 * Print a TS node to string using the printer.
 */
function printNode(printer, node) {
    const sourceFile = typescript_1.default.createSourceFile('temp.ts', '', typescript_1.default.ScriptTarget.Latest, false, typescript_1.default.ScriptKind.TS);
    return printer.printNode(typescript_1.default.EmitHint.Unspecified, node, sourceFile);
}
/**
 * Extract type arguments from a service binding variable's initializer.
 * e.g., `Lambda<Req, Res>('arn')` → "Req, Res"
 */
function extractServiceTypeArgs(checker, symbol) {
    const declarations = symbol.declarations;
    if (!declarations || declarations.length === 0)
        return undefined;
    const decl = declarations[0];
    if (!typescript_1.default.isVariableDeclaration(decl) || !decl.initializer)
        return undefined;
    const init = decl.initializer;
    if (typescript_1.default.isCallExpression(init) && init.typeArguments && init.typeArguments.length > 0) {
        const printer = typescript_1.default.createPrinter();
        return init.typeArguments.map(t => printNode(printer, t)).join(', ');
    }
    if (typescript_1.default.isNewExpression(init) && init.typeArguments && init.typeArguments.length > 0) {
        const printer = typescript_1.default.createPrinter();
        return init.typeArguments.map(t => printNode(printer, t)).join(', ');
    }
    return undefined;
}
/**
 * Get the initializer expression from a variable symbol's declaration.
 */
function getInitializerExpression(symbol) {
    const declarations = symbol.declarations;
    if (!declarations || declarations.length === 0)
        return undefined;
    const decl = declarations[0];
    if (typescript_1.default.isVariableDeclaration(decl))
        return decl.initializer;
    return undefined;
}
// ---------------------------------------------------------------------------
// Output AST Construction
// ---------------------------------------------------------------------------
/**
 * Build the replacement props object literal:
 *
 * {
 *   ...existingProps (minus workflow),
 *   __compiledAsl: '{"StartAt":...}',
 *   __runtimeBindings: [expr0, expr1, ...],
 * }
 */
function buildTransformedProps(originalProps, asl, runtimeBindingExprs) {
    // Keep all original properties except `workflow`
    const keptProps = originalProps.properties.filter(p => {
        if (typescript_1.default.isPropertyAssignment(p) && typescript_1.default.isIdentifier(p.name)) {
            return p.name.text !== 'workflow';
        }
        return true;
    });
    // Add __compiledAsl property
    const compiledAslProp = typescript_1.default.factory.createPropertyAssignment(typescript_1.default.factory.createIdentifier('__compiledAsl'), typescript_1.default.factory.createStringLiteral(asl));
    // Add __runtimeBindings property
    const bindingsArray = typescript_1.default.factory.createArrayLiteralExpression(runtimeBindingExprs, false);
    const runtimeBindingsProp = typescript_1.default.factory.createPropertyAssignment(typescript_1.default.factory.createIdentifier('__runtimeBindings'), bindingsArray);
    return typescript_1.default.factory.createObjectLiteralExpression([...keptProps, compiledAslProp, runtimeBindingsProp], true);
}
//# sourceMappingURL=index.js.map