"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.buildPatternLookup = exports.buildTextureLookup = exports.buildMaterialLookup = exports.buildPrimitiveLookup = void 0;
exports.findNodeById = findNodeById;
exports.findNode = findNode;
exports.applyNodeDimensions = applyNodeDimensions;
exports.applyNodeDimensionsAxis = applyNodeDimensionsAxis;
exports.updateChildPosition = updateChildPosition;
exports.updateChildPositionAxis = updateChildPositionAxis;
exports.anchorCenter = anchorCenter;
exports.collectPrimitiveUsage = collectPrimitiveUsage;
exports.computeComboBoundingSize = computeComboBoundingSize;
exports.computeEntityPixelsPerMeter = computeEntityPixelsPerMeter;
exports.collectPrimitiveDependencies = collectPrimitiveDependencies;
exports.ensureNoIdCollisions = ensureNoIdCollisions;
exports.selectByIdOrder = selectByIdOrder;
exports.mergeIncomingMaterials = mergeIncomingMaterials;
exports.applyMaterialRewriteToPrimitive = applyMaterialRewriteToPrimitive;
exports.applyMaterialRewriteToTexture = applyMaterialRewriteToTexture;
exports.cloneEntity = cloneEntity;
const textures_1 = require("./textures");
const materials_1 = require("./materials");
const buildPrimitiveLookup = (entity) => {
    const lookup = new Map();
    entity.primitives.forEach(primitive => lookup.set(primitive.id, primitive));
    return lookup;
};
exports.buildPrimitiveLookup = buildPrimitiveLookup;
const buildMaterialLookup = (entity) => {
    const lookup = new Map();
    entity.materials.forEach(material => lookup.set(material.id, material));
    return lookup;
};
exports.buildMaterialLookup = buildMaterialLookup;
const buildTextureLookup = (entity) => (0, textures_1.collectEntityTextureLookup)(entity);
exports.buildTextureLookup = buildTextureLookup;
const buildPatternLookup = (entity) => {
    const lookup = new Map();
    entity.patterns.forEach(pattern => lookup.set(pattern.id, pattern));
    return lookup;
};
exports.buildPatternLookup = buildPatternLookup;
function findNodeById(root, targetId) {
    if (root.id === targetId) {
        return root;
    }
    for (const child of root.children ?? []) {
        const found = findNodeById(child, targetId);
        if (found) {
            return found;
        }
    }
    return null;
}
function findNode(root, targetId) {
    const result = findNodeById(root, targetId);
    return result === null ? undefined : result;
}
function applyNodeDimensions(node, primitive, originalSize, originalCenter) {
    node.size = [primitive.size[0], primitive.size[1], primitive.size[2]];
    const center = [
        Number(originalCenter[0]),
        (Number(originalCenter[1]) - Number(originalSize[1]) / 2) + primitive.size[1] / 2,
        Number(originalCenter[2]),
    ];
    node.center = center;
}
function applyNodeDimensionsAxis(node, primitive, originalSize, originalCenter, axis) {
    node.size = [primitive.size[0], primitive.size[1], primitive.size[2]];
    const center = [
        Number(originalCenter[0]),
        Number(originalCenter[1]),
        Number(originalCenter[2]),
    ];
    const axisIdx = axis === 'x' ? 0 : 1;
    center[axisIdx] =
        (Number(originalCenter[axisIdx]) - Number(originalSize[axisIdx]) / 2) + primitive.size[axisIdx] / 2;
    node.center = center;
}
function updateChildPosition(parent, childId, offsetY) {
    if (!Array.isArray(parent.children)) {
        return;
    }
    parent.children.forEach(child => {
        if (child.id !== childId || !Array.isArray(child.position)) {
            return;
        }
        const updated = [...child.position];
        updated[1] = offsetY;
        child.position = updated;
    });
}
function updateChildPositionAxis(parent, childId, axis, value) {
    if (!Array.isArray(parent.children)) {
        return;
    }
    const axisIdx = axis === 'x' ? 0 : 1;
    parent.children.forEach(child => {
        if (child.id !== childId) {
            return;
        }
        const current = Array.isArray(child.position)
            ? [...child.position]
            : [0, 0, 0];
        current[axisIdx] = value;
        child.position = current;
    });
}
function anchorCenter(originalCenter, originalSize, newSize, orientation) {
    const innerBoundary = orientation > 0 ? originalCenter - originalSize / 2 : originalCenter + originalSize / 2;
    return orientation > 0 ? innerBoundary + newSize / 2 : innerBoundary - newSize / 2;
}
function collectPrimitiveUsage(entity, primitiveId) {
    const nodeIds = [];
    const comboIds = [];
    const visitNode = (node) => {
        if (node.mesh === primitiveId) {
            nodeIds.push(node.id);
        }
        node.children?.forEach(visitNode);
    };
    visitNode(entity.geometry.root);
    entity.primitives.forEach(primitive => {
        if (primitive.kind !== 'combo_box')
            return;
        const combo = primitive;
        combo.slots.forEach(slot => {
            if (slot.primitive === primitiveId) {
                comboIds.push(combo.id);
            }
        });
    });
    return { nodeIds, comboIds };
}
function computeComboBoundingSize(combo, primitiveLookup) {
    if (!combo.slots || combo.slots.length === 0) {
        return [
            combo.size?.[0] ?? 1,
            combo.size?.[1] ?? 1,
            combo.size?.[2] ?? 1,
        ];
    }
    let minX = Infinity;
    let minY = Infinity;
    let minZ = Infinity;
    let maxX = -Infinity;
    let maxY = -Infinity;
    let maxZ = -Infinity;
    let tracked = 0;
    combo.slots.forEach(slot => {
        const child = primitiveLookup.get(slot.primitive);
        if (!child) {
            return;
        }
        const size = child.size ?? [0, 0, 0];
        const halfSize = [size[0] / 2, size[1] / 2, size[2] / 2];
        minX = Math.min(minX, slot.offset[0] - halfSize[0]);
        minY = Math.min(minY, slot.offset[1] - halfSize[1]);
        minZ = Math.min(minZ, slot.offset[2] - halfSize[2]);
        maxX = Math.max(maxX, slot.offset[0] + halfSize[0]);
        maxY = Math.max(maxY, slot.offset[1] + halfSize[1]);
        maxZ = Math.max(maxZ, slot.offset[2] + halfSize[2]);
        tracked += 1;
    });
    if (tracked === 0 || !Number.isFinite(minX)) {
        return [
            combo.size?.[0] ?? 1,
            combo.size?.[1] ?? 1,
            combo.size?.[2] ?? 1,
        ];
    }
    const derived = [
        Math.max(0, maxX - minX),
        Math.max(0, maxY - minY),
        Math.max(0, maxZ - minZ),
    ];
    return [
        combo.size?.[0] ?? derived[0],
        combo.size?.[1] ?? derived[1],
        combo.size?.[2] ?? derived[2],
    ];
}
/**
 * Estimate the pixel-per-meter scale of an entity using its root scale vector.
 * Returns an integer value (e.g. 16 for classic avatars, 32 for HD avatars) or null when it cannot be derived.
 */
