"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.generateMembers = exports.generateNestedTypes = exports.generateClassExpression = exports.generateClassData = exports.generateConstructor = exports.writeFunctions = exports.writeFieldNameList = exports.generateAddressProperty = exports.generateNameProperty = exports.generateSizeProperty = void 0;
const tslib_1 = require("tslib");
const typescript_1 = tslib_1.__importStar(require("typescript"));
const core_1 = require("@il2js/core");
// eslint-disable-next-line import/no-cycle
const FieldHelpers_1 = require("./FieldHelpers");
// eslint-disable-next-line import/no-cycle
const TypeNameHelpers_1 = require("./TypeNameHelpers");
const Constants_1 = require("./Constants");
const Utilities_1 = require("../../../Utilities");
function generateSizeProperty(typeDef, context, relativeTo) {
    var _a, _b;
    const lastField = (_a = typeDef.Fields) === null || _a === void 0 ? void 0 : _a[typeDef.Fields.length - 1];
    const lastOffset = (_b = lastField === null || lastField === void 0 ? void 0 : lastField.Offset) !== null && _b !== void 0 ? _b : 0;
    const lastFieldProperty = lastField && TypeNameHelpers_1.generateTypePath(lastField.Type, typescript_1.factory.createPropertyAccessExpression, context, relativeTo);
    const returnExpression = lastField && lastFieldProperty
        ? typescript_1.factory.createBinaryExpression(typescript_1.factory.createNumericLiteral(lastOffset), typescript_1.factory.createToken(typescript_1.default.SyntaxKind.PlusToken), lastField.Type.Indirection > 1
            ? typescript_1.factory.createNumericLiteral(8)
            : typescript_1.factory.createCallExpression(typescript_1.factory.createPropertyAccessExpression(typescript_1.factory.createPropertyAccessExpression(typescript_1.factory.createIdentifier('il2js'), typescript_1.factory.createIdentifier('NativeStruct')), typescript_1.factory.createIdentifier('sizeof')), undefined, [lastField.Type.IsPrimitive ? typescript_1.factory.createStringLiteral(lastField.Type.TypeName) : lastFieldProperty]))
        : typescript_1.factory.createNumericLiteral(0);
    return typescript_1.factory.createGetAccessorDeclaration(undefined, [typescript_1.factory.createModifier(typescript_1.default.SyntaxKind.StaticKeyword)], typescript_1.factory.createIdentifier('size'), [], undefined, typescript_1.factory.createBlock([typescript_1.factory.createReturnStatement(returnExpression)], false));
}
exports.generateSizeProperty = generateSizeProperty;
function generateNameProperty(typeDef, context) {
    return typescript_1.factory.createPropertyDeclaration(undefined, typescript_1.factory.createModifiersFromModifierFlags(typescript_1.default.ModifierFlags.Public | typescript_1.default.ModifierFlags.Static), typescript_1.factory.createComputedPropertyName(typescript_1.factory.createIdentifier('TypeName')), undefined, undefined, typescript_1.factory.createStringLiteral(context.types.getTypeName(typeDef.Type, context)));
}
exports.generateNameProperty = generateNameProperty;
function generateAddressProperty(typeDef) {
    return typeDef.Type.Address
        ? typescript_1.factory.createPropertyDeclaration(undefined, typescript_1.factory.createModifiersFromModifierFlags(typescript_1.default.ModifierFlags.Public | typescript_1.default.ModifierFlags.Static), typescript_1.factory.createIdentifier('address'), undefined, undefined, typescript_1.factory.createNumericLiteral(core_1.addressToNumber(typeDef.Type.Address)))
        : undefined;
}
exports.generateAddressProperty = generateAddressProperty;
function writeFieldNameList({ Fields, Type }, context) {
    const elements = [];
    if (Type.BaseType) {
        elements.push(typescript_1.factory.createSpreadElement(typescript_1.factory.createPropertyAccessExpression(TypeNameHelpers_1.generateTypePath(Type.BaseType, typescript_1.factory.createPropertyAccessExpression, context, Type), typescript_1.factory.createIdentifier('fieldNames'))));
    }
    if (Fields) {
        for (const { Name } of Fields) {
            elements.push(typescript_1.factory.createStringLiteral(Utilities_1.fixName(Name)));
        }
    }
    return typescript_1.factory.createPropertyDeclaration(undefined, typescript_1.factory.createModifiersFromModifierFlags(typescript_1.default.ModifierFlags.Public | typescript_1.default.ModifierFlags.Static), typescript_1.factory.createIdentifier('fieldNames'), undefined, typescript_1.factory.createArrayTypeNode(typescript_1.factory.createKeywordTypeNode(typescript_1.default.SyntaxKind.StringKeyword)), typescript_1.factory.createArrayLiteralExpression(elements, false));
}
exports.writeFieldNameList = writeFieldNameList;
function writeFunctions(typeDef, context) {
    var _a, _b;
    const fns = (_a = context.typeFunctions[
    // hack: omit root namespace for this name since the original datasource isn't prefixed
    context.types.getTypeName(typeDef.Type, Object.assign(Object.assign({}, context), { rootNamespace: '' }), undefined /* relativeTo */)]) !== null && _a !== void 0 ? _a : [];
    const uniqueNames = new Map();
    const elements = [];
    if (typeDef.Type.BaseType) {
        elements.push(typescript_1.factory.createSpreadAssignment(typescript_1.factory.createPropertyAccessExpression(TypeNameHelpers_1.generateTypePath(typeDef.Type.BaseType, typescript_1.factory.createPropertyAccessExpression, context, typeDef.Type), typescript_1.factory.createIdentifier('staticMethods'))));
    }
    for (const fn of fns) {
        let name = `${(_b = fn.TypeArgs) !== null && _b !== void 0 ? _b : ''}${fn.Name}`;
        if (!uniqueNames.has(name)) {
            uniqueNames.set(name, 1);
        }
        else {
            const nDuplicates = uniqueNames.get(name);
            uniqueNames.set(name, nDuplicates + 1);
            name += `_${nDuplicates}`;
        }
        elements.push(typescript_1.factory.createPropertyAssignment(typescript_1.factory.createStringLiteral(name), typescript_1.factory.createArrowFunction(undefined, undefined, [], undefined, typescript_1.factory.createToken(typescript_1.default.SyntaxKind.EqualsGreaterThanToken), typescript_1.factory.createNewExpression(typescript_1.factory.createIdentifier('il2js.MethodInfo'), undefined, [
            typescript_1.factory.createNumericLiteral(fn.Address),
        ]))));
    }
    return typescript_1.factory.createPropertyDeclaration(undefined, typescript_1.factory.createModifiersFromModifierFlags(typescript_1.default.ModifierFlags.Public | typescript_1.default.ModifierFlags.Static), typescript_1.factory.createIdentifier('staticMethods'), undefined, undefined, typescript_1.factory.createObjectLiteralExpression(elements, true));
}
exports.writeFunctions = writeFunctions;
function generateConstructor(typeDef) {
    var _a;
    if (!((_a = typeDef.Type.TemplateArgumentNames) === null || _a === void 0 ? void 0 : _a.length)) {
        return [];
    }
    const addressParameter = typescript_1.factory.createParameterDeclaration(undefined, undefined, undefined, typescript_1.factory.createIdentifier('address'), undefined, typescript_1.factory.createTypeReferenceNode('Address'), undefined);
    const constructorArguments = [];
    let n = 0;
    for (const TName of typeDef.Type.TemplateArgumentNames) {
        constructorArguments.push(typescript_1.factory.createParameterDeclaration(undefined, undefined, undefined, typescript_1.factory.createIdentifier(`_t${n++}`), undefined, typescript_1.factory.createTypeReferenceNode(typescript_1.factory.createIdentifier(TName), undefined), typescript_1.factory.createAsExpression(typescript_1.factory.createTypeOfExpression(typescript_1.factory.createIdentifier('il2js.UnknownObject')), typescript_1.factory.createKeywordTypeNode(typescript_1.default.SyntaxKind.AnyKeyword))));
    }
    return [
        // factory.createConstructorDeclaration(undefined, undefined, [addressParameter], undefined),
        typescript_1.factory.createConstructorDeclaration(undefined, undefined, [addressParameter, ...constructorArguments], typescript_1.factory.createBlock([
            typescript_1.factory.createExpressionStatement(typescript_1.factory.createCallExpression(typescript_1.factory.createSuper(), undefined, [typescript_1.factory.createIdentifier('address')])),
        ])),
    ];
}
exports.generateConstructor = generateConstructor;
function generateClassData(typeDef, context) {
    var _a, _b;
    const type = typeDef.Type;
    const heritage = [TypeNameHelpers_1.generateInheritanceReference((_a = type.BaseType) !== null && _a !== void 0 ? _a : Constants_1.BuiltinTypes.NativeStruct, context)];
    // eslint-disable-next-line @typescript-eslint/no-use-before-define
    const members = generateMembers(typeDef, context);
    const typeParameters = ((_b = type.TemplateArgumentNames) === null || _b === void 0 ? void 0 : _b.length)
        ? type.TemplateArgumentNames.map((name) => typescript_1.factory.createTypeParameterDeclaration(name, undefined, typescript_1.factory.createKeywordTypeNode(typescript_1.default.SyntaxKind.UnknownKeyword)))
        : undefined;
    const className = typescript_1.factory.createIdentifier(Utilities_1.fixName(type.TypeName));
    return { className, typeParameters, heritage, members };
}
exports.generateClassData = generateClassData;
function generateClassExpression(typeDef, context) {
    const { className, typeParameters, heritage, members } = generateClassData(typeDef, context);
    const classExpr = typescript_1.factory.createClassExpression(undefined, undefined, className, typeParameters, heritage, members);
    return classExpr;
}
exports.generateClassExpression = generateClassExpression;
function generateNestedTypes(typeDef, context) {
    if (!typeDef.NestedTypes) {
        return [];
    }
    return typeDef.NestedTypes.map((nestedTypeDef) => typescript_1.factory.createPropertyDeclaration(undefined, [typescript_1.factory.createModifier(typescript_1.default.SyntaxKind.PublicKeyword), typescript_1.factory.createModifier(typescript_1.default.SyntaxKind.StaticKeyword)], typescript_1.factory.createIdentifier(Utilities_1.fixName(nestedTypeDef.Type.TypeName)), undefined, undefined, generateClassExpression(nestedTypeDef, context)));
}
exports.generateNestedTypes = generateNestedTypes;
function generateMembers(typeDef, context, ...addMembers) {
    const { Fields, StaticFields } = typeDef;
    const members = [
        generateNameProperty(typeDef, context),
        generateSizeProperty(typeDef, context, typeDef.Type),
        generateAddressProperty(typeDef),
        writeFieldNameList(typeDef, context),
        ...generateConstructor(typeDef),
        ...addMembers,
        ...generateNestedTypes(typeDef, context),
    ].filter(Boolean);
    if (Fields) {
        members.push(...Fields.map((field) => FieldHelpers_1.generateFieldAccessor(field, context, typeDef.Type, false /* isStatic */)));
    }
    if (StaticFields) {
        members.push(...StaticFields.map((field) => FieldHelpers_1.generateFieldAccessor(field, context, typeDef.Type, true /* isStatic */)));
    }
    const functionMember = writeFunctions(typeDef, context);
    if (functionMember) {
        members.push(functionMember);
    }
    // if (StaticMethods) {
    //   members.push(...StaticMethods.map(generateStaticMethodInfo));
    // }
    return members;
}
exports.generateMembers = generateMembers;
//# sourceMappingURL=MemberHelpers.js.map