"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.buildParameters = buildParameters;
exports.buildChoiceRule = buildChoiceRule;
const typescript_1 = __importDefault(require("typescript"));
const diagnosticCodes_js_1 = require("../diagnosticCodes.js");
const variableResolver_js_1 = require("../analysis/variableResolver.js");
const types_js_1 = require("../analysis/types.js");
const pathDialect_js_1 = require("./pathDialect.js");
// ---------------------------------------------------------------------------
// Parameter building
// ---------------------------------------------------------------------------
/**
 * Convert a TS object literal expression to ASL Parameters/Arguments.
 *
 * JSONPath: { id: input.id, name: "fixed" } → { "id.$": "$.id", "name": "fixed" }
 * JSONata: { id: input.id, name: "fixed" } → { "id": "{% $states.input.id %}", "name": "fixed" }
 */
function buildParameters(context, expr, variables, dialect = pathDialect_js_1.JSON_PATH_DIALECT) {
    const result = {};
    for (const prop of expr.properties) {
        if (typescript_1.default.isShorthandPropertyAssignment(prop)) {
            const propName = prop.name.text;
            // For shorthand { item }, resolve the value symbol (not the property symbol)
            const valueSym = context.checker.getShorthandAssignmentValueSymbol(prop);
            let resolved = { kind: 'unknown' };
            if (valueSym) {
                const varInfo = variables.variables.get(valueSym);
                if (varInfo) {
                    if (varInfo.intrinsicPath) {
                        resolved = { kind: 'intrinsic', path: varInfo.intrinsicPath };
                    }
                    else if (varInfo.type === types_js_1.StepVariableType.Constant && varInfo.literalValue !== undefined) {
                        resolved = { kind: 'literal', value: varInfo.literalValue };
                    }
                    else if (varInfo.jsonPath) {
                        resolved = { kind: 'jsonpath', path: varInfo.jsonPath };
                    }
                }
                // Check deferred inline bindings for helper function parameters
                if (resolved.kind === 'unknown' && variables.deferredBindings) {
                    const argExpr = variables.deferredBindings.get(valueSym);
                    if (argExpr) {
                        resolved = (0, variableResolver_js_1.resolveExpression)(context, argExpr, variables, dialect);
                    }
                }
            }
            switch (resolved.kind) {
                case 'literal':
                    result[propName] = resolved.value;
                    break;
                case 'jsonpath':
                    result[dialect.dynamicKey(propName)] = dialect.wrapDynamicValue(resolved.path);
                    break;
                case 'intrinsic':
                    result[dialect.dynamicKey(propName)] = dialect.wrapDynamicValue(resolved.path);
                    break;
                case 'unknown': {
                    const exprText = prop.name.text;
                    context.addError(prop.name, `Cannot resolve '${exprText}' to an ASL value. ` +
                        `Expressions must be input references (input.x), service call results, ` +
                        `compile-time constants, or ASL intrinsic functions (Steps.format, Steps.add, etc.).`, diagnosticCodes_js_1.ErrorCodes.Expr.UncompilableExpression.code);
                    break;
                }
            }
            continue;
        }
        if (!typescript_1.default.isPropertyAssignment(prop)) {
            context.addError(prop, 'Spread properties are not yet supported in ASL parameters', diagnosticCodes_js_1.ErrorCodes.Expr.SpreadNotSupported.code);
            continue;
        }
        const propName = getPropertyName(prop.name);
        if (!propName) {
            context.addError(prop, 'Computed property names are not supported in ASL parameters', diagnosticCodes_js_1.ErrorCodes.Expr.ComputedPropertyName.code);
            continue;
        }
        // Nested object literal: recurse into buildParameters
        if (typescript_1.default.isObjectLiteralExpression(prop.initializer)) {
            result[propName] = buildParameters(context, prop.initializer, variables, dialect);
            continue;
        }
        // Array literal: recurse into each element
        if (typescript_1.default.isArrayLiteralExpression(prop.initializer)) {
            result[propName] = buildParameterArray(context, prop.initializer, variables, dialect);
            continue;
        }
        const resolved = (0, variableResolver_js_1.resolveExpression)(context, prop.initializer, variables, dialect);
        switch (resolved.kind) {
            case 'literal':
                result[propName] = resolved.value;
                break;
            case 'jsonpath':
                result[dialect.dynamicKey(propName)] = dialect.wrapDynamicValue(resolved.path);
                break;
            case 'intrinsic':
                result[dialect.dynamicKey(propName)] = dialect.wrapDynamicValue(resolved.path);
                break;
            case 'unknown': {
                const exprText = prop.initializer.getText().substring(0, 80);
                context.addError(prop.initializer, `Cannot resolve '${exprText}' to an ASL value. ` +
                    `Expressions must be input references (input.x), service call results, ` +
                    `compile-time constants, or ASL intrinsic functions (Steps.format, Steps.add, etc.).`, diagnosticCodes_js_1.ErrorCodes.Expr.UncompilableExpression.code);
                break;
            }
        }
    }
    return result;
}
/**
 * Convert a TS array literal to an ASL-compatible array.
 * Object elements are recursed via buildParameters; scalar elements
 * are resolved normally. This allows nested structures like
 * ContainerOverrides: [{ Name: 'x', Environment: [...] }].
 */
