class Queue<T> {
    private items: T[];

    constructor() {
        this.items = [];
    }

    enqueue(item: T): void {
        this.items.push(item);
    }

    dequeue(): T | undefined {
        return this.items.shift();
    }

    isEmpty(): boolean {
        return this.items.length === 0;
    }
}

type index = {
    x: number;
    y: number;
}

export class BFSPathfinding {
    private map: number[][];
    private visited: boolean[][];
    private rows: number;
    private cols: number;
    private directions: index[];

    constructor() {
        this.map = [];
        this.rows = 0;
        this.cols = 0;
        this.visited = [];
        this.directions = [];
    }

    setMap(map: number[][]) {
        this.map = map;
        this.rows = this.map.length;
        this.cols = this.map[0].length;
        this.visited = Array.from({ length: this.rows }, () => Array(this.cols).fill(false));
        this.directions = [{ x: 0, y: -1 }, { x: 0, y: 1 }, { x: -1, y: 0 }, { x: 1, y: 0 }];
    }

    findPath(start: index, end: index): index[] {
        if (this.map[start.y][start.x] !== 0 ||
            this.map[end.y][end.x] !== 0) {
            return [];
        }

        const queue = new Queue<index>();
        const prev: index[][] = Array.from({ length: this.rows }, () => Array(this.cols).fill(null));

        queue.enqueue(start);
        this.visited[start.y][start.x] = true;

        while (!queue.isEmpty()) {
            const current = queue.dequeue()!;
            if (current.x === end.x && current.y === end.y) {
                return this.constructPath(prev, start, end);
            }

            for (const direction of this.directions) {
                const nextX = current.x + direction.x;
                const nextY = current.y + direction.y;

                if (this.isValid(nextX, nextY) && this.map[nextY][nextX] === 0 && !this.visited[nextY][nextX]) {
                    queue.enqueue({ x: nextX, y: nextY });
                    this.visited[nextY][nextX] = true;
                    prev[nextY][nextX] = current;
                }
            }
        }

        return [];
    }

    private isValid(x: number, y: number): boolean {
        return x >= 0 && x < this.cols && y >= 0 && y < this.rows;
    }

    private constructPath(prev: index[][], start: index, end: index): index[] {
        const path: index[] = [];
        let current = end;

        while (current !== start) {
            path.unshift(current);
            current = prev[current.y][current.x]!;
        }
        path.unshift(start);

        return path;
    }
}