import {AfterViewInit, Component, ElementRef, inject, ViewChild} from '@angular/core';
import {CommonModule} from '@angular/common';
import {FormsModule} from '@angular/forms';
import {MatButtonModule} from '@angular/material/button';
import {MatCardModule} from '@angular/material/card';
import {MatFormFieldModule} from '@angular/material/form-field';
import {MatInputModule} from '@angular/material/input';
import {TranslateModule, TranslateService} from '@ngx-translate/core';
import {MatSnackBar} from '@angular/material/snack-bar';

import {DEFAULT_GRID_COLS, DEFAULT_GRID_ROWS, MAX_GRID_PX, MAX_GRID_SIZE, MIN_GRID_SIZE, FourColorNode, Region} from './four-color.models';
import {AlgorithmInformation} from '../information/information.models';
import {Information} from '../information/information';
import {GridPos} from '../../../shared/components/generic-grid/generic-grid';
import {SharedFunctions} from '../../../shared/SharedFunctions';
import {UrlConstants} from '../../../constants/UrlConstants';

@Component({
  selector: 'app-four-color',
  standalone: true,
  imports: [
    CommonModule,
    FormsModule,
    MatButtonModule,
    MatCardModule,
    MatFormFieldModule,
    MatInputModule,
    TranslateModule,
    Information
  ],
  templateUrl: './four-color.component.html',
  styleUrl: './four-color.component.scss'
})
export class FourColorComponent implements AfterViewInit {
  private readonly translate = inject(TranslateService);
  private readonly snackBar = inject(MatSnackBar);

  readonly MIN_GRID_SIZE = MIN_GRID_SIZE;
  readonly MAX_GRID_SIZE = MAX_GRID_SIZE;

  algoInformation: AlgorithmInformation = {
    title: 'FOUR_COLOR.EXPLANATION.TITLE',
    entries: [
      {
        name: 'FOUR_COLOR.TITLE',
        translateName: true,
        description: 'FOUR_COLOR.EXPLANATION.EXPLANATION',
        link: UrlConstants.FOUR_COLOR_THEOREM
      }
    ],
    disclaimer: 'FOUR_COLOR.EXPLANATION.DISCLAIMER',
    disclaimerBottom: 'FOUR_COLOR.EXPLANATION.DISCLAIMER_BOTTOM',
    disclaimerListEntry: [
      'FOUR_COLOR.EXPLANATION.DISCLAIMER_1',
      'FOUR_COLOR.EXPLANATION.DISCLAIMER_2',
      'FOUR_COLOR.EXPLANATION.DISCLAIMER_3',
      'FOUR_COLOR.EXPLANATION.DISCLAIMER_4'
    ]
  };

  gridRows = DEFAULT_GRID_ROWS;
  gridCols = DEFAULT_GRID_COLS;
  grid: FourColorNode[][] = [];
  regions: Region[] = [];
  executionTime = 0;
  solutionStatus: 'INCOMPLETE' | 'SOLVED' | 'CONFLICTS' | 'INVALID' = 'INCOMPLETE';

  @ViewChild('fourColorCanvas') canvasRef!: ElementRef<HTMLCanvasElement>;
  private ctx!: CanvasRenderingContext2D;
  private nodeSize = 0;

  ngAfterViewInit(): void {
    this.ctx = this.canvasRef.nativeElement.getContext('2d')!;
    this.initializeGrid();
  }

  applyGridSize(): void {
    if (this.gridRows < MIN_GRID_SIZE) this.gridRows = MIN_GRID_SIZE;
    if (this.gridRows > MAX_GRID_SIZE) this.gridRows = MAX_GRID_SIZE;
    if (this.gridCols < MIN_GRID_SIZE) this.gridCols = MIN_GRID_SIZE;
    if (this.gridCols > MAX_GRID_SIZE) this.gridCols = MAX_GRID_SIZE;

    this.initializeGrid();
  }

  initializeGrid(): void {
    this.grid = [];
    this.solutionStatus = 'INCOMPLETE';
    for (let r = 0; r < this.gridRows; r++) {
      const row: FourColorNode[] = [];
      for (let c = 0; c < this.gridCols; c++) {
        row.push({
          row: r,
          col: c,
          regionId: -1,
          color: 0,
        });
      }
      this.grid.push(row);
    }

    this.generateRegions();
    this.resizeCanvas();
    this.drawGrid();
  }

