import {AfterViewInit, Component, inject, ViewChild} from '@angular/core';
import {CommonModule} from '@angular/common';
import {FormsModule} from '@angular/forms';

import {MatButtonModule} from '@angular/material/button';
import {MatButtonToggleModule} from '@angular/material/button-toggle';
import {MatFormFieldModule} from '@angular/material/form-field';
import {MatInputModule} from '@angular/material/input';
import {MatSnackBar} from '@angular/material/snack-bar';

import {TranslateModule, TranslateService} from '@ngx-translate/core';

import {DEFAULT_GRID_COLS, DEFAULT_GRID_ROWS, MAX_GRID_PX, MAX_GRID_SIZE, MAX_RANDOM_WALLS_FACTORS, MIN_GRID_SIZE, Node} from './pathfinding.models';
import {PathfindingService} from './service/pathfinding.service';
import {UrlConstants} from '../../../constants/UrlConstants';
import {MatCard, MatCardContent, MatCardHeader, MatCardTitle} from '@angular/material/card';
import {Information} from '../information/information';
import {AlgorithmInformation} from '../information/information.models';
import {GenericGridComponent, GridPos} from '../../../shared/components/generic-grid/generic-grid';
import {SharedFunctions} from '../../../shared/SharedFunctions';

enum NodeType {
  Start = 'start',
  End = 'end',
  Wall = 'wall',
  None = 'none'
}

@Component({
  selector: 'app-pathfinding',
  imports: [
    CommonModule,
    FormsModule,
    MatButtonModule,
    MatButtonToggleModule,
    MatFormFieldModule,
    MatInputModule,
    TranslateModule,
    MatCard,
    MatCardHeader,
    MatCardTitle,
    MatCardContent,
    Information,
    GenericGridComponent
  ],
  templateUrl: './pathfinding.component.html',
})
export class PathfindingComponent implements AfterViewInit {
  private readonly pathfindingService = inject(PathfindingService);
  private readonly translate = inject(TranslateService);
  private readonly snackBar = inject(MatSnackBar);

  readonly NodeType = NodeType;
  readonly MIN_GRID_SIZE = MIN_GRID_SIZE;
  readonly MAX_GRID_SIZE = MAX_GRID_SIZE;
  readonly MAX_GRID_PX = MAX_GRID_PX;

  algoInformation: AlgorithmInformation = {
    title: 'PATHFINDING.EXPLANATION.TITLE',
    entries: [
      {
        name: 'Dijkstra',
        description: 'PATHFINDING.EXPLANATION.DIJKSTRA_EXPLANATION',
        link: UrlConstants.DIJKSTRA_WIKI
      },
      {
        name: 'A*',
        description: 'PATHFINDING.EXPLANATION.ASTAR_EXPLANATION',
        link: UrlConstants.ASTAR_WIKI
      }
    ],
    disclaimer: 'PATHFINDING.EXPLANATION.DISCLAIMER',
    disclaimerBottom: '',
    disclaimerListEntry: []
  };

  gridRows = DEFAULT_GRID_ROWS;
  gridCols = DEFAULT_GRID_COLS;

  grid: Node[][] = [];
  startNode: Node | null = null;
  endNode: Node | null = null;

  selectedNodeType: NodeType = NodeType.None;
  private shouldAddWall = true; // Moved here

  animationSpeed = 3;
  pathLength = "0";
  executionTime = 0;

  private timeoutIds: number[] = [];

  @ViewChild(GenericGridComponent) genericGridComponent!: GenericGridComponent;

  ngAfterViewInit(): void {
    // Canvas logic is now handled by GenericGridComponent
    // Ensure genericGridComponent is initialized
    if (this.genericGridComponent) {
      this.genericGridComponent.initializationFn = this.initializePathfindingGrid;
      this.genericGridComponent.createNodeFn = this.createPathfindingNode;
      this.genericGridComponent.getNodeColorFn = this.getPathfindingNodeColor;
      this.genericGridComponent.applySelectionFn = this.applyPathfindingSelection;
      this.genericGridComponent.gridRows = this.gridRows;
      this.genericGridComponent.gridCols = this.gridCols;
      this.genericGridComponent.minGridSize = this.MIN_GRID_SIZE;
      this.genericGridComponent.maxGridSize = this.MAX_GRID_SIZE;
      this.genericGridComponent.maxGridPx = MAX_GRID_PX;
      this.genericGridComponent.applyGridSize(); // Trigger initial grid setup
    }
    this.createCase({withWalls: true, scenario: "normal"});
  }

  // --- Callbacks for GenericGridComponent ---
  createPathfindingNode = (row: number, col: number): Node => {
    return {
      row,
      col,
      isStart: false,
      isEnd: false,
      isWall: false,
      isVisited: false,
      isPath: false,
      nodeData: Infinity,
      linkedNode: null,
      hScore: 0,
      fScore: Infinity,
    };
  };

  getPathfindingNodeColor = (node: Node): string => {
    if (node.isStart) return 'green';
    if (node.isEnd) return 'red';
    if (node.isPath) return 'gold';
    if (node.isVisited) return 'skyblue';
    if (node.isWall) return 'black';
    return 'lightgray';
  };

