"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.splitTexturedBox = splitTexturedBox;
const textures_1 = require("../../textures");
const validation_1 = require("../../validation");
const infer_face_1 = require("./infer-face");
const FACE_DIMENSIONS = {
    north: { widthAxis: 'x', heightAxis: 'y' },
    south: { widthAxis: 'x', heightAxis: 'y' },
    east: { widthAxis: 'z', heightAxis: 'y' },
    west: { widthAxis: 'z', heightAxis: 'y' },
    top: { widthAxis: 'x', heightAxis: 'z' },
    bottom: { widthAxis: 'x', heightAxis: 'z' },
};
const FACE_POSITIVE = { x: 'east', y: 'top', z: 'north' };
const FACE_NEGATIVE = { x: 'west', y: 'bottom', z: 'south' };
const FACE_WIDTH_FLIP = { south: true };
const overlayProps = {
    north: 'northOverlay',
    south: 'southOverlay',
    east: 'eastOverlay',
    west: 'westOverlay',
    top: 'topOverlay',
    bottom: 'bottomOverlay',
};
const clearOverlayFallbacks = (primitive) => {
    ['sidesOverlay', 'allOverlay'].forEach(prop => {
        if (primitive[prop] !== undefined) {
            primitive[prop] = undefined;
        }
    });
};
const axisIndex = { x: 0, y: 1, z: 2 };
const toNumber = (value) => {
    const num = Number(value);
    return Number.isFinite(num) ? num : 0;
};
const gridFromPattern = (pattern) => {
    const width = Math.max(1, Math.round(toNumber(pattern.size?.[0])));
    const height = Math.max(1, Math.round(toNumber(pattern.size?.[1])));
    const data = pattern.data ?? [];
    const grid = [];
    for (let y = 0; y < height; y += 1) {
        const row = [];
        for (let x = 0; x < width; x += 1) {
            row.push(data[y * width + x] ?? 0);
        }
        grid.push(row);
    }
    return grid;
};
const patternFromGrid = (id, grid) => {
    const height = grid.length;
    const width = height > 0 ? grid[0]?.length ?? 0 : 0;
    const data = [];
    for (let y = 0; y < height; y += 1) {
        for (let x = 0; x < width; x += 1) {
            data.push(grid[y]?.[x] ?? 0);
        }
    }
    return { id, size: [width, height], data };
};
const splitColumns = (grid, leftPixels) => {
    const left = [];
    const right = [];
    grid.forEach(row => {
        const cells = row ?? [];
        left.push(cells.slice(0, leftPixels));
        right.push(cells.slice(leftPixels));
    });
    return { left, right };
};
const splitRows = (grid, topPixels) => ({
    top: grid.slice(0, topPixels),
    bottom: grid.slice(topPixels),
});
const clonePattern = (pattern) => ({
    id: pattern.id,
    size: Array.isArray(pattern.size) ? [...pattern.size] : [1, 1],
    data: Array.isArray(pattern.data) ? pattern.data.slice() : [],
});
const cloneTexture = (texture) => ({
    id: texture.id,
    pattern: texture.pattern,
    materials: texture.materials.slice(),
});
const ensureMaterial = (entity, color, alpha) => {
    const existing = entity.materials.find(material => material.baseColor?.[0] === color[0] &&
        material.baseColor?.[1] === color[1] &&
        material.baseColor?.[2] === color[2] &&
        (material.opacity ?? 1) === alpha);
    if (existing) {
        return existing.id;
    }
    const id = `mat_${entity.materials.length + 1}`;
    entity.materials.push({
        id,
        baseColor: [...color],
        ...(alpha < 1 ? { opacity: alpha } : {}),
    });
    return id;
};
const addPatternTexture = (entity, baseId, grid, materials) => {
    const patternId = `${baseId}_pattern_${entity.patterns.length + 1}`;
    const pattern = patternFromGrid(patternId, grid);
    entity.patterns.push(pattern);
    const textureId = `${baseId}_texture_${entity.textures.length + 1}`;
    const texture = {
        id: textureId,
        pattern: pattern.id,
        materials: materials.slice(),
    };
    entity.textures.push(texture);
    return texture;
};
const findMaterialColor = (entity, materialId) => {
    const material = entity.materials.find(entry => entry.id === materialId);
    const base = Array.isArray(material?.baseColor) ? material.baseColor : [255, 255, 255];
    const color = [
        Math.max(0, Math.min(255, Math.round(base[0] ?? 255))),
        Math.max(0, Math.min(255, Math.round(base[1] ?? 255))),
        Math.max(0, Math.min(255, Math.round(base[2] ?? 255))),
    ];
    const alpha = material?.opacity !== undefined ? Number(material.opacity) : 1;
    return { color, alpha };
};
const deriveSeamFill = (entity, primitive, face) => {
    const textureId = (0, textures_1.resolveFaceTextureId)(primitive, face);
    if (!textureId) {
        return { mode: 'solid', color: [255, 255, 255], alpha: 1 };
    }
    const texture = entity.textures.find(entry => entry.id === textureId);
    if (!texture) {
        return { mode: 'solid', color: [255, 255, 255], alpha: 1 };
    }
    const pattern = entity.patterns.find(entry => entry.id === texture.pattern);
    let materialId;
    if (pattern && Array.isArray(pattern.data) && Array.isArray(texture.materials)) {
        for (const value of pattern.data) {
            const index = Number(value);
            if (Number.isInteger(index) && index >= 0 && index < texture.materials.length) {
                const candidate = texture.materials[index];
                if (candidate) {
                    materialId = candidate;
                    break;
                }
            }
        }
    }
    if (!materialId && Array.isArray(texture.materials) && texture.materials.length > 0) {
        materialId = texture.materials[0];
    }
    const { color, alpha } = findMaterialColor(entity, materialId);
    return { mode: 'solid', color, alpha };
};
const setFaceTexture = (primitive, face, textureId) => {
    switch (face) {
        case 'north':
            primitive.north = textureId;
            break;
        case 'south':
            primitive.south = textureId;
            break;
        case 'east':
            primitive.east = textureId;
            break;
        case 'west':
            primitive.west = textureId;
            break;
        case 'top':
            primitive.top = textureId;
            break;
        case 'bottom':
            primitive.bottom = textureId;
            break;
        default:
            break;
    }
};
const setOverlayTexture = (primitive, face, textureId) => {
    const prop = overlayProps[face];
    if (!prop) {
        return;
    }
    primitive[prop] = textureId;
};
const splitFaceTextures = (entity, partA, partB, original, face, axis, plane) => {
    const textureId = (0, textures_1.resolveFaceTextureId)(original, face);
    if (!textureId) {
        setFaceTexture(partA, face, undefined);
        setFaceTexture(partB, face, undefined);
        return { partA: {}, partB: {} };
    }
    const originalTexture = entity.textures.find(entry => entry.id === textureId);
    const originalPattern = originalTexture ? entity.patterns.find(entry => entry.id === originalTexture.pattern) : undefined;
    if (!originalTexture || !originalPattern) {
        setFaceTexture(partA, face, textureId);
        setFaceTexture(partB, face, textureId);
        return { partA: { texture: originalTexture }, partB: { texture: originalTexture } };
    }
    const pattern = clonePattern(originalPattern);
    const texture = cloneTexture(originalTexture);
    const grid = gridFromPattern(pattern);
    const dims = FACE_DIMENSIONS[face];
    const widthAxis = dims.widthAxis;
    const heightAxis = dims.heightAxis;
    const dimension = Math.max(1, Math.round(toNumber(original.size?.[axisIndex[axis]])));
    const widthPixels = grid[0]?.length ?? 0;
    const heightPixels = grid.length;
    const partAssets = { partA: {}, partB: {} };
    if (widthAxis === axis) {
        const leftPixels = Math.max(1, Math.min(widthPixels - 1, Math.round((plane / dimension) * widthPixels)));
        const splitPixels = Math.max(1, Math.min(widthPixels - 1, Math.round((plane / dimension) * widthPixels)));
        const flip = FACE_WIDTH_FLIP[face] ?? false;
        const negativeGrid = grid.map(row => flip ? row.slice(widthPixels - splitPixels) : row.slice(0, splitPixels));
        const positiveGrid = grid.map(row => flip ? row.slice(0, widthPixels - splitPixels) : row.slice(splitPixels));
        const negativeTexture = addPatternTexture(entity, `${texture.id}_${face}_negative`, negativeGrid, texture.materials);
        const positiveTexture = addPatternTexture(entity, `${texture.id}_${face}_positive`, positiveGrid, texture.materials);
        setFaceTexture(partA, face, negativeTexture.id);
        setFaceTexture(partB, face, positiveTexture.id);
        partAssets.partA = { grid: negativeGrid, texture: negativeTexture };
        partAssets.partB = { grid: positiveGrid, texture: positiveTexture };
        return partAssets;
    }
    if (heightAxis === axis) {
        const lowerPixels = Math.max(1, Math.min(heightPixels - 1, Math.round((plane / dimension) * heightPixels)));
        const splitIndex = Math.max(1, Math.min(heightPixels - 1, heightPixels - lowerPixels));
        const { top: upper, bottom: lower } = splitRows(grid, splitIndex);
        const lowerTexture = addPatternTexture(entity, `${texture.id}_${face}_lower`, lower, texture.materials);
        const upperTexture = addPatternTexture(entity, `${texture.id}_${face}_upper`, upper, texture.materials);
        setFaceTexture(partA, face, lowerTexture.id);
        setFaceTexture(partB, face, upperTexture.id);
        partAssets.partA = { grid: lower, texture: lowerTexture };
        partAssets.partB = { grid: upper, texture: upperTexture };
        return partAssets;
    }
    setFaceTexture(partA, face, textureId);
    setFaceTexture(partB, face, textureId);
    partAssets.partA = { grid, texture };
    partAssets.partB = { grid, texture };
    return partAssets;
};
const splitOverlayTextures = (entity, partA, partB, original, face, axis, plane) => {
    const overlayId = (0, textures_1.resolveOverlayFaceTextureId)(original, face);
    if (!overlayId) {
        setOverlayTexture(partA, face, undefined);
        setOverlayTexture(partB, face, undefined);
        return;
    }
    const texture = entity.textures.find(entry => entry.id === overlayId);
    const pattern = texture ? entity.patterns.find(entry => entry.id === texture.pattern) : undefined;
    if (!texture || !pattern) {
        setOverlayTexture(partA, face, overlayId);
        setOverlayTexture(partB, face, overlayId);
        return;
    }
    const grid = gridFromPattern(pattern);
    const dims = FACE_DIMENSIONS[face];
    const dimension = Math.max(1, Math.round(toNumber(original.size?.[axisIndex[axis]])));
    if (dims.widthAxis === axis) {
        const width = Math.max(1, grid[0]?.length ?? 1);
        const splitPixels = Math.max(1, Math.min(width - 1, Math.round((plane / dimension) * width)));
        const flip = FACE_WIDTH_FLIP[face] ?? false;
        const negativeGrid = grid.map(row => flip ? row.slice(width - splitPixels) : row.slice(0, splitPixels));
        const positiveGrid = grid.map(row => flip ? row.slice(0, width - splitPixels) : row.slice(splitPixels));
        const negativeTexture = addPatternTexture(entity, `${texture.id}_${face}_ov_negative`, negativeGrid, texture.materials);
        const positiveTexture = addPatternTexture(entity, `${texture.id}_${face}_ov_positive`, positiveGrid, texture.materials);
        setOverlayTexture(partA, face, negativeTexture.id);
        setOverlayTexture(partB, face, positiveTexture.id);
        return;
    }
    if (dims.heightAxis === axis) {
        const heightPixels = grid.length;
        const lowerPixels = Math.max(1, Math.min(heightPixels - 1, Math.round((plane / dimension) * heightPixels)));
        const splitIndex = Math.max(1, Math.min(heightPixels - 1, heightPixels - lowerPixels));
        const { top: upper, bottom: lower } = splitRows(grid, splitIndex);
        const lowerTexture = addPatternTexture(entity, `${texture.id}_${face}_ov_lower`, lower, texture.materials);
        const upperTexture = addPatternTexture(entity, `${texture.id}_${face}_ov_upper`, upper, texture.materials);
        setOverlayTexture(partA, face, lowerTexture.id);
        setOverlayTexture(partB, face, upperTexture.id);
        return;
    }
    setOverlayTexture(partA, face, overlayId);
    setOverlayTexture(partB, face, overlayId);
};
const applyCrossFill = (entity, part, face, fill) => {
    if (fill.mode === 'empty') {
        setFaceTexture(part, face, undefined);
        return;
    }
    const color = fill.color ?? [255, 255, 255];
    const alpha = fill.alpha ?? 1;
    const materialId = ensureMaterial(entity, color, alpha);
    const dims = FACE_DIMENSIONS[face];
    const width = Math.max(1, Math.round(toNumber(part.size?.[axisIndex[dims.widthAxis]])));
    const height = Math.max(1, Math.round(toNumber(part.size?.[axisIndex[dims.heightAxis]])));
    const grid = new Array(height).fill(null).map(() => new Array(width).fill(0));
    if (fill.mode === 'solid-edge') {
        grid.forEach((row, y) => {
            row[0] = 0;
            row[row.length - 1] = 0;
            if (y === 0 || y === grid.length - 1) {
                row.fill(0);
            }
        });
    }
    const texture = addPatternTexture(entity, `${part.id}_${face}_cross`, grid, [materialId]);
    setFaceTexture(part, face, texture.id);
};
const applyOverlayCrossFill = (entity, part, face, fill) => {
    if (fill.mode === 'empty') {
        setOverlayTexture(part, face, undefined);
        return;
    }
    const color = fill.color ?? [255, 255, 255];
    const alpha = fill.alpha ?? 1;
    const materialId = ensureMaterial(entity, color, alpha);
    const dims = FACE_DIMENSIONS[face];
    const width = Math.max(1, Math.round(toNumber(part.size?.[axisIndex[dims.widthAxis]])));
    const height = Math.max(1, Math.round(toNumber(part.size?.[axisIndex[dims.heightAxis]])));
    const grid = new Array(height).fill(null).map(() => new Array(width).fill(0));
    const texture = addPatternTexture(entity, `${part.id}_${face}_overlay_cross`, grid, [materialId]);
    setOverlayTexture(part, face, texture.id);
};
function splitTexturedBox(entity, primitiveId, options) {
    const index = entity.primitives.findIndex(primitive => primitive.id === primitiveId);
    if (index === -1) {
        throw new Error(`Textured primitive ${primitiveId} not found`);
    }
    const primitive = entity.primitives[index];
    if (primitive.kind !== 'textured_box') {
        throw new Error(`Primitive ${primitiveId} is not a textured box`);
    }
    const axis = options.axis;
    const plane = Math.round(options.plane);
    const axisIdx = axisIndex[axis];
    const extent = Math.max(1, Math.round(toNumber(primitive.size?.[axisIdx])));
    if (plane <= 0 || plane >= extent) {
        throw new Error(`Split plane ${plane} must be within (0, ${extent}) for axis ${axis}`);
    }
    const baseFill = options.fill ?? { mode: 'empty' };
    const overlayFill = options.overlayFill ?? baseFill;
    const partA = { ...primitive, id: `${primitive.id}_part_a` };
    const partB = { ...primitive, id: `${primitive.id}_part_b` };
    partA.size = [...primitive.size];
    partB.size = [...primitive.size];
    partA.size[axisIdx] = plane;
    partB.size[axisIdx] = extent - plane;
    ['north', 'south', 'east', 'west', 'top', 'bottom'].forEach(face => {
        splitFaceTextures(entity, partA, partB, primitive, face, axis, plane);
        splitOverlayTextures(entity, partA, partB, primitive, face, axis, plane);
    });
    const positiveFace = FACE_POSITIVE[axis];
    const negativeFace = FACE_NEGATIVE[axis];
    const seamNegativeTexture = (0, infer_face_1.buildFaceFromNeighbors)({
        entity,
        primitive: partA,
        face: positiveFace,
        prefix: `${primitive.id}_negative`,
    });
    if (seamNegativeTexture) {
        setFaceTexture(partA, positiveFace, seamNegativeTexture);
    }
    else {
        const positiveFill = baseFill.mode === 'empty' ? deriveSeamFill(entity, primitive, positiveFace) : baseFill;
        applyCrossFill(entity, partA, positiveFace, positiveFill);
    }
    const seamPositiveTexture = (0, infer_face_1.buildFaceFromNeighbors)({
        entity,
        primitive: partB,
        face: negativeFace,
        prefix: `${primitive.id}_positive`,
    });
    if (seamPositiveTexture) {
        setFaceTexture(partB, negativeFace, seamPositiveTexture);
    }
    else {
        const negativeFill = baseFill.mode === 'empty' ? deriveSeamFill(entity, primitive, negativeFace) : baseFill;
        applyCrossFill(entity, partB, negativeFace, negativeFill);
    }
    applyOverlayCrossFill(entity, partA, positiveFace, overlayFill);
    applyOverlayCrossFill(entity, partB, negativeFace, overlayFill);
    clearOverlayFallbacks(partA);
    clearOverlayFallbacks(partB);
    entity.primitives.splice(index, 1, partA, partB);
    const negativeId = partA.id;
    const positiveId = partB.id;
    const { issues } = (0, validation_1.validateBoxel)(entity);
    if (issues.length > 0) {
        const summary = issues
            .slice(0, 3)
            .map(issue => `${issue.path}: ${issue.message}`)
            .join('; ');
        throw new Error(`Split textured box produced invalid entity — ${summary}`);
    }
    return { firstId: partA.id, secondId: partB.id, negativeId, positiveId };
}
//# sourceMappingURL=slice.js.map