/**
 * Binding manifest types - maps JS/TS names to CLR types/members
 * See spec/bindings.md for full manifest format
 */
const isValidEmitCallStyle = (value) => value === "receiver" || value === "static";
const isValidEmitCallableStaticAccessorKind = (value) => value === "property" || value === "field";
const validateEmitSemantics = (value, filePath, label) => {
    if (value === undefined)
        return undefined;
    if (value === null || typeof value !== "object" || Array.isArray(value)) {
        return `${filePath}: '${label}' must be an object`;
    }
    const record = value;
    if ("callStyle" in record &&
        record.callStyle !== undefined &&
        !isValidEmitCallStyle(record.callStyle)) {
        return `${filePath}: '${label}.callStyle' must be 'receiver' or 'static'`;
    }
    if ("callableStaticAccessorKind" in record &&
        record.callableStaticAccessorKind !== undefined &&
        !isValidEmitCallableStaticAccessorKind(record.callableStaticAccessorKind)) {
        return `${filePath}: '${label}.callableStaticAccessorKind' must be 'property' or 'field'`;
    }
    if (record.callStyle === undefined &&
        record.callableStaticAccessorKind === undefined) {
        return `${filePath}: '${label}' must define at least one known emit semantics property`;
    }
    return undefined;
};
const validateTypeSemantics = (value, filePath, label) => {
    if (value === undefined)
        return undefined;
    if (value === null || typeof value !== "object" || Array.isArray(value)) {
        return `${filePath}: '${label}' must be an object`;
    }
    const record = value;
    if (typeof record.contributesTypeIdentity !== "boolean") {
        return `${filePath}: '${label}.contributesTypeIdentity' must be a boolean`;
    }
    return undefined;
};
const validateBindingSemanticSignature = (value, filePath, label) => {
    if (value === undefined)
        return undefined;
    if (value === null || typeof value !== "object" || Array.isArray(value)) {
        return `${filePath}: '${label}' must be an object`;
    }
    const record = value;
    if ("typeParameters" in record &&
        record.typeParameters !== undefined &&
        !(Array.isArray(record.typeParameters) &&
            record.typeParameters.every((entry) => typeof entry === "string"))) {
        return `${filePath}: '${label}.typeParameters' must be a string array when present`;
    }
    if (!Array.isArray(record.parameters)) {
        return `${filePath}: '${label}.parameters' must be an array`;
    }
    return undefined;
};
const validateTsbindgenExport = (value, filePath, label) => {
    if (value === null || typeof value !== "object" || Array.isArray(value)) {
        return `${filePath}: '${label}' must be an object`;
    }
    const record = value;
    const kind = record.kind;
    if (kind !== "method" &&
        kind !== "property" &&
        kind !== "field" &&
        kind !== "functionType") {
        return `${filePath}: '${label}.kind' must be 'method', 'property', 'field', or 'functionType'`;
    }
    if (typeof record.clrName !== "string") {
        return `${filePath}: '${label}.clrName' must be a string`;
    }
    if (typeof record.declaringClrType !== "string") {
        return `${filePath}: '${label}.declaringClrType' must be a string`;
    }
    if (typeof record.declaringAssemblyName !== "string") {
        return `${filePath}: '${label}.declaringAssemblyName' must be a string`;
    }
    const semanticSignatureError = validateBindingSemanticSignature(record.semanticSignature, filePath, `${label}.semanticSignature`);
    if (semanticSignatureError)
        return semanticSignatureError;
    if ("semanticOptional" in record &&
        record.semanticOptional !== undefined &&
        typeof record.semanticOptional !== "boolean") {
        return `${filePath}: '${label}.semanticOptional' must be a boolean when present`;
    }
    return undefined;
};
/**
 * Type guard to check if a manifest is the full format
 */
export const isFullBindingManifest = (manifest) => {
    return "assembly" in manifest && "namespaces" in manifest;
};
/**
 * Type guard to check if a manifest is the tsbindgen format
 */
export const isTsbindgenBindingFile = (manifest) => {
    return ("namespace" in manifest &&
        "types" in manifest &&
        !("namespaces" in manifest) &&
        !("dotnet" in manifest));
};
/**
 * Validate that a parsed JSON object is a valid binding file format.
 * Returns an error message if invalid, undefined if valid.
 */