  applyPathfindingSelection = (pos: GridPos, grid: Node[][]): void => {
    this.grid = grid; // Keep internal grid in sync
    const node = grid[pos.row][pos.col];

    // Determine if we should add or remove a wall
    if (this.selectedNodeType === NodeType.Wall && this.genericGridComponent.isDrawing && this.genericGridComponent['lastCell'] === null) {
      this.shouldAddWall = !node.isWall;
    }

    switch (this.selectedNodeType) {
      case NodeType.Start:
        this.trySetStart(node);
        break;

      case NodeType.End:
        this.trySetEnd(node);
        break;

      case NodeType.Wall:
        this.tryToggleWall(node, this.shouldAddWall);
        break;

      case NodeType.None:
        this.tryClearNode(node);
        break;
    }
  };

  visualize(algorithm: string): void {
    if (!this.ensureStartAndEnd()) {
      return;
    }

    this.stopAnimations();
    this.clearPath();

    const startTime = performance.now();
    let result;

    switch (algorithm) {
      case 'dijkstra': result = this.pathfindingService.dijkstra(
        this.grid,
        this.grid[this.startNode!.row][this.startNode!.col],
        this.grid[this.endNode!.row][this.endNode!.col]
      );
        break;
      case 'astar': result = this.pathfindingService.aStar(
        this.grid,
        this.grid[this.startNode!.row][this.startNode!.col],
        this.grid[this.endNode!.row][this.endNode!.col]
      );
        break;
    }

    if (!result)
    {
      return;
    }

    const endTime = performance.now();
    const lengthOfShortestPath = result.nodesInShortestPathOrder.length;
    if (lengthOfShortestPath === 0)
    {
      this.pathLength = "∞"
    }
    else
    {
      this.pathLength = result.nodesInShortestPathOrder.length + "";
    }
    this.executionTime = endTime - startTime;

    this.animateAlgorithm(result.visitedNodesInOrder, result.nodesInShortestPathOrder);
  }

  initializePathfindingGrid = (grid: Node[][]): void => {
    this.grid = grid; // Update the component's grid reference
    const {start, end} = this.getScenarioStartEnd('normal'); // Default scenario
    this.startNode = this.grid[start.row][start.col];
    this.endNode = this.grid[end.row][end.col];

    this.startNode.isStart = true;
    this.endNode.isEnd = true;

    this.placeDefaultDiagonalWall('normal');
  };

  // --- Helper methods for node manipulation (kept local) ---
  private trySetStart(node: Node): void {
    if (!this.canBeStart(node)) {
      return;
    }

    if (this.startNode) {
      this.startNode.isStart = false;
      this.genericGridComponent.drawNode(this.startNode); // Redraw old start node
    }

    node.isStart = true;
    this.startNode = node;
  }

  private trySetEnd(node: Node): void {
    if (!this.canBeEnd(node)) {
      return;
    }

    if (this.endNode) {
      this.endNode.isEnd = false;
      this.genericGridComponent.drawNode(this.endNode); // Redraw old end node
    }

    node.isEnd = true;
    this.endNode = node;
  }

  private tryToggleWall(node: Node, shouldBeWall: boolean): void {
    if (!this.canBeWall(node)) {
      return;
    }
    node.isWall = shouldBeWall;
  }

  private tryClearNode(node: Node): void {
    if (node.isStart) {
      node.isStart = false;
      this.startNode = null;
      return;
    }

    if (node.isEnd) {
      node.isEnd = false;
      this.endNode = null;
      return;
    }

    if (node.isWall) {
      node.isWall = false;
    }
  }

  private canBeStart(node: Node): boolean {
    return !node.isEnd && !node.isWall;
  }

  private canBeEnd(node: Node): boolean {
    return !node.isStart && !node.isWall;
  }

  private canBeWall(node: Node): boolean {
    return !node.isStart && !node.isEnd;
  }

  // --- Grid manipulation for scenarios (kept local) ---
  createCase({withWalls, scenario}: { withWalls: boolean, scenario: "normal" | "edge" | "random" }): void {
    this.stopAnimations();
    // Reinitialize grid through the generic component
    this.genericGridComponent.initializationFn = (grid) => {
      this.grid = grid;
      const {start, end} = this.getScenarioStartEnd(scenario);
      this.startNode = this.grid[start.row][start.col];
      this.endNode = this.grid[end.row][end.col];
      this.startNode.isStart = true;
      this.endNode.isEnd = true;

      if (withWalls) {
        this.placeDefaultDiagonalWall(scenario);
      }
    };
    this.genericGridComponent.initializeGrid(); // Trigger re-initialization and redraw
  }

  private getScenarioStartEnd(scenario: 'normal' | 'edge' | 'random'): { start: GridPos; end: GridPos } {
    if (scenario === 'edge') {
      return {
        start: {row: 0, col: 0},
        end: {row: this.gridRows - 1, col: this.gridCols - 1}
      };
    } else if (scenario === 'random') {
      return this.createRandomStartEndPosition();
    } else {
      // normal: mid-left -> mid-right
      const midRow = Math.floor(this.gridRows / 2);
      return {
        start: {row: midRow, col: 0},
        end: {row: midRow, col: this.gridCols - 1}
      };
    }
  }

