export class Game {
  mines: boolean[][] = [];
  minesCount: number = 0;
  isRevealed: boolean[][] = [];
  isFlagged: boolean[][] = [];
  isGameOver: boolean = false;
  startTime: number = Date.now();

  constructor(width: number, height: number, mines: number) {
    if (mines > width * height) {
      throw new Error("Too many mines");
    }
    this.minesCount = mines;
    for (let i = 0; i < width; i++) {
      this.mines.push(new Array(height).fill(false));
      this.isRevealed.push(new Array(height).fill(false));
      this.isFlagged.push(new Array(height).fill(false));
    }
    while (mines > 0) {
      const x = Math.floor(Math.random() * width);
      const y = Math.floor(Math.random() * height);
      if (!this.mines[x][y]) {
        this.mines[x][y] = true;
        mines--;
      }
    }
  }

  getWidth() {
    return this.mines.length;
  }

  getHeight() {
    return this.mines[0].length;
  }

  isMine(x: number, y: number) {
    return this.mines[x][y];
  }

  flag(x: number, y: number) {
    this.isFlagged[x][y] = !this.isFlagged[x][y];
  }

  isValid(x: number, y: number) {
    return x >= 0 && x < this.getWidth() && y >= 0 && y < this.getHeight();
  }

  reveal(x: number, y: number) {
    if (!this.isValid(x, y)) return;
    this.isRevealed[x][y] = true;
    if (this.isMine(x, y)) {
      this.isGameOver = true;
      return;
    }
    const value = this.getValue(x, y);
    if (value === 0) {
      if (this.isValid(x - 1, y - 1) && !this.isRevealed[x - 1]?.[y - 1])
        this.reveal(x - 1, y - 1);
      if (this.isValid(x, y - 1) && !this.isRevealed[x]?.[y - 1])
        this.reveal(x, y - 1);
      if (this.isValid(x + 1, y - 1) && !this.isRevealed[x + 1]?.[y - 1])
        this.reveal(x + 1, y - 1);
      if (this.isValid(x - 1, y) && !this.isRevealed[x - 1]?.[y])
        this.reveal(x - 1, y);
      if (this.isValid(x + 1, y) && !this.isRevealed[x + 1]?.[y])
        this.reveal(x + 1, y);
      if (this.isValid(x - 1, y + 1) && !this.isRevealed[x - 1]?.[y + 1])
        this.reveal(x - 1, y + 1);
      if (this.isValid(x, y + 1) && !this.isRevealed[x]?.[y + 1])
        this.reveal(x, y + 1);
      if (this.isValid(x + 1, y + 1) && !this.isRevealed[x + 1]?.[y + 1])
        this.reveal(x + 1, y + 1);
    }
  }

  getHasWon() {
    if (this.isGameOver) {
      return false;
    }
    for (let i = 0; i < this.getWidth(); i++) {
      for (let j = 0; j < this.getHeight(); j++) {
        if (!this.isRevealed[i][j] && !this.isFlagged[i][j]) {
          return false;
        }
        if (this.isMine(i, j) && !this.isFlagged[i][j]) {
          return false;
        }
      }
    }
    return true;
  }

  getMinesLeft() {
    return this.minesCount - this.isFlagged.flat().filter((m) => m).length;
  }

  getNeighborFlags(x: number, y: number) {
    const neighbors = [
      this.isFlagged[x - 1]?.[y - 1],
      this.isFlagged[x]?.[y - 1],
      this.isFlagged[x + 1]?.[y - 1],
      this.isFlagged[x - 1]?.[y],
      this.isFlagged[x + 1]?.[y],
      this.isFlagged[x - 1]?.[y + 1],
      this.isFlagged[x]?.[y + 1],
      this.isFlagged[x + 1]?.[y + 1],
    ];
    return neighbors;
  }

  getNeighborMines(x: number, y: number) {
    const neighbors = [
      this.mines[x - 1]?.[y - 1],
      this.mines[x]?.[y - 1],
      this.mines[x + 1]?.[y - 1],
      this.mines[x - 1]?.[y],
      this.mines[x + 1]?.[y],
      this.mines[x - 1]?.[y + 1],
      this.mines[x]?.[y + 1],
      this.mines[x + 1]?.[y + 1],
    ];
    return neighbors;
  }

  getValue(x: number, y: number) {
    const neighbors = this.getNeighborMines(x, y);
    const mines = neighbors.filter((n) => n).length;
    return mines;
  }

  quickStart() {
    for (let i = 0; i < this.getWidth(); i++) {
      for (let j = 0; j < this.getHeight(); j++) {
        const value = this.getValue(i, j);
        const isMine = this.isMine(i, j);
        if (value === 0 && !isMine) {
          this.reveal(i, j);
          return;
        }
      }
    }
  }
}
