"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.buildFaceFromNeighbors = buildFaceFromNeighbors;
const textures_1 = require("../../textures");
const EDGE_SOURCES = {
    east: {
        left: { kind: 'column', face: 'north', index: 'last' },
        right: { kind: 'column', face: 'south', index: 'first', reverse: true },
        top: { kind: 'column', face: 'top', index: 'last' },
        bottom: { kind: 'column', face: 'bottom', index: 'first', reverse: true },
    },
    west: {
        left: { kind: 'column', face: 'south', index: 'last' },
        right: { kind: 'column', face: 'north', index: 'first', reverse: true },
        top: { kind: 'column', face: 'top', index: 'first', reverse: true },
        bottom: { kind: 'column', face: 'bottom', index: 'last' },
    },
    north: {
        left: { kind: 'column', face: 'west', index: 'last' },
        right: { kind: 'column', face: 'east', index: 'first' },
        top: { kind: 'row', face: 'top', index: 'first' },
        bottom: { kind: 'row', face: 'bottom', index: 'last', reverse: true },
    },
    south: {
        left: { kind: 'column', face: 'east', index: 'last' },
        right: { kind: 'column', face: 'west', index: 'first' },
        top: { kind: 'row', face: 'top', index: 'last' },
        bottom: { kind: 'row', face: 'bottom', index: 'first', reverse: true },
    },
    top: {
        left: { kind: 'row', face: 'west', index: 'first', reverse: true },
        right: { kind: 'row', face: 'east', index: 'first' },
        top: { kind: 'row', face: 'north', index: 'first', reverse: true },
        bottom: { kind: 'row', face: 'south', index: 'last' },
    },
    bottom: {
        left: { kind: 'row', face: 'west', index: 'last', reverse: true },
        right: { kind: 'row', face: 'east', index: 'last' },
        top: { kind: 'row', face: 'south', index: 'first' },
        bottom: { kind: 'row', face: 'north', index: 'last', reverse: true },
    },
};
const toNumber = (value) => {
    const num = Number(value);
    return Number.isFinite(num) ? num : 0;
};
const toDimension = (value) => Math.max(1, Math.round(toNumber(value)));
const getSeamDimensions = (size, face) => {
    const sx = toDimension(size[0]);
    const sy = toDimension(size[1]);
    const sz = toDimension(size[2]);
    switch (face) {
        case 'top':
        case 'bottom':
            return [sx, sz];
        case 'east':
        case 'west':
            return [sz, sy];
        case 'north':
        case 'south':
        default:
            return [sx, sy];
    }
};
const getFaceGrid = (entity, primitive, face) => {
    const textureId = (0, textures_1.resolveFaceTextureId)(primitive, face);
    if (!textureId) {
        return undefined;
    }
    const texture = entity.textures.find(entry => entry.id === textureId);
    if (!texture) {
        return undefined;
    }
    const pattern = entity.patterns.find(entry => entry.id === texture.pattern);
    if (!pattern) {
        return undefined;
    }
    const width = toDimension(pattern.size?.[0]);
    const height = toDimension(pattern.size?.[1]);
    const grid = [];
    for (let y = 0; y < height; y += 1) {
        const row = [];
        for (let x = 0; x < width; x += 1) {
            const index = pattern.data?.[y * width + x];
            row.push(index !== undefined ? texture.materials[index] : undefined);
        }
        grid.push(row);
    }
    return { width, height, grid };
};
const extractColumn = (grid, index, reverse) => {
    if (!grid || index < 0 || index >= grid.width) {
        return undefined;
    }
    const column = [];
    for (let y = 0; y < grid.height; y += 1) {
        column.push(grid.grid[y][index]);
    }
    return reverse ? column.reverse() : column;
};
const extractRow = (grid, index, reverse) => {
    if (!grid || index < 0 || index >= grid.height) {
        return undefined;
    }
    const row = [...grid.grid[index]];
    return reverse ? row.reverse() : row;
};
const enqueueSeed = (grid, x, y, value, queue) => {
    if (value === undefined) {
        return;
    }
    if (grid[y][x] !== undefined) {
        return;
    }
    grid[y][x] = value;
    queue.push({ x, y });
};
const propagate = (grid) => {
    const height = grid.length;
    const width = grid[0]?.length ?? 0;
    const queue = [];
    for (let y = 0; y < height; y += 1) {
        for (let x = 0; x < width; x += 1) {
            if (grid[y][x] !== undefined) {
                queue.push({ x, y });
            }
        }
    }
    const directions = [
        [1, 0],
        [-1, 0],
        [0, 1],
        [0, -1],
    ];
    while (queue.length > 0) {
        const { x, y } = queue.shift();
        const value = grid[y][x];
        if (value === undefined) {
            continue;
        }
        for (const [dx, dy] of directions) {
            const nx = x + dx;
            const ny = y + dy;
            if (nx < 0 || ny < 0 || nx >= width || ny >= height) {
                continue;
            }
            if (grid[ny][nx] !== undefined) {
                continue;
            }
            grid[ny][nx] = value;
            queue.push({ x: nx, y: ny });
        }
    }
};
const createPatternAndTexture = (entity, grid, prefix) => {
    const height = grid.length;
    const width = grid[0]?.length ?? 0;
    const materialIds = new Map();
    const materials = [];
    const data = [];
    for (let y = 0; y < height; y += 1) {
        for (let x = 0; x < width; x += 1) {
            const material = grid[y][x];
            if (material === undefined) {
                return undefined;
            }
            let index = materialIds.get(material);
            if (index === undefined) {
                index = materials.length;
                materials.push(material);
                materialIds.set(material, index);
            }
            data.push(index);
        }
    }
    if (materials.length === 0) {
        return undefined;
    }
    const patternId = `${prefix}_pattern_${entity.patterns.length + 1}`;
    const textureId = `${prefix}_texture_${entity.textures.length + 1}`;
    entity.patterns.push({
        id: patternId,
        size: [width, height],
        data,
    });
    entity.textures.push({
        id: textureId,
        pattern: patternId,
        materials,
    });
    return textureId;
};
function buildFaceFromNeighbors(options) {
    const { entity, primitive, face, prefix } = options;
    const [width, height] = getSeamDimensions([
        toDimension(primitive.size?.[0]),
        toDimension(primitive.size?.[1]),
        toDimension(primitive.size?.[2]),
    ], face);
    const materialLookup = new Map(entity.materials.map(material => [material.id, material]));
    const getMaterialOpacity = (materialId) => {
        if (!materialId) {
            return 0;
        }
        const material = materialLookup.get(materialId);
        if (!material) {
            return 1;
        }
        return material.opacity !== undefined ? Number(material.opacity) : 1;
    };
    const grids = new Map();
    ['north', 'south', 'east', 'west', 'top', 'bottom'].forEach(faceName => {
        const grid = getFaceGrid(entity, primitive, faceName);
        if (grid) {
            grids.set(faceName, grid);
        }
    });
    const mapping = EDGE_SOURCES[face];
    const seamGrid = new Array(height)
        .fill(null)
        .map(() => new Array(width).fill(undefined));
    const queue = [];
    let seeded = false;
    let hasTransparentEdge = false;
    const seedColumn = (targetColumn, selector) => {
        if (selector.kind !== 'column') {
            return;
        }
        const source = grids.get(selector.face);
        const index = selector.index === 'first' ? 0 : (source?.width ?? 1) - 1;
        const column = extractColumn(source, index, selector.reverse ?? false);
        if (!column) {
            return;
        }
        for (let y = 0; y < Math.min(height, column.length); y += 1) {
            const value = column[y];
            if (value === undefined || getMaterialOpacity(value) === 0) {
                hasTransparentEdge = true;
                continue;
            }
            enqueueSeed(seamGrid, targetColumn, y, value, queue);
            seeded = true;
        }
    };
    const seedRow = (targetRow, selector) => {
        if (selector.kind !== 'row') {
            return;
        }
        const source = grids.get(selector.face);
        const index = selector.index === 'first' ? 0 : (source?.height ?? 1) - 1;
        const row = extractRow(source, index, selector.reverse ?? false);
        if (!row) {
            return;
        }
        for (let x = 0; x < Math.min(width, row.length); x += 1) {
            const value = row[x];
            if (value === undefined || getMaterialOpacity(value) === 0) {
                hasTransparentEdge = true;
                continue;
            }
            enqueueSeed(seamGrid, x, targetRow, value, queue);
            seeded = true;
        }
    };
    seedColumn(0, mapping.left);
    seedColumn(width - 1, mapping.right);
    seedRow(0, mapping.top);
    seedRow(height - 1, mapping.bottom);
    let transparentMaterialId = (() => {
        for (const material of entity.materials) {
            if ((material.opacity ?? 1) === 0) {
                return material.id;
            }
        }
        return undefined;
    })();
    const ensureTransparentMaterial = () => {
        if (transparentMaterialId) {
            return transparentMaterialId;
        }
        const newId = `${prefix ?? primitive.id}_seam_transparent_${entity.materials.length + 1}`;
        entity.materials.push({
            id: newId,
            baseColor: [255, 255, 255],
            opacity: 0,
        });
        transparentMaterialId = newId;
        return newId;
    };
    const createTransparentSeam = () => {
        const materialId = ensureTransparentMaterial();
        const patternId = `${prefix ?? primitive.id}_${face}_seam_transparent_pattern_${entity.patterns.length + 1}`;
        const textureId = `${prefix ?? primitive.id}_${face}_seam_transparent_texture_${entity.textures.length + 1}`;
        entity.patterns.push({
            id: patternId,
            size: [width, height],
            data: new Array(width * height).fill(0),
        });
        entity.textures.push({
            id: textureId,
            pattern: patternId,
            materials: [materialId],
        });
        return textureId;
    };
    if (hasTransparentEdge) {
        return createTransparentSeam();
    }
    if (!seeded || queue.length === 0) {
        return undefined;
    }
    propagate(seamGrid);
    const identifier = `${prefix ?? primitive.id}_${face}_seam`;
    return createPatternAndTexture(entity, seamGrid, identifier);
}
//# sourceMappingURL=infer-face.js.map