  private createRandomStartEndPosition(): { start: GridPos; end: GridPos } {
    const midCol = Math.floor(this.gridCols / 2);

    const startRow: number = SharedFunctions.randomIntFromInterval(0, this.gridRows - 1);
    const startCol: number = SharedFunctions.randomIntFromInterval(0, this.gridCols - 1);

    const endRow: number = SharedFunctions.randomIntFromInterval(0, this.gridRows - 1);
    let endCol: number;

    if (startCol <= midCol) {
      endCol = SharedFunctions.randomIntFromInterval(midCol + 1, this.gridCols - 1);
    } else {
      endCol = SharedFunctions.randomIntFromInterval(0, midCol);
    }

    return {
      start: {row: startRow, col: startCol},
      end: {row: endRow, col: endCol}
    };
  }

  private placeDefaultDiagonalWall(scenario: 'normal' | 'edge' | 'random'): void {
    if (scenario === 'edge') {
      this.createDiagonalWall();
    } else if (scenario === 'normal') {
      this.createVerticalWall();
    } else if (scenario === 'random') {
      this.createRandomWalls();
    }
  }

  private createRandomWalls() {
    const maxNumberOfWalls = Math.floor(MAX_RANDOM_WALLS_FACTORS * this.gridCols * this.gridRows);

    for (let wall = 0; wall < maxNumberOfWalls; wall++) {

      const row: number = SharedFunctions.randomIntFromInterval(0, this.gridRows - 1);
      const col: number = SharedFunctions.randomIntFromInterval(0, this.gridCols - 1);

      if (!this.grid[row][col]) { // Use the grid passed from GenericGrid
        wall--;
        continue;
      }

      const node = this.grid[row][col];
      if (node.isStart || node.isEnd) {
        wall--;
        continue;
      }

      node.isWall = true;
    }

  }

  private createVerticalWall() {
    const height = this.gridRows;
    const startCol = Math.floor(this.gridCols / 2);

    for (let i = 5; i < (height - 5); i++) {
      const row = i;

      if (!this.grid[row]?.[startCol]) {
        continue;
      }

      const node = this.grid[row][startCol];
      if (node.isStart || node.isEnd) {
        continue;
      }

      node.isWall = true;
    }

  }

  private createDiagonalWall() {
    // Diagonal-ish wall; avoids start/end
    const len = Math.min(this.gridRows, this.gridCols);
    const startCol = Math.floor((this.gridCols - len) / 2);

    for (let i = 0; i < Math.max(0, len - 10); i++) {
      const row = len - i - 1;
      const col = startCol + i;

      if (!this.grid[row]?.[col]) {
        continue;
      }

      const node = this.grid[row][col];
      if (node.isStart || node.isEnd) {
        continue;
      }

      node.isWall = true;
    }
  }

  // --- Animation (adapted to use genericGridComponent for redraw) ---
  private stopAnimations(): void {
    for (const id of this.timeoutIds) {
      clearTimeout(id);
    }
    this.timeoutIds = [];
  }

  private clearPath(): void {
    for (let row = 0; row < this.gridRows; row++) {
      for (let col = 0; col < this.gridCols; col++) {
        const node = this.grid[row][col];
        node.isVisited = false;
        node.isPath = false;
        node.nodeData = Infinity;
        node.linkedNode = null;
      }
    }
    this.genericGridComponent?.drawGrid(); // Redraw the grid via generic grid component
  }

  private animateAlgorithm(visited: Node[], path: Node[]): void {
    for (let i = 0; i <= visited.length; i++) {
      if (i === visited.length) {
        const id = globalThis.setTimeout(() => this.animateShortestPath(path), this.animationSpeed * i);
        this.timeoutIds.push(id);
        return;
      }

      const node = visited[i];
      const id = globalThis.setTimeout(() => {
        if (!node.isStart && !node.isEnd) {
          node.isVisited = true;
          this.genericGridComponent?.drawNode(node); // Redraw single node
        }
      }, this.animationSpeed * i);

      this.timeoutIds.push(id);
    }
  }

  private animateShortestPath(path: Node[]): void {
    for (let i = 0; i < path.length; i++) {
      const node = path[i];
      const id = globalThis.setTimeout(() => {
        if (!node.isStart && !node.isEnd) {
          node.isPath = true;
          this.genericGridComponent?.drawNode(node); // Redraw single node
        }
      }, this.animationSpeed * i);

      this.timeoutIds.push(id);
    }
  }

  // --- Validation ---
  private ensureStartAndEnd(): boolean {
    if (this.startNode && this.endNode) {
      return true;
    }

    const message = this.translate.instant('PATHFINDING.ALERT.START_END_NODES');
    this.snackBar.open(message, 'OK', { duration: 5000, horizontalPosition: 'center', verticalPosition: 'top' });
    return false;
  }

}
