"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.TypeRegistry = void 0;
const core_1 = require("@il2js/core");
const Utilities_1 = require("./Utilities");
const Compiler_1 = require("./Types/Compiler");
function mergeTypeTree(left, right) {
    const keys = Object.keys(right);
    for (const key of keys) {
        const leftValue = left[key];
        const rightValue = right[key];
        if (!leftValue) {
            // eslint-disable-next-line no-param-reassign
            left[key] = rightValue;
        }
        else {
            // can only merge namespaces, not types
            core_1.assert(!!rightValue);
            core_1.assert(!core_1.isNativeType(leftValue));
            core_1.assert(!core_1.isNativeType(rightValue));
            mergeTypeTree(leftValue, right[key]);
        }
    }
}
class TypeRegistry {
    constructor() {
        this.types = {};
        this.typeIds = new Map();
        this.imports = [];
    }
    findType(type) {
        var _a;
        const namePath = [...(((_a = type.Namespace) === null || _a === void 0 ? void 0 : _a.split('.')) || []), Utilities_1.fixName(type.TypeName)].filter(Boolean);
        let node = this.types;
        while (namePath.length > 0) {
            const [currentNodeName] = namePath.splice(0, 1);
            if (!node[currentNodeName]) {
                return undefined;
            }
            node = node[currentNodeName];
        }
        return node;
    }
    getTypeDisposition(type) {
        if (this.typeIds.has(type.TypeIndex)) {
            return Compiler_1.TypeDisposition.Generated;
        }
        if (this.findType(type)) {
            return Compiler_1.TypeDisposition.Imported;
        }
        return Compiler_1.TypeDisposition.Missing;
    }
    hasType(type) {
        if (type.IsPrimitive) {
            return true;
        }
        if (!this.typeIds.has(type.TypeIndex) && !this.findType(type)) {
            return false;
        }
        if (type.TypeArguments && !type.TypeArguments.every((arg) => this.hasType(arg) || type.IsPrimitive)) {
            return false;
        }
        if (type.BaseType && !this.hasType(type.BaseType)) {
            return false;
        }
        return true;
    }
    getTypeName(typeDef, context, relativeToType) {
        var _a, _b;
        const relativeTo = relativeToType &&
            (relativeToType.Namespace ? relativeToType.Namespace : this.getTypeName(relativeToType, context));
        const disposition = context.types.getTypeDisposition(typeDef);
        if (disposition === Compiler_1.TypeDisposition.Imported) {
            return [typeDef.Namespace, Utilities_1.fixName(typeDef.TypeName)].filter(Boolean).join('.');
        }
        core_1.assert(!(typeDef.Namespace && typeDef.DeclaringType), `Not expected to have both a namespace ('${typeDef.Namespace}' AND a declaring type ('${(_a = typeDef.DeclaringType) === null || _a === void 0 ? void 0 : _a.Namespace}.${(_b = typeDef.DeclaringType) === null || _b === void 0 ? void 0 : _b.TypeName}'))`);
        if (disposition === Compiler_1.TypeDisposition.Generated) {
            // eslint-disable-next-line no-param-reassign
            typeDef = this.typeIds.get(typeDef.TypeIndex);
        }
        const nsParts = [];
        if (disposition === Compiler_1.TypeDisposition.Generated && context.rootNamespace) {
            nsParts.push(context.rootNamespace);
        }
        if (typeDef.DeclaringType) {
            if (typeDef.DeclaringType.Namespace) {
                nsParts.push(typeDef.DeclaringType.Namespace);
            }
            nsParts.push(Utilities_1.fixName(`${typeDef.DeclaringType.TypeName}_${typeDef.TypeName}`));
        }
        else {
            if (typeDef.Namespace) {
                nsParts.push(typeDef.Namespace);
            }
            nsParts.push(Utilities_1.fixName(typeDef.TypeName));
        }
        const ns = nsParts.slice(0, nsParts.length - 1).join('.');
        const fullyQualifiedName = nsParts.join('.');
        if (ns === relativeTo || fullyQualifiedName === relativeTo) {
            return Utilities_1.fixName(typeDef.TypeName);
        }
        return fullyQualifiedName;
    }
    addTypeDefs(types) {
        let typesToAdd = types;
        while (typesToAdd.length > 0) {
            this.addTypeInfo(typesToAdd.map((type) => type.Type));
            typesToAdd = core_1.excludeUndefined(typesToAdd.flatMap((type) => type.NestedTypes));
        }
    }
    addTypeInfo(types) {
        types.forEach((type) => this.typeIds.set(type.TypeIndex, type));
    }
    addTypes({ from, types }) {
        const names = Object.keys(types);
        this.imports.push([names, from]);
        mergeTypeTree(this.types, types);
    }
}
exports.TypeRegistry = TypeRegistry;
//# sourceMappingURL=TypeRegistry.js.map