import randomWords from "random-words";

import { games, gameDB } from "./store";
import { PLAYER_COLORS, type Game } from "./types";
import { addChatMessage, clearPlayer, stopTurnTimer } from "./helpers";
import newPlayer from "./playerFactory";
import { info } from "./constants";
import { layout, staticData } from "../../util/layout";
import { shuffleArray } from "./utils";
import { pickRobber } from "./robber";
import { audio } from "../webrtc-signaling";

export const resetGame = (game: any) => {
  Object.assign(game, {
    startTime: Date.now(),
    state: "lobby",
    turns: 0,
    step: 0 /* used for the suffix # in game backups */,
    turn: {},
    sheep: 19,
    ore: 19,
    wool: 19,
    brick: 19,
    wheat: 19,
    placements: {
      corners: [],
      roads: [],
    },
    developmentCards: [],
    chat: [],
    activities: [],
    pipOrder: game.pipOrder,
    borderOrder: game.borderOrder,
    tileOrder: game.tileOrder,
    signature: game.signature,
    players: game.players,
    stolen: {
      robber: {
        stole: {
          total: 0,
        },
      },
      total: 0,
    },
    longestRoad: "",
    longestRoadLength: 0,
    largestArmy: "",
    largestArmySize: 0,
    mostDeveloped: "",
    mostDevelopmentCards: 0,
    mostPorts: "",
    mostPortCount: 0,
    winner: undefined,
    active: 0,
  });

  stopTurnTimer(game);

  /* Populate the game corner and road placement data as cleared */
  for (let i = 0; i < layout.corners.length; i++) {
    game.placements.corners[i] = {
      color: undefined,
      type: undefined,
    };
  }

  for (let i = 0; i < layout.roads.length; i++) {
    game.placements.roads[i] = {
      color: undefined,
      longestRoad: undefined,
    };
  }

  /* Put the robber back on the Desert */
  for (let i = 0; i < game.pipOrder.length; i++) {
    if (game.pipOrder[i] === 18) {
      game.robber = i;
      break;
    }
  }

  /* Populate the game development cards with a fresh deck */
  for (let i = 1; i <= 14; i++) {
    game.developmentCards.push({
      type: "army",
      card: i,
    });
  }

  ["monopoly", "monopoly", "road-1", "road-2", "year-of-plenty", "year-of-plenty"].forEach((card) =>
    game.developmentCards.push({
      type: "progress",
      card: card,
    })
  );

  ["market", "library", "palace", "university"].forEach((card) =>
    game.developmentCards.push({
      type: "vp",
      card: card,
    })
  );

  shuffleArray(game.developmentCards);

  /* Reset all player data, and add in any missing colors */
  PLAYER_COLORS.forEach((color) => {
    if (color in game.players) {
      clearPlayer(game.players[color]);
    } else {
      game.players[color] = newPlayer(color);
    }
  });

  /* Ensure sessions are connected to player objects */
  for (let key in game.sessions) {
    const session = game.sessions[key];
    if (session.color !== "unassigned") {
      game.active++;
      session.player = game.players[session.color];
      session.player.status = "Active";
      session.player.lastActive = Date.now();
      session.player.live = session.live;
      session.player.name = session.name;
      session.player.color = session.color;
    }
  }

  game.animationSeeds = [];
  for (let i = 0; i < game.tileOrder.length; i++) {
    game.animationSeeds.push(Math.random());
  }
};

export const setBeginnerGame = (game: Game) => {
  pickRobber(game);
  shuffleArray(game.developmentCards);
  game.borderOrder = [];
  for (let i = 0; i < 6; i++) {
    game.borderOrder.push(i);
  }
  game.tileOrder = [9, 12, 1, 5, 16, 13, 17, 6, 2, 0, 3, 10, 4, 11, 7, 14, 18, 8, 15];
  game.robber = 9;
  game.animationSeeds = [];
  for (let i = 0; i < game.tileOrder.length; i++) {
    game.animationSeeds.push(Math.random());
  }
  game.pipOrder = [5, 1, 6, 7, 2, 9, 11, 12, 8, 18, 3, 4, 10, 16, 13, 0, 14, 15, 17];
  game.signature = gameSignature(game);
};

