class Node {
    constructor() {
        this.distance = null;
        this.predecessor = null;
    }
}

class Queue {
    constructor() {
        this.queue = [];
    }

    enqueue(thing) {
        this.queue.push(thing);
    }

    dequeue() {
        return this.queue.shift();
    }

    isEmpty() {
        return this.queue.length === 0;
    }
}

class Graph {
    constructor(columns, rows) {
        this.adjMatrix = [];
        this.rows = rows;
        this.columns = columns;

        // Build the adj list
        this.buildAdjList();
    }
    buildAdjList() {
        this.adjList = [];

        // Possible knight moves relative to its current position
        const moves = [
            [-1, -2],
            [-2, -1],
            [-2, 1],
            [-1, 2],
            [1, -2],
            [2, -1],
            [2, 1],
            [1, 2],
        ];

        for (let y = 0; y < this.rows; y++) {
            let rowData = [];
            for (let x = 0; x < this.columns; x++) {
                let adjPositions = [];

                // Generate adjacent positions for the current square
                for (const [dx, dy] of moves) {
                    const newX = x + dx;
                    const newY = y + dy;

                    // Check if the new position is within the board boundaries
                    if (
                        newX >= 0 &&
                        newX < this.columns &&
                        newY >= 0 &&
                        newY < this.rows
                    ) {
                        adjPositions.push([newX, newY]);
                    }
                }

                rowData.push(adjPositions);
            }
            this.adjList.push(rowData);
        }
    }

    knightMoves(start, finish) {
        this.bfsInfo = [];
        let itemFound = false;
        this.start = start;
        this.finish = finish;

        for (let i = 0; i < this.columns; i++) {
            this.bfsInfo[i] = [];
            for (let j = 0; j < this.rows; j++) {
                this.bfsInfo[i][j] = new Node();
            }
        }

        // This is to mark where we're starting the loop
        this.bfsInfo[start[0]][start[1]].distance = 0;

        // instantiate the queue
        let queue = new Queue();
        queue.enqueue(start);

        while (!queue.isEmpty() && !itemFound) {
            let vertex = queue.dequeue();
            if (vertex[0] === finish[0] && vertex[1] === finish[1]) {
                itemFound = true;
                break;
            }

            for (
                let i = 0;
                i < this.adjList[vertex[1]][vertex[0]].length;
                i++
            ) {
                let neighbor = this.adjList[vertex[1]][vertex[0]][i];
                let x = neighbor[0];
                let y = neighbor[1];

                if (this.bfsInfo[x][y].distance === null) {
                    this.bfsInfo[x][y].distance =
                        this.bfsInfo[vertex[0]][vertex[1]].distance + 1;
                    this.bfsInfo[x][y].predecessor = [vertex[0], vertex[1]];
                }

                queue.enqueue(neighbor);
            }
        }
    }

    lookAtBfs() {
        this.path = [this.finish];

        let queue = new Queue();
        let x = this.finish[0];
        let y = this.finish[1];

        queue.enqueue(this.bfsInfo[x][y]);

        while (!queue.isEmpty()) {
            let current = queue.dequeue();

            if (current.predecessor) {
                this.path.unshift(current.predecessor);
                x = current.predecessor[0];
                y = current.predecessor[1];
                let previousNode = this.bfsInfo[x][y];
                queue.enqueue(previousNode);
            }
        }
        return this.path;
    }
}

let g = new Graph(8, 8);
console.dir(g.adjList);
console.table(g.adjList[0]);
console.table(g.adjList[0][0]);
g.knightMoves([0, 0], [7, 7]);
console.log(g.lookAtBfs());
g.knightMoves([0, 0], [3, 3]);
console.log(g.lookAtBfs());
g.knightMoves([3, 3], [4, 3]);
console.log(g.lookAtBfs());