import { PathfindingService } from './pathfinding.service';
import { Node } from '../pathfinding.models';

function createNode(row: number, col: number): Node {
  return {
    row,
    col,
    isStart: false,
    isEnd: false,
    isWall: false,
    isVisited: false,
    isPath: false,
    nodeData: Infinity,
    linkedNode: null,
    fScore: Infinity,
    hScore: Infinity,
  };
}

function createGrid(rows: number, cols: number): Node[][] {
  const grid: Node[][] = [];
  for (let row = 0; row < rows; row++) {
    const currentRow: Node[] = [];
    for (let col = 0; col < cols; col++) {
      currentRow.push(createNode(row, col));
    }
    grid.push(currentRow);
  }
  return grid;
}

describe('PathfindingService', () => {
  let service: PathfindingService;

  beforeEach(() => {
    service = new PathfindingService();
  });

  it('should be created', () => {
    expect(service).toBeTruthy();
  });

  describe('getUnvisitedNeighbors', () => {
    it('returns the four orthogonal neighbors for an inner node', () => {
      const grid = createGrid(3, 3);
      const neighbors = service.getUnvisitedNeighbors(grid[1][1], grid);

      expect(neighbors.length).toBe(4);
    });

    it('excludes walls and already visited nodes', () => {
      const grid = createGrid(3, 3);
      grid[0][1].isWall = true;
      grid[1][0].isVisited = true;
      const neighbors = service.getUnvisitedNeighbors(grid[1][1], grid);

      expect(neighbors.length).toBe(2);
    });

    it('returns only in-bounds neighbors for a corner node', () => {
      const grid = createGrid(3, 3);
      const neighbors = service.getUnvisitedNeighbors(grid[0][0], grid);

      expect(neighbors.length).toBe(2);
    });
  });

  describe('getNodesInShortestPath', () => {
    it('reconstructs the path by following linkedNode references', () => {
      const grid = createGrid(1, 3);
      grid[0][1].linkedNode = grid[0][0];
      grid[0][2].linkedNode = grid[0][1];
      const path = service.getNodesInShortestPath(grid[0][2]);

      expect(path).toEqual([grid[0][0], grid[0][1], grid[0][2]]);
    });
  });

  describe('dijkstra', () => {
    it('finds the shortest path on an open grid', () => {
      const grid = createGrid(5, 5);
      const start = grid[0][0];
      const end = grid[4][4];

      const { visitedNodesInOrder, nodesInShortestPathOrder } = service.dijkstra(grid, start, end);

      expect(visitedNodesInOrder).toContain(end);
      // Manhattan distance 8 => 9 nodes including both endpoints.
      expect(nodesInShortestPathOrder.length).toBe(9);
      expect(nodesInShortestPathOrder[0]).toBe(start);
      expect(nodesInShortestPathOrder[nodesInShortestPathOrder.length - 1]).toBe(end);
    });

    it('returns an empty path when the end node is walled off', () => {
      const grid = createGrid(3, 3);
      const start = grid[0][0];
      const end = grid[2][2];
      grid[1][2].isWall = true;
      grid[2][1].isWall = true;

      const { nodesInShortestPathOrder } = service.dijkstra(grid, start, end);

      expect(nodesInShortestPathOrder).toEqual([]);
    });
  });

  describe('aStar', () => {
    it('reaches the end and returns a connected path on an open grid', () => {
      const grid = createGrid(5, 5);
      const start = grid[0][0];
      const end = grid[4][4];

      const { visitedNodesInOrder, nodesInShortestPathOrder } = service.aStar(grid, start, end);

      expect(visitedNodesInOrder).toContain(end);
      expect(nodesInShortestPathOrder.length).toBeGreaterThan(0);
      expect(nodesInShortestPathOrder[0]).toBe(start);
      expect(nodesInShortestPathOrder[nodesInShortestPathOrder.length - 1]).toBe(end);
    });
  });
});
