/* ────────────────────────────────────────────────────────────────
   Tilemap — Grid-based tile rendering and collision
──────────────────────────────────────────────────────────────── */
/* ── Tilemap ──────────────────────────────────────────────────── */
class Tilemap {
    constructor(width, height, tileWidth = 16, tileHeight = 16) {
        this.tileset = null;
        this.width = width;
        this.height = height;
        this.tileWidth = tileWidth;
        this.tileHeight = tileHeight;
        this.layers = [];
    }
    get pixelWidth() {
        return this.width * this.tileWidth;
    }
    get pixelHeight() {
        return this.height * this.tileHeight;
    }
    /* ── Tileset ────────────────────────────────────────────────── */
    setTileset(ts) {
        this.tileset = ts;
    }
    getTileset() {
        return this.tileset;
    }
    /* ── Layers ─────────────────────────────────────────────────── */
    addLayer(name) {
        const data = [];
        for (let y = 0; y < this.height; y++) {
            data.push(new Array(this.width).fill(0));
        }
        const layer = { name, visible: true, opacity: 1, data };
        this.layers.push(layer);
        return layer;
    }
    getLayer(name) {
        return this.layers.find(l => l.name === name);
    }
    getLayers() {
        return this.layers;
    }
    /* ── Tile access ────────────────────────────────────────────── */
    getTile(layerName, x, y) {
        const layer = this.getLayer(layerName);
        if (!layer || x < 0 || y < 0 || x >= this.width || y >= this.height)
            return 0;
        return layer.data[y][x];
    }
    setTile(layerName, x, y, tileId) {
        const layer = this.getLayer(layerName);
        if (!layer || x < 0 || y < 0 || x >= this.width || y >= this.height)
            return;
        layer.data[y][x] = tileId;
    }
    /** Fill a rectangle with a tile. */
    fillRect(layerName, x, y, w, h, tileId) {
        for (let iy = y; iy < y + h; iy++) {
            for (let ix = x; ix < x + w; ix++) {
                this.setTile(layerName, ix, iy, tileId);
            }
        }
    }
    /** Flood fill from a position. */
    floodFill(layerName, x, y, tileId) {
        const layer = this.getLayer(layerName);
        if (!layer)
            return;
        const target = this.getTile(layerName, x, y);
        if (target === tileId)
            return;
        const stack = [[x, y]];
        const visited = new Set();
        while (stack.length > 0) {
            const [cx, cy] = stack.pop();
            const key = `${cx},${cy}`;
            if (visited.has(key))
                continue;
            visited.add(key);
            if (cx < 0 || cy < 0 || cx >= this.width || cy >= this.height)
                continue;
            if (layer.data[cy][cx] !== target)
                continue;
            layer.data[cy][cx] = tileId;
            stack.push([cx + 1, cy], [cx - 1, cy], [cx, cy + 1], [cx, cy - 1]);
        }
    }
    /* ── World ↔ Tile coordinates ───────────────────────────────── */
    worldToTile(worldX, worldY) {
        return {
            x: Math.floor(worldX / this.tileWidth),
            y: Math.floor(worldY / this.tileHeight),
        };
    }
    tileToWorld(tileX, tileY) {
        return {
            x: tileX * this.tileWidth + this.tileWidth / 2,
            y: tileY * this.tileHeight + this.tileHeight / 2,
        };
    }
    /* ── Collision query ────────────────────────────────────────── */
    isSolid(tileX, tileY) {
        var _a;
        if (!this.tileset)
            return false;
        for (const layer of this.layers) {
            const id = (_a = layer.data[tileY]) === null || _a === void 0 ? void 0 : _a[tileX];
            if (id) {
                const def = this.tileset.tiles.find(t => t.id === id);
                if (def === null || def === void 0 ? void 0 : def.solid)
                    return true;
            }
        }
        return false;
    }
    /** Get all solid tiles overlapping a world-space AABB. */
    getSolidTilesInRect(rect) {
        const result = [];
        const x0 = Math.max(0, Math.floor(rect.x / this.tileWidth));
        const y0 = Math.max(0, Math.floor(rect.y / this.tileHeight));
        const x1 = Math.min(this.width - 1, Math.floor((rect.x + rect.width) / this.tileWidth));
        const y1 = Math.min(this.height - 1, Math.floor((rect.y + rect.height) / this.tileHeight));
        for (let y = y0; y <= y1; y++) {
            for (let x = x0; x <= x1; x++) {
                if (this.isSolid(x, y))
                    result.push({ x, y });
            }
        }
        return result;
    }
    /* ── Rendering ──────────────────────────────────────────────── */
    render(ctx, tilesetImage, camera) {
        if (!this.tileset)
            return;
        const ts = this.tileset;
        const tw = this.tileWidth;
        const th = this.tileHeight;
        // Only render visible tiles
        const x0 = Math.max(0, Math.floor(camera.x / tw));
        const y0 = Math.max(0, Math.floor(camera.y / th));
        const x1 = Math.min(this.width - 1, Math.ceil((camera.x + camera.width) / tw));
        const y1 = Math.min(this.height - 1, Math.ceil((camera.y + camera.height) / th));
        for (const layer of this.layers) {
            if (!layer.visible)
                continue;
            ctx.globalAlpha = layer.opacity;
            for (let y = y0; y <= y1; y++) {
                for (let x = x0; x <= x1; x++) {
                    const tileId = layer.data[y][x];
                    if (tileId === 0)
                        continue;
                    const def = ts.tiles.find(t => t.id === tileId);
                    if (!def)
                        continue;
                    const sr = def.srcRect;
                    ctx.drawImage(tilesetImage, sr.x, sr.y, sr.width, sr.height, x * tw, y * th, tw, th);
                }
            }
        }
        ctx.globalAlpha = 1;
    }
    /* ── Serialization ──────────────────────────────────────────── */
    toJSON() {
        var _a, _b;
        return {
            width: this.width,
            height: this.height,
            tileWidth: this.tileWidth,
            tileHeight: this.tileHeight,
            tilesetId: (_b = (_a = this.tileset) === null || _a === void 0 ? void 0 : _a.name) !== null && _b !== void 0 ? _b : '',
            layers: this.layers.map(l => ({
                name: l.name,
                visible: l.visible,
                opacity: l.opacity,
                data: l.data.map(row => [...row]),
            })),
        };
    }
    static fromJSON(data) {
        const map = new Tilemap(data.width, data.height, data.tileWidth, data.tileHeight);
        for (const layerData of data.layers) {
            const layer = map.addLayer(layerData.name);
            layer.visible = layerData.visible;
            layer.opacity = layerData.opacity;
            layer.data = layerData.data.map(row => [...row]);
        }
        return map;
    }
}
/* ── Auto-tiling (4-bit bitmask) ──────────────────────────────── */
function computeAutoTileMask(map, layerName, x, y) {
    let mask = 0;
    if (map.getTile(layerName, x, y - 1) > 0)
        mask |= 1; // top
    if (map.getTile(layerName, x + 1, y) > 0)
        mask |= 2; // right
    if (map.getTile(layerName, x, y + 1) > 0)
        mask |= 4; // bottom
    if (map.getTile(layerName, x - 1, y) > 0)
        mask |= 8; // left
    return mask;
}

export { Tilemap, computeAutoTileMask };
//# sourceMappingURL=Tilemap.js.map
