"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const Utilities_1 = require("../core/Utilities");
const IField_1 = require("../dataform/IField");
const Database_1 = require("../minecraft/Database");
const LegacyDocumentationDefinition_1 = require("../minecraft/docs/LegacyDocumentationDefinition");
const JsonSchemaDefinition_1 = require("../jsonschema/JsonSchemaDefinition");
const StorageUtilities_1 = require("../storage/StorageUtilities");
const ContentIndex_1 = require("../core/ContentIndex");
const DataFormUtilities_1 = require("../dataform/DataFormUtilities");
const EntityTypeDefinition_1 = require("../minecraft/EntityTypeDefinition");
const ICondition_1 = require("../dataform/ICondition");
const FieldUtilities_1 = require("../dataform/FieldUtilities");
const JsonFormExclusionList = ["is_a", "in_the", "_with_"];
const MAX_FORM_DEPTH = 100;
class FormJsonDocumentationGenerator {
    defsById = {};
    defsByTitle = {};
    defRefs = {};
    defCategories = {};
    async updateFormSource(folder, isPreview) {
        this.defsById = {};
        this.defsByTitle = {};
        const metadataFolder = isPreview
            ? await Database_1.default.loadPreviewMetadataFolder()
            : await Database_1.default.loadReleaseMetadataFolder();
        const schemaFolder = metadataFolder?.ensureFolder("json_schemas");
        if (schemaFolder) {
            await this.loadSchemas(schemaFolder, "misc");
        }
        const formJsonFolder = folder.ensureFolder("forms");
        await formJsonFolder.ensureExists();
        await this.exportJsonSchemaForms(formJsonFolder);
        const aiGoalsNode = await LegacyDocumentationDefinition_1.default.loadNode("entities", "/Server Entity Documentation/AI Goals/", isPreview);
        if (aiGoalsNode) {
            await this.generateFormNodesFromLegacyDocNode(formJsonFolder, aiGoalsNode, "entity");
        }
        const attributesNode = await LegacyDocumentationDefinition_1.default.loadNode("entities", "/Server Entity Documentation/Attributes/", isPreview);
        if (attributesNode) {
            await this.generateFormNodesFromLegacyDocNode(formJsonFolder, attributesNode, "entity");
        }
        const propertiesNode = await LegacyDocumentationDefinition_1.default.loadNode("entities", "/Server Entity Documentation/Properties/", isPreview);
        if (propertiesNode) {
            await this.generateFormNodesFromLegacyDocNode(formJsonFolder, propertiesNode, "entity");
        }
        const entityComponentsNode = await LegacyDocumentationDefinition_1.default.loadNode("entities", "/Server Entity Documentation/Components/", isPreview);
        if (entityComponentsNode) {
            await this.generateFormNodesFromLegacyDocNode(formJsonFolder, entityComponentsNode, "entity");
        }
        const triggersComponentsNode = await LegacyDocumentationDefinition_1.default.loadNode("entities", "/Server Entity Documentation/Triggers/", isPreview);
        if (triggersComponentsNode) {
            await this.generateFormNodesFromLegacyDocNode(formJsonFolder, triggersComponentsNode, "entity");
        }
        const filtersComponentsNode = await LegacyDocumentationDefinition_1.default.loadNode("entities", "/Filters/", isPreview);
        if (filtersComponentsNode) {
            await this.generateFormNodesFromLegacyDocNode(formJsonFolder, filtersComponentsNode, "entityfilters");
        }
        const entityEventsComponentsNode = await LegacyDocumentationDefinition_1.default.loadNode("entity-events", "/", isPreview);
        if (entityEventsComponentsNode) {
            await this.generateFormNodesFromLegacyDocNode(formJsonFolder, entityEventsComponentsNode, "entityevents");
        }
        const blocksComponentsNode = await LegacyDocumentationDefinition_1.default.loadNode("blocks", "/Blocks/Block Components/", isPreview);
        if (blocksComponentsNode) {
            await this.generateFormNodesFromLegacyDocNode(formJsonFolder, blocksComponentsNode, "block");
        }
        const schemasNode = await LegacyDocumentationDefinition_1.default.loadNode("schemas", "/Schemas/", isPreview);
        if (schemasNode) {
            await this.generateFormNodesFromPseudoSchemaDocs(formJsonFolder, schemasNode, "visual");
        }
        const fogsNode = await LegacyDocumentationDefinition_1.default.loadNode("fogs", "/Fog Definitions/Fog Schema/", isPreview);
        if (fogsNode) {
            await this.generateFormNodesFromPseudoSchemaDocs(formJsonFolder, fogsNode, "fogs");
        }
        /* These come from JSON schema now.
        const biomesNode = await LegacyDocumentationDefinition.loadNode("biomes", "/Schema/", isPreview);
    
        if (biomesNode) {
          await this.generateFormNodesFromNode(formJsonFolder, biomesNode, "biomes");
        }
          */
        const featuresNode = await LegacyDocumentationDefinition_1.default.loadNode("features", "/Supported features/", isPreview);
        if (featuresNode) {
            const resultForms = await this.generateFormNodesFromPseudoSchemaDocs(formJsonFolder, featuresNode, "features");
            if (resultForms) {
                this.generateSubformsFromFields(formJsonFolder, resultForms, "features");
            }
        }
        const molangQfNode = await LegacyDocumentationDefinition_1.default.loadNode("molang", "/Query Functions/List of Entity Queries/", isPreview);
        if (molangQfNode) {
            await this.generateFormNodesFromLegacyDocNode(formJsonFolder, molangQfNode, "molang");
        }
    }
    async generateSubformsFromFields(formJsonFolder, resultForms, prefix) {
        const outerForms = [];
        for (const form of resultForms) {
            if (form && form.fields) {
                for (const field of form.fields) {
                    if (field.subForm) {
                        const newForm = {
                            id: field.id,
                            title: field.title,
                            fields: field.subForm.fields,
                        };
                        outerForms.push(newForm);
                    }
                }
            }
        }
        for (const form of outerForms) {
            if (form && form.id) {
                const name = this.getFormFileName(form.id, form.dataVersion);
                DataFormUtilities_1.default.mergeFields(form);
                DataFormUtilities_1.default.fixupFields(form);
                await this.annotateFormJson(form, name, prefix);
                await this.mergeToFile(formJsonFolder, name, form, prefix);
            }
        }
        return outerForms;
    }
    async generateFormNodesFromPseudoSchemaDocs(formJsonFolder, node, prefix) {
        if (!node.description && !node.examples) {
            return undefined;
        }
        const formStack = [];
        let formStackIndex = -1;
        const outerForms = [];
        const objectSkippedAt = [];
        let lastField = [];
        let ignoreNextObject = 0;
        let integrateNextProperty = false;
        let integrateNextNextProperty = false;
        let nodeSet = node.description;
        if (!nodeSet && node.examples) {
            nodeSet = [];
            for (const examp of node.examples) {
                nodeSet.push(...examp.text);
            }
        }
        for (const docLine of nodeSet) {
            let docLineMod = docLine;
            let commentStr = undefined;
            integrateNextNextProperty = false;
            let commentIndex = docLine.indexOf(" //");
            if (commentIndex > 0) {
                commentStr = docLine.substring(commentIndex + 3);
                docLineMod = docLine.substring(0, commentIndex);
            }
            const docLineTrim = docLineMod.replace(/ /gi, "").trim();
            const startQuote = docLineMod.indexOf('"');
            let endQuote = docLineMod.lastIndexOf('"');
            const endCompare = docLineMod.lastIndexOf(">");
            let mainStr = undefined;
            if (startQuote >= 0 && endQuote > startQuote) {
                if (endCompare === endQuote + 1) {
                    endQuote = endCompare;
                }
                mainStr = docLineMod.substring(startQuote + 1, endQuote);
            }
            if (docLineTrim.endsWith(":{") && !mainStr) {
                // this is the pattern from schemas.json
                const firstColon = docLineTrim.indexOf(":");
                if (firstColon >= 0) {
                    formStackIndex++;
                    for (let i = formStackIndex; i < MAX_FORM_DEPTH; i++) {
                        lastField[i] = undefined;
                        objectSkippedAt[i] = 0;
                    }
                    const form = { id: docLineTrim.substring(0, firstColon), fields: [] };
                    outerForms.push(form);
                    formStack[formStackIndex] = form;
                    const secondColon = docLineTrim.indexOf(":", firstColon + 1);
                    if (secondColon > firstColon) {
                        const verStr = docLineTrim.substring(firstColon + 1, secondColon);
                        if (Utilities_1.default.isVersionString(verStr)) {
                            form.dataVersion = Utilities_1.default.normalizeVersionString(verStr);
                        }
                    }
                }
            }
            else if (docLineTrim === "{" && formStackIndex < 0) {
                // this is the pattern from fogs.json, features.json (only one form/object, typically) - handle the outer level form
                formStackIndex++;
                for (let i = formStackIndex; i < MAX_FORM_DEPTH; i++) {
                    lastField[i] = undefined;
                    objectSkippedAt[i] = 0;
                }
                const form = {
                    id: (prefix ? prefix : "obj") + (outerForms.length > 0 ? outerForms.length + 1 : ""),
                    fields: [],
                };
                outerForms.push(form);
                formStack[formStackIndex] = form;
            }
            else if (docLineTrim === "{") {
                if (ignoreNextObject > 0) {
                    ignoreNextObject--;
                    objectSkippedAt[formStackIndex]++;
                }
                else {
                    const lastFieldStack = lastField[formStackIndex];
                    formStackIndex++;
                    for (let i = formStackIndex; i < MAX_FORM_DEPTH; i++) {
                        lastField[i] = undefined;
                        objectSkippedAt[i] = 0;
                    }
                    if (lastFieldStack && !lastFieldStack.subForm) {
                        const form = { id: undefined, fields: [] };
                        formStack[formStackIndex] = form;
                        lastFieldStack.subForm = form;
                    }
                }
            }
            else if (docLineTrim === "}" && formStackIndex >= 0) {
                if (objectSkippedAt[formStackIndex] > 0) {
                    objectSkippedAt[formStackIndex]--;
                }
                else {
                    formStackIndex--;
                }
            }
            if (formStackIndex >= 0 && formStack[formStackIndex] && mainStr) {
                let fieldDefinition = undefined;
                if (docLineTrim.startsWith('int"')) {
                    fieldDefinition = {
                        dataType: IField_1.FieldDataType.int,
                        id: mainStr,
                        title: Utilities_1.default.humanifyMinecraftName(mainStr),
                    };
                }
                else if (docLineTrim.startsWith('bool"')) {
                    fieldDefinition = {
                        dataType: IField_1.FieldDataType.boolean,
                        id: mainStr,
                        title: Utilities_1.default.humanifyMinecraftName(mainStr),
                    };
                }
                else if (docLineTrim.startsWith('string"')) {
                    fieldDefinition = {
                        dataType: IField_1.FieldDataType.string,
                        id: mainStr,
                        title: Utilities_1.default.humanifyMinecraftName(mainStr),
                    };
                }
                else if (docLineTrim.startsWith('molang"')) {
                    fieldDefinition = {
                        dataType: IField_1.FieldDataType.molang,
                        id: mainStr,
                        title: Utilities_1.default.humanifyMinecraftName(mainStr),
                    };
                }
                else if (docLineTrim.startsWith('array"')) {
                    fieldDefinition = {
                        dataType: IField_1.FieldDataType.stringArray,
                        id: mainStr,
                        title: Utilities_1.default.humanifyMinecraftName(mainStr),
                    };
                    const firstArrow = mainStr.indexOf("<");
                    const secondArrow = mainStr.indexOf(">");
                    const lastFieldStack = lastField[formStackIndex - 1];
                    if (firstArrow >= 0 && secondArrow > firstArrow) {
                        if (lastFieldStack &&
                            (lastFieldStack.dataType === IField_1.FieldDataType.object ||
                                lastFieldStack.dataType === IField_1.FieldDataType.objectArray)) {
                            lastFieldStack.dataType = IField_1.FieldDataType.keyedObjectCollection;
                            lastFieldStack.keyDescription = mainStr;
                        }
                    }
                    integrateNextNextProperty = true;
                    ignoreNextObject++;
                }
                else if (docLineTrim.startsWith('enumerated_value"')) {
                    const firstArrow = mainStr.indexOf("<");
                    const secondArrow = mainStr.indexOf(">");
                    if (firstArrow >= 0 && secondArrow > firstArrow) {
                        const fieldId = mainStr.substring(0, firstArrow);
                        const choiceStr = mainStr.substring(firstArrow + 1, secondArrow);
                        const choices = choiceStr.split(",");
                        const choiceSet = [];
                        for (const choice of choices) {
                            if (choice.length > 0) {
                                choiceSet.push({ id: Utilities_1.default.removeQuotes(choice) });
                            }
                        }
                        fieldDefinition = {
                            dataType: IField_1.FieldDataType.string,
                            id: fieldId,
                            choices: choiceSet,
                            title: Utilities_1.default.humanifyMinecraftName(mainStr),
                        };
                    }
                    else {
                        fieldDefinition = {
                            dataType: IField_1.FieldDataType.string,
                            id: mainStr,
                            title: Utilities_1.default.humanifyMinecraftName(mainStr),
                        };
                    }
                }
                else if (docLineTrim.startsWith('object"')) {
                    let fieldDataType = IField_1.FieldDataType.object;
                    const firstArrow = mainStr.indexOf("<");
                    const secondArrow = mainStr.indexOf(">");
                    const lastFieldStack = lastField[formStackIndex - 1];
                    const curFieldStack = lastField[formStackIndex];
                    if (firstArrow >= 0 && secondArrow > firstArrow) {
                        const subStr = mainStr.substring(firstArrow, secondArrow - firstArrow);
                        if (subStr.indexOf("array") >= 0 && curFieldStack) {
                            if (!curFieldStack.alternates) {
                                curFieldStack.alternates = [];
                            }
                            const newField = {
                                id: curFieldStack.id,
                                dataType: IField_1.FieldDataType.objectArray,
                            };
                            curFieldStack.alternates.push(newField);
                            lastField[formStackIndex] = newField;
                        }
                        else {
                            if (lastFieldStack &&
                                (lastFieldStack.dataType === IField_1.FieldDataType.object ||
                                    lastFieldStack.dataType === IField_1.FieldDataType.objectArray)) {
                                lastFieldStack.dataType = IField_1.FieldDataType.keyedObjectCollection;
                                lastFieldStack.keyDescription = mainStr;
                            }
                            ignoreNextObject++;
                        }
                    }
                    else {
                        fieldDefinition = {
                            dataType: fieldDataType,
                            id: mainStr,
                            title: Utilities_1.default.humanifyMinecraftName(mainStr),
                        };
                    }
                }
                else if (docLineTrim.startsWith('version"')) {
                    fieldDefinition = {
                        dataType: IField_1.FieldDataType.version,
                        id: mainStr,
                        title: Utilities_1.default.humanifyMinecraftName(mainStr),
                    };
                }
                if (fieldDefinition) {
                    if (integrateNextProperty && fieldDefinition.id.indexOf("<") >= 0) {
                        if (fieldDefinition.dataType === IField_1.FieldDataType.molang) {
                            const lastFieldStack = lastField[formStackIndex];
                            if (lastFieldStack) {
                                lastFieldStack.dataType = IField_1.FieldDataType.molangArray;
                            }
                        }
                        integrateNextProperty = false;
                    }
                    else {
                        integrateNextProperty = false;
                        if (commentStr) {
                            fieldDefinition.description = commentStr.trim();
                        }
                        if (docLineTrim.indexOf(":opt") >= 0) {
                            fieldDefinition.isRequired = false;
                        }
                        else {
                            fieldDefinition.isRequired = true;
                        }
                        lastField[formStackIndex] = fieldDefinition;
                        formStack[formStackIndex].fields.push(fieldDefinition);
                    }
                }
                if (integrateNextNextProperty) {
                    integrateNextProperty = true;
                }
            }
        }
        for (const form of outerForms) {
            if (form && form.id) {
                const name = this.getFormFileName(form.id, form.dataVersion);
                DataFormUtilities_1.default.mergeFields(form);
                DataFormUtilities_1.default.fixupFields(form);
                await this.annotateFormJson(form, name, prefix);
                await this.mergeToFile(formJsonFolder, name, form, prefix);
            }
        }
        return outerForms;
    }
    async generateFormJson(inputFolder, outputFolder) {
        await outputFolder.deleteAllFolderContents();
        await this.generateFormJsonFromFolder(inputFolder, outputFolder);
    }
    async generateFormJsonFromFolder(inputFolder, outputFolder) {
        await outputFolder.ensureExists();
        await inputFolder.load();
        const fileList = { files: [], folders: [] };
        for (const folderName in inputFolder.folders) {
            const folder = inputFolder.folders[folderName];
            if (folder) {
                await this.generateFormJsonFromFolder(folder, outputFolder.ensureFolder(folderName));
                fileList.folders.push(folderName);
            }
        }
        for (const fileName in inputFolder.files) {
            const file = inputFolder.files[fileName];
            try {
                if (file) {
                    await file.loadContent();
                    const jsonO = StorageUtilities_1.default.getJsonObject(file);
                    if (jsonO) {
                        const outputFile = outputFolder.ensureFile(fileName);
                        fileList.files.push(fileName);
                        await this.finalizeJsonForm(jsonO, outputFile);
                    }
                }
            }
            catch (e) {
                console.log("Error processing " + fileName + ": " + e);
            }
        }
        const indexFile = outputFolder.ensureFile("index.json");
        indexFile.setContent(JSON.stringify(fileList));
        await indexFile.saveContent();
    }
    async finalizeJsonForm(formObj, outputFile) {
        if (!formObj.generated_doNotEdit && !formObj.generatedFromSchema_doNotEdit && formObj.id) {
            const id = formObj.id.replace(/:/gi, "_").replace(/\./gi, "_");
            await outputFile.loadContent();
            const originalNode = StorageUtilities_1.default.getJsonObject(outputFile);
            await this.annotateFormJson(formObj, id, outputFile.parentFolder.name, originalNode);
        }
        if (formObj.generatedFromSchema_doNotEdit) {
            this.mergeOntoForm(formObj, formObj.generatedFromSchema_doNotEdit);
        }
        if (formObj.generated_doNotEdit) {
            this.mergeOntoForm(formObj, formObj.generated_doNotEdit);
        }
        formObj.generated_doNotEdit = undefined;
        formObj.generatedFromSchema_doNotEdit = undefined;
        outputFile.setContent(JSON.stringify(formObj, undefined, 2));
        await outputFile.saveContent();
    }
    mergeOntoForm(targetForm, formToMergeOn) {
        if (!targetForm.description || targetForm.description === "") {
            targetForm.description = formToMergeOn.description;
        }
        if (!targetForm.title || targetForm.title === "") {
            targetForm.title = formToMergeOn.title;
        }
        if (targetForm.samples) {
            for (const samplePath in formToMergeOn.samples) {
                targetForm.samples[samplePath] = formToMergeOn.samples[samplePath];
            }
        }
        else {
            targetForm.samples = formToMergeOn.samples;
        }
        if (!targetForm.id) {
            targetForm.id = formToMergeOn.id;
        }
        if (!targetForm.note) {
            targetForm.note = formToMergeOn.note;
        }
        if (!targetForm.note2) {
            targetForm.note2 = formToMergeOn.note2;
        }
        if (!targetForm.note3) {
            targetForm.note3 = formToMergeOn.note3;
        }
        if (!targetForm.restrictions) {
            targetForm.restrictions = formToMergeOn.restrictions;
        }
        if (!targetForm.requires) {
            targetForm.requires = formToMergeOn.requires;
        }
        if (!targetForm.scalarField) {
            targetForm.scalarField = formToMergeOn.scalarField;
        }
        if (!targetForm.customField) {
            targetForm.customField = formToMergeOn.customField;
        }
        if (!targetForm.scalarFieldUpgradeName) {
            targetForm.scalarFieldUpgradeName = formToMergeOn.scalarFieldUpgradeName;
        }
        if (!targetForm.isDeprecated) {
            targetForm.isDeprecated = formToMergeOn.isDeprecated;
        }
        if (!targetForm.versionIntroduced) {
            targetForm.versionIntroduced = formToMergeOn.versionIntroduced;
        }
        if (!targetForm.versionDeprecated) {
            targetForm.versionDeprecated = formToMergeOn.versionDeprecated;
        }
        if (!targetForm.tags) {
            targetForm.tags = formToMergeOn.tags;
        }
        if (!targetForm.isInternal) {
            targetForm.isInternal = formToMergeOn.isInternal;
        }
        if (!targetForm.dataVersion) {
            targetForm.dataVersion = formToMergeOn.dataVersion;
        }
        if (targetForm.fields && targetForm.fields.length === 0) {
            targetForm.fields = formToMergeOn.fields;
        }
        else {
            const formFields = {};
            if (!targetForm.fields) {
                targetForm.fields = formToMergeOn.fields;
            }
            else {
                for (const targetField of targetForm.fields) {
                    formFields[targetField.id] = targetField;
                }
                for (const mergeOnField of formToMergeOn.fields) {
                    const targetField = formFields[mergeOnField.id];
                    if (!targetField) {
                        targetForm.fields.push(mergeOnField);
                    }
                    else {
                        if (targetField.isRemoved) {
                            formFields[mergeOnField.id] = undefined;
                            const newFieldArr = [];
                            for (const updatedField of targetForm.fields) {
                                if (updatedField.id !== mergeOnField.id) {
                                    newFieldArr.push(updatedField);
                                }
                            }
                            targetForm.fields = newFieldArr;
                        }
                        else {
                            targetField.samples = mergeOnField.samples;
                            if (!targetField.defaultValue) {
                                targetField.defaultValue = mergeOnField.defaultValue;
                            }
                            if (mergeOnField.subFormId) {
                                targetField.subFormId = mergeOnField.subFormId;
                            }
                            if (mergeOnField.subForm) {
                                if (!targetField.subForm) {
                                    targetField.subForm = {
                                        fields: [],
                                    };
                                }
                                this.mergeOntoForm(targetField.subForm, mergeOnField.subForm);
                            }
                            if (!targetField.alternates) {
                                targetField.alternates = mergeOnField.alternates;
                            }
                            if (!targetField.description) {
                                targetField.description = mergeOnField.description;
                            }
                            if (!targetField.title) {
                                targetField.title = mergeOnField.title;
                            }
                            if (!targetField.versionDeprecated) {
                                targetField.versionDeprecated = mergeOnField.versionDeprecated;
                            }
                            if (!targetField.versionIntroduced) {
                                targetField.versionIntroduced = mergeOnField.versionIntroduced;
                            }
                            if (!targetField.humanifyValues) {
                                targetField.humanifyValues = mergeOnField.humanifyValues;
                            }
                            if (!targetField.tags) {
                                targetField.tags = mergeOnField.tags;
                            }
                            if (!targetField.minLength) {
                                targetField.minLength = mergeOnField.minLength;
                            }
                            if (!targetField.maxLength) {
                                targetField.maxLength = mergeOnField.maxLength;
                            }
                            if (!targetField.minValue) {
                                targetField.minValue = mergeOnField.minValue;
                            }
                            if (!targetField.priority) {
                                targetField.priority = mergeOnField.priority;
                            }
                            if (!targetField.note) {
                                targetField.note = mergeOnField.note;
                            }
                            if (!targetField.note2) {
                                targetField.note2 = mergeOnField.note2;
                            }
                            if (!targetField.note3) {
                                targetField.note3 = mergeOnField.note3;
                            }
                            if (!targetField.fixedLength) {
                                targetField.fixedLength = mergeOnField.fixedLength;
                            }
                            if (!targetField.undefinedIfEmpty) {
                                targetField.undefinedIfEmpty = mergeOnField.undefinedIfEmpty;
                            }
                            if (!targetField.allowedKeys) {
                                targetField.allowedKeys = mergeOnField.allowedKeys;
                            }
                            if (!targetField.objectArrayTitleFieldKey) {
                                targetField.objectArrayTitleFieldKey = mergeOnField.objectArrayTitleFieldKey;
                            }
                            if (!targetField.objectArrayToSubFieldKey) {
                                targetField.objectArrayToSubFieldKey = mergeOnField.objectArrayToSubFieldKey;
                            }
                            if (!targetField.matchObjectArrayLengthToSubFieldLength) {
                                targetField.matchObjectArrayLengthToSubFieldLength =
                                    mergeOnField.matchObjectArrayLengthToSubFieldLength;
                            }
                            if (!targetField.matchObjectArrayToSubFieldKey) {
                                targetField.matchObjectArrayToSubFieldKey = mergeOnField.matchObjectArrayToSubFieldKey;
                            }
                            if (!targetField.keyDescription) {
                                targetField.keyDescription = mergeOnField.keyDescription;
                            }
                            if (!targetField.maxValue) {
                                targetField.maxValue = mergeOnField.maxValue;
                            }
                            if (!targetField.suggestedMinValue) {
                                targetField.suggestedMinValue = mergeOnField.suggestedMinValue;
                            }
                            if (!targetField.suggestedMaxValue) {
                                targetField.suggestedMaxValue = mergeOnField.suggestedMaxValue;
                            }
                            if (!targetField.isRequired) {
                                targetField.isRequired = mergeOnField.isRequired;
                            }
                            if (targetField.dataType === undefined) {
                                targetField.dataType = mergeOnField.dataType;
                            }
                            if (mergeOnField.choices) {
                                if (!targetField.choices) {
                                    targetField.choices = [];
                                }
                                for (const mergeOnChoice of mergeOnField.choices) {
                                    let foundChoice = false;
                                    for (const targetChoice of targetField.choices) {
                                        if (targetChoice.id === mergeOnChoice.id) {
                                            foundChoice = true;
                                            if (!targetChoice.title) {
                                                targetChoice.title = mergeOnChoice.title;
                                            }
                                            if (!targetChoice.description) {
                                                targetChoice.description = mergeOnChoice.description;
                                            }
                                            if (!targetChoice.isDeprecated) {
                                                targetChoice.isDeprecated = mergeOnChoice.isDeprecated;
                                            }
                                            if (!targetChoice.iconImage) {
                                                targetChoice.iconImage = mergeOnChoice.iconImage;
                                            }
                                            if (!targetChoice.versionIntroduced) {
                                                targetChoice.versionIntroduced = mergeOnChoice.versionIntroduced;
                                            }
                                            if (!targetChoice.versionDeprecated) {
                                                targetChoice.versionDeprecated = mergeOnChoice.versionDeprecated;
                                            }
                                            break;
                                        }
                                    }
                                    if (!foundChoice) {
                                        targetField.choices.push(mergeOnChoice);
                                    }
                                }
                                targetField.choices = mergeOnField.choices;
                            }
                            if (!targetField.validity) {
                                targetField.validity = mergeOnField.validity;
                            }
                        }
                    }
                }
            }
        }
    }
    async exportJsonSchemaForms(formJsonFolder) {
        for (const key in this.defsByTitle) {
            if (this.getIsStandaloneSchemaFile(key)) {
                await this.processAndExportJsonSchemaNode(formJsonFolder, key);
            }
        }
    }
    getIsStandaloneSchemaFile(keyOrTitle) {
        if (keyOrTitle.startsWith("#/definitions/")) {
            keyOrTitle = keyOrTitle.substring(14);
        }
        let formNode = this.defsById[keyOrTitle];
        if (!formNode) {
            formNode = this.defsByTitle[keyOrTitle];
        }
        if (formNode && formNode.properties && Object.keys(formNode.properties).length > 0) {
            if (formNode.title) {
                keyOrTitle = formNode.title;
            }
            return !this.isDisallowedSchemaFile(keyOrTitle) && keyOrTitle.indexOf(" - ") < 0 && keyOrTitle.indexOf("_-_") < 0;
        }
        return false;
    }
    isDisallowedSchemaFile(key) {
        if (key.indexOf("struct ") >= 0) {
            return true;
        }
        return false;
    }
    getFormPathForJsonSchemaForm(schemaKey) {
        if (schemaKey.startsWith("#/definitions/")) {
            schemaKey = schemaKey.substring(14);
        }
        const form = this.getDefinitionFromId(schemaKey);
        let title = form && form.title ? form.title : schemaKey;
        let versionlessTitle = title.substring(title.lastIndexOf(".") + 1);
        versionlessTitle = this.getVersionlessString(versionlessTitle);
        let name = this.getFormFileNameBase(versionlessTitle);
        const category = this.defCategories[schemaKey];
        if (category) {
            name = category + "/" + name;
        }
        return name;
    }
    async processAndExportJsonSchemaNode(formJsonFolder, title) {
        const formNode = await this.getJsonFormFromJsonSchemaKey(title);
        if (formNode && formNode.fields.length > 0) {
            if (title.indexOf("omponents") >= 0) {
                if (formNode.fields) {
                    for (const field of formNode.fields) {
                        if (field.subForm) {
                            const name = this.getFormFileNameBase(field.id);
                            const category = this.defCategories[title];
                            field.subForm.id = field.id;
                            await this.annotateFormJson(field.subForm, name, category);
                            await this.mergeToFile(formJsonFolder, name, field.subForm, category, true);
                        }
                    }
                }
            }
            let versionlessTitle = title.substring(title.lastIndexOf(".") + 1);
            versionlessTitle = this.getVersionlessString(versionlessTitle);
            const name = this.getFormFileNameBase(versionlessTitle);
            let matchesExclusion = false;
            for (const exclusion of JsonFormExclusionList) {
                if (name.indexOf(exclusion) >= 0) {
                    matchesExclusion = true;
                    break;
                }
            }
            if (!matchesExclusion) {
                const category = this.defCategories[title];
                await this.annotateFormJson(formNode, name, category);
                await this.mergeToFile(formJsonFolder, name, formNode, category, true);
            }
        }
    }
    getFormFileNameBase(key) {
        key = key.toLowerCase();
        if (key.startsWith("struct_") || key.startsWith("struct ")) {
            key = key.substring(7);
        }
        if ((key.startsWith("enum_") || key.startsWith("enum ")) && key.indexOf("num_property") < 0) {
            key = key.substring(5);
        }
        key = key.replace("sharedtypes", "");
        key = StorageUtilities_1.default.sanitizePathBasic(key);
        return key;
    }
    getVersionlessString(key) {
        let verStart = key.indexOf(" v1.");
        if (verStart >= 0) {
            const nextSpace = key.indexOf(" ", verStart + 4);
            if (nextSpace >= 0) {
                key = key.substring(0, verStart) + key.substring(nextSpace + 1);
            }
            else {
                key = key.substring(0, verStart);
            }
        }
        return key;
    }
    async getJsonFormFromJsonSchemaKey(keyName) {
        let rootNodeName = undefined;
        let rootNodeNameVersionless = undefined;
        let rootNode = undefined;
        const keyVersionless = this.getVersionlessString(keyName);
        // attempt to get the latest version of a component by sorting on the node name e.g., minecraft:item v1.21.60 should sort later than minecraft:item v1.21.40
        // though we should replace this with a more sophisticated sorter for version :-/
        for (const candidateKey in this.defsByTitle) {
            const candidateKeyVersionless = this.getVersionlessString(candidateKey);
            if (candidateKeyVersionless === keyVersionless) {
                if (!rootNodeName ||
                    (candidateKey.localeCompare(rootNodeName) > 0 && candidateKeyVersionless === rootNodeNameVersionless)) {
                    rootNodeName = candidateKey;
                    rootNodeNameVersionless = candidateKeyVersionless;
                    rootNode = this.defsByTitle[candidateKey];
                }
            }
        }
        for (const key in this.defsById) {
            if (key.indexOf(keyName) >= 0) {
                const keyVersionless = this.getVersionlessString(key);
                if (!rootNodeName || (key.localeCompare(rootNodeName) > 0 && keyVersionless === rootNodeNameVersionless)) {
                    rootNodeName = key;
                    rootNodeNameVersionless = keyVersionless;
                    rootNode = this.defsByTitle[key];
                }
            }
        }
        if (rootNode === undefined) {
            return;
        }
        if (rootNodeName !== keyName) {
            return;
        }
        return await this.getJsonFormFromJsonSchemaDefinition(rootNode, keyName);
    }
    async getJsonFormFromJsonSchemaDefinition(node, nodeName, fieldList) {
        const fields = [];
        if (node.properties) {
            for (const propName in node.properties) {
                const propNode = node.properties[propName];
                if (propNode && typeof propNode !== "boolean") {
                    const field = await this.getFieldFromJsonPropertyNode(propNode, propName, fieldList);
                    if (field) {
                        fields.push(field);
                    }
                }
            }
        }
        if (node.required) {
            for (const propName of node.required) {
                for (const field of fields) {
                    if (field.id === propName) {
                        field.isRequired = true;
                        break;
                    }
                }
            }
        }
        if (!nodeName) {
            if (node.title) {
                nodeName = node.title;
            }
            else {
                nodeName = "";
            }
        }
        const docForm = {
            id: this.humanifyId(nodeName),
            title: Utilities_1.default.humanifyMinecraftName(nodeName),
            description: node.description ? node.description : undefined,
            fields: fields,
        };
        return docForm;
    }
    async loadSchemas(schemaFolder, categoryName) {
        await schemaFolder.load();
        for (const fileName in schemaFolder.files) {
            const file = schemaFolder.files[fileName];
            if (file && file.type === "json" && file.name !== "index.json") {
                const jsonSchema = await JsonSchemaDefinition_1.default.ensureOnFile(file);
                if (jsonSchema && jsonSchema.data) {
                    this.processDef(jsonSchema.data, categoryName);
                }
            }
        }
        for (const folderName in schemaFolder.folders) {
            const folder = schemaFolder.folders[folderName];
            if (folder) {
                if (!folder.name.startsWith("v1") && folder.name !== "common" && folder.name !== "components") {
                    categoryName = folder.name;
                }
                if (categoryName.startsWith("1.")) {
                    categoryName = "misc";
                }
                if ((schemaFolder.parentFolder?.name === "client" ||
                    schemaFolder.parentFolder?.parentFolder?.name === "client") &&
                    categoryName.indexOf("client") < 0) {
                    categoryName = "client_" + categoryName;
                }
                await this.loadSchemas(folder, categoryName);
            }
        }
    }
    processDef(schemaDef, category, depth = 0) {
        if (schemaDef["$id"]) {
            this.defsById[schemaDef["$id"]] = schemaDef;
            if (this.defRefs[schemaDef["$id"]] === undefined) {
                this.defRefs[schemaDef["$id"]] = depth ? 1 : 0;
            }
            if (schemaDef.title && category.indexOf("client") < 0) {
                if (schemaDef.title?.indexOf("lient ") >= 0 && category.indexOf("client") < 0) {
                    category = "client_" + category;
                }
            }
            this.defCategories[schemaDef["$id"]] = category;
        }
        if (schemaDef.title) {
            this.defsByTitle[category + "." + schemaDef.title] = schemaDef;
            if (this.defRefs[category + "." + schemaDef.title] === undefined) {
                this.defRefs[category + "." + schemaDef.title] = depth ? 1 : 0;
            }
            this.defCategories[category + "." + schemaDef.title] = category;
        }
        for (const propName in schemaDef.properties) {
            const propNode = schemaDef.properties[propName];
            if (propNode && typeof propNode !== "boolean") {
                if (propNode.$ref && Utilities_1.default.isUsableAsObjectKey(propNode.$ref)) {
                    if (this.defRefs[propNode.$ref] === undefined) {
                        this.defRefs[propNode.$ref] = 1;
                    }
                    else {
                        this.defRefs[propNode.$ref]++;
                    }
                }
            }
        }
        if (schemaDef.definitions) {
            for (const defName in schemaDef.definitions) {
                const def = schemaDef.definitions[defName];
                if (def && typeof def !== "boolean" && Utilities_1.default.isUsableAsObjectKey(defName.toString())) {
                    this.defsById[defName.toString()] = def;
                    this.defCategories[defName.toString()] = category;
                    this.processDef(def, category, depth + 1);
                }
            }
        }
    }
    async generateFormNodesFromLegacyDocNode(formJsonFolder, node, prefix) {
        for (const childNode of node.nodes) {
            if (childNode.name) {
                const name = this.getFormFileName(childNode.name);
                const formDocNode = this.getFormFromDocNode(childNode, childNode.name);
                const genFormDocNode = await this.getOriginalFormDefinition(formJsonFolder, name, prefix);
                await this.annotateFormJson(formDocNode, name, prefix, genFormDocNode);
                await this.mergeToFile(formJsonFolder, name, formDocNode, prefix);
            }
        }
    }
    async getOriginalFormDefinition(formJsonFolder, name, categoryName) {
        if (categoryName && categoryName.length > 0) {
            formJsonFolder = formJsonFolder.ensureFolder(categoryName);
        }
        name = name.toLowerCase();
        name = name.replace(/ /gi, "_");
        const file = formJsonFolder.ensureFile(name + ".form.json");
        await file.loadContent();
        return StorageUtilities_1.default.getJsonObject(file);
    }
    async mergeToFile(formJsonFolder, name, formDefNode, categoryName, isSchema) {
        if (categoryName && categoryName.length > 0) {
            formJsonFolder = formJsonFolder.ensureFolder(categoryName);
        }
        name = name.toLowerCase();
        name = name.replace(/ /gi, "_");
        const file = formJsonFolder.ensureFile(name + ".form.json");
        await file.loadContent();
        let jsonO = StorageUtilities_1.default.getJsonObject(file);
        if (isSchema) {
            if (jsonO) {
                jsonO.generatedFromSchema_doNotEdit = formDefNode;
            }
            else {
                jsonO = {
                    id: formDefNode.id,
                    fields: [],
                    generatedFromSchema_doNotEdit: formDefNode,
                };
            }
        }
        else {
            if (jsonO) {
                jsonO.generated_doNotEdit = formDefNode;
            }
            else {
                jsonO = {
                    id: formDefNode.id,
                    fields: [],
                    generated_doNotEdit: formDefNode,
                };
            }
        }
        file.setContent(JSON.stringify(jsonO, undefined, 2));
        await file.saveContent();
    }
    async annotateFormJson(formDefNode, name, prefix, originalNode) {
        let canonName = EntityTypeDefinition_1.default.getComponentFromBaseFileName(name);
        let isMinecraftComponent = false;
        if (name.startsWith("minecraft_")) {
            isMinecraftComponent = true;
            if (canonName.startsWith("behavior.")) {
                let hasPrioNode = false;
                for (const field of formDefNode.fields) {
                    if (field.id === "priority") {
                        hasPrioNode = true;
                    }
                }
                if (!hasPrioNode) {
                    formDefNode.fields.push({
                        id: "priority",
                        title: "Priority",
                        hideSamples: true,
                        description: "As priority approaches 0, the priority is increased. The higher the priority, the sooner this behavior will be executed as a goal.",
                        dataType: 0,
                    });
                }
            }
        }
        if (prefix === "entity" && isMinecraftComponent) {
            await this.addVanillaMatches(formDefNode, canonName, [
                ContentIndex_1.AnnotationCategory.entityComponentDependent,
                ContentIndex_1.AnnotationCategory.entityComponentDependentInGroup,
            ]);
            await this.addSamplesMatches(formDefNode, canonName, [
                ContentIndex_1.AnnotationCategory.entityComponentDependent,
                ContentIndex_1.AnnotationCategory.entityComponentDependentInGroup,
            ]);
        }
        else if (prefix === "item" && isMinecraftComponent) {
            await this.addVanillaMatches(formDefNode, canonName, [ContentIndex_1.AnnotationCategory.itemComponentDependent]);
            await this.addSamplesMatches(formDefNode, canonName, [ContentIndex_1.AnnotationCategory.itemComponentDependent]);
        }
        else if (prefix === "block" && isMinecraftComponent) {
            await this.addVanillaMatches(formDefNode, canonName, [ContentIndex_1.AnnotationCategory.blockComponentDependent]);
            await this.addSamplesMatches(formDefNode, canonName, [ContentIndex_1.AnnotationCategory.blockComponentDependent]);
        }
        else if (prefix === "entityfilters") {
            await this.addVanillaMatches(formDefNode, canonName, [ContentIndex_1.AnnotationCategory.entityFilter]);
            await this.addSamplesMatches(formDefNode, canonName, [ContentIndex_1.AnnotationCategory.entityFilter]);
        }
        else if (prefix === "features") {
            await this.addVanillaMatches(formDefNode, canonName, [ContentIndex_1.AnnotationCategory.entityFilter]);
            await this.addSamplesMatches(formDefNode, canonName, [ContentIndex_1.AnnotationCategory.entityFilter]);
        }
        if (formDefNode.samples) {
            this.distributeSampleValues(formDefNode.samples, formDefNode, [], originalNode);
        }
    }
    async distributeSampleValues(samplesNode, formDefNode, pathTokens, originalNode) {
        if (!formDefNode.fields) {
            return;
        }
        const fieldsById = {};
        for (const field of formDefNode.fields) {
            if (Utilities_1.default.isUsableAsObjectKey(field.id)) {
                fieldsById[field.id] = field;
            }
        }
        const originalFieldsById = {};
        if (originalNode && originalNode.fields) {
            for (const field of originalNode.fields) {
                if (Utilities_1.default.isUsableAsObjectKey(field.id)) {
                    originalFieldsById[field.id] = field;
                }
            }
        }
        for (const exampleFilePath in samplesNode) {
            const sampleList = samplesNode[exampleFilePath];
            if (sampleList) {
                for (const sample of sampleList) {
                    if (sample.content && typeof sample.content === "object") {
                        let obj = sample.content;
                        if (obj) {
                            for (const fieldName in obj) {
                                const fieldVal = obj[fieldName];
                                let subForm = undefined;
                                if (!fieldsById[fieldName] &&
                                    (!originalNode || !originalNode.customField || originalFieldsById[fieldName])) {
                                    let dataType = IField_1.FieldDataType.string;
                                    if (fieldName.startsWith("on_")) {
                                        dataType = IField_1.FieldDataType.minecraftEventTrigger;
                                    }
                                    else {
                                        if (typeof fieldVal === "number") {
                                            dataType = IField_1.FieldDataType.number;
                                        }
                                        else if (Array.isArray(fieldVal)) {
                                            if (fieldVal.length > 0) {
                                                if (typeof fieldVal[0] === "object") {
                                                    dataType = IField_1.FieldDataType.objectArray;
                                                    subForm = DataFormUtilities_1.default.generateFormFromObject(fieldName, fieldVal[0], exampleFilePath);
                                                }
                                                else if (typeof fieldVal[0] === "string") {
                                                    dataType = IField_1.FieldDataType.stringArray;
                                                }
                                            }
                                        }
                                        else if (typeof fieldVal === "object") {
                                            dataType = IField_1.FieldDataType.object;
                                            subForm = DataFormUtilities_1.default.generateFormFromObject(fieldName, fieldVal, exampleFilePath);
                                        }
                                    }
                                    const newField = {
                                        id: fieldName,
                                        title: Utilities_1.default.humanifyMinecraftName(fieldName),
                                        dataType: dataType,
                                        subForm: subForm,
                                    };
                                    if (Utilities_1.default.isUsableAsObjectKey(fieldName)) {
                                        fieldsById[fieldName] = newField;
                                    }
                                    formDefNode.fields.push(newField);
                                }
                            }
                        }
                    }
                }
            }
        }
        for (const field of formDefNode.fields) {
            const pathTokensToSearch = pathTokens.slice();
            pathTokensToSearch.push(field.id);
            for (const exampleFilePath in samplesNode) {
                const sampleList = samplesNode[exampleFilePath];
                if (sampleList) {
                    for (const sample of sampleList) {
                        if (sample.content && typeof sample.content === "object") {
                            let obj = sample.content;
                            for (let i = 0; i < pathTokensToSearch.length; i++) {
                                if (obj && obj[pathTokensToSearch[i]] !== undefined) {
                                    obj = obj[pathTokensToSearch[i]];
                                }
                                else {
                                    obj = undefined;
                                }
                            }
                            if (obj) {
                                let exampleInstanceCount = 0;
                                if (!field.samples) {
                                    field.samples = {};
                                }
                                if (field.subForm) {
                                    let exampleObj = obj;
                                    if (Array.isArray(exampleObj) && exampleObj.length > 0) {
                                        const subSamples = {};
                                        let subSamplesAdded = 0;
                                        const path = exampleFilePath;
                                        for (const subObj of exampleObj) {
                                            if (typeof subObj === "object" && Utilities_1.default.isUsableAsObjectKey(path)) {
                                                if (!subSamples[path]) {
                                                    subSamples[path] = [];
                                                }
                                                subSamples[path].push({
                                                    path: sample.path + "|" + pathTokensToSearch.join(".") + "|" + subSamplesAdded.toString(),
                                                    content: subObj,
                                                });
                                                subSamplesAdded++;
                                            }
                                        }
                                        if (subSamplesAdded > 0) {
                                            await this.distributeSampleValues(subSamples, field.subForm, pathTokensToSearch);
                                        }
                                    }
                                }
                                const exampleSamp = JSON.stringify(obj);
                                const path = exampleFilePath;
                                for (const path in field.samples) {
                                    for (let i = 0; i < field.samples[path].length; i++) {
                                        if (JSON.stringify(field.samples[path][i].content) === exampleSamp) {
                                            exampleInstanceCount++;
                                        }
                                    }
                                }
                                if (exampleInstanceCount < 2) {
                                    if (!field.samples[path]) {
                                        field.samples[path] = [];
                                    }
                                    field.samples[path].push({ path: sample.path, content: obj });
                                }
                            }
                        }
                    }
                }
            }
        }
    }
    async addVanillaMatches(formDefNode, name, annotations) {
        const vanillaMatches = await Database_1.default.getPreviewVanillaMatches(name, true, annotations);
        if (vanillaMatches && vanillaMatches.length > 0) {
            if (!formDefNode.samples) {
                formDefNode.samples = {};
            }
            const vanillaPreview = await Database_1.default.getPreviewVanillaFolder();
            if (!vanillaPreview) {
                return;
            }
            for (const match of vanillaMatches) {
                if (match.value.startsWith("/") && match.value.indexOf("metadata") < 0) {
                    const file = await vanillaPreview.getFileFromRelativePath(match.value);
                    if (file && (await file.exists())) {
                        await file.loadContent();
                        const jsonO = StorageUtilities_1.default.getJsonObject(file);
                        if (jsonO) {
                            if (!formDefNode.samples["/vanilla" + match.value]) {
                                formDefNode.samples["/vanilla" + match.value] = [];
                            }
                            this.appendNodesByName(formDefNode.samples["/vanilla" + match.value], "minecraft:" + name, jsonO, "/");
                        }
                    }
                }
            }
        }
    }
    async addSamplesMatches(formDefNode, name, annotations) {
        const samplesMatches = await Database_1.default.getSamplesMatches(name, true, annotations);
        if (samplesMatches && samplesMatches.length > 0) {
            if (!formDefNode.samples) {
                formDefNode.samples = {};
            }
            const samples = await Database_1.default.getSamplesFolder();
            if (!samples) {
                return;
            }
            for (const match of samplesMatches) {
                if (match.value.startsWith("/")) {
                    const file = await samples.getFileFromRelativePath(match.value);
                    if (file && (await file.exists())) {
                        await file.loadContent();
                        const jsonO = StorageUtilities_1.default.getJsonObject(file);
                        if (jsonO) {
                            if (!formDefNode.samples["/samples" + match.value]) {
                                formDefNode.samples["/samples" + match.value] = [];
                            }
                            this.appendNodesByName(formDefNode.samples["/samples" + match.value], "minecraft:" + name, jsonO, "/");
                        }
                    }
                }
            }
        }
    }
    appendNodesByName(exampleList, nodeName, source, path) {
        for (const attributeName in source) {
            const childItem = source[attributeName];
            if (attributeName === nodeName && childItem !== undefined) {
                exampleList.push({ path: path + nodeName + "/", content: childItem });
            }
            else if (attributeName === "test" && "minecraft:" + childItem === nodeName) {
                exampleList.push({ path: path, content: source });
            }
            else if (typeof childItem === "object") {
                this.appendNodesByName(exampleList, nodeName, childItem, path + attributeName + "/");
            }
        }
    }
    getFormFromDocNode(childNode, name) {
        /*"title": "Break doors annotation",
        "description": "Allows an entity to break doors, assuming that that flags set up for the component to use in navigation. Requires the entity's navigation component to have the parameter can_break_doors set to true.",
        "fields": [
          {
            "id": "break_time",
            "description": "The time in seconds required to break through doors.",
            "dataType": 0
          },
          {
            "id": "min_difficulty",
            "title": "Minimum Difficulty",
            "description": "The minimum difficulty that the world must be on for this entity to break doors.",
            "dataType": 8,
            "lookupId": "difficulty"
          }
        ]*/
        const fields = [];
        if (childNode.nodes) {
            for (const fieldNode of childNode.nodes) {
                const field = this.getFieldFromDocNode(fieldNode);
                if (field) {
                    fields.push(field);
                }
            }
        }
        const docForm = {
            id: name,
            title: name ? Utilities_1.default.humanifyMinecraftName(name) : undefined,
            description: childNode.description ? childNode.description.join("\r\n") : undefined,
            fields: fields,
        };
        if (childNode.examples) {
            const examples = [];
            for (const example of childNode.examples) {
                if (example.name && example.text) {
                    examples.push({
                        path: example.name,
                        content: example.text.join("\r\n"),
                    });
                }
            }
            if (examples.length > 0) {
                if (!docForm.samples) {
                    docForm.samples = {};
                }
                docForm.samples["samples"] = examples;
            }
        }
        if (childNode.type &&
            childNode.type !== "JSON Object" &&
            childNode.type !== "List" &&
            childNode.type !== "Array" &&
            childNode.type !== "Trigger") {
            if (childNode.type.indexOf("Boolean") >= 0) {
                docForm.scalarField = {
                    id: "boolean",
                    title: "Boolean",
                    dataType: IField_1.FieldDataType.boolean,
                };
            }
            else if (childNode.type.indexOf("String") >= 0) {
                if (!docForm.title ||
                    (docForm.title.indexOf("eplaceable") < 0 &&
                        docForm.title.indexOf("edstone Cond") < 0 &&
                        docForm.title.indexOf("iquid Detection") < 0 &&
                        docForm.title.indexOf("tem Visual") < 0))
                    docForm.scalarField = {
                        id: "string",
                        title: "String",
                        dataType: IField_1.FieldDataType.string,
                    };
            }
            else if (childNode.type.indexOf("Number") >= 0 || childNode.type.indexOf("Decimal") >= 0) {
                docForm.scalarField = {
                    id: "number",
                    title: "Number",
                    dataType: IField_1.FieldDataType.number,
                };
            }
            else if (childNode.type.indexOf("Integer") >= 0) {
                docForm.scalarField = {
                    id: "integer",
                    title: "Integer",
                    dataType: IField_1.FieldDataType.int,
                };
            }
        }
        return docForm;
    }
    getFieldFromDocNode(childNode) {
        let type = IField_1.FieldDataType.string;
        let typeStr = childNode.type;
        if (typeStr === undefined) {
            typeStr = childNode.name; // this fixup is likely because of mistakes in the source
        }
        switch (typeStr) {
            case "Range [a, b]":
                type = IField_1.FieldDataType.intRange;
                break;
            case "Trigger":
                type = IField_1.FieldDataType.minecraftEventTrigger;
                break;
            case "Positive Integer":
            case "Integer":
                type = IField_1.FieldDataType.int;
                break;
            case "Decimal":
                type = IField_1.FieldDataType.float;
                break;
            case "Minecraft Filter":
                type = IField_1.FieldDataType.minecraftFilter;
                break;
            case "Boolean":
                type = IField_1.FieldDataType.boolean;
                break;
            case "Vector [a, b, c]":
                type = IField_1.FieldDataType.point3;
                break;
            case "Vector [a, b]":
                type = IField_1.FieldDataType.point2;
                break;
            case "Molang":
                type = IField_1.FieldDataType.string;
                break;
            case "Array":
            case "List":
            case "Item Description Properties":
                type = IField_1.FieldDataType.stringArray;
                break;
            case "JSON Object":
                type = IField_1.FieldDataType.objectArray;
                break;
            case "Localization String":
                type = IField_1.FieldDataType.localizableString;
                break;
            case "String":
                type = IField_1.FieldDataType.string;
                break;
            default:
                type = IField_1.FieldDataType.string;
                break;
        }
        if (childNode.nodes && childNode.nodes.length > 0 && type === IField_1.FieldDataType.string) {
            for (const subChildNode of childNode.nodes) {
                if (subChildNode.type === "Array" || (subChildNode.nodes && subChildNode.nodes.length > 0)) {
                    type = IField_1.FieldDataType.object;
                }
            }
        }
        if ((childNode.name === "grow_up" ||
            childNode.name === "event" ||
            childNode.name.endsWith("_event") ||
            childNode.name.startsWith("on_")) &&
            !childNode.name.endsWith("_sound_event") &&
            !childNode.name.startsWith("on_increase") &&
            !childNode.name.startsWith("on_break") &&
            (type === IField_1.FieldDataType.string ||
                type === IField_1.FieldDataType.object ||
                type === IField_1.FieldDataType.objectArray ||
                type === IField_1.FieldDataType.stringArray)) {
            type = IField_1.FieldDataType.minecraftEventTrigger;
        }
        let defaultVal = undefined;
        if (childNode.default) {
            defaultVal = childNode.default;
            if (typeof childNode.default === "string" &&
                childNode.default.startsWith("[") &&
                childNode.default.endsWith("]") &&
                type === IField_1.FieldDataType.point3) {
                let coordStr = childNode.default.substring(1, childNode.default.length - 1);
                let coordElements = coordStr.split(",");
                let coordArray = [];
                for (let str of coordElements) {
                    if (str.length > 0) {
                        try {
                            const numVal = parseFloat(str);
                            coordArray.push(numVal);
                        }
                        catch (e) { }
                    }
                }
                defaultVal = coordArray;
            }
            else if (type === IField_1.FieldDataType.int || type === IField_1.FieldDataType.intRange) {
                try {
                    defaultVal = parseInt(defaultVal);
                }
                catch (e) { }
            }
            else if (type === IField_1.FieldDataType.boolean) {
                try {
                    defaultVal = defaultVal === "true" ? true : false;
                }
                catch (e) { }
            }
            else if (type === IField_1.FieldDataType.float) {
                try {
                    defaultVal = parseFloat(defaultVal);
                }
                catch (e) { }
            }
            if (typeof defaultVal === "number" && isNaN(defaultVal)) {
                defaultVal = undefined;
            }
        }
        if (defaultVal === "N/A") {
            defaultVal = undefined;
        }
        const fieldNode = {
            id: childNode.name,
            title: Utilities_1.default.humanifyMinecraftName(childNode.name),
            description: childNode.description ? childNode.description.join("\r\n") : undefined,
            defaultValue: defaultVal,
            dataType: type,
        };
        if (childNode.type === "Positive Integer") {
            fieldNode.validity = [];
            fieldNode.validity.push({
                comparison: ICondition_1.ComparisonType.greaterThanOrEqualTo,
                value: 0,
            });
        }
        if (childNode.nodes && childNode.nodes.length >= 0) {
            if (type === IField_1.FieldDataType.string) {
                fieldNode.choices = [];
                for (const choiceNode of childNode.nodes) {
                    if (choiceNode.name) {
                        let desc = undefined;
                        if (typeof choiceNode.description === "string") {
                            desc = choiceNode.description;
                        }
                        else if (Array.isArray(choiceNode.description)) {
                            desc = choiceNode.description.join("\r\n");
                        }
                        fieldNode.choices.push({
                            id: choiceNode.name,
                            title: Utilities_1.default.humanifyMinecraftName(choiceNode.name),
                            description: desc,
                        });
                    }
                }
            }
            else {
                const form = this.getFormFromDocNode(childNode);
                if (form &&
                    (type === IField_1.FieldDataType.object ||
                        type === IField_1.FieldDataType.objectArray ||
                        type === IField_1.FieldDataType.minecraftFilter)) {
                    fieldNode.subForm = form;
                }
                else if (form && type === IField_1.FieldDataType.stringArray) {
                    fieldNode.dataType = IField_1.FieldDataType.objectArray;
                    fieldNode.subForm = form;
                }
            }
        }
        if (childNode.examples) {
            const examples = [];
            for (const example of childNode.examples) {
                if (example.name && example.text) {
                    examples.push({
                        path: example.name,
                        content: example.text.join("\r\n"),
                    });
                }
            }
            if (examples.length > 0) {
                if (!fieldNode.samples) {
                    fieldNode.samples = {};
                }
                fieldNode.samples["samples"] = examples;
            }
        }
        return fieldNode;
    }
    getDefinitionFromId(definitionString) {
        if (definitionString.startsWith("#/definitions/")) {
            definitionString = definitionString.substring(14);
        }
        try {
            let defId = parseInt(definitionString);
            if (defId !== undefined && !isNaN(defId)) {
                return this.defsById[defId + ""];
            }
        }
        catch (e) { }
        return undefined;
    }
    async getFieldFromJsonPropertyNode(childNode, propName, alreadyProcessedFieldList) {
        let id = childNode.$id ? childNode.$id : propName;
        let title = childNode.title ? childNode.title : id;
        title = Utilities_1.default.humanifyMinecraftName(this.humanifyJsonMinecraftName(title));
        if (!alreadyProcessedFieldList) {
            alreadyProcessedFieldList = [];
        }
        else {
            alreadyProcessedFieldList = alreadyProcessedFieldList.slice();
        }
        const fieldNode = {
            id: id,
            title: title,
            description: childNode.description ? childNode.description : undefined,
            dataType: IField_1.FieldDataType.object,
        };
        if (childNode.minItems !== undefined) {
            fieldNode.minLength = childNode.minItems;
        }
        if (childNode.maxItems !== undefined) {
            fieldNode.maxLength = childNode.maxItems;
        }
        if (childNode.pattern) {
            if (fieldNode.validity === undefined) {
                fieldNode.validity = [];
            }
            fieldNode.validity.push({
                comparison: ICondition_1.ComparisonType.matchesPattern,
                value: childNode.pattern,
            });
        }
        if (childNode.required) {
            fieldNode.readOnly = true;
        }
        if (childNode.minimum) {
            if (fieldNode.validity === undefined) {
                fieldNode.validity = [];
            }
            fieldNode.validity.push({
                comparison: ICondition_1.ComparisonType.greaterThanOrEqualTo,
                value: childNode.minimum,
            });
        }
        if (childNode.maximum) {
            if (fieldNode.validity === undefined) {
                fieldNode.validity = [];
            }
            fieldNode.validity.push({
                comparison: ICondition_1.ComparisonType.lessThanOrEqualTo,
                value: childNode.maximum,
            });
        }
        if (childNode.title === "associative container") {
            if (childNode.additionalProperties &&
                typeof childNode.additionalProperties !== "boolean" &&
                childNode.additionalProperties.type === "string") {
                fieldNode.dataType = IField_1.FieldDataType.keyedStringCollection;
            }
            else if (childNode.additionalProperties &&
                typeof childNode.additionalProperties !== "boolean" &&
                childNode.additionalProperties.type === "integer") {
                /*                    "blocks": {
                  "minecraft:cauldron": 60,
                  "minecraft:water": 60,
                  "minecraft:lava": 60
              }*/
                fieldNode.dataType = IField_1.FieldDataType.keyedNumberCollection;
            }
            else if (childNode.additionalProperties &&
                typeof childNode.additionalProperties !== "boolean" &&
                childNode.additionalProperties.$ref) {
                const id = childNode.additionalProperties.$ref;
                if (!alreadyProcessedFieldList.includes(id)) {
                    alreadyProcessedFieldList.push(id);
                    if (this.getIsStandaloneSchemaFile(id)) {
                        fieldNode.subFormId = this.getFormPathForJsonSchemaForm(id);
                    }
                    else {
                        const subDefNode = this.getDefinitionFromId(id);
                        if (subDefNode) {
                            if (subDefNode.title === "compound_proxy") {
                                // this is technically an associative array of strings to strings, bools, or ints
                                // "states": { "foo": "bar", "baz": 3, "bal": true }
                                fieldNode.dataType = IField_1.FieldDataType.keyedStringCollection;
                            }
                            else {
                                await this.addChildSchemaNode(fieldNode, propName, subDefNode, IField_1.FieldDataType.keyedStringCollection, alreadyProcessedFieldList);
                            }
                        }
                    }
                }
            }
            else if (childNode.additionalProperties &&
                typeof childNode.additionalProperties !== "boolean" &&
                childNode.additionalProperties.oneOf) {
                // this is probobably  an associative array of strings to strings, bools, or ints
                // "states": { "foo": "bar", "baz": 3, "bal": true }
                /// todo: loop through oneOf to validate this
                fieldNode.dataType = IField_1.FieldDataType.keyedStringCollection;
            }
        }
        else if (childNode.items && childNode.items.$ref) {
            const id = childNode.items.$ref;
            if (!alreadyProcessedFieldList.includes(id)) {
                alreadyProcessedFieldList.push(id);
                if (this.getIsStandaloneSchemaFile(id)) {
                    fieldNode.subFormId = this.getFormPathForJsonSchemaForm(id);
                }
                else {
                    const subDefNode = this.getDefinitionFromId(id);
                    if (subDefNode) {
                        await this.addChildSchemaNode(fieldNode, propName, subDefNode, IField_1.FieldDataType.objectArray, alreadyProcessedFieldList);
                    }
                }
            }
            else if (childNode.items && childNode.items.type === "string") {
                fieldNode.dataType = IField_1.FieldDataType.stringArray;
            }
        }
        else if (childNode.$ref && (!childNode.type || childNode.type === "object")) {
            const id = childNode.$ref;
            if (this.getIsStandaloneSchemaFile(id)) {
                fieldNode.subFormId = this.getFormPathForJsonSchemaForm(id);
            }
            else {
                if (!alreadyProcessedFieldList.includes(id)) {
                    alreadyProcessedFieldList.push(id);
                    const subDefNode = this.getDefinitionFromId(id);
                    if (subDefNode) {
                        await this.addChildSchemaNode(fieldNode, propName, subDefNode, IField_1.FieldDataType.object, alreadyProcessedFieldList);
                    }
                }
            }
        }
        else if (childNode.properties && (!childNode.type || childNode.type === "object" || childNode.type === "array")) {
            fieldNode.subForm = await this.getJsonFormFromJsonSchemaDefinition(childNode, propName, alreadyProcessedFieldList);
            fieldNode.dataType = childNode.type === "array" ? IField_1.FieldDataType.objectArray : IField_1.FieldDataType.object;
        }
        else if (childNode.additionalProperties &&
            childNode.additionalProperties.$ref &&
            (!childNode.type || childNode.type === "object" || childNode.type === "array")) {
            const id = childNode.additionalProperties.$ref;
            if (this.getIsStandaloneSchemaFile(id)) {
                fieldNode.subFormId = this.getFormPathForJsonSchemaForm(id);
            }
            else {
                if (!alreadyProcessedFieldList.includes(id)) {
                    const subDefNode = this.getDefinitionFromId(id);
                    if (subDefNode) {
                        await this.addChildSchemaNode(fieldNode, propName, subDefNode, childNode.type === "array" ? IField_1.FieldDataType.objectArray : IField_1.FieldDataType.object, alreadyProcessedFieldList);
                    }
                }
            }
        }
        else if (childNode.additionalProperties &&
            (!childNode.type || childNode.type === "object" || childNode.type === "array")) {
            const keyTypeNodes = [];
            if (childNode.additionalProperties.type) {
                keyTypeNodes.push(childNode.additionalProperties);
            }
            if (childNode.additionalProperties.oneOf) {
                keyTypeNodes.push(...childNode.additionalProperties.oneOf);
            }
            const altFields = [];
            let isFirst = true;
            for (const valObj of keyTypeNodes) {
                let childFieldNode = { id: fieldNode.id, dataType: IField_1.FieldDataType.object };
                switch (valObj.type) {
                    case "integer":
                    case "number":
                        childFieldNode.dataType = IField_1.FieldDataType.keyedNumberCollection;
                        break;
                    case "boolean":
                        childFieldNode.dataType = IField_1.FieldDataType.keyedBooleanCollection;
                        break;
                    case "string":
                        childFieldNode.dataType = IField_1.FieldDataType.keyedStringCollection;
                        break;
                    case "object":
                        childFieldNode.dataType = IField_1.FieldDataType.keyedObjectCollection;
                        break;
                }
                if (isFirst) {
                    fieldNode.dataType = childFieldNode.dataType;
                    isFirst = false;
                }
                else if (this.getFieldIsDifferentType(childFieldNode, fieldNode)) {
                    altFields.push(childFieldNode);
                }
            }
            if (altFields.length > 0) {
                fieldNode.alternates = altFields;
            }
        }
        else {
            switch (childNode.type) {
                case "integer":
                    fieldNode.dataType = IField_1.FieldDataType.int;
                    break;
                case "number":
                    fieldNode.dataType = IField_1.FieldDataType.float;
                    break;
                case "boolean":
                    fieldNode.dataType = IField_1.FieldDataType.boolean;
                    break;
                case "string":
                    fieldNode.dataType = IField_1.FieldDataType.string;
                    break;
                case "object":
                    fieldNode.dataType = IField_1.FieldDataType.object;
                    break;
                case "array":
                    if (childNode.items && childNode.items.type === "string") {
                        fieldNode.dataType = IField_1.FieldDataType.stringArray;
                    }
                    else if (childNode.items &&
                        (childNode.items.type === "integer" || childNode.items.type === "number")) {
                        fieldNode.dataType = IField_1.FieldDataType.numberArray;
                    }
                    else if (fieldNode.subForm) {
                        fieldNode.dataType = IField_1.FieldDataType.objectArray;
                    }
                    else {
                        fieldNode.dataType = IField_1.FieldDataType.stringArray;
                    }
                    break;
            }
        }
        return fieldNode;
    }
    getFieldIsDifferentType(fieldA, fieldB) {
        if (fieldA.dataType !== fieldB.dataType) {
            return true;
        }
        if (fieldA.subFormId !== fieldB.subFormId) {
            return true;
        }
        if ((fieldA.subForm && !fieldB.subForm) || (!fieldA.subForm && fieldB.subForm)) {
            return true;
        }
        return false;
    }
    async addChildSchemaNode(fieldNode, propName, subDefNode, dataType, alreadyProcessedFieldList) {
        alreadyProcessedFieldList = alreadyProcessedFieldList.slice();
        if (subDefNode.oneOf) {
            const altFields = [];
            let isFirst = true;
            for (const subDef of subDefNode.oneOf) {
                if (typeof subDef !== "boolean") {
                    let childFieldNode = { id: fieldNode.id, dataType: IField_1.FieldDataType.object };
                    if (subDef && subDef.$ref) {
                        const id = subDef.$ref;
                        if (!alreadyProcessedFieldList.includes(id)) {
                            const propSubDefNode = this.getDefinitionFromId(id);
                            alreadyProcessedFieldList.push(id);
                            if (propSubDefNode) {
                                if (propSubDefNode.oneOf && !propSubDefNode.properties) {
                                    for (const propField of propSubDefNode.oneOf) {
                                        if (typeof propField !== "boolean") {
                                            const propChildFieldNode = await this.getFieldFromJsonPropertyNode(propField, propName, alreadyProcessedFieldList.slice());
                                            propChildFieldNode.dataType = FieldUtilities_1.default.getStringKeyedFieldType(propChildFieldNode.dataType);
                                            if (isFirst) {
                                                fieldNode.dataType = propChildFieldNode.dataType;
                                                fieldNode.subForm = propChildFieldNode.subForm;
                                                isFirst = false;
                                            }
                                            else if (this.getFieldIsDifferentType(propChildFieldNode, fieldNode)) {
                                                propChildFieldNode.description = undefined;
                                                propChildFieldNode.title = undefined;
                                                altFields.push(propChildFieldNode);
                                            }
                                        }
                                    }
                                    childFieldNode = undefined;
                                }
                                else {
                                    childFieldNode.dataType = dataType;
                                    childFieldNode.subForm = await this.getJsonFormFromJsonSchemaDefinition(propSubDefNode, propName);
                                }
                            }
                        }
                    }
                    else {
                        childFieldNode = await this.getFieldFromJsonPropertyNode(subDef, propName, alreadyProcessedFieldList);
                    }
                    if (childFieldNode) {
                        if (subDefNode.title && subDefNode.title.indexOf("<") >= 0) {
                            childFieldNode.dataType = FieldUtilities_1.default.getStringKeyedFieldType(childFieldNode.dataType);
                        }
                        if (isFirst) {
                            fieldNode.dataType = childFieldNode.dataType;
                            fieldNode.subForm = childFieldNode.subForm;
                            isFirst = false;
                        }
                        else if (this.getFieldIsDifferentType(childFieldNode, fieldNode)) {
                            childFieldNode.description = undefined;
                            childFieldNode.title = undefined;
                            altFields.push(childFieldNode);
                        }
                    }
                }
            }
            fieldNode.alternates = altFields;
        }
        else if (subDefNode.properties) {
            const subForm = await this.getJsonFormFromJsonSchemaDefinition(subDefNode, undefined, alreadyProcessedFieldList);
            fieldNode.subForm = subForm;
            fieldNode.dataType = dataType;
        }
        else {
            fieldNode.dataType = dataType;
        }
    }
    humanifyId(title) {
        let i = title.indexOf(".");
        if (i > 1) {
            title = title.substring(i + 1);
        }
        title = title.replace(/::/gi, "_");
        title = title.replace("Struct ", "");
        title = title.replace("struct ", "");
        title = title.replace("Struct_", "");
        title = title.replace("struct_", "");
        title = title.replace("Enum ", "");
        title = title.replace("enum ", "");
        title = title.replace("Enum_", "");
        title = title.replace("enum_", "");
        title = title.replace("SharedTypes ", "");
        title = title.replace("sharedtypes ", "");
        title = title.replace("SharedTypes_", "");
        title = title.replace("sharedtypes_", "");
        title = title.trim();
        return title;
    }
    humanifyJsonMinecraftName(title) {
        while (title.length > 1 && title.startsWith(":")) {
            title = title.substring(1);
        }
        title = title.replace(/::/gi, "_");
        title = title.replace(/:/gi, "_");
        title = title.replace("Struct ", "");
        title = title.replace("struct ", "");
        title = title.replace("Struct_", "");
        title = title.replace("struct_", "");
        title = title.replace("Enum ", "");
        title = title.replace("enum ", "");
        title = title.replace("Enum_", "");
        title = title.replace("enum_", "");
        title = title.replace("SharedTypes ", "");
        title = title.replace("sharedtypes ", "");
        title = title.replace("SharedTypes_", "");
        title = title.replace("sharedtypes_", "");
        title = title.trim();
        return title;
    }
    getFormFileName(name, dataVersion) {
        name = name.replace(/:/gi, "_").replace(/\./gi, "_");
        // fix up bone visibility
        if (name === "bone_visibility") {
            name = "minecraft_bone_visibility";
        }
        if (dataVersion) {
            name += ".v" + dataVersion;
        }
        return this.getFormFileNameBase(name);
    }
}
exports.default = FormJsonDocumentationGenerator;

//# sourceMappingURL=../maps/docgen/FormJsonDocumentationGenerator.js.map