function buildParameterArray(context, expr, variables, dialect) {
    const arr = [];
    for (const elem of expr.elements) {
        if (typescript_1.default.isObjectLiteralExpression(elem)) {
            arr.push(buildParameters(context, elem, variables, dialect));
        }
        else if (typescript_1.default.isArrayLiteralExpression(elem)) {
            arr.push(buildParameterArray(context, elem, variables, dialect));
        }
        else {
            const resolved = (0, variableResolver_js_1.resolveExpression)(context, elem, variables, dialect);
            switch (resolved.kind) {
                case 'literal':
                    arr.push(resolved.value);
                    break;
                case 'jsonpath':
                case 'intrinsic':
                    arr.push(dialect.wrapDynamicValue(resolved.path));
                    break;
                default: {
                    const exprText = elem.getText().substring(0, 80);
                    context.addError(elem, `Cannot resolve '${exprText}' to an ASL value. ` +
                        `Expressions must be input references (input.x), service call results, ` +
                        `compile-time constants, or ASL intrinsic functions (Steps.format, Steps.add, etc.).`, diagnosticCodes_js_1.ErrorCodes.Expr.UncompilableExpression.code);
                }
            }
        }
    }
    return arr;
}
// ---------------------------------------------------------------------------
// Choice rule building
// ---------------------------------------------------------------------------
/**
 * Convert a TS condition expression to an ASL ChoiceRule.
 *
 * JSONPath: input.mode === 'fast' → { Variable: "$.mode", StringEquals: "fast", Next }
 * JSONata: input.mode === 'fast' → { Condition: "{% $states.input.mode = 'fast' %}", Next }
 */
function buildChoiceRule(context, expr, nextState, variables, dialect = pathDialect_js_1.JSON_PATH_DIALECT) {
    if (dialect.isJsonata()) {
        return buildJsonataChoiceRule(context, expr, nextState, variables, dialect);
    }
    // ── JSONPath choice rule logic (unchanged) ──────────────────────
    // Negation: !expr
    if (typescript_1.default.isPrefixUnaryExpression(expr) && expr.operator === typescript_1.default.SyntaxKind.ExclamationToken) {
        const inner = buildComparisonFromExpression(context, expr.operand, '', variables, dialect);
        if (inner) {
            return { Not: inner, Next: nextState };
        }
        // Fallback: treat as boolean check on the operand
        const resolved = (0, variableResolver_js_1.resolveExpression)(context, expr.operand, variables, dialect);
        if (resolved.kind === 'jsonpath') {
            return {
                Not: { Variable: resolved.path, BooleanEquals: true, Next: '' },
                Next: nextState,
            };
        }
    }
    // And: a && b
    if (typescript_1.default.isBinaryExpression(expr) && expr.operatorToken.kind === typescript_1.default.SyntaxKind.AmpersandAmpersandToken) {
        const left = buildChoiceRule(context, expr.left, '', variables, dialect);
        const right = buildChoiceRule(context, expr.right, '', variables, dialect);
        return { And: [stripNext(left), stripNext(right)], Next: nextState };
    }
    // Or: a || b
    if (typescript_1.default.isBinaryExpression(expr) && expr.operatorToken.kind === typescript_1.default.SyntaxKind.BarBarToken) {
        const left = buildChoiceRule(context, expr.left, '', variables, dialect);
        const right = buildChoiceRule(context, expr.right, '', variables, dialect);
        return { Or: [stripNext(left), stripNext(right)], Next: nextState };
    }
    // Binary comparison: a === b, a !== b, a > b, etc.
    if (typescript_1.default.isBinaryExpression(expr)) {
        return buildChoiceRuleFromBinary(context, expr, nextState, variables, dialect);
    }
    // Bare truthiness check: variable.field
    const resolved = (0, variableResolver_js_1.resolveExpression)(context, expr, variables, dialect);
    if (resolved.kind === 'jsonpath') {
        return {
            Variable: resolved.path,
            BooleanEquals: true,
            Next: nextState,
        };
    }
    context.addError(expr, 'Cannot compile condition expression to ASL Choice rule', diagnosticCodes_js_1.ErrorCodes.Expr.UncompilableCondition.code);
    return { Variable: '$', BooleanEquals: true, Next: nextState };
}
// ---------------------------------------------------------------------------
// JSONata choice rule building
// ---------------------------------------------------------------------------
/**
 * Build a JSONata Condition-based ChoiceRule.
 *
 * Instead of JSONPath's { Variable, StringEquals, ... }, JSONata uses a single
 * Condition field with a boolean expression: { Condition: "{% expr %}", Next }.
 */
