"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.buildHumanoidR15TexturedEntity = buildHumanoidR15TexturedEntity;
const slice_1 = require("../../transforms/textured-boxes/slice");
const entity_utils_1 = require("../../entity-utils");
const entity_utils_2 = require("../../entity-utils");
const common_1 = require("./common");
const REQUIRED_NODES = [
    'Head',
    'Chest',
    'Hip',
    'Right_Thigh',
    'Right_Leg',
    'Right_Foot',
    'Left_Thigh',
    'Left_Leg',
    'Left_Foot',
    'Right_Arm',
    'Right_Forearm',
    'Right_Hand',
    'Left_Arm',
    'Left_Forearm',
    'Left_Hand',
];
function getNode(nodes, id) {
    const node = nodes.get(id);
    if (!node) {
        throw new Error(`Humanoid R15 template is missing "${id}" node`);
    }
    return node;
}
function getTexturedPrimitive(entity, primitiveId) {
    const primitive = entity.primitives.find(entry => entry.id === primitiveId);
    if (!primitive || primitive.kind !== 'textured_box') {
        throw new Error(`Textured primitive "${primitiveId}" not found`);
    }
    return primitive;
}
const OVERLAY_FACE_PROPS = [
    { face: 'north', overlay: 'northOverlay' },
    { face: 'south', overlay: 'southOverlay' },
    { face: 'east', overlay: 'eastOverlay' },
    { face: 'west', overlay: 'westOverlay' },
    { face: 'top', overlay: 'topOverlay' },
    { face: 'bottom', overlay: 'bottomOverlay' },
];
function buildOverlayPrimitive(primitive) {
    const overlay = {
        id: `${primitive.id}_overlay_source`,
        kind: 'textured_box',
        size: Array.isArray(primitive.size) ? [...primitive.size] : [1, 1, 1],
    };
    let hasOverlay = false;
    OVERLAY_FACE_PROPS.forEach(({ face, overlay: overlayProp }) => {
        const overlayId = primitive[overlayProp];
        if (typeof overlayId === 'string' && overlayId.trim().length > 0) {
            overlay[face] = overlayId;
            hasOverlay = true;
        }
    });
    return hasOverlay ? overlay : undefined;
}
function buildHumanoidR15TexturedEntity(options) {
    const { template, source, outputId } = options;
    const templateClone = (0, common_1.cloneTemplate)(template, outputId);
    const working = (0, entity_utils_2.cloneEntity)(source);
    const workingRoot = working.geometry?.root;
    if (workingRoot) {
        ['torso', 'arm_right', 'arm_left', 'leg_right', 'leg_left', 'head'].forEach(id => {
            const node = (0, entity_utils_1.findNode)(workingRoot, id);
            if (node) {
                node.mesh = undefined;
            }
        });
    }
    const nodesById = (0, common_1.collectNodes)(templateClone.geometry.root, (node) => {
        node.mesh = undefined;
    });
    const headNode = getNode(nodesById, 'Head');
    const chestNode = getNode(nodesById, 'Chest');
    const hipNode = getNode(nodesById, 'Hip');
    const rightThighNode = getNode(nodesById, 'Right_Thigh');
    const rightLegNode = getNode(nodesById, 'Right_Leg');
    const rightFootNode = getNode(nodesById, 'Right_Foot');
    const leftThighNode = getNode(nodesById, 'Left_Thigh');
    const leftLegNode = getNode(nodesById, 'Left_Leg');
    const leftFootNode = getNode(nodesById, 'Left_Foot');
    const rightArmNode = getNode(nodesById, 'Right_Arm');
    const rightForearmNode = getNode(nodesById, 'Right_Forearm');
    const rightHandNode = getNode(nodesById, 'Right_Hand');
    const leftArmNode = getNode(nodesById, 'Left_Arm');
    const leftForearmNode = getNode(nodesById, 'Left_Forearm');
    const leftHandNode = getNode(nodesById, 'Left_Hand');
    const hipOriginalSize = [
        Number(hipNode.size?.[0] ?? 0),
        Number(hipNode.size?.[1] ?? 0),
        Number(hipNode.size?.[2] ?? 0),
    ];
    const chestOriginalSize = [
        Number(chestNode.size?.[0] ?? 0),
        Number(chestNode.size?.[1] ?? 0),
        Number(chestNode.size?.[2] ?? 0),
    ];
    const hipOriginalCenter = [
        Number(hipNode.center?.[0] ?? 0),
        Number(hipNode.center?.[1] ?? (hipOriginalSize[1] || 0) / 2),
        Number(hipNode.center?.[2] ?? 0),
    ];
    const chestOriginalCenter = [
        Number(chestNode.center?.[0] ?? 0),
        Number(chestNode.center?.[1] ?? (chestOriginalSize[1] || 0) / 2),
        Number(chestNode.center?.[2] ?? 0),
    ];
    const hipCenterYOffset = hipOriginalCenter[1] - hipOriginalSize[1] / 2;
    const chestCenterYOffset = chestOriginalCenter[1] - chestOriginalSize[1] / 2;
    const headPrimitive = getTexturedPrimitive(working, 'head_primitive');
    headPrimitive.id = `${headNode.id}_textured`;
    headNode.mesh = headPrimitive.id;
    const headSizeX = Number(headPrimitive.size?.[0] ?? headNode.size?.[0] ?? 0);
    const headSizeY = Number(headPrimitive.size?.[1] ?? headNode.size?.[1] ?? 0);
    const headSizeZ = Number(headPrimitive.size?.[2] ?? headNode.size?.[2] ?? 0);
    headNode.size = [headSizeX + 1, headSizeY + 1, headSizeZ + 1];
    headNode.center = [...(headNode.center ?? [0, headSizeY / 2, 0])];
    const torsoPrimitive = getTexturedPrimitive(working, 'torso_primitive');
    const hipTarget = (0, common_1.roundSize)(hipOriginalSize[1]);
    const chestTarget = (0, common_1.roundSize)(chestOriginalSize[1]);
    const targetTotal = Math.max(hipTarget + chestTarget, 1);
    const torsoHeight = (0, common_1.roundSize)(torsoPrimitive.size[1]);
    const idealSplit = Math.round((hipTarget / targetTotal) * torsoHeight);
    const splitPlane = Math.min(Math.max(idealSplit, 1), torsoHeight - 1);
    const torsoSplit = (0, slice_1.splitTexturedBox)(working, torsoPrimitive.id, {
        axis: 'y',
        plane: splitPlane,
    });
    const hipPrimitive = getTexturedPrimitive(working, torsoSplit.negativeId);
    const chestPrimitive = getTexturedPrimitive(working, torsoSplit.positiveId);
    hipPrimitive.id = `${hipNode.id}_textured`;
    chestPrimitive.id = `${chestNode.id}_textured`;
    hipNode.mesh = hipPrimitive.id;
    chestNode.mesh = chestPrimitive.id;
    hipNode.size = [hipPrimitive.size[0], hipPrimitive.size[1], hipPrimitive.size[2]];
    hipNode.center = [
        hipOriginalCenter[0],
        hipCenterYOffset + hipPrimitive.size[1] / 2,
        hipOriginalCenter[2],
    ];
    chestNode.size = [chestPrimitive.size[0], chestPrimitive.size[1], chestPrimitive.size[2]];
    const chestCenter = [
        chestOriginalCenter[0],
        chestPrimitive.size[1] / 2,
        chestOriginalCenter[2],
    ];
    chestNode.center = chestCenter;
    const chestPosition = [...(chestNode.position ?? [0, 0, 0])];
    chestPosition[1] = hipPrimitive.size[1];
    chestNode.position = chestPosition;
    const chestSizeDelta = chestPrimitive.size[1] - chestOriginalSize[1];
    if (Math.abs(chestSizeDelta) > 1e-6 && Array.isArray(chestNode.children)) {
        chestNode.children.forEach((child) => {
            if (!Array.isArray(child.position)) {
                return;
            }
            const updatedPosition = [...child.position];
            updatedPosition[1] += chestSizeDelta;
            child.position = updatedPosition;
        });
    }
    const legConfigs = [
        { primitiveId: 'leg_right_primitive', thighNode: rightThighNode, legNode: rightLegNode, footNode: rightFootNode },
        { primitiveId: 'leg_left_primitive', thighNode: leftThighNode, legNode: leftLegNode, footNode: leftFootNode },
    ];
    legConfigs.forEach(config => {
        const original = working.primitives.find((entry) => entry.id === config.primitiveId);
        if (!original || original.kind !== 'textured_box') {
            throw new Error(`Textured primitive "${config.primitiveId}" not found`);
        }
        const totalHeight = (0, common_1.roundSize)(Number(original.size?.[1] ?? 0));
        const thighOriginalSize = [
            Number(config.thighNode.size?.[0] ?? 0),
            Number(config.thighNode.size?.[1] ?? 0),
            Number(config.thighNode.size?.[2] ?? 0),
        ];
        const thighOriginalCenter = [
            Number(config.thighNode.center?.[0] ?? 0),
            Number(config.thighNode.center?.[1] ?? 0),
            Number(config.thighNode.center?.[2] ?? 0),
        ];
        const legOriginalSize = [
            Number(config.legNode.size?.[0] ?? 0),
            Number(config.legNode.size?.[1] ?? 0),
            Number(config.legNode.size?.[2] ?? 0),
        ];
        const legOriginalCenter = [
            Number(config.legNode.center?.[0] ?? 0),
            Number(config.legNode.center?.[1] ?? 0),
            Number(config.legNode.center?.[2] ?? 0),
        ];
        const footOriginalSize = [
            Number(config.footNode.size?.[0] ?? 0),
            Number(config.footNode.size?.[1] ?? 0),
            Number(config.footNode.size?.[2] ?? 0),
        ];
        const footOriginalCenter = [
            Number(config.footNode.center?.[0] ?? 0),
            Number(config.footNode.center?.[1] ?? 0),
            Number(config.footNode.center?.[2] ?? 0),
        ];
        const targetThighHeight = (0, common_1.roundSize)(thighOriginalSize[1]);
        const targetLegHeight = (0, common_1.roundSize)(legOriginalSize[1]);
        const targetFootHeight = (0, common_1.roundSize)(footOriginalSize[1]);
        const targetTotalHeight = Math.max(targetThighHeight + targetLegHeight + targetFootHeight, 1);
        let thighHeight = Math.max(1, Math.round((targetThighHeight / targetTotalHeight) * totalHeight));
        let legHeight = Math.max(1, Math.round((targetLegHeight / targetTotalHeight) * totalHeight));
        let footHeight = Math.max(1, totalHeight - thighHeight - legHeight);
        const heightSum = thighHeight + legHeight + footHeight;
        if (heightSum < totalHeight) {
            footHeight += totalHeight - heightSum;
        }
        else if (heightSum > totalHeight) {
            let diff = heightSum - totalHeight;
            const reducers = [
                () => {
                    if (footHeight > 1) {
                        footHeight -= 1;
                        return true;
                    }
                    return false;
                },
                () => {
                    if (legHeight > 1) {
                        legHeight -= 1;
                        return true;
                    }
                    return false;
                },
                () => {
                    if (thighHeight > 1) {
                        thighHeight -= 1;
                        return true;
                    }
                    return false;
                },
            ];
            while (diff > 0) {
                let reduced = false;
                for (const reducer of reducers) {
                    if (reducer()) {
                        diff -= 1;
                        reduced = true;
                        if (diff === 0) {
                            break;
                        }
                    }
                }
                if (!reduced) {
                    break;
                }
            }
        }
        if (thighHeight + legHeight + footHeight !== totalHeight) {
            footHeight = Math.max(1, totalHeight - thighHeight - legHeight);
        }
        const firstSplit = (0, slice_1.splitTexturedBox)(working, original.id, { axis: 'y', plane: Math.max(1, Math.min(footHeight, totalHeight - 1)) });
        const footPrimitive = getTexturedPrimitive(working, firstSplit.negativeId);
        footPrimitive.id = `${config.footNode.id}_textured`;
        const remainingId = firstSplit.positiveId;
        const remainingPrimitive = getTexturedPrimitive(working, remainingId);
        const remainingHeight = (0, common_1.roundSize)(Number(remainingPrimitive.size?.[1] ?? 0));
        const legPlane = Math.max(1, Math.min(legHeight, remainingHeight - 1));
        const secondSplit = (0, slice_1.splitTexturedBox)(working, remainingId, { axis: 'y', plane: legPlane });
        const legPrimitive = getTexturedPrimitive(working, secondSplit.negativeId);
        legPrimitive.id = `${config.legNode.id}_textured`;
        const thighPrimitive = getTexturedPrimitive(working, secondSplit.positiveId);
        thighPrimitive.id = `${config.thighNode.id}_textured`;
        config.thighNode.mesh = thighPrimitive.id;
        config.legNode.mesh = legPrimitive.id;
        config.footNode.mesh = footPrimitive.id;
        config.thighNode.size = [thighPrimitive.size[0], thighPrimitive.size[1], thighPrimitive.size[2]];
        config.thighNode.center = [
            thighOriginalCenter[0],
            thighOriginalCenter[1] - thighOriginalSize[1] / 2 + thighPrimitive.size[1] / 2,
            thighOriginalCenter[2],
        ];
        config.legNode.size = [legPrimitive.size[0], legPrimitive.size[1], legPrimitive.size[2]];
        config.legNode.center = [
            legOriginalCenter[0],
            legOriginalCenter[1] - legOriginalSize[1] / 2 + legPrimitive.size[1] / 2,
            legOriginalCenter[2],
        ];
        config.footNode.size = [footPrimitive.size[0], footPrimitive.size[1], footPrimitive.size[2]];
        config.footNode.center = [
            footOriginalCenter[0],
            footOriginalCenter[1] - footOriginalSize[1] / 2 + footPrimitive.size[1] / 2,
            footOriginalCenter[2],
        ];
        const thighBottom = (config.thighNode.center?.[1] ?? 0) - thighPrimitive.size[1] / 2;
        (0, entity_utils_1.updateChildPositionAxis)(config.thighNode, config.legNode.id, 'y', thighBottom);
        const legBottom = (config.legNode.center?.[1] ?? 0) - legPrimitive.size[1] / 2;
        (0, entity_utils_1.updateChildPositionAxis)(config.legNode, config.footNode.id, 'y', legBottom);
        const thighSizeDelta = thighPrimitive.size[1] - thighOriginalSize[1];
        if (Math.abs(thighSizeDelta) > 1e-6 && Array.isArray(config.thighNode.children)) {
            config.thighNode.children.forEach(child => {
                if (child.id === config.legNode.id || !Array.isArray(child.position)) {
                    return;
                }
                const updated = [...child.position];
                updated[1] += thighSizeDelta;
                child.position = updated;
            });
        }
        const legSizeDelta = legPrimitive.size[1] - legOriginalSize[1];
        if (Math.abs(legSizeDelta) > 1e-6 && Array.isArray(config.legNode.children)) {
            config.legNode.children.forEach(child => {
                if (child.id === config.footNode.id || !Array.isArray(child.position)) {
                    return;
                }
                const updated = [...child.position];
                updated[1] += legSizeDelta;
                child.position = updated;
            });
        }
    });
    const applyArmDimensions = (node, primitive, originalSize, originalCenter, orientation) => {
        node.size = [primitive.size[0], primitive.size[1], primitive.size[2]];
        const center = [
            Number(originalCenter[0]),
            Number(originalCenter[1]),
            Number(originalCenter[2]),
        ];
        center[0] = (0, entity_utils_1.anchorCenter)(Number(originalCenter[0]), Number(originalSize[0]), primitive.size[0], orientation);
        node.center = center;
    };
    const armConfigs = [
        {
            primitiveId: 'arm_left_primitive',
            armNode: leftArmNode,
            forearmNode: leftForearmNode,
            handNode: leftHandNode,
            orientation: 1,
        },
        {
            primitiveId: 'arm_right_primitive',
            armNode: rightArmNode,
            forearmNode: rightForearmNode,
            handNode: rightHandNode,
            orientation: -1,
        },
    ];
    armConfigs.forEach(config => {
        const original = working.primitives.find((entry) => entry.id === config.primitiveId);
        if (!original || original.kind !== 'textured_box') {
            throw new Error(`Textured primitive "${config.primitiveId}" not found`);
        }
        const totalWidth = (0, common_1.roundSize)(Number(original.size?.[0] ?? 0));
        const armOriginalSize = [
            Number(config.armNode.size?.[0] ?? 0),
            Number(config.armNode.size?.[1] ?? 0),
            Number(config.armNode.size?.[2] ?? 0),
        ];
        const armOriginalCenter = [
            Number(config.armNode.center?.[0] ?? 0),
            Number(config.armNode.center?.[1] ?? 0),
            Number(config.armNode.center?.[2] ?? 0),
        ];
        const forearmOriginalSize = [
            Number(config.forearmNode.size?.[0] ?? 0),
            Number(config.forearmNode.size?.[1] ?? 0),
            Number(config.forearmNode.size?.[2] ?? 0),
        ];
        const forearmOriginalCenter = [
            Number(config.forearmNode.center?.[0] ?? 0),
            Number(config.forearmNode.center?.[1] ?? 0),
            Number(config.forearmNode.center?.[2] ?? 0),
        ];
        const handOriginalSize = [
            Number(config.handNode.size?.[0] ?? 0),
            Number(config.handNode.size?.[1] ?? 0),
            Number(config.handNode.size?.[2] ?? 0),
        ];
        const handOriginalCenter = [
            Number(config.handNode.center?.[0] ?? 0),
            Number(config.handNode.center?.[1] ?? 0),
            Number(config.handNode.center?.[2] ?? 0),
        ];
        const armWidth = Math.min(Math.max((0, common_1.roundSize)(armOriginalSize[0]), 1), totalWidth - 2);
        const remainderAfterArm = Math.max(totalWidth - armWidth, 2);
        const forearmWidth = Math.min(Math.max((0, common_1.roundSize)(forearmOriginalSize[0]), 1), remainderAfterArm - 1);
        let armPrimitive;
        let forearmPrimitive;
        let handPrimitive;
        if (config.orientation === 1) {
            const armSplit = (0, slice_1.splitTexturedBox)(working, original.id, { axis: 'x', plane: armWidth });
            armPrimitive = getTexturedPrimitive(working, armSplit.negativeId);
            const remainingId = armSplit.positiveId;
            const remainingPrimitive = getTexturedPrimitive(working, remainingId);
            const remainingWidth = (0, common_1.roundSize)(Number(remainingPrimitive.size?.[0] ?? 0));
            const forearmPlane = Math.min(Math.max(forearmWidth, 1), remainingWidth - 1);
            const forearmSplit = (0, slice_1.splitTexturedBox)(working, remainingId, { axis: 'x', plane: forearmPlane });
            forearmPrimitive = getTexturedPrimitive(working, forearmSplit.negativeId);
            handPrimitive = getTexturedPrimitive(working, forearmSplit.positiveId);
        }
        else {
            const armPlane = Math.min(Math.max(totalWidth - armWidth, 1), totalWidth - 1);
            const armSplit = (0, slice_1.splitTexturedBox)(working, original.id, { axis: 'x', plane: armPlane });
            armPrimitive = getTexturedPrimitive(working, armSplit.positiveId);
            const remainderId = armSplit.negativeId;
            const remainderPrimitive = getTexturedPrimitive(working, remainderId);
            const remainderWidth = (0, common_1.roundSize)(Number(remainderPrimitive.size?.[0] ?? 0));
            const forearmPlane = Math.min(Math.max(remainderWidth - forearmWidth, 1), remainderWidth - 1);
            const forearmSplit = (0, slice_1.splitTexturedBox)(working, remainderId, { axis: 'x', plane: forearmPlane });
            handPrimitive = getTexturedPrimitive(working, forearmSplit.negativeId);
            forearmPrimitive = getTexturedPrimitive(working, forearmSplit.positiveId);
        }
        armPrimitive.id = `${config.armNode.id}_textured`;
        forearmPrimitive.id = `${config.forearmNode.id}_textured`;
        handPrimitive.id = `${config.handNode.id}_textured`;
        config.armNode.mesh = armPrimitive.id;
        config.forearmNode.mesh = forearmPrimitive.id;
        config.handNode.mesh = handPrimitive.id;
        applyArmDimensions(config.armNode, armPrimitive, armOriginalSize, armOriginalCenter, config.orientation);
        applyArmDimensions(config.forearmNode, forearmPrimitive, forearmOriginalSize, forearmOriginalCenter, config.orientation);
        applyArmDimensions(config.handNode, handPrimitive, handOriginalSize, handOriginalCenter, config.orientation);
    });
    const newPrimitives = working.primitives.filter((primitive) => primitive.kind === 'textured_box');
    templateClone.primitives = newPrimitives;
    templateClone.materials = working.materials;
    templateClone.patterns = working.patterns;
    templateClone.textures = working.textures;
    return templateClone;
}
//# sourceMappingURL=textured-builder.js.map