"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const IProjectItemData_1 = require("../../app/IProjectItemData");
const IInfoItemData_1 = require("../IInfoItemData");
const Skin_1 = require("../../minecraft/skins/Skin");
const StorageUtilities_1 = require("../../storage/StorageUtilities");
const TestDefinition_1 = require("../tests/TestDefinition");
const ProjectItemUtilities_1 = require("../../app/ProjectItemUtilities");
const SkinPack_1 = require("../../minecraft/skins/SkinPack");
const TextureUtilities_1 = require("../../minecraft/textures/TextureUtilities");
const FileTexture_1 = require("../../minecraft/textures/FileTexture");
const MaxFreeSkins = 2;
const MaxSkinsInAPack = 80;
const LeadingOrTrailingSpaceRegex = /^\s+|\s+$/;
const Tests = {
    JsonNotFoundFile: {
        id: 101,
        title: "Skin Pack Json File Not Found",
        severity: IInfoItemData_1.InfoItemType.error,
        defaultMessage: "skins.json file not found.",
    },
    InvalidJsonFile: { id: 102, title: "Invalid Json File" },
    InvalidPackLocName: {
        id: 103,
        title: "Invalid Localization Name",
        severity: IInfoItemData_1.InfoItemType.error,
        defaultMessage: "skins.json localization_name and serialize_name must be the same.",
    },
    TooManyFreeSkins: { id: 104, title: "More Free Skins Than Allowed" },
    DuplicateTextures: { id: 105, title: "Duplicate Textures Found", severity: IInfoItemData_1.InfoItemType.warning },
    CapeTextureNotAllowed: { id: 106, title: "Cape Texture Not Allowed" },
    InvalidTextureSize: { id: 107, title: "Texture Invalid Size" },
    MCCreatorPropertyNotAllowed: { id: 108, title: "Minecraft Creator Property Not Allowed" },
    FailedToReadFile: { id: 109, title: "File Read Failed" },
    OrphanedTexture: { id: 110, title: "Texture Not Found in skins.json" },
    OrphanedLocKey: { id: 111, title: "Loc Key Not Found in Lang File" },
    LocalizedKeyNotFoundInSkinsJson: { id: 112, title: "Localized Key Not Found In skins.json" },
    InvalidSpacingOnLocalizedKey: { id: 113, title: "Localized Key Cannot Have Leading Or Trailing Spaces" },
    InvalidSkinType: { id: 114, title: "Skin Purchase Type Not Allowed" },
    InvalidSkinModelTarget: { id: 115, title: "Invalid Skin Model Target" },
    InvalidNumberOfSkins: {
        id: 116,
        title: "Invalid Number Of Skins",
        defaultMessage: `Maximum Allowable skins is: ${MaxSkinsInAPack}`,
    },
    OuterAreaIsBlank: { id: 117, title: "Outer Area Blank" },
    ModelInvisible: { id: 118, title: "Model Invisible From Some Angles" },
    ModelPartiallyInvisible: { id: 119, title: "Model Partially Invisible", severity: IInfoItemData_1.InfoItemType.warning },
    CouldNotFindRelatedPack: { id: 120, title: "Could Not Find Related Skin Pack" },
};
class CheckSkinPackJsonGenerator {
    id = "CSPJ";
    title = "Check Skin Pack Json Generator";
    canAlwaysProcess = true;
    async generate(project) {
        const skinPackManifestItems = project.getItemsByType(IProjectItemData_1.ProjectItemType.skinPackManifestJson);
        const allResults = [];
        for (const skinPackManifestItem of skinPackManifestItems) {
            if (!skinPackManifestItems.length) {
                return (0, TestDefinition_1.notApplicable)();
            }
            const skinPack = await skinPackManifestItem.getPack();
            if (!skinPack) {
                allResults.push((0, TestDefinition_1.resultFromTest)(Tests.CouldNotFindRelatedPack, this.id, "Could not read skin pack manifest pack", skinPackManifestItem, skinPackManifestItem.name));
                continue;
            }
            const packItems = skinPack.getPackItems();
            const skinCatalogJsonFile = await (0, ProjectItemUtilities_1.getEnsuredFileOfType)(packItems, IProjectItemData_1.ProjectItemType.skinCatalogJson);
            if (!skinCatalogJsonFile) {
                return [
                    (0, TestDefinition_1.resultFromTest)(Tests.JsonNotFoundFile, this.id, "Could not find skins.json file", skinPackManifestItem),
                ];
            }
            // read skin pack from json and return error results if it can't be read or validated
            const skinCatalogJson = await StorageUtilities_1.default.getJsonObject(skinCatalogJsonFile);
            const [skinPackManifestObj, errors] = (0, SkinPack_1.validateSkinPackJson)(skinCatalogJson);
            if (errors) {
                return errors.map((error) => (0, TestDefinition_1.resultFromTest)(Tests.InvalidJsonFile, this.id, error.message, skinPackManifestItem));
            }
            if (!hasValidLocalizationNames(skinPackManifestObj.localization_name, skinPackManifestObj.serialize_name)) {
                return [(0, TestDefinition_1.resultFromTest)(Tests.InvalidPackLocName, this.id, undefined, skinPackManifestItem)];
            }
            allResults.push(...this.validateSkins(skinPackManifestObj.skins, project.isMinecraftCreator));
            allResults.push(...(await this.validateTextures(packItems, skinPackManifestObj.skins, project.isMinecraftCreator)));
            allResults.push(...this.checkSkinLocalizations(project.loc, skinPackManifestObj));
        }
        return allResults;
    }
    getTopicData(topicId) {
        const title = Object.values(Tests).find((value) => value.id === topicId)?.title || `Unknown Test: ${topicId}`;
        return { title };
    }
    summarize() { }
    validateSkins(skins, isMCCreator) {
        const results = [];
        const invalidSkins = skins
            .filter((skin) => !(0, Skin_1.isValidSkinPurchaseType)(skin.type))
            .map(() => (0, TestDefinition_1.resultFromTest)(Tests.InvalidSkinType, this.id));
        results.push(...invalidSkins);
        const freeSkins = skins.filter((skin) => skin.type === "free");
        if (freeSkins.length > MaxFreeSkins) {
            const message = `${freeSkins.length} free skins found. Only ${MaxFreeSkins} allowed.`;
            results.push((0, TestDefinition_1.resultFromTest)(Tests.TooManyFreeSkins, this.id, message));
        }
        if (!isMCCreator) {
            const nonFPResults = this.validateNonMCRestrictionsForSkin(skins);
            results.push(...nonFPResults);
        }
        return results;
    }
    async validateTextures(packItems, skins, isMCCreator) {
        //check for duplicate capes
        const capeTextureNames = skins.map((skin) => skin.cape).filter((cape) => !!cape);
        // CSPJ105
        const duplicateCapeTexturesResults = capeTextureNames
            .filter((name, index) => capeTextureNames.indexOf(name) !== index)
            .map((duplicate) => (0, TestDefinition_1.resultFromTest)(Tests.DuplicateTextures, this.id, `Duplicate cape texture found: ${duplicate}`));
        //check cape textures
        const capeTextureSet = new Set(capeTextureNames);
        const capeTextures = await (0, ProjectItemUtilities_1.findEnsuredFiles)(packItems, (item) => capeTextureSet.has(item.name));
        const capeCheck = await Promise.all(capeTextures.map((cape) => this.validateCapeTextureImage(cape)));
        const capeResults = capeCheck.filter(TestDefinition_1.isResult);
        //check for duplicate skin textures
        const skinTextureNames = skins.map((skin) => skin.texture).filter((texture) => !!texture);
        const duplicateTextureResults = skinTextureNames
            .filter((name, index) => skinTextureNames.indexOf(name) !== index)
            .map((duplicate) => (0, TestDefinition_1.resultFromTest)(Tests.DuplicateTextures, this.id, `Duplicate skin texture found: ${duplicate}`));
        //check skin textures
        const textureCheck = await Promise.all(skins.map((skin) => this.validateTextureImage(skin, packItems, isMCCreator)));
        const textureResults = textureCheck.flatMap((result) => result);
        //check orphaned textures
        const knownTextures = new Set([...skinTextureNames, ...capeTextureSet]);
        const allTexturesInProject = packItems.filter((item) => item.itemType === IProjectItemData_1.ProjectItemType.texture);
        const orphaned = allTexturesInProject.filter((texItem) => !knownTextures.has(texItem.name));
        // CSPJ110
        const orphanResults = orphaned.map((orphan) => (0, TestDefinition_1.resultFromTest)(Tests.OrphanedTexture, this.id, `${orphan.name} in skin pack not found in skins.json`));
        // combine and return results
        return [
            ...duplicateCapeTexturesResults,
            ...capeResults,
            ...duplicateTextureResults,
            ...textureResults,
            ...orphanResults,
        ];
    }
    validateNonMCRestrictionsForSkin(skins) {
        const results = [];
        if (skins.length > MaxSkinsInAPack) {
            results.push((0, TestDefinition_1.resultFromTest)(Tests.InvalidNumberOfSkins, this.id));
        }
        if (skins.find((skin) => hasMCOnlyProperties(skin))) {
            const message = "animations and enable_attachables not allowed if not Minecraft Creator";
            results.push((0, TestDefinition_1.resultFromTest)(Tests.MCCreatorPropertyNotAllowed, this.id, message));
        }
        if (skins.find((skin) => !!skin.cape)) {
            results.push((0, TestDefinition_1.resultFromTest)(Tests.CapeTextureNotAllowed, this.id));
        }
        return results;
    }
    async validateCapeTextureImage(textureFile) {
        if (!textureFile) {
            return null;
        }
        const texture = await FileTexture_1.default.readTextureFromProjectFile(textureFile);
        if (!texture) {
            const message = `Failed to read file: ${textureFile.name}`;
            return (0, TestDefinition_1.resultFromTest)(Tests.FailedToReadFile, this.id, message);
        }
        if (!(0, Skin_1.isValidCapeSize)([texture.width, texture.height])) {
            const message = `Texture: ${textureFile.name} is invalid size (${texture.width}x${texture.height})`;
            return (0, TestDefinition_1.resultFromTest)(Tests.InvalidTextureSize, this.id, message);
        }
        return null;
    }
    async validateTextureImage(skin, items, isMCCreator) {
        const textureFile = await (0, ProjectItemUtilities_1.getEnsuredFile)(items, (item) => item.name === skin.texture);
        if (!textureFile) {
            return [];
        }
        const textureName = textureFile.name;
        const texture = await FileTexture_1.default.readTextureFromProjectFile(textureFile);
        if (!texture) {
            const message = `Failed to read file: ${textureName}`;
            return [(0, TestDefinition_1.resultFromTest)(Tests.FailedToReadFile, this.id, message)];
        }
        if (!(0, Skin_1.isValidSkinModelTarget)([texture.width, texture.height])) {
            const message = `Texture: ${textureName} is invalid size (${texture.width}x${texture.height})`;
            return [(0, TestDefinition_1.resultFromTest)(Tests.InvalidTextureSize, this.id, message)];
        }
        // remaining checks do not apply to MC creator
        if (isMCCreator) {
            return [];
        }
        const skinTarget = (0, TextureUtilities_1.getSkinTargetFromName)(textureName);
        if (!skinTarget) {
            return [
                (0, TestDefinition_1.resultFromTest)(Tests.InvalidSkinModelTarget, this.id, `No intended model tag in name. Slim model skins must have the prefix or suffix of  'a', 'alex', 'slim', 'customSlim' separated by an '_' ex. <name>_customSlim. Custom model skins must have the prefix or suffix of  's', 'steve', 'custom' separated by an '_' ex. <name>_custom.`),
            ];
        }
        const geoSkinSize = (0, Skin_1.getModelTargetGeometry)(skin);
        if (!geoSkinSize) {
            return [(0, TestDefinition_1.resultFromTest)(Tests.InvalidSkinModelTarget, this.id, `geometry property: ${skin.geometry} not allowed`)];
        }
        const pixelSkinSize = (0, TextureUtilities_1.getSkinTargetByUniquePixelLocations)(texture);
        const isSizeConsistent = skinTarget === geoSkinSize && skinTarget === pixelSkinSize;
        if (!isSizeConsistent) {
            const message = `Model size indicators are inconsistent. name: ${skinTarget}, geometry: ${geoSkinSize}, image data: ${pixelSkinSize}`;
            return [(0, TestDefinition_1.resultFromTest)(Tests.InvalidSkinModelTarget, this.id, message)];
        }
        if (!(0, TextureUtilities_1.isOuterAreaIsBlank)(texture, skinTarget)) {
            const message = `[${textureFile.name}]: Ensure that no pixels not visible on the model are filled in with an alpha greater than 0`;
            return [(0, TestDefinition_1.resultFromTest)(Tests.OuterAreaIsBlank, this.id, message)];
        }
        const segmentVisibilities = (0, TextureUtilities_1.getSegmentsVisibilities)(texture, skinTarget);
        const visErrors = segmentVisibilities
            .flatMap((segment) => {
            const messages = [];
            if (!segment.visibilities.top && !segment.visibilities.bottom) {
                messages.push(`The ${segment.segmentName} is not visible from the top and the bottom. This could cause this part of the model to be completely invisible from certain angles.`);
            }
            if (!segment.visibilities.front && segment.visibilities.back) {
                messages.push(`The ${segment.segmentName} is not visible from the front and the back. This could cause this part of the model to be completely invisible from certain angles.`);
            }
            if (!segment.visibilities.left && !segment.visibilities.right) {
                messages.push(`The ${segment.segmentName} is not visible from the right and the left. This could cause this part of the model to be completely invisible from certain angles.`);
            }
            return messages;
        })
            .map((message) => (0, TestDefinition_1.resultFromTest)(Tests.ModelInvisible, this.id, message));
        const visWarnings = segmentVisibilities
            .flatMap((segment) => Object.entries(segment.visibilities)
            .filter(([_side, isVisible]) => !isVisible)
            .map(([side]) => `Side: [${side}] of segment: [${segment.segmentName}] is not visible!`))
            .map((message) => (0, TestDefinition_1.resultFromTest)(Tests.ModelPartiallyInvisible, this.id, message));
        return [...visErrors, ...visWarnings];
    }
    checkSkinLocalizations(locManager, skinPack) {
        const locKeysFromSkinPack = (0, SkinPack_1.getLocKeysFromSkinPack)(skinPack);
        const knownKeys = new Set(locManager.getAllTokenKeys());
        const orphanLocKeyResults = locKeysFromSkinPack
            .filter((key) => !knownKeys.has(key))
            .map((key) => (0, TestDefinition_1.resultFromTest)(Tests.OrphanedLocKey, this.id, `Loc key [${key}] in skins.json not found in en_US.lang file`));
        const knownSkinsKeys = new Set(locKeysFromSkinPack);
        const localizedOrphansResults = [];
        const invalidSpaceResults = [];
        for (const lang of locManager.getAllLanguages()) {
            const orphansResultsForLang = lang
                .getLocKeys()
                .filter((key) => !knownSkinsKeys.has(key) && (key.startsWith("skin.") || key.startsWith("skinpack.")))
                .map((orphanedKey) => `Loc Key: [${orphanedKey}] found in ${lang.language}.lang not found in skins.json`)
                .map((message) => (0, TestDefinition_1.resultFromTest)(Tests.LocalizedKeyNotFoundInSkinsJson, this.id, message));
            const invalidSpaceResultsForLang = lang
                .getLocKeys()
                .filter((key) => LeadingOrTrailingSpaceRegex.test(key))
                .map((invalidSpacedKey) => `Loc string for key [${invalidSpacedKey}] in ${lang.language}.lang must not contain leading or trailing spaces.`)
                .map((message) => (0, TestDefinition_1.resultFromTest)(Tests.InvalidSpacingOnLocalizedKey, this.id, message));
            localizedOrphansResults.push(...orphansResultsForLang);
            invalidSpaceResults.push(...invalidSpaceResultsForLang);
        }
        return [...orphanLocKeyResults, ...localizedOrphansResults, ...invalidSpaceResults];
    }
}
exports.default = CheckSkinPackJsonGenerator;
function hasValidLocalizationNames(localizationName, serializeName) {
    return !!localizationName && !!serializeName && localizationName === serializeName;
}
function hasMCOnlyProperties(skin) {
    return !!skin.animations || !!skin.enable_attachables;
}

//# sourceMappingURL=../../maps/info/projectGenerators/CheckSkinPackJsonGenerator.js.map