  private resizeCanvas(): void {
    const canvas = this.canvasRef.nativeElement;
    const maxDim = Math.max(this.gridRows, this.gridCols);
    this.nodeSize = Math.floor(MAX_GRID_PX / maxDim);

    canvas.width = this.gridCols * this.nodeSize;
    canvas.height = this.gridRows * this.nodeSize;
  }

  generateRegions(): void {
    const numRegions = Math.floor((this.gridRows * this.gridCols) / 30);
    this.regions = [];
    const seeds = this.determineSeeds(numRegions);
    this.regionGrowth(seeds);
    this.determineAdjacency();
  }

  private determineAdjacency() {
    for (let row = 0; row < this.gridRows; row++) {
      for (let col = 0; col < this.gridCols; col++) {
        const currentRegionId = this.grid[row][col].regionId;
        const neighbors = this.getNeighbors(row, col);
        for (const neighbor of neighbors) {
          const neighborRegionId = this.grid[neighbor.row][neighbor.col].regionId;
          if (neighborRegionId !== -1 && neighborRegionId !== currentRegionId) {
            this.regions[currentRegionId].neighbors.add(neighborRegionId);
            this.regions[neighborRegionId].neighbors.add(currentRegionId);
          }
        }
      }
    }
  }

  private regionGrowth(seeds: GridPos[]) {
    const queue: GridPos[] = [...seeds];
    while (queue.length > 0) {
      const {row, col} = queue.shift()!;
      const regionId = this.grid[row][col].regionId;

      const neighbors = this.getNeighbors(row, col);
      for (const neighbor of neighbors) {
        if (this.grid[neighbor.row][neighbor.col].regionId === -1) {
          this.grid[neighbor.row][neighbor.col].regionId = regionId;
          queue.push(neighbor);
        }
      }
    }
  }

  private determineSeeds(numRegions: number) {
    const seeds: GridPos[] = [];
    for (let i = 0; i < numRegions; i++) {
      let r = SharedFunctions.randomIntFromInterval(0, this.gridRows - 1);
      let c = SharedFunctions.randomIntFromInterval(0, this.gridCols - 1);
      while (this.grid[r][c].regionId !== -1) {
        r = SharedFunctions.randomIntFromInterval(0, this.gridRows - 1);
        c = SharedFunctions.randomIntFromInterval(0, this.gridCols - 1);
      }
      this.grid[r][c].regionId = i;
      seeds.push({row: r, col: c});
      this.regions.push({id: i, color: 0, neighbors: new Set<number>()});
    }
    return seeds;
  }

  private getNeighbors(row: number, col: number): GridPos[] {
    const res: GridPos[] = [];
    if (row > 0) res.push({row: row - 1, col});
    if (row < this.gridRows - 1) res.push({row: row + 1, col});
    if (col > 0) res.push({row, col: col - 1});
    if (col < this.gridCols - 1) res.push({row, col: col + 1});
    return res;
  }

  drawGrid(): void {
    if (!this.ctx) return;

    this.ctx.clearRect(0, 0, this.canvasRef.nativeElement.width, this.canvasRef.nativeElement.height);

    // 1. Draw Cell Backgrounds
    for (let r = 0; r < this.gridRows; r++) {
      for (let c = 0; c < this.gridCols; c++) {
        const node = this.grid[r][c];
        this.ctx.fillStyle = this.getNodeColor(node);
        this.ctx.fillRect(c * this.nodeSize, r * this.nodeSize, this.nodeSize, this.nodeSize);
      }
    }

    // 2. Draw Region Borders
    this.ctx.strokeStyle = '#000';
    this.ctx.lineWidth = 2;
    this.ctx.beginPath();
    for (let r = 0; r < this.gridRows; r++) {
      for (let c = 0; c < this.gridCols; c++) {
        const currentRegion = this.grid[r][c].regionId;

        // Right border
        if (c < this.gridCols - 1 && this.grid[r][c+1].regionId !== currentRegion) {
          this.ctx.moveTo((c + 1) * this.nodeSize, r * this.nodeSize);
          this.ctx.lineTo((c + 1) * this.nodeSize, (r + 1) * this.nodeSize);
        }

        // Bottom border
        if (r < this.gridRows - 1 && this.grid[r+1][c].regionId !== currentRegion) {
          this.ctx.moveTo(c * this.nodeSize, (r + 1) * this.nodeSize);
          this.ctx.lineTo((c + 1) * this.nodeSize, (r + 1) * this.nodeSize);
        }
      }
    }
    this.ctx.stroke();

    // 3. Draw Outer Border
    this.ctx.strokeStyle = '#000';
    this.ctx.lineWidth = 2;
    this.ctx.strokeRect(0, 0, this.gridCols * this.nodeSize, this.gridRows * this.nodeSize);
  }