function computeEntityPixelsPerMeter(entity) {
    const rootScale = entity.geometry?.root?.scale;
    if (Array.isArray(rootScale) && rootScale.length > 0) {
        const positive = rootScale
            .map(value => Number(value))
            .filter(value => Number.isFinite(value) && value > 0);
        if (positive.length > 0) {
            const averageScale = positive.reduce((sum, value) => sum + value, 0) / positive.length;
            if (averageScale > 0) {
                const pixelsPerMeter = Math.round(1 / averageScale);
                if (pixelsPerMeter > 0) {
                    return pixelsPerMeter;
                }
            }
        }
    }
    return null;
}
function collectPrimitiveDependencies(entity, rootPrimitiveId) {
    const primitiveLookup = (0, exports.buildPrimitiveLookup)(entity);
    if (!primitiveLookup.has(rootPrimitiveId)) {
        throw new Error(`Primitive "${rootPrimitiveId}" not found in source entity`);
    }
    const pending = [rootPrimitiveId];
    const visited = new Set();
    const primitives = new Set();
    const materials = new Set();
    const textures = new Set();
    const patterns = new Set();
    const textureLookup = (0, exports.buildTextureLookup)(entity);
    const patternLookup = (0, exports.buildPatternLookup)(entity);
    const materialLookup = (0, exports.buildMaterialLookup)(entity);
    while (pending.length > 0) {
        const primitiveId = pending.pop();
        if (visited.has(primitiveId)) {
            continue;
        }
        visited.add(primitiveId);
        const primitive = primitiveLookup.get(primitiveId);
        if (!primitive) {
            throw new Error(`Primitive "${primitiveId}" references missing definition in source entity`);
        }
        primitives.add(primitive.id);
        switch (primitive.kind) {
            case 'plain_box': {
                const box = primitive;
                if (!materialLookup.has(box.material)) {
                    throw new Error(`Primitive "${primitive.id}" references unknown material "${box.material}"`);
                }
                materials.add(box.material);
                break;
            }
            case 'textured_box': {
                const textured = primitive;
                const summary = (0, textures_1.collectTexturedBoxDependencies)(textured, textureLookup);
                summary.textures.forEach(textureId => {
                    const texture = textureLookup.get(textureId);
                    if (!texture) {
                        throw new Error(`Primitive "${textured.id}" references unknown texture "${textureId}"`);
                    }
                    textures.add(textureId);
                    if (!patternLookup.has(texture.pattern)) {
                        throw new Error(`Texture "${textureId}" references missing pattern "${texture.pattern}"`);
                    }
                });
                summary.patterns.forEach(patternId => patterns.add(patternId));
                summary.materials.forEach(materialId => {
                    if (!materialLookup.has(materialId)) {
                        throw new Error(`Texture dependency references unknown material "${materialId}"`);
                    }
                    materials.add(materialId);
                });
                break;
            }
            case 'voxel_box': {
                const voxel = primitive;
                (voxel.palette ?? []).forEach(materialId => {
                    if (!materialLookup.has(materialId)) {
                        throw new Error(`Voxel primitive "${voxel.id}" references unknown material "${materialId}"`);
                    }
                    materials.add(materialId);
                });
                break;
            }
            case 'combo_box': {
                const combo = primitive;
                combo.slots.forEach(slot => {
                    pending.push(slot.primitive);
                });
                break;
            }
            default:
                throw new Error(`Unsupported primitive kind "${primitive.kind}"`);
        }
    }
    return { primitives, materials, textures, patterns };
}
function ensureNoIdCollisions(existing, incomingIds, entitySection) {
    for (const item of existing) {
        if (incomingIds.has(item.id)) {
            throw new Error(`Target entity already contains ${entitySection} with id "${item.id}". Rename it before importing or choose a different primitive.`);
        }
    }
}
function selectByIdOrder(items, ids) {
    return items.filter(item => ids.has(item.id)).map(item => ({ ...item }));
}
function mergeIncomingMaterials(existingMaterials, incomingMaterials) {
    const { additions, rewrites } = (0, materials_1.mergeMaterials)(existingMaterials, incomingMaterials);
    return { additions, rewrites };
}
function applyMaterialRewriteToPrimitive(primitive, rewrite) {
    switch (primitive.kind) {
        case 'plain_box':
            return {
                ...primitive,
                material: rewrite(primitive.material),
            };
        case 'voxel_box':
            return {
                ...primitive,
                palette: Array.isArray(primitive.palette) ? primitive.palette.map(rewrite) : [],
            };
        case 'textured_box':
        case 'combo_box':
        default:
            return { ...primitive };
    }
}
function applyMaterialRewriteToTexture(texture, rewrite) {
    return {
        ...texture,
        materials: Array.isArray(texture.materials) ? texture.materials.map(rewrite) : [],
    };
}
function cloneEntity(value) {
    if (typeof structuredClone === 'function') {
        return structuredClone(value);
    }
    return JSON.parse(JSON.stringify(value));
}
//# sourceMappingURL=entity-utils.js.map