"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.generateFileAsync = exports.processNestedStructs = exports.groupStructsByNamespace = void 0;
const tslib_1 = require("tslib");
const typescript_1 = tslib_1.__importStar(require("typescript"));
const toposort_1 = tslib_1.__importDefault(require("toposort"));
const core_1 = require("@il2js/core");
const StructHelpers_1 = require("./Utils/StructHelpers");
function orderTypes(typeDef, context) {
    const allStructs = new Map(typeDef.map((struct) => [
        context.types.getTypeName(struct.Type, context),
        struct,
    ]));
    const edges = typeDef
        .filter((s) => s.Type.BaseType && s.Type.TypeIndex !== s.Type.BaseType.TypeIndex)
        .map((s) => [
        allStructs.get(context.types.getTypeName(s.Type.BaseType, context)),
        allStructs.get(context.types.getTypeName(s.Type, context)),
    ])
        .filter((edge) => edge[0] && edge[1]);
    return toposort_1.default.array(typeDef, edges);
}
async function sleep(ms) {
    return new Promise((resolve) => setTimeout(resolve, ms));
}
async function generateFlatCode(types, context, progressCallback) {
    const orderedTypes = types; // orderTypes(types, context); // should already be ordered before entering
    const result = [];
    let n = 10;
    for (const typeDef of orderedTypes) {
        progressCallback === null || progressCallback === void 0 ? void 0 : progressCallback(n++, types.length + 10, typeDef.Type.TypeName, typeDef);
        const block = StructHelpers_1.generateClass(typeDef, context);
        if (!block) {
            continue;
        }
        result.push(typescript_1.factory.createModuleDeclaration(undefined, [typescript_1.factory.createModifier(typescript_1.default.SyntaxKind.ExportKeyword)], typescript_1.factory.createIdentifier([context.rootNamespace, typeDef.Type.Namespace].filter(Boolean).join('.')), typescript_1.factory.createModuleBlock([block]), typescript_1.default.NodeFlags.Namespace));
        // eslint-disable-next-line no-await-in-loop
        if (n % 100 === 0)
            await sleep(100);
    }
    return result;
}
function groupStructsByNamespace(typeDefs) {
    const root = { name: 'global', childNamespaces: [], childTypes: [] };
    function forNS(ns) {
        if (!ns) {
            return root;
        }
        let result = root.childNamespaces.find((child) => child.name === ns);
        if (!result) {
            result = { name: ns, childTypes: [], childNamespaces: [] };
            root.childNamespaces.push(result);
        }
        return result;
    }
    for (const typeDef of typeDefs) {
        const nsObj = forNS(typeDef.Type.Namespace);
        nsObj.childTypes.push(typeDef);
    }
    return root;
}
exports.groupStructsByNamespace = groupStructsByNamespace;
function scopeTypeToDeclaringType(type) {
    var _a;
    if (!(type === null || type === void 0 ? void 0 : type.DeclaringType)) {
        return;
    }
    scopeTypeToDeclaringType(type.BaseType);
    (_a = type.TypeArguments) === null || _a === void 0 ? void 0 : _a.forEach(scopeTypeToDeclaringType);
    // eslint-disable-next-line no-param-reassign
    type.Namespace = type.DeclaringType.Namespace;
    // eslint-disable-next-line no-param-reassign
    type.TypeName = `${type.DeclaringType.TypeName}_${type.TypeName}`;
    // eslint-disable-next-line no-param-reassign
    delete type.DeclaringType;
}
function processNestedStructs(structs) {
    var _a, _b;
    for (const struct of structs) {
        scopeTypeToDeclaringType(struct.Type);
        (_a = struct.Fields) === null || _a === void 0 ? void 0 : _a.forEach((field) => scopeTypeToDeclaringType(field.Type));
        (_b = struct.StaticFields) === null || _b === void 0 ? void 0 : _b.forEach((field) => scopeTypeToDeclaringType(field.Type));
    }
    return structs;
}
exports.processNestedStructs = processNestedStructs;
function filterStructs(structs) {
    return structs.filter((si) => {
        // remove invalid named structs
        if (si.Type.TypeName.indexOf('<') > -1) {
            return false;
        }
        if (si.IsGenericInstance) {
            return false;
        }
        return true;
    });
}
async function generateFileAsync({ TypeInfoList, TypeNameToStaticMethods }, assembly, version, types, opts, progressCallback) {
    var _a;
    const context = {
        rootNamespace: (_a = opts.rootNamespace) !== null && _a !== void 0 ? _a : 'codegen',
        typeFunctions: TypeNameToStaticMethods,
        types,
    };
    let n = 0;
    progressCallback === null || progressCallback === void 0 ? void 0 : progressCallback(n++, TypeInfoList.length + 10, 'Ordering dependencies');
    let typesList = orderTypes(TypeInfoList, context);
    progressCallback === null || progressCallback === void 0 ? void 0 : progressCallback(n++, TypeInfoList.length + 10, 'Nesting types');
    typesList = processNestedStructs(typesList);
    progressCallback === null || progressCallback === void 0 ? void 0 : progressCallback(n++, TypeInfoList.length + 10, 'Filtering input');
    typesList = filterStructs(typesList);
    progressCallback === null || progressCallback === void 0 ? void 0 : progressCallback(n++, typesList.length + 10, 'Removing empty types');
    // add to type registry
    types.addTypeDefs(typesList);
    const exportedTypes = await generateFlatCode(typesList, context, progressCallback);
    const imports = new core_1.DefaultedMap(() => []);
    imports.get('@il2js/core').push('Address', 'TypeName', 'bindTypeArgs');
    for (const [names, from] of context.types.imports) {
        imports.get(from).push(...names);
    }
    const importDecls = [];
    for (const [from, names] of imports.entries()) {
        importDecls.push(typescript_1.factory.createImportDeclaration(undefined, undefined, typescript_1.factory.createImportClause(false, undefined, typescript_1.factory.createNamedImports(names.map((name) => typescript_1.factory.createImportSpecifier(undefined, typescript_1.factory.createIdentifier(name))))), typescript_1.factory.createStringLiteral(from)));
    }
    return [
        ...importDecls,
        typescript_1.factory.createVariableStatement(typescript_1.factory.createModifiersFromModifierFlags(typescript_1.default.ModifierFlags.Export), typescript_1.factory.createVariableDeclarationList([
            typescript_1.factory.createVariableDeclaration(typescript_1.factory.createIdentifier('GameAssemblyInfo'), undefined, undefined, typescript_1.factory.createObjectLiteralExpression([
                typescript_1.factory.createPropertyAssignment(typescript_1.factory.createIdentifier('assemblyName'), typescript_1.factory.createStringLiteral(assembly)),
                typescript_1.factory.createPropertyAssignment(typescript_1.factory.createIdentifier('version'), typescript_1.factory.createStringLiteral(version)),
            ], true)),
        ], typescript_1.default.NodeFlags.Const)),
        ...exportedTypes,
    ];
}
exports.generateFileAsync = generateFileAsync;
//# sourceMappingURL=GeneratorMethods.js.map