"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.upscaleEntity = upscaleEntity;
exports.upscaleEntityGeometry = upscaleEntityGeometry;
const entity_utils_1 = require("../entity-utils");
const validation_1 = require("../validation");
const DEFAULT_SCALE = [1, 1, 1];
const EPSILON = 1e-6;
const toNumber = (value) => {
    const result = Number(value);
    return Number.isFinite(result) ? result : 0;
};
const round = (value) => Math.round(value * 1e6) / 1e6;
const scaleVec = (input, factor) => {
    if (!Array.isArray(input)) {
        return undefined;
    }
    return [
        round(toNumber(input[0]) * factor),
        round(toNumber(input[1]) * factor),
        round(toNumber(input[2]) * factor),
    ];
};
const divideVec = (input, factor) => {
    const source = Array.isArray(input) ? input : DEFAULT_SCALE;
    return [
        round(toNumber(source[0]) / factor),
        round(toNumber(source[1]) / factor),
        round(toNumber(source[2]) / factor),
    ];
};
const upscalePatternInPlace = (pattern, factor) => {
    const width = Math.max(1, Math.round(toNumber(pattern.size?.[0])));
    const height = Math.max(1, Math.round(toNumber(pattern.size?.[1])));
    const source = Array.isArray(pattern.data) ? pattern.data.slice() : [];
    const scaledWidth = width * factor;
    const scaledHeight = height * factor;
    const scaled = new Array(scaledWidth * scaledHeight).fill(0);
    for (let y = 0; y < height; y += 1) {
        for (let x = 0; x < width; x += 1) {
            const value = source[y * width + x] ?? 0;
            for (let dy = 0; dy < factor; dy += 1) {
                for (let dx = 0; dx < factor; dx += 1) {
                    const targetX = x * factor + dx;
                    const targetY = y * factor + dy;
                    scaled[targetY * scaledWidth + targetX] = value;
                }
            }
        }
    }
    pattern.size = [scaledWidth, scaledHeight];
    pattern.data = scaled;
};
const upscaleVoxelInPlace = (voxel, factor) => {
    const [sx, sy, sz] = voxel.size;
    const nx = Math.max(1, Math.round(sx * factor));
    const ny = Math.max(1, Math.round(sy * factor));
    const nz = Math.max(1, Math.round(sz * factor));
    const source = voxel.voxels ?? [];
    const result = new Array(nx * ny * nz).fill(0);
    const sourceIndex = (x, y, z) => x + sx * (y + sy * z);
    const targetIndex = (x, y, z) => x + nx * (y + ny * z);
    for (let z = 0; z < sz; z += 1) {
        for (let y = 0; y < sy; y += 1) {
            for (let x = 0; x < sx; x += 1) {
                const value = source[sourceIndex(x, y, z)] ?? 0;
                if (value === 0)
                    continue;
                for (let dz = 0; dz < factor; dz += 1) {
                    for (let dy = 0; dy < factor; dy += 1) {
                        for (let dx = 0; dx < factor; dx += 1) {
                            const tx = x * factor + dx;
                            const ty = y * factor + dy;
                            const tz = z * factor + dz;
                            result[targetIndex(tx, ty, tz)] = value;
                        }
                    }
                }
            }
        }
    }
    voxel.size = [nx, ny, nz];
    voxel.voxels = result;
};
const upscalePrimitive = (primitive, factor) => {
    switch (primitive.kind) {
        case 'plain_box': {
            const box = primitive;
            box.size = box.size.map(value => round(toNumber(value) * factor));
            break;
        }
        case 'textured_box': {
            const box = primitive;
            if (Array.isArray(box.size)) {
                box.size = box.size.map(value => round(toNumber(value) * factor));
            }
            break;
        }
        case 'voxel_box': {
            upscaleVoxelInPlace(primitive, factor);
            break;
        }
        case 'combo_box': {
            const combo = primitive;
            if (Array.isArray(combo.size)) {
                combo.size = combo.size.map(value => round(toNumber(value) * factor));
            }
            combo.slots.forEach(slot => {
                slot.offset = slot.offset.map(value => round(toNumber(value) * factor));
            });
            break;
        }
        default:
            break;
    }
};
const scalePrimitiveSizes = (entity, factor) => {
    if (Math.abs(Math.round(factor) - factor) > EPSILON) {
        throw new Error('Upscale factor must be an integer for primitive scaling');
    }
    const intFactor = Math.max(1, Math.round(factor));
    if (intFactor === 1) {
        return;
    }
    entity.primitives.forEach(primitive => {
        upscalePrimitive(primitive, intFactor);
    });
};
const upscaleEntityPrimitives = (entity, factor) => {
    if (Math.abs(Math.round(factor) - factor) > EPSILON) {
        throw new Error('Upscale factor must be an integer for primitive scaling');
    }
    const intFactor = Math.max(1, Math.round(factor));
    if (intFactor === 1) {
        return;
    }
    entity.patterns.forEach(pattern => {
        upscalePatternInPlace(pattern, intFactor);
    });
    scalePrimitiveSizes(entity, factor);
};
const scaleNode = (node, factor) => {
    if (node.position) {
        node.position = scaleVec(node.position, factor) ?? node.position;
    }
    if (node.center) {
        node.center = scaleVec(node.center, factor) ?? node.center;
    }
    if (node.size) {
        node.size = scaleVec(node.size, factor) ?? node.size;
    }
    node.children?.forEach(child => scaleNode(child, factor));
};
const scaleAnimationPositions = (entity, factor) => {
    entity.animations?.forEach(animation => {
        animation.channels?.forEach(channel => {
            if (channel.property !== 'position') {
                return;
            }
            channel.keyframes?.forEach(keyframe => {
                const value = keyframe.value;
                if (!Array.isArray(value)) {
                    return;
                }
                keyframe.value = [
                    round(toNumber(value[0]) * factor),
                    round(toNumber(value[1]) * factor),
                    round(toNumber(value[2]) * factor),
                ];
            });
        });
    });
};
function upscaleEntityInternal(entity, factor = 2, options) {
    if (factor <= EPSILON) {
        throw new Error('Scale factor must be greater than zero');
    }
    const clone = (0, entity_utils_1.cloneEntity)(entity);
    if (!clone.geometry?.root) {
        return clone;
    }
    if (!clone.geometry.root.scale || !Array.isArray(clone.geometry.root.scale)) {
        clone.geometry.root.scale = [...DEFAULT_SCALE];
    }
    clone.geometry.root.scale = divideVec(clone.geometry.root.scale, factor);
    scaleNode(clone.geometry.root, factor);
    scaleAnimationPositions(clone, factor);
    if (options.scaleTextures) {
        upscaleEntityPrimitives(clone, factor);
    }
    else {
        scalePrimitiveSizes(clone, factor);
    }
    const { issues } = (0, validation_1.validateBoxel)(clone);
    if (issues.length > 0) {
        const summary = issues
            .slice(0, 3)
            .map(issue => `${issue.path}: ${issue.message}`)
            .join('; ');
        throw new Error(`Upscaled entity failed validation — ${summary}`);
    }
    return clone;
}
function upscaleEntity(entity, factor = 2) {
    return upscaleEntityInternal(entity, factor, { scaleTextures: true });
}
function upscaleEntityGeometry(entity, factor = 2) {
    return upscaleEntityInternal(entity, factor, { scaleTextures: false });
}
//# sourceMappingURL=upscale.js.map