function buildJsonataChoiceRule(context, expr, nextState, variables, dialect) {
    const condition = buildJsonataConditionExpr(context, expr, variables, dialect);
    return { Condition: `{% ${condition} %}`, Next: nextState };
}
/**
 * Recursively build a JSONata boolean expression string from a TS expression.
 */
function buildJsonataConditionExpr(context, expr, variables, dialect) {
    // Negation: !expr
    if (typescript_1.default.isPrefixUnaryExpression(expr) && expr.operator === typescript_1.default.SyntaxKind.ExclamationToken) {
        const inner = buildJsonataConditionExpr(context, expr.operand, variables, dialect);
        return `$not(${inner})`;
    }
    // And: a && b
    if (typescript_1.default.isBinaryExpression(expr) && expr.operatorToken.kind === typescript_1.default.SyntaxKind.AmpersandAmpersandToken) {
        const left = buildJsonataConditionExpr(context, expr.left, variables, dialect);
        const right = buildJsonataConditionExpr(context, expr.right, variables, dialect);
        return `(${left}) and (${right})`;
    }
    // Or: a || b
    if (typescript_1.default.isBinaryExpression(expr) && expr.operatorToken.kind === typescript_1.default.SyntaxKind.BarBarToken) {
        const left = buildJsonataConditionExpr(context, expr.left, variables, dialect);
        const right = buildJsonataConditionExpr(context, expr.right, variables, dialect);
        return `(${left}) or (${right})`;
    }
    // Binary comparison: a === b, a !== b, a > b, etc.
    if (typescript_1.default.isBinaryExpression(expr)) {
        return buildJsonataBinaryCondition(context, expr, variables, dialect);
    }
    // Bare truthiness check
    const resolved = (0, variableResolver_js_1.resolveExpression)(context, expr, variables, dialect);
    if (resolved.kind === 'jsonpath' || resolved.kind === 'intrinsic') {
        return resolved.path;
    }
    if (resolved.kind === 'literal') {
        return serializeJsonataValue(resolved.value);
    }
    context.addError(expr, 'Cannot compile condition expression to JSONata Condition', diagnosticCodes_js_1.ErrorCodes.Expr.UncompilableCondition.code);
    return 'true';
}
/**
 * Build a JSONata binary comparison expression.
 */
function buildJsonataBinaryCondition(context, expr, variables, dialect) {
    const op = expr.operatorToken.kind;
    const left = (0, variableResolver_js_1.resolveExpression)(context, expr.left, variables, dialect);
    const right = (0, variableResolver_js_1.resolveExpression)(context, expr.right, variables, dialect);
    const leftStr = serializeJsonataOperand(left);
    const rightStr = serializeJsonataOperand(right);
    if (leftStr === null || rightStr === null) {
        context.addError(expr, 'Cannot compile comparison to JSONata Condition', diagnosticCodes_js_1.ErrorCodes.Expr.UncompilableComparison.code);
        return 'true';
    }
    const isNot = op === typescript_1.default.SyntaxKind.ExclamationEqualsEqualsToken ||
        op === typescript_1.default.SyntaxKind.ExclamationEqualsToken;
    let jsonataOp;
    switch (op) {
        case typescript_1.default.SyntaxKind.EqualsEqualsEqualsToken:
        case typescript_1.default.SyntaxKind.EqualsEqualsToken:
            jsonataOp = '=';
            break;
        case typescript_1.default.SyntaxKind.ExclamationEqualsEqualsToken:
        case typescript_1.default.SyntaxKind.ExclamationEqualsToken:
            jsonataOp = '='; // will be wrapped in $not()
            break;
        case typescript_1.default.SyntaxKind.LessThanToken:
            jsonataOp = '<';
            break;
        case typescript_1.default.SyntaxKind.GreaterThanToken:
            jsonataOp = '>';
            break;
        case typescript_1.default.SyntaxKind.LessThanEqualsToken:
            jsonataOp = '<=';
            break;
        case typescript_1.default.SyntaxKind.GreaterThanEqualsToken:
            jsonataOp = '>=';
            break;
        default:
            context.addError(expr, 'Unsupported comparison operator for JSONata Condition', diagnosticCodes_js_1.ErrorCodes.Expr.UnsupportedOperator.code);
            return 'true';
    }
    const comparison = `${leftStr} ${jsonataOp} ${rightStr}`;
    return isNot ? `$not(${comparison})` : comparison;
}
/**
 * Serialize a resolved expression as a JSONata operand in a Condition.
 */
