"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ContractSchemaImpl = exports.ContractStorage = exports.ContractStorageSlot = exports.ContractStorageField = exports.Feature = void 0;
var Feature;
(function (Feature) {
    Feature["FRAGMENTMULTI"] = "FRAGMENTMULTI";
    Feature["FRAGMENTSINGLE"] = "FRAGMENTSINGLE";
    Feature["PATCH"] = "PATCH";
    Feature["PATCHACCOUNT"] = "ACCOUNTPATCH";
    Feature["PATCH1155"] = "1155PATCH";
    Feature["MINTABLE"] = "MINTABLE";
    Feature["REVERSIBLE"] = "REVERSIBLE";
})(Feature = exports.Feature || (exports.Feature = {}));
class ContractStorageField {
}
exports.ContractStorageField = ContractStorageField;
class ContractStorageSlot {
    constructor() {
        this.fieldIDs = [];
    }
}
exports.ContractStorageSlot = ContractStorageSlot;
class ContractStorage {
    constructor() {
        this.slots = [];
        this.fields = [];
    }
}
exports.ContractStorage = ContractStorage;
// ContractConfig from user or file ->> ContractSchemaImpl
class ContractSchemaImpl {
    constructor(config) {
        this.scopeName = config.scopeName;
        this.name = config.name;
        this.symbol = config.symbol;
        this.schemaURI = config.schemaURI;
        this.imageURI = config.imageURI;
        this.fields = config.fields;
        this.features = config.features;
        this.storage = this.buildStorage(config.fields);
    }
    buildStorage(entries) {
        let fields = entries.map((entry, index) => {
            const fieldTypeEnum = this.getFieldTypeEnum(entry.fieldType);
            const fieldArrayLength = entry.arrayLength === undefined ? 1 : entry.arrayLength;
            const bits = fieldTypeEnum.bits * fieldArrayLength;
            const field = {
                id: entry.id,
                permissionId: entry.permissionId,
                solidityType: fieldTypeEnum.solidityType,
                fieldType: entry.fieldType,
                fieldTypeSolidityEnum: fieldTypeEnum.name,
                arrayLength: fieldArrayLength,
                visibility: "FieldVisibility.PUBLIC",
                isString: fieldTypeEnum.isString,
                elementBits: fieldTypeEnum.bits,
                totalBits: bits,
                key: entry.key,
                slot: -1,
                offset: -1,
                description: entry.description
            };
            return field;
        });
        fields.sort((a, b) => {
            // sort static arrays to the end so that other fields can pack easily before we start needing 0 offsets
            if (a.arrayLength > 1 && b.arrayLength == 1) {
                return 1;
            }
            if (a.arrayLength == 1 && b.arrayLength > 1) {
                return -1;
            }
            if (a.totalBits >= 256 && b.totalBits >= 256) {
                return 0;
            }
            return b.totalBits - a.totalBits;
        });
        let slots = [];
        let slot = new ContractStorageSlot();
        slots.push(slot);
        let slotNumber = 0;
        let offset = 0;
        function nextSlot() {
            slot = new ContractStorageSlot();
            slots.push(slot);
            slotNumber += Math.floor(offset / 256);
            offset = offset % 256;
        }
        fields = fields.map((field, index) => {
            if (field.arrayLength == 0) {
                field.slot = 0;
                field.offset = 0;
            }
            else if (field.arrayLength > 1) {
                if (offset > 0) {
                    offset = 256;
                    nextSlot();
                }
                field.slot = slotNumber;
                field.offset = 0;
            }
            else {
                field.slot = slotNumber;
                field.offset = offset;
            }
            slot.fieldIDs.push(field.id);
            // how many more slots this spans
            let slotSpan = Math.floor(field.totalBits / 256);
            let bitsUsedThisSlot = field.totalBits;
            if (slotSpan > 1) {
                for (let i = 0; i < slotSpan - 1; i++) {
                    nextSlot();
                    slot.fieldIDs.push(field.id);
                }
                bitsUsedThisSlot = field.totalBits - ((slotSpan - 1) * 256);
            }
            // skip this if there aren't more bits later
            if (index < entries.length - 1 && fields[index + 1].totalBits > 0) {
                offset += bitsUsedThisSlot;
                if (offset >= 256) {
                    nextSlot();
                }
            }
            return field;
        });
        return { slots: slots, fields: fields };
    }
    hasLiteRef() {
        return this.fields.some((field) => field.fieldType === "literef");
    }
    getMetadataStructName() {
        return `Metadata`;
    }
    liteRefFieldCount() {
        // Count fields where fieldType is "literef"
        return this.storage.fields.filter((field) => field.fieldTypeSolidityEnum === "LITEREF").length;
    }
    liteRefArrayLength(which) {
        // Filter fields by fieldType "literef" and find by index
        const liteRefFields = this.storage.fields.filter((field) => field.fieldTypeSolidityEnum === "LITEREF");
        const liteRefField = liteRefFields[which];
        return liteRefField ? liteRefField.arrayLength : 0;
    }
    liteRefSlotNumber(which) {
        // Filter fields by fieldType "literef" and find by index
        const liteRefFields = this.storage.fields.filter((field) => field.fieldTypeSolidityEnum === "LITEREF");
        const liteRefField = liteRefFields[which];
        return liteRefField ? liteRefField.slot : 0;
    }
    validate() {
        const patchTypes = [Feature.PATCH, Feature.PATCH1155, Feature.PATCHACCOUNT];
        const patchTypeCount = this.features.filter(feature => patchTypes.includes(feature)).length;
        if (patchTypeCount > 1) {
            throw new Error('PATCH, 1155PATCH, and ACCOUNTPATCH are mutually exclusive.');
        }
        const fragmentTypes = [Feature.FRAGMENTMULTI, Feature.FRAGMENTSINGLE];
        const fragmentTypeCount = this.features.filter(feature => fragmentTypes.includes(feature)).length;
        if (fragmentTypeCount > 1) {
            throw new Error('FRAGMENTMULTI and FRAGMENTSINGLE are mutually exclusive.');
        }
        const hasReversible = this.features.includes(Feature.REVERSIBLE);
        if (hasReversible && patchTypeCount === 0) {
            throw new Error('REVERSIBLE feature requires at least one of PATCH, 1155PATCH, or ACCOUNTPATCH to be present.');
        }
    }
    slots() {
        return this.storage.slots.length;
    }
    getField(fieldID) {
        for (let i = 0; i < this.storage.fields.length; i++) {
            if (this.storage.fields[i].id == fieldID) {
                return this.storage.fields[i];
            }
        }
        throw Error("No field with ID " + fieldID);
    }
    getFieldTypeEnum(name) {
        const fieldTypeMap = {
            bool: { solidityType: "bool", name: "BOOLEAN", bits: 1, isString: false },
            int8: { solidityType: "int8", name: "INT8", bits: 8, isString: false },
            int16: { solidityType: "int16", name: "INT16", bits: 16, isString: false },
            int32: { solidityType: "int32", name: "INT32", bits: 32, isString: false },
            int64: { solidityType: "int64", name: "INT64", bits: 64, isString: false },
            int128: { solidityType: "int128", name: "INT128", bits: 128, isString: false },
            int256: { solidityType: "int256", name: "INT256", bits: 256, isString: false },
            uint8: { solidityType: "uint8", name: "UINT8", bits: 8, isString: false },
            uint16: { solidityType: "uint16", name: "UINT16", bits: 16, isString: false },
            uint32: { solidityType: "uint32", name: "UINT32", bits: 32, isString: false },
            uint64: { solidityType: "uint64", name: "UINT64", bits: 64, isString: false },
            uint128: { solidityType: "uint128", name: "UINT128", bits: 128, isString: false },
            uint256: { solidityType: "uint256", name: "UINT256", bits: 256, isString: false },
            char8: { solidityType: "string", name: "CHAR8", bits: 64, isString: true },
            char16: { solidityType: "string", name: "CHAR16", bits: 128, isString: true },
            char32: { solidityType: "string", name: "CHAR32", bits: 256, isString: true },
            char64: { solidityType: "string", name: "CHAR64", bits: 512, isString: true },
            literef: { solidityType: "uint64", name: "LITEREF", bits: 64, isString: false },
        };
        const fieldType = fieldTypeMap[name];
        if (!fieldType) {
            throw new Error(`Unknown field type: ${name}`);
        }
        return fieldType;
    }
}
exports.ContractSchemaImpl = ContractSchemaImpl;
