"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const Versioning_1 = require("../../../app/Versioning");
const ArrayUtilities_1 = require("../../../core/ArrayUtilities");
const ObjectUtilities_1 = require("../../../core/ObjectUtilities");
const Utilities_1 = require("../../../core/Utilities");
const SemanticVersion_1 = require("../../../core/versioning/SemanticVersion");
const Pack_1 = require("../../../minecraft/Pack");
const ProjectInfoUtilities_1 = require("../../ProjectInfoUtilities");
const TestDefinition_1 = require("../../tests/TestDefinition");
const Manifest_1 = require("../../../minecraft/manifests/Manifest");
const CheckManifestData_1 = require("./CheckManifestData");
const ValidationData = require("./CheckManifestData");
const IProjectItemData_1 = require("../../../app/IProjectItemData");
class CheckManifestGenerator {
    id = "CHKMANIF";
    title = "Check Manifest Generator";
    canAlwaysProcess = true;
    async generate(project, contentIndex) {
        const packs = project.packs;
        const invalidManifests = packs
            .filter((pack) => {
            const manifests = pack.getPackItems().filter((item) => item.name === "manifest.json");
            return manifests.length === 0 || manifests.length > 1;
        })
            .map(() => (0, TestDefinition_1.resultFromTest)(CheckManifestData_1.Tests.InvalidNumberOfManifests, { id: this.id }));
        if (invalidManifests.length > 0) {
            return invalidManifests;
        }
        const packManifests = packs.map((pack) => [pack.getManifest(), pack]);
        const worldManifests = project.items
            .filter((item) => item.itemType === IProjectItemData_1.ProjectItemType.worldTemplateManifestJson)
            .map((manifest) => [manifest, { isWorld: true }]);
        const allManifests = [...worldManifests, ...packManifests];
        const results = await Promise.all(allManifests.map(([desc, manifest]) => this.validateManifest(desc, manifest)));
        return results.flat();
    }
    async validateManifest(manifestItem, pack) {
        const json = await manifestItem.getContentAsJson();
        const [manifest, parseErrors] = (0, Manifest_1.parseManifest)(json);
        if (parseErrors) {
            return parseErrors
                .map((error) => ({ id: this.id, message: error.message, item: manifestItem, data: error.propertyName }))
                .map((resultData) => (0, TestDefinition_1.resultFromTest)(CheckManifestData_1.Tests.InvalidManifestSchema, resultData));
        }
        const results = [
            ...this.validateFormatVersion(pack, manifest, manifestItem),
            ...this.validateIds(pack, manifest, manifestItem),
            ...this.validateHeader(pack, manifest, manifestItem),
            ...this.validateModules(pack, manifest, manifestItem),
            ...this.validateDependencies(pack, manifest, manifestItem),
            ...this.validateSubpacks(pack, manifest, manifestItem),
            ...this.validateCapabilties(pack, manifest, manifestItem),
            ...this.validateSettings(pack, manifest, manifestItem),
        ];
        return results;
    }
    validateIds(pack, manifest, manifestItem) {
        const allIds = [
            manifest.header.uuid,
            ...(manifest.modules?.map((module) => module.uuid) || []),
            ...(manifest.dependencies?.map((dep) => dep.uuid).filter(ObjectUtilities_1.notEmpty) || []),
        ];
        const [validIds, invalidIds] = (0, ArrayUtilities_1.filterAndSeparate)(allIds, (id) => Utilities_1.default.isValidUuid(id));
        const invalidIdResults = invalidIds
            .map((invalid) => ({ id: this.id, item: manifestItem, data: invalid }))
            .map((data) => (0, TestDefinition_1.resultFromTest)(CheckManifestData_1.Tests.InvalidId, data));
        const dupIdResults = (0, ArrayUtilities_1.findDuplicates)(validIds)
            .map((dupId) => ({ id: this.id, item: manifestItem, data: dupId }))
            .map((data) => (0, TestDefinition_1.resultFromTest)(CheckManifestData_1.Tests.DuplicateId, data));
        return [...invalidIdResults, ...dupIdResults];
    }
    validateSettings(pack, manifest, manifestItem) {
        const settings = manifest.settings;
        if (!settings) {
            return [];
        }
        const dupNameResults = (0, ArrayUtilities_1.findDuplicates)(settings.map((setting) => setting.name))
            .map((dup) => ({ id: this.id, data: dup, item: manifestItem }))
            .map((data) => (0, TestDefinition_1.resultFromTest)(CheckManifestData_1.Tests.DuplicateSettingsName, data));
        const noNamespaces = settings
            .filter((setting) => setting.name && ValidationData.NamespaceFormat.test(setting.name))
            .map((setting) => ({ id: this.id, item: manifestItem, data: setting.name }))
            .map((data) => (0, TestDefinition_1.resultFromTest)(CheckManifestData_1.Tests.SettingsNamespaceRequired, data));
        const toggles = settings.filter((setting) => setting.type === "toggle");
        const sliders = settings.filter((setting) => setting.type === "slider");
        const dropdowns = settings.filter((setting) => setting.type === "dropdown");
        const knownTypes = new Set(["label", "toggle", "slider", "dropdown"]);
        const invalidTypes = settings
            .filter((settings) => !knownTypes.has(settings.type))
            .map((settings) => ({ id: this.id, data: settings.type, item: manifestItem }))
            .map((data) => (0, TestDefinition_1.resultFromTest)(CheckManifestData_1.Tests.InvalidSettingType, data));
        //note: type and text are always required, so we use the schema to validate that
        //for other properties that are only conditionally required, we'll confirm post parsing
        const missingPropResults = [
            ...toggles
                .map((toggle) => [toggle, (0, ObjectUtilities_1.findMissingProperty)(toggle, ["name", "default"])])
                .filter(([, missingProperty]) => !!missingProperty)
                .map(([, missingProperty]) => ({ id: this.id, item: manifestItem, data: missingProperty }))
                .map((data) => (0, TestDefinition_1.resultFromTest)(CheckManifestData_1.Tests.MissingSettingsProperty, data)),
            ...sliders
                .map((slider) => [slider, (0, ObjectUtilities_1.findMissingProperty)(slider, ["name", "min", "max", "step", "default"])])
                .filter(([, missingProperty]) => !!missingProperty)
                .map(([, missingProperty]) => ({ id: this.id, item: manifestItem, data: missingProperty }))
                .map((data) => (0, TestDefinition_1.resultFromTest)(CheckManifestData_1.Tests.MissingSettingsProperty, data)),
            ...dropdowns
                .map((dropdown) => [dropdown, (0, ObjectUtilities_1.findMissingProperty)(dropdown, ["name", "default", "options"])])
                .filter(([, missingProperty]) => !!missingProperty)
                .map(([, missingProperty]) => ({ id: this.id, item: manifestItem, data: missingProperty }))
                .map((data) => (0, TestDefinition_1.resultFromTest)(CheckManifestData_1.Tests.MissingSettingsProperty, data)),
        ];
        const invalidMins = sliders
            // undefined values are reported by the missing property result, ignore for now
            .filter((slider) => !!slider.min && !!slider.max)
            // we've already filtered undefined values, '!' is safe
            .filter((slider) => slider.min > slider.max)
            .map((slider) => ({ id: this.id, item: manifestItem, data: slider.min }))
            .map((data) => (0, TestDefinition_1.resultFromTest)(CheckManifestData_1.Tests.InvalidSettingsMin, data));
        const invalidDefaults = sliders
            .filter((slider) => !!slider.default && !!slider.min && !!slider.max)
            // we've already filtered undefined values, '!' is safe
            .filter((slider) => typeof slider.default !== "number" || slider.default > slider.max || slider.default < slider.min)
            .map((slider) => ({ id: this.id, item: manifestItem, data: slider.default }))
            .map((data) => (0, TestDefinition_1.resultFromTest)(CheckManifestData_1.Tests.InvalidSliderDefault, data));
        const invalidSteps = sliders
            .filter((slider) => !!slider.step && !!slider.min && !!slider.max)
            // we've already filtered undefined values, '!' is safe
            .filter((slider) => slider.step <= 0 || slider.step > slider.max - slider.min)
            .map((slider) => ({ id: this.id, item: manifestItem, data: slider.step }))
            .map((data) => (0, TestDefinition_1.resultFromTest)(CheckManifestData_1.Tests.InvalidSettingsStep, data));
        const notEnoughOptions = dropdowns
            .filter((dropdown) => !dropdown.options || dropdown.options.length < ValidationData.MinDropDownOptions)
            .map(() => ({ id: this.id, item: manifestItem }))
            .map((data) => (0, TestDefinition_1.resultFromTest)(CheckManifestData_1.Tests.NotEnoughSettingsOptions, data));
        const duplicateOptions = dropdowns
            .flatMap((dropdown) => (dropdown.options ? (0, ArrayUtilities_1.findDuplicates)(dropdown.options.map((opt) => opt.name)) : []))
            .map((duplicate) => ({ id: this.id, item: manifestItem, data: duplicate }))
            .map((data) => (0, TestDefinition_1.resultFromTest)(CheckManifestData_1.Tests.DuplicateOptions, data));
        const badDefaults = dropdowns
            .filter((dropdown) => dropdown.options &&
            typeof dropdown.default === "string" &&
            dropdown.options.map((opt) => opt.name).includes(dropdown.default))
            .map((dropdown) => ({ id: this.id, item: manifestItem, data: dropdown.default }))
            .map((data) => (0, TestDefinition_1.resultFromTest)(CheckManifestData_1.Tests.InvalidSliderDefault, data));
        return [
            ...noNamespaces,
            ...dupNameResults,
            ...invalidTypes,
            ...missingPropResults,
            ...invalidMins,
            ...invalidDefaults,
            ...invalidSteps,
            ...notEnoughOptions,
            ...duplicateOptions,
            ...badDefaults,
        ];
    }
    validateCapabilties(pack, manifest, manifestItem) {
        const capabilities = manifest.capabilities;
        if (!capabilities) {
            return [];
        }
        return capabilities
            .filter((capability) => !ValidationData.AllowedCapabilities.has(capability))
            .map((capability) => ({ id: this.id, item: manifestItem, data: capability }))
            .map((data) => (0, TestDefinition_1.resultFromTest)(CheckManifestData_1.Tests.InvalidCapability, data));
    }
    validateSubpacks(pack, manifest, manifestItem) {
        const subpacks = manifest.subpacks;
        if (!subpacks) {
            return [];
        }
        const dupFolderResults = (0, ArrayUtilities_1.findDuplicates)(subpacks.map((subp) => subp.folderName))
            .map((folder) => ({ id: this.id, item: manifestItem, data: folder }))
            .map((data) => (0, TestDefinition_1.resultFromTest)(CheckManifestData_1.Tests.DuplicateSubpackFolder, data));
        const dupNameResults = (0, ArrayUtilities_1.findDuplicates)(subpacks.map((subp) => subp.name))
            .map((name) => ({ id: this.id, item: manifestItem, data: name }))
            .map((data) => (0, TestDefinition_1.resultFromTest)(CheckManifestData_1.Tests.DuplicateSubpackName, data));
        /*
        Disable name checks and .memory tier checks for
        const validNames = new Set(Object.values(SubpackTypes).map((type) => type.name));
    
        const invalidNameResults = subpacks
          .filter((subp) => !validNames.has(subp.name))
          .map((subp) => ({ id: this.id, item: manifestItem, data: subp.name }))
          .map((data) => resultFromTest(Tests.InvalidSubpackName, data));
        
    
        const invalidMemoryTierResults = subpacks
          .map((subp) => [subp, SubpackTypes[subp.name]?.minTier] as const)
          .filter(([, minTier]) => !!minTier)
          .filter(([subp, minTier]) => subp.memoryTier < minTier)
          .map(([subp]) => ({ id: this.id, item: manifestItem, data: subp.memoryTier }))
          .map((data) => resultFromTest(Tests.InvalidSubpackMemoryTier, data));
        */
        return [...dupFolderResults, ...dupNameResults]; //, ...invalidNameResults, ...invalidMemoryTierResults];
    }
    validateDependencies(pack, manifest, manifestItem) {
        const dependencies = manifest.dependencies;
        if (!dependencies) {
            return [];
        }
        const noIdResults = dependencies
            .filter((dependency) => !dependency.uuid && !dependency.moduleName)
            .map(() => (0, TestDefinition_1.resultFromTest)(CheckManifestData_1.Tests.NoDependencyIdentifier, { id: this.id, item: manifestItem }));
        const multipleIdResults = dependencies
            .filter((dependency) => !!dependency.uuid && !!dependency.moduleName)
            .map(() => (0, TestDefinition_1.resultFromTest)(CheckManifestData_1.Tests.MultipleDependencyIdentifier, { id: this.id, item: manifestItem }));
        const allowedDependencyModulesResults = dependencies
            .filter((dependency) => !!dependency.moduleName && !ValidationData.AllowedDependencyModules[dependency.moduleName])
            .map((dependency) => ({ id: this.id, item: manifestItem, data: dependency.moduleName }))
            .map((data) => (0, TestDefinition_1.resultFromTest)(CheckManifestData_1.Tests.ModuleNameNotAllowed, data));
        const dependencyVersions = dependencies.map((dep) => [dep, SemanticVersion_1.default.parse(dep.version)]);
        const invalidVersionResults = dependencyVersions
            .filter(([, version]) => !version)
            .map(([dep]) => ({ id: this.id, item: manifestItem, data: dep.version }))
            .map((data) => (0, TestDefinition_1.resultFromTest)(CheckManifestData_1.Tests.UnableToParseVersion, data));
        const versionsBelowMinResults = dependencyVersions
            // we only care about dependencies that use module name, non-allowed modules are handled by a different check
            .filter(([dep, version]) => !!dep.moduleName && !!ValidationData.AllowedDependencyModules[dep.moduleName] && !!version)
            //  '!' is safe here, we've already filtered falsey moduleNames and versions
            .filter(([dep, version]) => version.compareTo(ValidationData.AllowedDependencyModules[dep.moduleName]) <= 0)
            .map(([dep]) => (0, TestDefinition_1.resultFromTest)(CheckManifestData_1.Tests.BelowMinVersion, { id: this.id, item: manifestItem, data: dep.version }));
        return [
            ...noIdResults,
            ...multipleIdResults,
            ...allowedDependencyModulesResults,
            ...invalidVersionResults,
            ...versionsBelowMinResults,
        ];
    }
    validateModules(pack, manifest, manifestItem) {
        const modules = manifest.modules;
        if (!modules) {
            return [];
        }
        if (modules.filter((module) => module.type === ValidationData.WorldTemplateModuleName).length > 1) {
            return [(0, TestDefinition_1.resultFromTest)(CheckManifestData_1.Tests.TooManyWorldTemplates, { id: this.id, item: manifestItem })];
        }
        return modules
            .filter((module) => !ValidationData.KnownModuleTypes.has(module.type))
            .map((module) => (0, TestDefinition_1.resultFromTest)(CheckManifestData_1.Tests.InvalidModuleType, { id: this.id, item: manifestItem, data: module.type }));
    }
    validateHeader(pack, manifest, manifestItem) {
        const header = manifest.header;
        const results = [];
        const isemptyDescriptionAllowed = pack.type === Pack_1.PackType.skin;
        if (!header.description && !isemptyDescriptionAllowed) {
            results.push((0, TestDefinition_1.resultFromTest)(CheckManifestData_1.Tests.MissingHeaderProperty, { id: this.id, item: manifestItem, data: "description" }));
        }
        if (pack.isWorld) {
            if (!!header.baseGameVersion && manifest.formatVersion < ValidationData.FormatVersion2) {
                const data = { id: this.id, item: manifestItem, data: header.baseGameVersion };
                results.push((0, TestDefinition_1.resultFromTest)(CheckManifestData_1.Tests.InvalidBaseGameVersion, data));
            }
            if (header.baseGameVersion === "*") {
                const data = { id: this.id, item: manifestItem, data: header.baseGameVersion };
                results.push((0, TestDefinition_1.resultFromTest)(CheckManifestData_1.Tests.WildCardGameVersion, data));
            }
            if (manifest.formatVersion > ValidationData.FormatVersion1 && header.lockTemplateOptions === undefined) {
                const data = { id: this.id, item: manifestItem, data: "lock_template_options" };
                results.push((0, TestDefinition_1.resultFromTest)(CheckManifestData_1.Tests.HeaderPropertyRequiredV2, data));
            }
        }
        if (pack.type === Pack_1.PackType.resource) {
            const infoItem = this.validateMinEngineVersion(header.minEngineVersion, pack, manifest.formatVersion, manifestItem);
            if (infoItem) {
                results.push(infoItem);
            }
        }
        if (header.packscope && ValidationData.AllowedPackScopes.has(header.packscope)) {
            results.push((0, TestDefinition_1.resultFromTest)(CheckManifestData_1.Tests.InvalidPackScope, { id: this.id, item: manifestItem, data: header.packscope }));
        }
        return results;
    }
    validateMinEngineVersion(mev, pack, formatVersion, manifestItem) {
        const minVersion = SemanticVersion_1.default.parse(mev);
        if (!minVersion && formatVersion > ValidationData.FormatVersion1) {
            const data = { id: this.id, item: manifestItem, data: "min_engine_version" };
            return (0, TestDefinition_1.resultFromTest)(CheckManifestData_1.Tests.HeaderPropertyRequiredV2, data);
        }
        const requiredVer = this.getMinimalVersionThatRequiresV2(pack.isEDUOffer === true);
        if (minVersion && minVersion.compareTo(requiredVer) >= 0 && formatVersion < ValidationData.FormatVersion2) {
            const data = { id: this.id, item: manifestItem, data: mev };
            return (0, TestDefinition_1.resultFromTest)(CheckManifestData_1.Tests.MinEngineVersionTooHigh, data);
        }
        return null;
    }
    getMinimalVersionThatRequiresV2(isEDUOffer) {
        return isEDUOffer ? Versioning_1.default.FirstMinEngineVersionForFormatV2EDU : Versioning_1.default.FirstMinEngineVersionForFormatV2;
    }
    validateFormatVersion(pack, manifest, manifestItem) {
        const format = manifest.formatVersion;
        if (!ValidationData.ValidFormatVersions.has(format)) {
            return [(0, TestDefinition_1.resultFromTest)(CheckManifestData_1.Tests.InvalidFormatVersion, { id: this.id, data: format })];
        }
        if (!(pack.type === Pack_1.PackType.skin || pack.type === Pack_1.PackType.persona) &&
            manifest.formatVersion < ValidationData.FormatVersion2) {
            return [
                (0, TestDefinition_1.resultFromTest)(CheckManifestData_1.Tests.InvalidFormatVersion, {
                    id: this.id,
                    data: format,
                    message: `All new content targeting published client version must conform to format version [${ValidationData.FormatVersion2}].`,
                }),
            ];
        }
        return [];
    }
    getTopicData(topicId) {
        const title = ProjectInfoUtilities_1.default.getGeneralTopicTitle(topicId) || (0, TestDefinition_1.getTestTitleById)(CheckManifestData_1.Tests, topicId);
        return { title };
    }
    summarize() { }
}
exports.default = CheckManifestGenerator;

//# sourceMappingURL=../../../maps/info/projectGenerators/checkManifest/CheckManifestGenerator.js.map
