"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.materialsApproximatelyEqual = materialsApproximatelyEqual;
exports.materialFingerprint = materialFingerprint;
exports.mergeMaterials = mergeMaterials;
exports.collectPaletteIds = collectPaletteIds;
exports.cloneMaterialsByPalette = cloneMaterialsByPalette;
exports.createMaterialEnsurer = createMaterialEnsurer;
const COLOR_COMPONENT_EPSILON = 1e-3;
const OPACITY_EPSILON = 1e-4;
const normalizeVec3 = (input) => {
    if (!Array.isArray(input) || input.length !== 3) {
        return null;
    }
    return [Number(input[0]) || 0, Number(input[1]) || 0, Number(input[2]) || 0];
};
const componentsEqual = (a, b, epsilon) => Math.abs(a - b) <= epsilon;
const vecsEqual = (a, b, epsilon) => {
    if (!a || !b)
        return !a && !b;
    for (let i = 0; i < 3; i += 1) {
        if (!componentsEqual(a[i], b[i], epsilon)) {
            return false;
        }
    }
    return true;
};
const normalizeOpacity = (value) => {
    if (typeof value !== 'number' || !Number.isFinite(value)) {
        return null;
    }
    return value;
};
function materialsApproximatelyEqual(a, b) {
    const baseA = normalizeVec3(a.baseColor);
    const baseB = normalizeVec3(b.baseColor);
    if (!vecsEqual(baseA, baseB, COLOR_COMPONENT_EPSILON)) {
        return false;
    }
    const emissiveA = normalizeVec3(a.emissiveColor);
    const emissiveB = normalizeVec3(b.emissiveColor);
    if (!vecsEqual(emissiveA, emissiveB, COLOR_COMPONENT_EPSILON)) {
        return false;
    }
    const opacityA = normalizeOpacity(a.opacity);
    const opacityB = normalizeOpacity(b.opacity);
    if (opacityA === null && opacityB === null) {
        return true;
    }
    if (opacityA === null || opacityB === null) {
        return false;
    }
    return componentsEqual(opacityA, opacityB, OPACITY_EPSILON);
}
function materialFingerprint(material) {
    const base = normalizeVec3(material.baseColor) ?? [0, 0, 0];
    const emissive = normalizeVec3(material.emissiveColor);
    const opacity = normalizeOpacity(material.opacity);
    const formatScalar = (value) => value.toFixed(4);
    const formatVec = (vec) => (vec ? vec.map(component => component.toFixed(4)).join(',') : 'null');
    return [
        formatVec(base),
        formatVec(emissive),
        opacity === null ? 'null' : formatScalar(opacity),
    ].join('|');
}
function mergeMaterials(existingMaterials, incomingMaterials) {
    const additions = [];
    const rewrites = new Map();
    const existingByFingerprint = new Map();
    existingMaterials.forEach(material => {
        existingByFingerprint.set(materialFingerprint(material), material.id);
    });
    incomingMaterials.forEach(material => {
        const fingerprint = materialFingerprint(material);
        const existingId = existingByFingerprint.get(fingerprint);
        if (existingId) {
            rewrites.set(material.id, existingId);
            return;
        }
        const duplicate = existingMaterials.find(candidate => materialsApproximatelyEqual(candidate, material));
        if (duplicate) {
            rewrites.set(material.id, duplicate.id);
            existingByFingerprint.set(fingerprint, duplicate.id);
            return;
        }
        additions.push(material);
        existingByFingerprint.set(fingerprint, material.id);
    });
    return { additions, rewrites };
}
function collectPaletteIds(primitives) {
    const paletteIds = new Set();
    for (const primitive of primitives) {
        const palette = primitive.palette;
        if (!Array.isArray(palette)) {
            continue;
        }
        for (const materialId of palette) {
            if (typeof materialId === 'string' && materialId.length > 0) {
                paletteIds.add(materialId);
            }
        }
    }
    return paletteIds;
}
function cloneMaterialsByPalette(materials, paletteIds) {
    return materials
        .filter(material => paletteIds.has(material.id))
        .map(material => JSON.parse(JSON.stringify(material)));
}
const clampChannel = (value) => {
    const numeric = Number(value);
    if (!Number.isFinite(numeric)) {
        return 0;
    }
    return Math.max(0, Math.min(255, Math.round(numeric)));
};
const clampOpacity = (value) => {
    const numeric = Number(value);
    if (!Number.isFinite(numeric)) {
        return 1;
    }
    return Math.max(0, Math.min(1, numeric));
};
const opacityToAlpha = (opacity) => {
    if (opacity === undefined) {
        return 255;
    }
    return clampChannel(Math.round(clampOpacity(opacity) * 255));
};
const toMaterialKey = (channels) => {
    const r = clampChannel(channels.r);
    const g = clampChannel(channels.g);
    const b = clampChannel(channels.b);
    const a = clampChannel(channels.a);
    return `${r},${g},${b},${a}`;
};
function createMaterialEnsurer(materials, options = {}) {
    const materialIdByColor = new Map();
    const prefix = options.prefix ?? 'humanoid_mat';
    const seedExistingMaterials = () => {
        materials.forEach(material => {
            if (!Array.isArray(material.baseColor) || material.baseColor.length !== 3) {
                return;
            }
            const baseColor = {
                r: clampChannel(material.baseColor[0]),
                g: clampChannel(material.baseColor[1]),
                b: clampChannel(material.baseColor[2]),
            };
            const alpha = opacityToAlpha(material.opacity);
            const key = toMaterialKey({ ...baseColor, a: alpha });
            if (!materialIdByColor.has(key)) {
                materialIdByColor.set(key, material.id);
            }
        });
    };
    seedExistingMaterials();
    return {
        ensure(color) {
            const sanitized = {
                r: clampChannel(color.r),
                g: clampChannel(color.g),
                b: clampChannel(color.b),
                a: clampChannel(color.a),
            };
            const key = toMaterialKey(sanitized);
            const existing = materialIdByColor.get(key);
            if (existing) {
                return existing;
            }
            const suffix = materials.length + 1;
            const id = `${prefix}_${suffix}`;
            const material = {
                id,
                baseColor: [sanitized.r, sanitized.g, sanitized.b],
            };
            if (sanitized.a < 255) {
                material.opacity = parseFloat(((sanitized.a / 255)).toFixed(4));
            }
            materials.push(material);
            materialIdByColor.set(key, id);
            return id;
        },
    };
}
//# sourceMappingURL=materials.js.map