/**
 * Anonymous Object Literal Synthesis (TSN7403)
 *
 * When an object literal has no contextual nominal type, we synthesize a
 * nominal type from the AST structure. This enables:
 * - Object literals without explicit type annotations
 * - Spread expressions in object literals
 * - Function-valued properties (as delegates)
 *
 * DETERMINISTIC IR TYPING (INV-0 compliant):
 * This module uses only AST-based synthesis. Property types come from the
 * converted expression's inferredType, not from TS computed types.
 *
 * Eligibility:
 * ✅ Allowed: identifier keys, string/numeric literal keys, deterministically-known computed literal keys,
 *            spreads with typed sources, function-valued properties, getters/setters
 * ❌ Rejected: non-deterministic computed keys, symbol keys
 */
import * as ts from "typescript";
import { getUnsupportedObjectLiteralMethodRuntimeReason } from "../../object-literal-method-runtime.js";
/**
 * Serialize an IrType to a stable string for shape signature
 */
const serializeType = (type) => {
    switch (type.kind) {
        case "primitiveType":
            return type.name;
        case "literalType":
            return `lit:${typeof type.value}:${String(type.value)}`;
        case "referenceType":
            if (type.typeArguments && type.typeArguments.length > 0) {
                return `ref:${type.name}<${type.typeArguments.map(serializeType).join(",")}>`;
            }
            return `ref:${type.name}`;
        case "arrayType":
            return `arr:${serializeType(type.elementType)}`;
        case "tupleType":
            return `tup:[${type.elementTypes.map(serializeType).join(",")}]`;
        case "functionType": {
            const params = type.parameters
                .map((p) => (p.type ? serializeType(p.type) : "any"))
                .join(",");
            return `fn:(${params})=>${serializeType(type.returnType)}`;
        }
        case "unionType":
            return `union:[${type.types.map(serializeType).join("|")}]`;
        case "typeParameterType":
            return `tp:${type.name}`;
        case "voidType":
            return "void";
        case "anyType":
            return "any";
        case "unknownType":
            return "unknown";
        case "neverType":
            return "never";
        case "objectType": {
            // Nested object types - serialize members (only property signatures)
            const members = type.members
                .filter((m) => m.kind === "propertySignature")
                .map((m) => `${m.name}${m.isOptional ? "?" : ""}:${serializeType(m.type)}`)
                .sort()
                .join(";");
            return `obj:{${members}}`;
        }
        case "dictionaryType":
            return `dict:[${serializeType(type.keyType)}]:${serializeType(type.valueType)}`;
        case "intersectionType":
            return `intersection:[${type.types.map(serializeType).join("&")}]`;
        default:
            return "unknown";
    }
};
/**
 * Compute a stable shape signature from property info (AST-based)
 *
 * DETERMINISTIC: Takes pre-computed PropertyInfo array, no TS type computation.
 *
 * Format: sorted properties with their types, optionality, and readonly flags
 * Example: "count:number;name?:string;readonly:id:string"
 */
export const computeShapeSignature = (props) => [...props]
    .sort((a, b) => a.name.localeCompare(b.name))
    .map((p) => {
    const prefix = p.readonly ? "ro:" : "";
    const suffix = p.optional ? "?" : "";
    return `${prefix}${p.name}${suffix}:${serializeType(p.type)}`;
})
    .join(";");
/**
 * Mutable registry for synthetic types (module-scoped)
 * Key: shape signature, Value: synthetic type entry
 */
const syntheticTypeRegistry = new Map();
/**
 * Reset the registry (call at the start of each file/module compilation)
 */
export const resetSyntheticRegistry = () => {
    syntheticTypeRegistry.clear();
};
/**
 * Generate a synthetic type name from location
 *
 * Format: __Anon_<FileStem>_<Line>_<Col>
 */