function serializeJsonataOperand(resolved) {
    switch (resolved.kind) {
        case 'jsonpath':
        case 'intrinsic':
            return resolved.path;
        case 'literal':
            return serializeJsonataValue(resolved.value);
        default:
            return null;
    }
}
/**
 * Serialize a literal value as a JSONata expression.
 */
function serializeJsonataValue(value) {
    if (typeof value === 'string') {
        // Single-quote strings in JSONata, escape single quotes
        return `'${value.replace(/\\/g, '\\\\').replace(/'/g, "\\'")}'`;
    }
    if (typeof value === 'number' || typeof value === 'boolean') {
        return String(value);
    }
    if (value === null) {
        return 'null';
    }
    return String(value);
}
// ---------------------------------------------------------------------------
// Internal helpers (JSONPath)
// ---------------------------------------------------------------------------
function getPropertyName(name) {
    if (typescript_1.default.isIdentifier(name))
        return name.text;
    if (typescript_1.default.isStringLiteral(name))
        return name.text;
    if (typescript_1.default.isNumericLiteral(name))
        return name.text;
    return undefined;
}
function buildComparisonFromExpression(context, expr, next, variables, dialect) {
    // Binary comparison
    if (typescript_1.default.isBinaryExpression(expr)) {
        const rule = buildChoiceRuleFromBinary(context, expr, next, variables, dialect);
        if ('Variable' in rule)
            return rule;
    }
    // Bare expression → boolean check
    const resolved = (0, variableResolver_js_1.resolveExpression)(context, expr, variables, dialect);
    if (resolved.kind === 'jsonpath') {
        return { Variable: resolved.path, BooleanEquals: true, Next: next };
    }
    return null;
}
function buildChoiceRuleFromBinary(context, expr, nextState, variables, dialect) {
    const op = expr.operatorToken.kind;
    const left = (0, variableResolver_js_1.resolveExpression)(context, expr.left, variables, dialect);
    const right = (0, variableResolver_js_1.resolveExpression)(context, expr.right, variables, dialect);
    // Ensure we have a variable on one side and a value on the other
    let variable;
    let comparand;
    let flipped = false;
    if (left.kind === 'jsonpath' && (right.kind === 'literal' || right.kind === 'jsonpath')) {
        variable = left.path;
        comparand = right;
    }
    else if (right.kind === 'jsonpath' && left.kind === 'literal') {
        // Flip: literal op variable → variable reverseOp literal
        variable = right.path;
        comparand = left;
        flipped = true;
    }
    if (!variable || !comparand) {
        context.addError(expr, 'Cannot compile comparison to ASL Choice rule', diagnosticCodes_js_1.ErrorCodes.Expr.UncompilableComparison.code);
        return { Variable: '$', BooleanEquals: true, Next: nextState };
    }
    const isNot = op === typescript_1.default.SyntaxKind.ExclamationEqualsEqualsToken ||
        op === typescript_1.default.SyntaxKind.ExclamationEqualsToken;
    const isEquals = op === typescript_1.default.SyntaxKind.EqualsEqualsEqualsToken ||
        op === typescript_1.default.SyntaxKind.EqualsEqualsToken || isNot;
    if (isEquals) {
        const comparison = buildEqualityComparison(variable, comparand, nextState);
        if (isNot) {
            return {
                Not: { ...comparison, Next: '' },
                Next: nextState,
            };
        }
        return comparison;
    }
    // Relational comparisons: <, >, <=, >=
    // When operands were flipped (e.g. `5 < x` → variable=x, comparand=5),
    // reverse the operator: < becomes >, <= becomes >=, etc.
    const effectiveOp = flipped ? flipRelationalOp(op) : op;
    if (comparand.kind === 'literal' && typeof comparand.value === 'number') {
        switch (effectiveOp) {
            case typescript_1.default.SyntaxKind.LessThanToken:
                return { Variable: variable, NumericLessThan: comparand.value, Next: nextState };
            case typescript_1.default.SyntaxKind.GreaterThanToken:
                return { Variable: variable, NumericGreaterThan: comparand.value, Next: nextState };
            case typescript_1.default.SyntaxKind.LessThanEqualsToken:
                return { Variable: variable, NumericLessThanEquals: comparand.value, Next: nextState };
            case typescript_1.default.SyntaxKind.GreaterThanEqualsToken:
                return { Variable: variable, NumericGreaterThanEquals: comparand.value, Next: nextState };
        }
    }
    if (comparand.kind === 'literal' && typeof comparand.value === 'string') {
        switch (effectiveOp) {
            case typescript_1.default.SyntaxKind.LessThanToken:
                return { Variable: variable, StringLessThan: comparand.value, Next: nextState };
            case typescript_1.default.SyntaxKind.GreaterThanToken:
                return { Variable: variable, StringGreaterThan: comparand.value, Next: nextState };
            case typescript_1.default.SyntaxKind.LessThanEqualsToken:
                return { Variable: variable, StringLessThanEquals: comparand.value, Next: nextState };
            case typescript_1.default.SyntaxKind.GreaterThanEqualsToken:
                return { Variable: variable, StringGreaterThanEquals: comparand.value, Next: nextState };
        }
    }
    // Variable-to-variable relational: use Numeric*Path as default (most common for relational ops)
    if (comparand.kind === 'jsonpath') {
        switch (effectiveOp) {
            case typescript_1.default.SyntaxKind.LessThanToken:
                return { Variable: variable, NumericLessThanPath: comparand.path, Next: nextState };
            case typescript_1.default.SyntaxKind.GreaterThanToken:
                return { Variable: variable, NumericGreaterThanPath: comparand.path, Next: nextState };
            case typescript_1.default.SyntaxKind.LessThanEqualsToken:
                return { Variable: variable, NumericLessThanEqualsPath: comparand.path, Next: nextState };
            case typescript_1.default.SyntaxKind.GreaterThanEqualsToken:
                return { Variable: variable, NumericGreaterThanEqualsPath: comparand.path, Next: nextState };
        }
    }
    context.addError(expr, 'Unsupported comparison operator for ASL Choice rule', diagnosticCodes_js_1.ErrorCodes.Expr.UnsupportedOperator.code);
    return { Variable: variable, BooleanEquals: true, Next: nextState };
}
function stripNext(rule) {
    const { Next, ...rest } = rule;
    return rest;
}
/**
 * Reverse a relational operator for when operands are flipped.
 * `5 < x` becomes `x > 5`, so LessThan becomes GreaterThan, etc.
 */
