import TileConnections from './tileConnections.js';
function isSameEdge(a, b) {
    return ((a.x1 === b.x1 && a.y1 === b.y1 && a.x2 === b.x2 && a.y2 === b.y2) ||
        (a.x1 === b.x2 && a.y1 === b.y2 && a.x2 === b.x1 && a.y2 === b.y1));
}
export default class GridConnections {
    constructor(edges) {
        Object.defineProperty(this, "edges", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        this.edges = GridConnections.deduplicateEdges(edges ?? []);
    }
    addEdge(edge) {
        if (this.edges.some(e => isSameEdge(e, edge))) {
            return this;
        }
        return new GridConnections([...this.edges, edge]);
    }
    removeEdge(edge) {
        return new GridConnections(this.edges.filter(e => !isSameEdge(e, edge)));
    }
    isConnected(edge) {
        if (edge.x1 === edge.x2 && edge.y1 === edge.y2)
            return true;
        return this.edges.some(e => isSameEdge(e, edge));
    }
    getConnectionsAt({ x, y }) {
        return this.edges.filter(e => (e.x1 === x && e.y1 === y) || (e.x2 === x && e.y2 === y));
    }
    getForTile({ x, y }) {
        const result = new TileConnections();
        // Get all connections within 2 steps of the tile
        const edges = this.getConnectionsAt({ x, y });
        const edges2 = [
            ...edges,
            ...edges.flatMap(edge => {
                if (edge.x1 === x && edge.y1 === y) {
                    return this.getConnectionsAt({ x: edge.x2, y: edge.y2 });
                }
                else {
                    return this.getConnectionsAt({ x: edge.x1, y: edge.y1 });
                }
            }),
        ];
        // Fill in the connections
        for (const edge of edges2) {
            if (edge.x1 !== x || edge.y1 !== y) {
                const offsetX = edge.x1 - x;
                const offsetY = edge.y1 - y;
                if (offsetX >= -1 && offsetX <= 1 && offsetY >= -1 && offsetY <= 1) {
                    result[offsetY][offsetX] = true;
                }
            }
            if (edge.x2 !== x || edge.y2 !== y) {
                const offsetX = edge.x2 - x;
                const offsetY = edge.y2 - y;
                if (offsetX >= -1 && offsetX <= 1 && offsetY >= -1 && offsetY <= 1) {
                    result[offsetY][offsetX] = true;
                }
            }
        }
        // Fix the center and corner connections
        result.center = true;
        if (!result.top || !result.left) {
            result.topLeft = false;
        }
        if (!result.top || !result.right) {
            result.topRight = false;
        }
        if (!result.bottom || !result.left) {
            result.bottomLeft = false;
        }
        if (!result.bottom || !result.right) {
            result.bottomRight = false;
        }
        return result;
    }
    getConnectedTiles({ x, y }) {
        const result = [];
        const visited = new Set();
        const queue = [{ x, y }];
        while (queue.length > 0) {
            const current = queue.pop();
            if (visited.has(`${current.x},${current.y}`)) {
                continue;
            }
            visited.add(`${current.x},${current.y}`);
            result.push(current);
            const edges = this.getConnectionsAt(current);
            for (const edge of edges) {
                if (edge.x1 === current.x && edge.y1 === current.y) {
                    queue.push({ x: edge.x2, y: edge.y2 });
                }
                else {
                    queue.push({ x: edge.x1, y: edge.y1 });
                }
            }
        }
        return result;
    }
    /**
     * Create new GridConnections from a string array.
     *
     * - Use `.` for cells that don't connect to anything.
     * - Use any other character for cells that connect to the same character.
     *
     * @param array - The string array to create the connections from.
     * @returns The created connections. You can apply this to a GridData object using GridData.withConnections.
     */
    static create(array) {
        const edges = [];
        for (let y = 0; y < array.length; y++) {
            for (let x = 0; x < array[y].length; x++) {
                if (array[y][x] === '.') {
                    continue;
                }
                if (x > 0 && array[y][x - 1] === array[y][x]) {
                    edges.push({ x1: x - 1, y1: y, x2: x, y2: y });
                }
                if (y > 0 && array[y - 1][x] === array[y][x]) {
                    edges.push({ x1: x, y1: y - 1, x2: x, y2: y });
                }
            }
        }
        return new GridConnections(edges);
    }
    /**
     * Check if two GridConnections objects are equal.
     * @param other The other GridConnections object to compare to.
     * @returns Whether the two objects are equal.
     */
    equals(other) {
        if (this.edges.length !== other.edges.length)
            return false;
        for (const edge of this.edges) {
            if (!other.isConnected(edge))
                return false;
        }
        return true;
    }
    /**
     * Deduplicate an array of edges.
     * @param edges The array of edges to deduplicate.
     * @returns The deduplicated array of edges.
     */
    static deduplicateEdges(edges) {
        return edges.filter((edge, index) => edges.findIndex(e => isSameEdge(e, edge)) === index);
    }
    insertColumn(index) {
        return new GridConnections(this.edges.flatMap(edge => {
            if ((edge.x1 < index && edge.x2 < index) ||
                (edge.x1 >= index && edge.x2 >= index)) {
                if (edge.x1 < index) {
                    return [edge];
                }
                return [
                    { x1: edge.x1 + 1, y1: edge.y1, x2: edge.x2 + 1, y2: edge.y2 },
                ];
            }
            return [
                { x1: edge.x1, y1: edge.y1, x2: edge.x2, y2: edge.y2 },
                { x1: edge.x1 + 1, y1: edge.y1, x2: edge.x2 + 1, y2: edge.y2 },
            ];
        }));
    }
    insertRow(index) {
        return new GridConnections(this.edges.flatMap(edge => {
            if ((edge.y1 < index && edge.y2 < index) ||
                (edge.y1 >= index && edge.y2 >= index)) {
                if (edge.y1 < index) {
                    return [edge];
                }
                return [
                    { x1: edge.x1, y1: edge.y1 + 1, x2: edge.x2, y2: edge.y2 + 1 },
                ];
            }
            return [
                { x1: edge.x1, y1: edge.y1, x2: edge.x2, y2: edge.y2 },
                { x1: edge.x1, y1: edge.y1 + 1, x2: edge.x2, y2: edge.y2 + 1 },
            ];
        }));
    }
    removeColumn(index) {
        const toProcess = new Map();
        const toKeep = [];
        this.edges.forEach(edge => {
            if ((edge.x1 < index && edge.x2 < index) ||
                (edge.x1 > index && edge.x2 > index)) {
                if (edge.x1 < index) {
                    toKeep.push(edge);
                }
                else {
                    toKeep.push({
                        x1: edge.x1 - 1,
                        y1: edge.y1,
                        x2: edge.x2 - 1,
                        y2: edge.y2,
                    });
                }
            }
            else if (edge.x1 !== index && edge.x2 !== index) {
                toKeep.push({
                    x1: edge.x1 > index ? edge.x1 - 1 : edge.x1,
                    y1: edge.y1,
                    x2: edge.x2 > index ? edge.x2 - 1 : edge.x2,
                    y2: edge.y2,
                });
            }
            else {
                if (edge.x1 === index) {
                    if (!toProcess.has(edge.y1)) {
                        toProcess.set(edge.y1, [edge]);
                    }
                    else {
                        toProcess.get(edge.y1).push(edge);
                    }
                }
                else if (edge.x2 === index) {
                    if (!toProcess.has(edge.y2)) {
                        toProcess.set(edge.y2, [edge]);
                    }
                    else {
                        toProcess.get(edge.y2).push(edge);
                    }
                }
            }
        });
        for (const [key, list] of toProcess.entries()) {
            for (let i = 1; i < list.length; i++) {
                if (!isSameEdge(list[i], list[i - 1])) {
                    let x1, y1, x2, y2;
                    if (list[i].x1 === index && list[i].y1 === key) {
                        x1 = list[i].x2 > index ? list[i].x2 - 1 : list[i].x2;
                        y1 = list[i].y2;
                    }
                    else {
                        x1 = list[i].x1 > index ? list[i].x1 - 1 : list[i].x1;
                        y1 = list[i].y1;
                    }
                    if (list[i - 1].x1 === index && list[i - 1].y1 === key) {
                        x2 = list[i - 1].x2 > index ? list[i - 1].x2 - 1 : list[i - 1].x2;
                        y2 = list[i - 1].y2;
                    }
                    else {
                        x2 = list[i - 1].x1 > index ? list[i - 1].x1 - 1 : list[i - 1].x1;
                        y2 = list[i - 1].y1;
                    }
                    toKeep.push({ x1, y1, x2, y2 });
                }
            }
        }
        return new GridConnections(toKeep);
    }
    removeRow(index) {
        const toProcess = new Map();
        const toKeep = [];
        this.edges.forEach(edge => {
            if ((edge.y1 < index && edge.y2 < index) ||
                (edge.y1 > index && edge.y2 > index)) {
                if (edge.y1 < index) {
                    toKeep.push(edge);
                }
                else {
                    toKeep.push({
                        x1: edge.x1,
                        y1: edge.y1 - 1,
                        x2: edge.x2,
                        y2: edge.y2 - 1,
                    });
                }
            }
            else if (edge.y1 !== index && edge.y2 !== index) {
                toKeep.push({
                    x1: edge.x1,
                    y1: edge.y1 > index ? edge.y1 - 1 : edge.y1,
                    x2: edge.x2,
                    y2: edge.y2 > index ? edge.y2 - 1 : edge.y2,
                });
            }
            else {
                if (edge.y1 === index) {
                    if (!toProcess.has(edge.x1)) {
                        toProcess.set(edge.x1, [edge]);
                    }
                    else {
                        toProcess.get(edge.x1).push(edge);
                    }
                }
                else if (edge.y2 === index) {
                    if (!toProcess.has(edge.x2)) {
                        toProcess.set(edge.x2, [edge]);
                    }
                    else {
                        toProcess.get(edge.x2).push(edge);
                    }
                }
            }
        });
        for (const [key, list] of toProcess.entries()) {
            for (let i = 1; i < list.length; i++) {
                if (!isSameEdge(list[i], list[i - 1])) {
                    let x1, y1, x2, y2;
                    if (list[i].y1 === index && list[i].x1 === key) {
                        x1 = list[i].x2;
                        y1 = list[i].y2 > index ? list[i].y2 - 1 : list[i].y2;
                    }
                    else {
                        x1 = list[i].x1;
                        y1 = list[i].y1 > index ? list[i].y1 - 1 : list[i].y1;
                    }
                    if (list[i - 1].y1 === index && list[i - 1].x1 === key) {
                        x2 = list[i - 1].x2;
                        y2 = list[i - 1].y2 > index ? list[i - 1].y2 - 1 : list[i - 1].y2;
                    }
                    else {
                        x2 = list[i - 1].x1;
                        y2 = list[i - 1].y1 > index ? list[i - 1].y1 - 1 : list[i - 1].y1;
                    }
                    toKeep.push({ x1, y1, x2, y2 });
                }
            }
        }
        return new GridConnections(toKeep);
    }
}