export const generateSyntheticName = (fileStem, line, col) => {
    // Sanitize file stem (remove special chars)
    const safeStem = fileStem.replace(/[^a-zA-Z0-9_]/g, "_");
    return `__Anon_${safeStem}_${line}_${col}`;
};
/**
 * Get or create a synthetic type for a shape
 *
 * DETERMINISTIC: Takes pre-computed PropertyInfo array, no TS type computation.
 * Returns existing type if shape was seen before (deduplication).
 */
export const getOrCreateSyntheticType = (shapeSignature, name, props, capturedTypeParams) => {
    // Check if we already have a synthetic for this shape
    const existing = syntheticTypeRegistry.get(shapeSignature);
    if (existing) {
        return existing;
    }
    // Create new synthetic interface declaration from pre-computed props
    const members = props.map((p) => ({
        kind: "propertySignature",
        name: p.name,
        type: p.type,
        isOptional: p.optional,
        isReadonly: p.readonly,
    }));
    const declaration = {
        kind: "interfaceDeclaration",
        name,
        typeParameters: capturedTypeParams.length > 0 ? capturedTypeParams : undefined,
        extends: [],
        members,
        isExported: true, // Synthetic types are always exported for accessibility
        isStruct: false,
    };
    const entry = {
        name,
        declaration,
        typeParameters: capturedTypeParams,
    };
    syntheticTypeRegistry.set(shapeSignature, entry);
    return entry;
};
/**
 * Get all synthetic type declarations (call after processing a file)
 */
export const getSyntheticDeclarations = () => {
    return Array.from(syntheticTypeRegistry.values()).map((e) => e.declaration);
};
/**
 * Check if an object literal expression is eligible for synthesis
 *
 * DETERMINISTIC IR TYPING (INV-0 compliant):
 * Uses symbol-based checks, not getTypeAtLocation.
 *
 * Eligible:
 * - All property keys are identifiers or string literals
 * - Spread expressions have typed sources (identifiers with type annotations)
 * - Method shorthand is allowed only when it does not rely on dynamic this/super/arguments
 * - No getters/setters
 * - No computed keys with non-literal expressions
 *
 * Phase 5 Step 4: Uses ProgramContext for unified semantic access.
 */