function flipRelationalOp(op) {
    switch (op) {
        case typescript_1.default.SyntaxKind.LessThanToken: return typescript_1.default.SyntaxKind.GreaterThanToken;
        case typescript_1.default.SyntaxKind.GreaterThanToken: return typescript_1.default.SyntaxKind.LessThanToken;
        case typescript_1.default.SyntaxKind.LessThanEqualsToken: return typescript_1.default.SyntaxKind.GreaterThanEqualsToken;
        case typescript_1.default.SyntaxKind.GreaterThanEqualsToken: return typescript_1.default.SyntaxKind.LessThanEqualsToken;
        default: return op;
    }
}
function buildEqualityComparison(variable, comparand, next) {
    if (comparand.kind === 'jsonpath') {
        // Variable-to-variable comparison: use StringEqualsPath as default.
        // ASL requires a typed operator; string comparison is the safest universal default
        // since it works for most JSON values. NumericEqualsPath/BooleanEqualsPath would
        // require compile-time type inference which is not always reliable.
        return { Variable: variable, StringEqualsPath: comparand.path, Next: next };
    }
    const value = comparand.value;
    if (typeof value === 'string') {
        return { Variable: variable, StringEquals: value, Next: next };
    }
    if (typeof value === 'number') {
        return { Variable: variable, NumericEquals: value, Next: next };
    }
    if (typeof value === 'boolean') {
        return { Variable: variable, BooleanEquals: value, Next: next };
    }
    // Fallback for null etc.
    return { Variable: variable, IsNull: true, Next: next };
}
//# sourceMappingURL=expressionMapper.js.map