/**
 * Attribute Collection — Argument Extraction & Descriptor Parsing
 *
 * Extracts attribute arguments from IR expressions and parses
 * attribute descriptor calls (A.attr(AttrCtor, ...args)).
 * Also contains shared constants, types, and utility functions
 * used across the attribute collection pass.
 */
import { createDiagnostic, } from "../../../types/diagnostic.js";
// ═══════════════════════════════════════════════════════════════════════════
// CONSTANTS
// ═══════════════════════════════════════════════════════════════════════════
export const ATTRIBUTES_IMPORT_SPECIFIER = "@tsonic/core/lang.js";
export const ATTRIBUTE_TARGETS_EXPORT_NAME = "AttributeTargets";
export const ATTRIBUTE_TARGETS = [
    "assembly",
    "module",
    "type",
    "method",
    "property",
    "field",
    "event",
    "param",
    "return",
];
export const ATTRIBUTE_TARGETS_SET = new Set(ATTRIBUTE_TARGETS);
// ═══════════════════════════════════════════════════════════════════════════
// UTILITY FUNCTIONS
// ═══════════════════════════════════════════════════════════════════════════
/**
 * Create a source location for error reporting
 */
export const createLocation = (filePath, sourceSpan) => sourceSpan ?? { file: filePath, line: 1, column: 1, length: 1 };
export const isAttributesApiIdentifier = (expr, apiNames) => expr.kind === "identifier" && apiNames.has(expr.name);
export const getAttributesApiLocalNames = (module) => {
    const names = new Set();
    for (const imp of module.imports) {
        if (imp.source !== ATTRIBUTES_IMPORT_SPECIFIER)
            continue;
        for (const spec of imp.specifiers) {
            if (spec.kind !== "named")
                continue;
            if (spec.name !== "attributes")
                continue;
            names.add(spec.localName);
        }
    }
    return names;
};
export const getAttributeTargetsApiLocalNames = (module) => {
    const names = new Set();
    for (const imp of module.imports) {
        if (imp.source !== ATTRIBUTES_IMPORT_SPECIFIER)
            continue;
        for (const spec of imp.specifiers) {
            if (spec.kind !== "named")
                continue;
            if (spec.name !== ATTRIBUTE_TARGETS_EXPORT_NAME)
                continue;
            names.add(spec.localName);
        }
    }
    return names;
};
// ═══════════════════════════════════════════════════════════════════════════
// ARGUMENT EXTRACTION
// ═══════════════════════════════════════════════════════════════════════════
/**
 * Try to extract an attribute argument from an IR expression.
 * Returns undefined if the expression is not a valid attribute argument.
 */