export const checkSynthesisEligibility = (node, ctx) => {
    const unwrapDeterministicKeyExpression = (expr) => {
        let current = expr;
        for (;;) {
            if (ts.isParenthesizedExpression(current)) {
                current = current.expression;
                continue;
            }
            if (ts.isAsExpression(current) ||
                ts.isTypeAssertionExpression(current) ||
                ts.isSatisfiesExpression(current)) {
                current = current.expression;
                continue;
            }
            return current;
        }
    };
    const tryResolveDeterministicComputedKeyName = (name, seenSymbols = new Set()) => {
        if (ts.isIdentifier(name) ||
            ts.isStringLiteral(name) ||
            ts.isNoSubstitutionTemplateLiteral(name) ||
            ts.isNumericLiteral(name)) {
            return String(name.text);
        }
        if (!ts.isComputedPropertyName(name)) {
            return undefined;
        }
        const expr = unwrapDeterministicKeyExpression(name.expression);
        if (ts.isStringLiteral(expr) ||
            ts.isNoSubstitutionTemplateLiteral(expr) ||
            ts.isNumericLiteral(expr)) {
            return String(expr.text);
        }
        if (!ts.isIdentifier(expr)) {
            return undefined;
        }
        const symbol = ctx.checker.getSymbolAtLocation(expr);
        if (!symbol || seenSymbols.has(symbol)) {
            return undefined;
        }
        seenSymbols.add(symbol);
        const visitDeclarations = (target) => {
            for (const decl of target.getDeclarations() ?? []) {
                if (ts.isVariableDeclaration(decl) &&
                    decl.initializer &&
                    ts.isVariableDeclarationList(decl.parent) &&
                    (decl.parent.flags & ts.NodeFlags.Const) !== 0) {
                    const resolved = tryResolveDeterministicComputedKeyName(ts.factory.createComputedPropertyName(decl.initializer), seenSymbols);
                    if (resolved !== undefined)
                        return resolved;
                }
            }
            return undefined;
        };
        const direct = visitDeclarations(symbol);
        if (direct !== undefined)
            return direct;
        if ((symbol.flags & ts.SymbolFlags.Alias) !== 0) {
            const aliased = ctx.checker.getAliasedSymbol(symbol);
            if (!seenSymbols.has(aliased)) {
                seenSymbols.add(aliased);
                return visitDeclarations(aliased);
            }
        }
        return undefined;
    };
    for (const prop of node.properties) {
        // Property assignment: check key type
        if (ts.isPropertyAssignment(prop)) {
            if (tryResolveDeterministicComputedKeyName(prop.name) === undefined) {
                return {
                    eligible: false,
                    reason: "Computed property key is not a deterministically known string/number literal",
                };
            }
            // Check for symbol keys
            if (ts.isPrivateIdentifier(prop.name)) {
                return {
                    eligible: false,
                    reason: `Private identifier (symbol) keys are not supported`,
                };
            }
        }
        // Shorthand property: always ok (identifier key)
        if (ts.isShorthandPropertyAssignment(prop)) {
            continue;
        }
        // Spread: full deterministic shape validation happens after expression conversion.
        if (ts.isSpreadAssignment(prop)) {
            continue;
        }
        // Method declarations are valid when they avoid unsupported runtime features.
        // `this` is supported via object-literal method binding.
        if (ts.isMethodDeclaration(prop)) {
            if (tryResolveDeterministicComputedKeyName(prop.name) === undefined) {
                return {
                    eligible: false,
                    reason: "Computed property key is not a deterministically known string/number literal",
                };
            }
            if (ts.isPrivateIdentifier(prop.name)) {
                return {
                    eligible: false,
                    reason: `Private identifier (symbol) keys are not supported`,
                };
            }
            const unsupportedRuntimeReason = getUnsupportedObjectLiteralMethodRuntimeReason(prop);
            if (unsupportedRuntimeReason) {
                return {
                    eligible: false,
                    reason: unsupportedRuntimeReason,
                };
            }
            continue;
        }
        // Getter/setter: allowed for synthesized object types
        if (ts.isGetAccessorDeclaration(prop) ||
            ts.isSetAccessorDeclaration(prop)) {
            if (tryResolveDeterministicComputedKeyName(prop.name) === undefined) {
                return {
                    eligible: false,
                    reason: "Computed property key is not a deterministically known string/number literal",
                };
            }
            continue;
        }
    }
    return { eligible: true };
};
// ============================================================================
// Type Parameter Capture
// ============================================================================
/**
 * Find type parameters used in a type
 */
export const findUsedTypeParameters = (type, inScopeParams) => {
    const used = new Set();
    const visit = (t) => {
        switch (t.kind) {
            case "typeParameterType":
                if (inScopeParams.has(t.name)) {
                    used.add(t.name);
                }
                break;
            case "referenceType":
                t.typeArguments?.forEach(visit);
                break;
            case "arrayType":
                visit(t.elementType);
                break;
            case "tupleType":
                t.elementTypes.forEach(visit);
                break;
            case "functionType":
                t.parameters.forEach((p) => {
                    if (p.type)
                        visit(p.type);
                });
                visit(t.returnType);
                break;
            case "unionType":
                t.types.forEach(visit);
                break;
            case "objectType":
                t.members.forEach((m) => {
                    if (m.kind === "propertySignature") {
                        visit(m.type);
                    }
                    else if (m.kind === "methodSignature") {
                        m.parameters.forEach((p) => {
                            if (p.type)
                                visit(p.type);
                        });
                        if (m.returnType)
                            visit(m.returnType);
                    }
                });
                break;
            case "dictionaryType":
                visit(t.keyType);
                visit(t.valueType);
                break;
        }
    };
    visit(type);
    return used;
};
//# sourceMappingURL=anonymous-synthesis.js.map