import { create } from "zustand";
import { newGame } from "./ws";

interface GameState {
  showFeed: boolean;
  mines: boolean[][];
  minesCount: number;
  isRevealed: boolean[][];
  isFlagged: boolean[][];
  isGameOver: boolean;
  startTime: number;
  width: number;
  height: number;
  stage: number;
  name: string;

  flag: (x: number, y: number) => void;
  reveal: (x: number, y: number) => boolean;
  getValue: (x: number, y: number) => number;
  getHasWon: () => boolean;
  getMinesLeft: () => number;
  quickStart: () => void;
  isValid: (x: number, y: number) => boolean;
  resetGame: (width: number, height: number, mines: number) => void;
  isMine: (x: number, y: number) => boolean;
  getNeighborMines: (x: number, y: number) => boolean[];
  getNeighborFlags: (x: number, y: number) => boolean[];
  getWidth: () => number;
  getHeight: () => number;
  isTouched: () => boolean;
  triggerPostGame: () => boolean;
  expandBoard: () => void;
  setName: (name: string) => void;
  setShowFeed: (showFeed: boolean) => void;
}

const useGameStore = create<GameState>((set, get) => ({
  mines: [[]],
  minesCount: 0,
  isRevealed: [[]],
  isFlagged: [[]],
  isGameOver: false,
  startTime: Date.now(),
  width: 0,
  height: 0,
  stage: 1,
  name: localStorage.getItem("name") || "No Name",
  showFeed: !localStorage.getItem("showFeed")
    ? true
    : localStorage.getItem("showFeed") === "true",

  flag: (x, y) => {
    set((state) => {
      const isFlagged = [...state.isFlagged];
      isFlagged[x][y] = !isFlagged[x][y];
      return { isFlagged };
    });
    const { triggerPostGame } = get();
    triggerPostGame();
  },

  reveal: (x, y) => {
    const { mines, isRevealed, isGameOver, getValue, triggerPostGame } = get();
    if (isGameOver || !get().isValid(x, y) || isRevealed[x][y]) return false;

    const newRevealed = [...isRevealed];
    newRevealed[x][y] = true;

    if (mines[x][y]) {
      set({ isGameOver: true, isRevealed: newRevealed });
      return true;
    } else {
      set({ isRevealed: newRevealed });
      const value = getValue(x, y);
      const neighborFlagCount = get()
        .getNeighborFlags(x, y)
        .filter((n) => n).length;
      if (value === 0 && neighborFlagCount === 0) {
        const revealNeighbors = (nx: number, ny: number) => {
          if (get().isValid(nx, ny) && !isRevealed[nx]?.[ny]) {
            get().reveal(nx, ny);
          }
        };

        revealNeighbors(x - 1, y - 1);
        revealNeighbors(x, y - 1);
        revealNeighbors(x + 1, y - 1);
        revealNeighbors(x - 1, y);
        revealNeighbors(x + 1, y);
        revealNeighbors(x - 1, y + 1);
        revealNeighbors(x, y + 1);
        revealNeighbors(x + 1, y + 1);
      }
    }
    return triggerPostGame();
  },

  getValue: (x, y) => {
    const { mines } = get();
    const neighbors = [
      mines[x - 1]?.[y - 1],
      mines[x]?.[y - 1],
      mines[x + 1]?.[y - 1],
      mines[x - 1]?.[y],
      mines[x + 1]?.[y],
      mines[x - 1]?.[y + 1],
      mines[x]?.[y + 1],
      mines[x + 1]?.[y + 1],
    ];
    return neighbors.filter((n) => n).length;
  },

  getHasWon: () => {
    const { mines, isRevealed, isFlagged, isGameOver, width, height } = get();
    if (isGameOver) return false;

    for (let i = 0; i < width; i++) {
      for (let j = 0; j < height; j++) {
        if (!isRevealed[i][j] && !isFlagged[i][j]) return false;
        if (mines[i][j] && !isFlagged[i][j]) return false;
        if (isFlagged[i][j] && !mines[i][j]) return false;
      }
    }

    return true;
  },

  getMinesLeft: () => {
    const { minesCount, isFlagged } = get();
    return minesCount - isFlagged.flat().filter((flag) => flag).length;
  },

  quickStart: () => {
    const { width, height, mines, getValue, reveal } = get();
    for (let i = 0; i < width; i++) {
      for (let j = 0; j < height; j++) {
        const value = getValue(i, j);
        if (value === 0 && !mines[i][j]) {
          reveal(i, j);
          return;
        }
      }
    }
  },
  isValid: (x: number, y: number) => {
    const { width, height } = get();
    return x >= 0 && x < width && y >= 0 && y < height;
  },
  resetGame: (width: number, height: number, mines: number) => {
    const { name } = get();
    newGame(name);
    if (mines > width * height) {
      throw new Error("Too many mines");
    }

    const minesArray = Array.from({ length: width }, () =>
      new Array(height).fill(false),
    );
    const isRevealedArray = Array.from({ length: width }, () =>
      new Array(height).fill(false),
    );
    const isFlaggedArray = Array.from({ length: width }, () =>
      new Array(height).fill(false),
    );

    let remainingMines = mines;
    while (remainingMines > 0) {
      const x = Math.floor(Math.random() * width);
      const y = Math.floor(Math.random() * height);
      if (!minesArray[x][y]) {
        minesArray[x][y] = true;
        remainingMines--;
      }
    }

    set({
      width,
      height,
      mines: minesArray,
      isRevealed: isRevealedArray,
      isFlagged: isFlaggedArray,
      minesCount: mines,
      isGameOver: false,
      startTime: Date.now(),
      stage: 1,
    });
  },
  isMine: (x: number, y: number) => {
    const { mines } = get();
    return mines[x][y];
  },
  getNeighborMines: (x: number, y: number) => {
    const { mines } = get();
    const neighbors = [
      mines[x - 1]?.[y - 1],
      mines[x]?.[y - 1],
      mines[x + 1]?.[y - 1],
      mines[x - 1]?.[y],
      mines[x + 1]?.[y],
      mines[x - 1]?.[y + 1],
      mines[x]?.[y + 1],
      mines[x + 1]?.[y + 1],
    ];
    return neighbors;
  },
  getNeighborFlags: (x: number, y: number) => {
    const { isFlagged } = get();
    const neighbors = [
      isFlagged[x - 1]?.[y - 1],
      isFlagged[x]?.[y - 1],
      isFlagged[x + 1]?.[y - 1],
      isFlagged[x - 1]?.[y],
      isFlagged[x + 1]?.[y],
      isFlagged[x - 1]?.[y + 1],
      isFlagged[x]?.[y + 1],
      isFlagged[x + 1]?.[y + 1],
    ];
    return neighbors;
  },
  getWidth: () => {
    const { width } = get();
    return width;
  },
  getHeight: () => {
    const { height } = get();
    return height;
  },
  isTouched: () => {
    const { isRevealed, isFlagged } = get();
    return (
      isRevealed.flat().filter((flag) => flag).length > 0 ||
      isFlagged.flat().filter((flag) => flag).length > 0
    );
  },
  triggerPostGame: () => {
    const { getHasWon, expandBoard } = get();
    if (getHasWon()) {
      expandBoard();
      return true;
    }
    return false;
  },
  expandBoard: () => {
    const { width, height, stage, mines, isFlagged, isRevealed } = get();
    let dir = stage % 2 === 0 ? "down" : "right";
    if (stage > 11) {
      dir = "down";
    }
    // Expand the board by the current board size 8x8 -> 16x8
    if (dir === "down") {
      const newHeight = Math.floor(height * 1.5);
      const newWidth = width;
      const newMinesCount = Math.floor(
        width * height * 0.5 * (0.2 + 0.003 * stage),
      );
      // expand mines array
      const newMines = Array.from({ length: newWidth }, () =>
        new Array(newHeight).fill(false),
      );
      const newIsRevealed = Array.from({ length: newWidth }, () =>
        new Array(newHeight).fill(false),
      );
      const newIsFlagged = Array.from({ length: newWidth }, () =>
        new Array(newHeight).fill(false),
      );
      for (let i = 0; i < newWidth; i++) {
        for (let j = 0; j < newHeight; j++) {
          const x = i;
          const y = j;
          if (mines[x]?.[y]) {
            newMines[i][j] = true;
          }
          if (isRevealed[x]?.[y]) {
            newIsRevealed[i][j] = true;
          }
          if (isFlagged[x]?.[y]) {
            newIsFlagged[i][j] = true;
          }
        }
      }
      // generate new mines
      let remainingMines = newMinesCount;
      while (remainingMines > 0) {
        const x = Math.floor(Math.random() * width);
        const y = height + Math.floor(Math.random() * (newHeight - height));
        if (!newMines[x][y]) {
          newMines[x][y] = true;
          remainingMines--;
        }
      }
      set({
        width: newWidth,
        height: newHeight,
        mines: newMines,
        minesCount: newMinesCount,
        stage: stage + 1,
        isRevealed: newIsRevealed,
        isFlagged: newIsFlagged,
      });
    }
    if (dir === "right") {
      const newWidth = Math.floor(width * 1.5);
      const newHeight = height;
      const newMinesCount = Math.floor(
        width * height * 0.5 * (0.2 + 0.003 * stage),
      );
      // expand mines array
      const newMines = Array.from({ length: newWidth }, () =>
        new Array(newHeight).fill(false),
      );
      const newIsRevealed = Array.from({ length: newWidth }, () =>
        new Array(newHeight).fill(false),
      );
      const newIsFlagged = Array.from({ length: newWidth }, () =>
        new Array(newHeight).fill(false),
      );
      for (let i = 0; i < newWidth; i++) {
        for (let j = 0; j < newHeight; j++) {
          const x = i;
          const y = j;
          if (mines[x]?.[y]) {
            newMines[i][j] = true;
          }
          if (isRevealed[x]?.[y]) {
            newIsRevealed[i][j] = true;
          }
          if (isFlagged[x]?.[y]) {
            newIsFlagged[i][j] = true;
          }
        }
      }
      // generate new mines
      let remainingMines = newMinesCount;
      while (remainingMines > 0) {
        const x = width + Math.floor(Math.random() * (newWidth - width));
        const y = Math.floor(Math.random() * height);
        if (!newMines[x][y]) {
          newMines[x][y] = true;
          remainingMines--;
        }
      }
      set({
        width: newWidth,
        height: newHeight,
        mines: newMines,
        minesCount: newMinesCount,
        stage: stage + 1,
        isRevealed: newIsRevealed,
        isFlagged: newIsFlagged,
      });
    }
    const newMinesCount = get()
      .mines.flat()
      .filter((m) => m).length;
    set({ minesCount: newMinesCount });
  },
  setName: (name) => {
    localStorage.setItem("name", name);
    set({ name });
  },
  setShowFeed: (showFeed) => {
    localStorage.setItem("showFeed", showFeed.toString());
    set({ showFeed });
  },
}));

export default useGameStore;
