"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.discoverServices = discoverServices;
const typescript_1 = __importDefault(require("typescript"));
const path_1 = __importDefault(require("path"));
const metadata_js_1 = require("../../runtime/services/metadata.js");
function isAlias(entry) {
    return 'aliasOf' in entry;
}
function isOptimizedAlias(entry) {
    return 'aliasOf' in entry;
}
// ---------------------------------------------------------------------------
// Discovery
// ---------------------------------------------------------------------------
/**
 * Resolve the services directory path within the package.
 */
function resolveServicesDir() {
    return path_1.default.resolve(__dirname, '../../runtime/services');
}
/** Get the base name of a file path without .ts extension. Works without Node's path module. */
function getBaseName(filePath) {
    const last = filePath.split('/').pop() ?? filePath;
    return last.endsWith('.ts') ? last.slice(0, -3) : last;
}
/**
 * Build the service registry by scanning runtime/services/ and matching
 * exported classes/functions against the OPTIMIZED_INTEGRATIONS table and
 * SERVICE_SDK_IDS for generic SDK integration fallback.
 */
function discoverServices(context, servicesDirOverride) {
    const servicesDir = servicesDirOverride ?? resolveServicesDir();
    const bindings = new Map();
    // Scan all source files in the program that live under the services directory
    for (const sourceFile of context.program.getSourceFiles()) {
        const filePath = sourceFile.fileName;
        // Only scan files in the services directory (exclude types, index, metadata)
        if (!filePath.startsWith(servicesDir))
            continue;
        const baseName = getBaseName(filePath);
        if (baseName === 'types' || baseName === 'index' || baseName === 'metadata'
            || baseName === 'types.d' || baseName === 'index.d' || baseName === 'metadata.d')
            continue;
        // Look for exported classes and functions
        for (const stmt of sourceFile.statements) {
            const modifiers = typescript_1.default.canHaveModifiers(stmt) ? typescript_1.default.getModifiers(stmt) : undefined;
            const isExported = modifiers?.some((m) => m.kind === typescript_1.default.SyntaxKind.ExportKeyword);
            if (!isExported)
                continue;
            if (typescript_1.default.isClassDeclaration(stmt) && stmt.name) {
                const className = stmt.name.escapedText;
                const binding = matchClassBinding(context, stmt, className);
                if (binding) {
                    bindings.set(className, binding);
                }
            }
            if (typescript_1.default.isFunctionDeclaration(stmt) && stmt.name) {
                const funcName = stmt.name.escapedText;
                const binding = matchFunctionBinding(context, stmt, funcName);
                if (binding) {
                    bindings.set(funcName, binding);
                }
            }
        }
    }
    context.logger.debug(`Discovered ${bindings.size} service bindings: ${[...bindings.keys()].join(', ')}`);
    return { bindings };
}
// ---------------------------------------------------------------------------
// Matching
// ---------------------------------------------------------------------------
function matchClassBinding(context, classDecl, className) {
    const optimized = metadata_js_1.OPTIMIZED_INTEGRATIONS[className];
    const sdkId = metadata_js_1.SERVICE_SDK_IDS[className];
    // Not a known service at all
    if (!optimized && !sdkId)
        return null;
    const classSymbol = context.checker.getSymbolAtLocation(classDecl.name);
    if (!classSymbol)
        return null;
    const methods = new Map();
    // Use index-based access instead of for-of to handle emit-phase nodes
    const members = classDecl.members;
    if (!members || typeof members.length !== 'number')
        return null;
    for (let i = 0; i < members.length; i++) {
        const member = members[i];
        if (!typescript_1.default.isMethodDeclaration(member))
            continue;
        const methodSymbol = context.checker.getSymbolAtLocation(member.name);
        if (!methodSymbol)
            continue;
        const methodName = methodSymbol.getName();
        // Check optimized integrations first
        if (optimized && methodName in optimized) {
            const entry = optimized[methodName];
            const config = resolveOptimizedEntry(optimized, entry);
            if (!config)
                continue;
            methods.set(methodName, {
                methodName,
                symbol: methodSymbol,
                integration: config.integration,
                sdkResource: config.sdkResource,
                hasOutput: config.hasOutput,
            });
        }
        else if (sdkId) {
            // Generic SDK integration fallback
            methods.set(methodName, {
                methodName,
                symbol: methodSymbol,
                integration: 'sdk',
                sdkResource: `arn:aws:states:::aws-sdk:${sdkId}:${methodName}`,
                hasOutput: true,
            });
        }
    }
    return { className, symbol: classSymbol, methods };
}
function matchFunctionBinding(context, funcDecl, funcName) {
    const optimized = metadata_js_1.OPTIMIZED_INTEGRATIONS[funcName];
    const sdkId = metadata_js_1.SERVICE_SDK_IDS[funcName];
    if (!optimized && !sdkId)
        return null;
    const funcSymbol = context.checker.getSymbolAtLocation(funcDecl.name);
    if (!funcSymbol)
        return null;
    const methods = new Map();
    // Check the return type for method signatures
    const signature = context.checker.getSignatureFromDeclaration(funcDecl);
    if (signature) {
        const returnType = context.checker.getReturnTypeOfSignature(signature);
        for (const prop of returnType.getProperties()) {
            const propName = prop.getName();
            if (optimized && propName in optimized) {
                const entry = optimized[propName];
                const config = resolveOptimizedEntry(optimized, entry);
                if (!config)
                    continue;
                methods.set(propName, {
                    methodName: propName,
                    symbol: prop,
                    integration: config.integration,
                    sdkResource: config.sdkResource,
                    hasOutput: config.hasOutput,
                });
            }
            else if (sdkId) {
                methods.set(propName, {
                    methodName: propName,
                    symbol: prop,
                    integration: 'sdk',
                    sdkResource: `arn:aws:states:::aws-sdk:${sdkId}:${propName}`,
                    hasOutput: true,
                });
            }
        }
    }
    return { className: funcName, symbol: funcSymbol, methods };
}
function resolveOptimizedEntry(mappings, entry) {
    if (!isOptimizedAlias(entry))
        return entry;
    const target = mappings[entry.aliasOf];
    if (!target)
        return null;
    if (isOptimizedAlias(target))
        return null; // Don't support chained aliases
    return target;
}
//# sourceMappingURL=serviceDiscovery.js.map