export const tryExtractAttributeArg = (expr) => {
    const tryExtractPrimitive = (e) => {
        const extracted = tryExtractAttributeArg(e);
        if (!extracted)
            return undefined;
        if (extracted.kind === "array")
            return undefined;
        return extracted;
    };
    if (expr.kind === "literal") {
        if (typeof expr.value === "string") {
            return { kind: "string", value: expr.value };
        }
        if (typeof expr.value === "number") {
            return { kind: "number", value: expr.value };
        }
        if (typeof expr.value === "boolean") {
            return { kind: "boolean", value: expr.value };
        }
    }
    // Arrays of compile-time constants are valid attribute arguments in C#.
    // Example: [Index(new[] { "PropertyId", "Ts" })]
    if (expr.kind === "array") {
        const arr = expr;
        const elements = [];
        if (arr.elements.length === 0)
            return undefined;
        for (const el of arr.elements) {
            if (!el || el.kind === "spread") {
                return undefined;
            }
            const v = tryExtractPrimitive(el);
            if (!v)
                return undefined;
            elements.push(v);
        }
        return { kind: "array", elements };
    }
    // typeof(SomeType) → C# typeof(SomeType) attribute argument
    if (expr.kind === "unary" && expr.operator === "typeof") {
        const targetType = expr.expression.inferredType;
        if (targetType && targetType.kind !== "unknownType") {
            return { kind: "typeof", type: targetType };
        }
    }
    // Enum.Member → enum literal argument
    if (expr.kind === "memberAccess" &&
        !expr.isComputed &&
        typeof expr.property === "string") {
        const object = expr.object;
        const enumType = expr.inferredType && expr.inferredType.kind === "referenceType"
            ? expr.inferredType
            : object.kind === "identifier" &&
                object.inferredType &&
                object.inferredType.kind === "referenceType"
                ? object.inferredType
                : undefined;
        if (enumType) {
            const binding = expr.memberBinding;
            const member = binding?.member ?? expr.property;
            return { kind: "enum", type: enumType, member };
        }
    }
    return undefined;
};
// ═══════════════════════════════════════════════════════════════════════════
// CLR TYPE RESOLUTION
// ═══════════════════════════════════════════════════════════════════════════
export const resolveClrTypeForAttributeCtor = (ctorIdent, module) => {
    if (ctorIdent.resolvedClrType)
        return ctorIdent.resolvedClrType;
    // Prefer CLR imports: these are the authoritative mapping for runtime type names.
    for (const imp of module.imports) {
        if (!imp.isClr)
            continue;
        if (!imp.resolvedNamespace)
            continue;
        for (const spec of imp.specifiers) {
            if (spec.kind !== "named")
                continue;
            if (spec.localName !== ctorIdent.name)
                continue;
            return `${imp.resolvedNamespace}.${spec.name}`;
        }
    }
    return undefined;
};
export const makeAttributeType = (ctorIdent, module) => {
    const resolvedClrType = resolveClrTypeForAttributeCtor(ctorIdent, module);
    if (resolvedClrType) {
        return {
            kind: "ok",
            value: {
                kind: "referenceType",
                name: ctorIdent.name,
                resolvedClrType,
            },
        };
    }
    // Allow locally-emitted attribute types (non-ambient class declarations).
    const hasLocalClass = module.body.some((s) => s.kind === "classDeclaration" && s.name === ctorIdent.name);
    if (hasLocalClass) {
        return {
            kind: "ok",
            value: { kind: "referenceType", name: ctorIdent.name },
        };
    }
    return {
        kind: "error",
        diagnostic: createDiagnostic("TSN4004", "error", `Missing CLR binding for attribute constructor '${ctorIdent.name}'. Import the attribute type from a CLR bindings module (e.g., @tsonic/dotnet) or define it as a local class.`, createLocation(module.filePath, ctorIdent.sourceSpan)),
    };
};
// ═══════════════════════════════════════════════════════════════════════════
// DESCRIPTOR PARSING
// ═══════════════════════════════════════════════════════════════════════════
export const parseAttrDescriptorCall = (expr, module, apiNames) => {
    if (expr.kind !== "call")
        return { kind: "notMatch" };
    const call = expr;
    if (call.callee.kind !== "memberAccess")
        return { kind: "notMatch" };
    const member = call.callee;
    if (member.isComputed || typeof member.property !== "string")
        return { kind: "notMatch" };
    if (member.property !== "attr")
        return { kind: "notMatch" };
    if (!isAttributesApiIdentifier(member.object, apiNames))
        return { kind: "notMatch" };
    if (call.arguments.length < 1) {
        return {
            kind: "error",
            diagnostic: createDiagnostic("TSN4005", "error", `Invalid attribute marker: A.attr(AttrCtor, ...args) requires at least the attribute constructor`, createLocation(module.filePath, call.sourceSpan)),
        };
    }
    const rawArgs = [];
    for (const arg of call.arguments) {
        if (!arg || arg.kind === "spread") {
            return {
                kind: "error",
                diagnostic: createDiagnostic("TSN4006", "error", `Invalid attribute argument: spreads are not allowed in attributes`, createLocation(module.filePath, call.sourceSpan)),
            };
        }
        rawArgs.push(arg);
    }
    const ctorExpr = rawArgs[0];
    if (!ctorExpr || ctorExpr.kind !== "identifier") {
        return {
            kind: "error",
            diagnostic: createDiagnostic("TSN4005", "error", `Invalid attribute marker: A.attr(...) attribute constructor must be an identifier`, createLocation(module.filePath, call.sourceSpan)),
        };
    }
    const attributeCtor = ctorExpr;
    const attributeTypeResult = makeAttributeType(attributeCtor, module);
    if (attributeTypeResult.kind !== "ok") {
        return attributeTypeResult;
    }
    const positionalArgs = [];
    const namedArgs = new Map();
    let sawNamed = false;
    for (const arg of rawArgs.slice(1)) {
        if (arg.kind === "object") {
            sawNamed = true;
            const obj = arg;
            for (const prop of obj.properties) {
                if (prop.kind === "spread") {
                    return {
                        kind: "error",
                        diagnostic: createDiagnostic("TSN4006", "error", `Invalid attribute argument: spreads are not allowed in named arguments`, createLocation(module.filePath, obj.sourceSpan)),
                    };
                }
                if (prop.kind !== "property" || typeof prop.key !== "string") {
                    return {
                        kind: "error",
                        diagnostic: createDiagnostic("TSN4006", "error", `Invalid attribute argument: named arguments must be simple { Name: value } properties`, createLocation(module.filePath, obj.sourceSpan)),
                    };
                }
                const v = tryExtractAttributeArg(prop.value);
                if (!v) {
                    return {
                        kind: "error",
                        diagnostic: createDiagnostic("TSN4006", "error", `Invalid attribute argument: named argument '${prop.key}' must be a compile-time constant`, createLocation(module.filePath, prop.value.sourceSpan)),
                    };
                }
                namedArgs.set(prop.key, v);
            }
            continue;
        }
        if (sawNamed) {
            return {
                kind: "error",
                diagnostic: createDiagnostic("TSN4006", "error", `Invalid attribute argument: positional arguments cannot appear after named arguments`, createLocation(module.filePath, arg.sourceSpan)),
            };
        }
        const v = tryExtractAttributeArg(arg);
        if (!v) {
            return {
                kind: "error",
                diagnostic: createDiagnostic("TSN4006", "error", `Invalid attribute argument: attribute arguments must be compile-time constants (string/number/boolean/typeof/enum/array)`, createLocation(module.filePath, arg.sourceSpan)),
            };
        }
        positionalArgs.push(v);
    }
    return {
        kind: "ok",
        value: {
            attributeType: attributeTypeResult.value,
            positionalArgs,
            namedArgs,
            sourceSpan: call.sourceSpan,
        },
    };
};
//# sourceMappingURL=arg-extractor.js.map