export const validateBindingFile = (obj, filePath) => {
    if (obj === null || typeof obj !== "object") {
        return `${filePath}: Expected object, got ${typeof obj}`;
    }
    const manifest = obj;
    if ("namespace" in manifest && "dotnet" in manifest) {
        if (typeof manifest.namespace !== "string") {
            return `${filePath}: 'namespace' must be a string`;
        }
        if (manifest.dotnet === null ||
            typeof manifest.dotnet !== "object" ||
            Array.isArray(manifest.dotnet)) {
            return `${filePath}: 'dotnet' must be an object`;
        }
        const dotnetRecord = manifest.dotnet;
        if (!Array.isArray(dotnetRecord.types)) {
            return `${filePath}: 'dotnet.types' must be an array`;
        }
        if ("exports" in dotnetRecord &&
            dotnetRecord.exports !== undefined &&
            (dotnetRecord.exports === null ||
                typeof dotnetRecord.exports !== "object" ||
                Array.isArray(dotnetRecord.exports))) {
            return `${filePath}: 'dotnet.exports' must be an object when present`;
        }
        if (!("semanticSurface" in manifest) ||
            manifest.semanticSurface === null ||
            typeof manifest.semanticSurface !== "object" ||
            Array.isArray(manifest.semanticSurface)) {
            return `${filePath}: 'semanticSurface' must be an object`;
        }
        const semanticSurface = manifest.semanticSurface;
        if (!Array.isArray(semanticSurface.types)) {
            return `${filePath}: 'semanticSurface.types' must be an array`;
        }
        if ("exports" in semanticSurface &&
            semanticSurface.exports !== undefined &&
            (semanticSurface.exports === null ||
                typeof semanticSurface.exports !== "object" ||
                Array.isArray(semanticSurface.exports))) {
            return `${filePath}: 'semanticSurface.exports' must be an object when present`;
        }
        const producer = manifest.producer;
        if (producer === null ||
            typeof producer !== "object" ||
            Array.isArray(producer)) {
            return `${filePath}: 'producer' must be an object`;
        }
        const producerRecord = producer;
        if (producerRecord.tool !== "tsonic") {
            return `${filePath}: 'producer.tool' must be "tsonic"`;
        }
        if (producerRecord.mode !== "tsonic-firstparty") {
            return `${filePath}: 'producer.mode' must be "tsonic-firstparty"`;
        }
        const normalizedV2 = {
            namespace: manifest.namespace,
            types: dotnetRecord.types,
            exports: dotnetRecord.exports,
        };
        return validateBindingFile(normalizedV2, filePath);
    }
    // Check for tsbindgen format
    if ("namespace" in manifest && "types" in manifest) {
        if (typeof manifest.namespace !== "string") {
            return `${filePath}: 'namespace' must be a string`;
        }
        if (!Array.isArray(manifest.types)) {
            return `${filePath}: 'types' must be an array`;
        }
        for (const [typeIndex, typeValue] of manifest.types.entries()) {
            if (typeValue === null || typeof typeValue !== "object")
                continue;
            const typeRecord = typeValue;
            if ("alias" in typeRecord &&
                typeRecord.alias !== undefined &&
                typeof typeRecord.alias !== "string") {
                return `${filePath}: 'types[${typeIndex}].alias' must be a string when present`;
            }
            const methods = Array.isArray(typeRecord.methods)
                ? typeRecord.methods
                : [];
            for (const [methodIndex, methodValue] of methods.entries()) {
                if (methodValue === null || typeof methodValue !== "object")
                    continue;
                const error = validateEmitSemantics(methodValue.emitSemantics, filePath, `types[${typeIndex}].methods[${methodIndex}].emitSemantics`);
                if (error)
                    return error;
            }
        }
        if ("exports" in manifest) {
            const exportsValue = manifest.exports;
            if (exportsValue !== undefined &&
                (exportsValue === null ||
                    typeof exportsValue !== "object" ||
                    Array.isArray(exportsValue))) {
                return `${filePath}: 'exports' must be an object when present`;
            }
            if (exportsValue && typeof exportsValue === "object") {
                for (const [exportName, exportValue] of Object.entries(exportsValue)) {
                    const error = validateTsbindgenExport(exportValue, filePath, `exports.${exportName}`);
                    if (error)
                        return error;
                }
            }
        }
        return undefined; // Valid tsbindgen format
    }
    // Check for full manifest format
    if ("assembly" in manifest && "namespaces" in manifest) {
        if (typeof manifest.assembly !== "string") {
            return `${filePath}: 'assembly' must be a string`;
        }
        if (!Array.isArray(manifest.namespaces)) {
            return `${filePath}: 'namespaces' must be an array`;
        }
        for (const [namespaceIndex, namespaceValue,] of manifest.namespaces.entries()) {
            if (namespaceValue === null || typeof namespaceValue !== "object")
                continue;
            const namespaceRecord = namespaceValue;
            const types = Array.isArray(namespaceRecord.types)
                ? namespaceRecord.types
                : [];
            for (const [typeIndex, typeValue] of types.entries()) {
                if (typeValue === null || typeof typeValue !== "object")
                    continue;
                const typeRecord = typeValue;
                const members = Array.isArray(typeRecord.members)
                    ? typeRecord.members
                    : [];
                for (const [memberIndex, memberValue] of members.entries()) {
                    if (memberValue === null || typeof memberValue !== "object")
                        continue;
                    const error = validateEmitSemantics(memberValue.emitSemantics, filePath, `namespaces[${namespaceIndex}].types[${typeIndex}].members[${memberIndex}].emitSemantics`);
                    if (error)
                        return error;
                }
            }
        }
        return undefined; // Valid full format
    }
    // Check for simple format (global/module bindings)
    if ("bindings" in manifest) {
        if (typeof manifest.bindings !== "object" || manifest.bindings === null) {
            return `${filePath}: 'bindings' must be an object`;
        }
        for (const [bindingName, bindingValue] of Object.entries(manifest.bindings)) {
            if (bindingValue === null || typeof bindingValue !== "object")
                continue;
            const error = validateTypeSemantics(bindingValue.typeSemantics, filePath, `bindings.${bindingName}.typeSemantics`);
            if (error)
                return error;
        }
        return undefined; // Valid simple format
    }
    return `${filePath}: Unrecognized binding file format. Expected tsbindgen (namespace+types), full (assembly+namespaces), or simple (bindings) format.`;
};
//# sourceMappingURL=binding-types.js.map