export const shuffleBoard = (game: any): void => {
  pickRobber(game);

  const seq = [];
  for (let i = 0; i < 6; i++) {
    seq.push(i);
  }
  shuffleArray(seq);
  game.borderOrder = seq.slice();

  for (let i = 6; i < 19; i++) {
    seq.push(i);
  }
  shuffleArray(seq);
  game.tileOrder = seq.slice();

  /* Pip order is from one of the random corners, then rotate around
   * and skip over the desert (robber) */

  /* Board:
   *       0     1     2
   *    3      4     5     6
   *  7    8    9      10    11
   *    12   13    14     15
   *       16   17     18
   */
  const order = [
    [0, 1, 2, 6, 11, 15, 18, 17, 16, 12, 7, 3, 4, 5, 10, 14, 13, 8, 9],
    [2, 6, 11, 15, 18, 17, 16, 12, 7, 3, 0, 1, 5, 10, 14, 13, 8, 4, 9],
    [11, 15, 18, 17, 16, 12, 7, 3, 0, 1, 2, 6, 10, 14, 13, 8, 4, 5, 9],
    [18, 17, 16, 12, 7, 3, 0, 1, 2, 6, 11, 15, 14, 13, 8, 4, 5, 10, 9],
    [16, 12, 7, 3, 0, 1, 2, 6, 11, 15, 18, 17, 13, 8, 4, 5, 10, 14, 9],
    [7, 3, 0, 1, 2, 6, 11, 15, 18, 17, 16, 12, 8, 4, 5, 10, 14, 13, 9],
  ];
  const sequence = order[Math.floor(Math.random() * order.length)];
  if (!sequence || !Array.isArray(sequence)) {
    // Defensive: should not happen, but guard for TS strictness
    return;
  }
  game.pipOrder = [];
  game.animationSeeds = [];
  for (let i = 0, p = 0; i < sequence.length; i++) {
    const target: number = sequence[i]!;
    /* If the target tile is the desert (18), then set the
     * pip value to the robber (18) otherwise set
     * the target pip value to the currently incremeneting
     * pip value. */
    const tileIdx = game.tileOrder[target];
    const tileType = staticData.tiles[tileIdx]?.type;
    if (!game.pipOrder) game.pipOrder = [];
    if (tileType === "desert") {
      game.robber = target;
      game.pipOrder[target] = 18;
    } else {
      game.pipOrder[target] = p++;
    }
    game.animationSeeds.push(Math.random());
  }

  shuffleArray(game.developmentCards);

  game.signature = gameSignature(game);
};

export const createGame = async (id: string | null = null) => {

  /* Look for a new game with random words that does not already exist */
  while (!id) {
    id = randomWords(4).join("-");
    try {
      /* If a game with this id exists in the DB, look for a new name */
      if (!gameDB || !gameDB.getGameById) {
        throw new Error("Game DB not available for uniqueness check");
      }
      let exists = false;
      try {
        const g = await gameDB.getGameById(id);
        if (g) exists = true;
      } catch (e) {
        // if DB check fails treat as non-existent and continue searching
      }
      if (exists) {
        id = "";
      }
    } catch (error) {
      break;
    }
  }
  console.log(`${info}: creating ${id}`);

  const game: Game = {
    id: id,
    developmentCards: [],
    playerOrder: [],
    turns: 0,
    players: {
      O: newPlayer("O"),
      R: newPlayer("R"),
      B: newPlayer("B"),
      W: newPlayer("W"),
    },
    mostPorts: null,
    mostPortCount: 0,
    sessions: {},
    unselected: [],
    placements: {
      corners: [],
      roads: [],
    },
    turn: {
      name: "",
      color: "unassigned",
      actions: [],
      limits: {},
      roll: 0,
      volcano: null,
      free: false,
      freeRoads: 0,
      select: {},
      active: null,
      robberInAction: false,
      placedRobber: false,
      developmentPurchased: false,
      offer: null,
    },
    rules: {
      "victory-points": {
        points: 10,
      },
    },
    pipOrder: [],
    borderOrder: [],
    tileOrder: [],
    step: 0 /* used for the suffix # in game backups */,
  };

  setBeginnerGame(game);
  resetGame(game);

  addChatMessage(game, null, `New game created with Beginner's Layout: ${game.id}`);

  games[game.id] = game;
  audio[game.id] = {};
  return game;
};

const gameSignature = (game: Game): string => {
  if (!game) {
    return "";
  }
  const salt = 251;
  const signature =
    (game.borderOrder || []).map((border: any) => `00${(Number(border) ^ salt).toString(16)}`.slice(-2)).join("") +
    "-" +
    (game.pipOrder || [])
      .map((pip: any, index: number) => `00${(Number(pip) ^ salt ^ (salt * index)).toString(16)}`.slice(-2))
      .join("") +
    "-" +
    (game.tileOrder || [])
      .map((tile: any, index: number) => `00${(Number(tile) ^ salt ^ (salt * index)).toString(16)}`.slice(-2))
      .join("");

  return signature;
};
