import { GameState, PerkId, RunParams } from "./types";
import { allLevels, allLevelsAndIcons, upgrades } from "./loadGameData";
import {
  defaultSounds,
  getHighScore,
  getPossibleUpgrades,
  highScoreText,
  makeEmptyPerksMap,
  sumOfValues,
} from "./game_utils";
import { resetBalls } from "./gameStateMutators";
import { isOptionOn } from "./options";
import { getHistory } from "./gameOver";
import { getSettingValue, getTotalScore } from "./settings";
import { isBlackListedForStart, isStartingPerk } from "./startingPerks";
import {
  isLevelLocked,
  reasonLevelIsLocked,
} from "./get_level_unlock_condition";
import { dontOfferTooSoon } from "./openUpgradesPicker";

export function getRunLevels(
  params: RunParams,
  randomGift: PerkId | undefined,
) {
  const unlockedBefore = new Set(
    getSettingValue("breakout_71_unlocked_levels", []),
  );

  const history = getHistory();
  const unlocked = allLevels.filter(
    (l, li) =>
      unlockedBefore.has(l.name) || !isLevelLocked(li, l.name, history),
  );
  const firstLevel = params?.level
    ? [params.level]
    : allLevelsAndIcons.filter((l) => l.name == "icon:" + randomGift);

  const restInRandomOrder = unlocked
    .filter((l) => l.name !== params?.level?.name)
    .filter((l) => l.name !== params?.levelToAvoid)
    .sort(() => Math.random() - 0.5);

  return firstLevel
    .concat(
      restInRandomOrder.slice(0, 7 + 3).sort((a, b) => a.sortKey - b.sortKey),
    )
    .concat(restInRandomOrder.slice(7 + 3));
}

export function newGameState(params: RunParams): GameState {
  const highScore = getHighScore();

  const perks = { ...makeEmptyPerksMap(upgrades), ...(params?.perks || {}) };

  let randomGift: PerkId | undefined = undefined;
  if (!sumOfValues(perks)) {
    let giftable = upgrades.filter((u) => isStartingPerk(u));
    if (!giftable.length) {
      giftable = upgrades.filter((u) => !isBlackListedForStart(u));
    }

    randomGift =
      (isOptionOn("kid") && "slow_down") ||
      giftable[Math.floor(Math.random() * giftable.length)].id;

    perks[randomGift] = 1;
  }

  const runLevels = getRunLevels(params, randomGift);

  const gameState: GameState = {
    startParams: params,
    runLevels,
    level: runLevels[0],
    currentLevel: 0,
    upgradesOfferedFor: -1,
    perks,
    puckWidth: 200,
    baseSpeed: 12,
    combo: 1,
    lastCombo: 1,
    gridSize: 12,
    running: false,
    isGameOver: false,
    ballStickToPuck: true,
    puckPosition: 400,
    lastPuckPosition: 400,
    lastPuckMove: 0,
    lastZenComboIncrease: 0,
    pauseTimeout: null,
    canvasWidth: 0,
    canvasHeight: 0,
    offsetX: 0,
    offsetXRoundedDown: 0,
    gameZoneWidth: 0,
    gameZoneWidthRoundedUp: 0,
    gameZoneHeight: 0,
    brickWidth: 0,
    score: 0,
    lastScoreIncrease: -1000,
    levelCoughtCoins: 0,
    lastExplosion: -1000,
    lastBrickBroken: 0,
    highScore,
    balls: [],
    ballsColor: "#FFFFFF",
    bricks: [],
    brickHP: [],
    lights: { indexMin: 0, total: 0, list: [] },
    particles: { indexMin: 0, total: 0, list: [] },
    texts: { indexMin: 0, total: 0, list: [] },
    coins: { indexMin: 0, total: 0, list: [] },
    respawns: { indexMin: 0, total: 0, list: [] },
    levelStartScore: 0,
    levelMisses: 0,
    levelSpawnedCoins: 0,
    puckColor: "#FFFFFF",
    ballSize: 20,
    coinSize: 14,
    puckHeight: 20,
    pauseUsesDuringRun: 0,
    keyboardPuckSpeed: 0,
    lastTick: performance.now(),
    lastTickDown: 0,
    runStatistics: {
      started: Date.now(),
      levelsPlayed: 0,
      runTime: 0,
      coins_spawned: 0,
      score: 0,
      bricks_broken: 0,
      misses: 0,
      balls_lost: 0,
      puck_bounces: 0,
      wall_bounces: 0,
      upgrades_picked: 1,
      max_combo: 1,
    },
    lastOffered: {},
    levelTime: 0,
    winAt: 0,
    levelWallBounces: 0,
    needsRender: true,
    autoCleanUses: 0,
    ...defaultSounds(),
    rerolls: 0,
    creative:
      params?.computer_controlled ||
      sumOfValues(params.perks) > 1 ||
      (params.level && !params.level.name.startsWith("icon:")),
  };
  resetBalls(gameState);

  for (let perk of upgrades) {
    if (perks[perk.id]) {
      dontOfferTooSoon(gameState, perk.id);
    }
  }
  return gameState;
}