  private getNodeColor(node: FourColorNode): string {
      switch (node.color) {
        case 1: return '#FF5252'; // Red
        case 2: return '#448AFF'; // Blue
        case 3: return '#4CAF50'; // Green
        case 4: return '#FFEB3B'; // Yellow
        default: return 'white';
      }
  }

  onMouseDown(event: MouseEvent): void {
    const pos = this.getGridPos(event);
    if (pos) this.handleInteraction(pos);
  }

  onMouseMove(event: MouseEvent): void {
    if (event.buttons !== 1){
      return;
    }
    this.getGridPos(event);
  }

  onTouchStart(event: TouchEvent): void {
    event.preventDefault();
    const touch = event.touches[0];
    const pos = this.getGridPos(touch);
    if (pos) this.handleInteraction(pos);
  }

  onTouchMove(event: TouchEvent): void {
    event.preventDefault();
  }

  private getGridPos(event: MouseEvent | Touch): GridPos | null {
    const rect = this.canvasRef.nativeElement.getBoundingClientRect();
    const x = event.clientX - rect.left;
    const y = event.clientY - rect.top;

    const col = Math.floor(x / (rect.width / this.gridCols));
    const row = Math.floor(y / (rect.height / this.gridRows));

    if (row >= 0 && row < this.gridRows && col >= 0 && col < this.gridCols) {
      return {row, col};
    }
    return null;
  }

  private handleInteraction(pos: GridPos): void {
    const node = this.grid[pos.row][pos.col];
    if (node.regionId === -1){
      return;
    }

    const region = this.regions[node.regionId];
    region.color = (region.color % 4) + 1;
    this.updateRegionColors(region);
    this.checkSolution();
    this.drawGrid();
  }

  private updateRegionColors(region: Region): void {
    for (let row = 0; row < this.gridRows; row++) {
      for (let col = 0; col < this.gridCols; col++) {
        if (this.grid[row][col].regionId === region.id) {
          this.grid[row][col].color = region.color;
        }
      }
    }
  }

  resetColors(): void {
    for (const region of this.regions) {
      region.color = 0;
      this.updateRegionColors(region);
    }
    this.solutionStatus = 'INCOMPLETE';
    this.drawGrid();
  }

  autoSolve(): void {
    const startTime = performance.now();
    this.resetColors();

    const success = this.backtrackSolve(0);
    const endTime = performance.now();
    this.executionTime = endTime - startTime;

    if (success) {
      this.checkSolution();
      this.drawGrid();
    } else {
      const message = this.translate.instant('FOUR_COLOR.ALERT.NO_SOLUTION');
      this.snackBar.open(message, 'ALERT');
    }
  }

  private backtrackSolve(regionIndex: number): boolean {
    if (regionIndex === this.regions.length) return true;

    const region = this.regions[regionIndex];
    const availableColors = [1, 2, 3, 4];

    for (const color of availableColors) {
      if (this.isColorValid(region, color)) {
        region.color = color;
        this.updateRegionColors(region);

        if (this.backtrackSolve(regionIndex + 1)) {
          return true;
        }

        region.color = 0;
        //this.updateRegionColors(region);
      }
    }
    return false;
  }

  private isColorValid(region: Region, color: number): boolean {
    for (const neighborId of region.neighbors) {
      if (this.regions[neighborId].color === color){
        return false;
      }
    }
    return true;
  }

  checkSolution(): void {
    let allColored = true;
    let hasConflicts = false;

    for (const region of this.regions) {
      if (region.color === 0) {
        allColored = false;
      }
      if (region.color > 0 && !this.isColorValid(region, region.color)) {
        hasConflicts = true;
      }
    }

    if (hasConflicts) {
      this.solutionStatus = allColored ? 'INVALID' : 'CONFLICTS';
    } else {
      this.solutionStatus = allColored ? 'SOLVED' : 'INCOMPLETE';
    }
  }

  generateNewMap(): void {
    this.initializeGrid();
  }
}
