import * as EncounterPhaseUtils from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { Status } from "#app/data/status-effect";
import { CommandPhase } from "#app/phases/command-phase";
import { MessagePhase } from "#app/phases/message-phase";
import {
  MysteryEncounterBattlePhase,
  MysteryEncounterOptionSelectedPhase,
  MysteryEncounterPhase,
  MysteryEncounterRewardsPhase,
} from "#app/phases/mystery-encounter-phases";
import { VictoryPhase } from "#app/phases/victory-phase";
import type MessageUiHandler from "#app/ui/message-ui-handler";
import type MysteryEncounterUiHandler from "#app/ui/mystery-encounter-ui-handler";
import type PartyUiHandler from "#app/ui/party-ui-handler";
import type OptionSelectUiHandler from "#app/ui/settings/option-select-ui-handler";
import { Mode } from "#app/ui/ui";
import { isNullOrUndefined } from "#app/utils";
import { Button } from "#enums/buttons";
import { StatusEffect } from "#enums/status-effect";
import type GameManager from "#test/testUtils/gameManager";
import { expect, vi } from "vitest";

/**
 * Runs a {@linkcode MysteryEncounter} to either the start of a battle, or to the {@linkcode MysteryEncounterRewardsPhase}, depending on the option selected
 * @param game
 * @param optionNo Human number, not index
 * @param secondaryOptionSelect
 * @param isBattle If selecting option should lead to battle, set to `true`
 */
export async function runMysteryEncounterToEnd(
  game: GameManager,
  optionNo: number,
  secondaryOptionSelect?: { pokemonNo: number; optionNo?: number },
  isBattle = false,
) {
  vi.spyOn(EncounterPhaseUtils, "selectPokemonForOption");
  await runSelectMysteryEncounterOption(game, optionNo, secondaryOptionSelect);

  // run the selected options phase
  game.onNextPrompt(
    "MysteryEncounterOptionSelectedPhase",
    Mode.MESSAGE,
    () => {
      const uiHandler = game.scene.ui.getHandler<MysteryEncounterUiHandler>();
      uiHandler.processInput(Button.ACTION);
    },
    () => game.isCurrentPhase(MysteryEncounterBattlePhase) || game.isCurrentPhase(MysteryEncounterRewardsPhase),
  );

  if (isBattle) {
    game.onNextPrompt(
      "DamageAnimPhase",
      Mode.MESSAGE,
      () => {
        game.setMode(Mode.MESSAGE);
        game.endPhase();
      },
      () => game.isCurrentPhase(CommandPhase),
    );

    game.onNextPrompt(
      "CheckSwitchPhase",
      Mode.CONFIRM,
      () => {
        game.setMode(Mode.MESSAGE);
        game.endPhase();
      },
      () => game.isCurrentPhase(CommandPhase),
    );

    game.onNextPrompt(
      "CheckSwitchPhase",
      Mode.MESSAGE,
      () => {
        game.setMode(Mode.MESSAGE);
        game.endPhase();
      },
      () => game.isCurrentPhase(CommandPhase),
    );

    // If a battle is started, fast forward to end of the battle
    game.onNextPrompt("CommandPhase", Mode.COMMAND, () => {
      game.scene.clearPhaseQueue();
      game.scene.clearPhaseQueueSplice();
      game.scene.unshiftPhase(new VictoryPhase(0));
      game.endPhase();
    });

    // Handle end of battle trainer messages
    game.onNextPrompt("TrainerVictoryPhase", Mode.MESSAGE, () => {
      const uiHandler = game.scene.ui.getHandler<MessageUiHandler>();
      uiHandler.processInput(Button.ACTION);
    });

    // Handle egg hatch dialogue
    game.onNextPrompt("EggLapsePhase", Mode.MESSAGE, () => {
      const uiHandler = game.scene.ui.getHandler<MessageUiHandler>();
      uiHandler.processInput(Button.ACTION);
    });

    await game.phaseInterceptor.to(CommandPhase);
  } else {
    await game.phaseInterceptor.to(MysteryEncounterRewardsPhase);
  }
}

export async function runSelectMysteryEncounterOption(
  game: GameManager,
  optionNo: number,
  secondaryOptionSelect?: { pokemonNo: number; optionNo?: number },
) {
  // Handle any eventual queued messages (e.g. weather phase, etc.)
  game.onNextPrompt(
    "MessagePhase",
    Mode.MESSAGE,
    () => {
      const uiHandler = game.scene.ui.getHandler<MessageUiHandler>();
      uiHandler.processInput(Button.ACTION);
    },
    () => game.isCurrentPhase(MysteryEncounterOptionSelectedPhase),
  );

  if (game.isCurrentPhase(MessagePhase)) {
    await game.phaseInterceptor.run(MessagePhase);
  }

  // dispose of intro messages
  game.onNextPrompt(
    "MysteryEncounterPhase",
    Mode.MESSAGE,
    () => {
      const uiHandler = game.scene.ui.getHandler<MysteryEncounterUiHandler>();
      uiHandler.processInput(Button.ACTION);
    },
    () => game.isCurrentPhase(MysteryEncounterOptionSelectedPhase),
  );

  await game.phaseInterceptor.to(MysteryEncounterPhase, true);

  // select the desired option
  const uiHandler = game.scene.ui.getHandler<MysteryEncounterUiHandler>();
  uiHandler.unblockInput(); // input are blocked by 1s to prevent accidental input. Tests need to handle that

  switch (optionNo) {
    case 2:
      uiHandler.processInput(Button.RIGHT);
      break;
    case 3:
      uiHandler.processInput(Button.DOWN);
      break;
    case 4:
      uiHandler.processInput(Button.RIGHT);
      uiHandler.processInput(Button.DOWN);
      break;
    default:
      // no movement needed. Default cursor position
      break;
  }

  if (!isNullOrUndefined(secondaryOptionSelect?.pokemonNo)) {
    await handleSecondaryOptionSelect(game, secondaryOptionSelect.pokemonNo, secondaryOptionSelect.optionNo);
  } else {
    uiHandler.processInput(Button.ACTION);
  }
}

async function handleSecondaryOptionSelect(game: GameManager, pokemonNo: number, optionNo?: number) {
  // Handle secondary option selections
  const partyUiHandler = game.scene.ui.handlers[Mode.PARTY] as PartyUiHandler;
  vi.spyOn(partyUiHandler, "show");

  const encounterUiHandler = game.scene.ui.getHandler<MysteryEncounterUiHandler>();
  encounterUiHandler.processInput(Button.ACTION);

  await vi.waitFor(() => expect(partyUiHandler.show).toHaveBeenCalled());

  for (let i = 1; i < pokemonNo; i++) {
    partyUiHandler.processInput(Button.DOWN);
  }

  // Open options on Pokemon
  partyUiHandler.processInput(Button.ACTION);
  // Click "Select" on Pokemon options
  partyUiHandler.processInput(Button.ACTION);

  // If there is a second choice to make after selecting a Pokemon
  if (!isNullOrUndefined(optionNo)) {
    // Wait for Summary menu to close and second options to spawn
    const secondOptionUiHandler = game.scene.ui.handlers[Mode.OPTION_SELECT] as OptionSelectUiHandler;
    vi.spyOn(secondOptionUiHandler, "show");
    await vi.waitFor(() => expect(secondOptionUiHandler.show).toHaveBeenCalled());

    // Navigate down to the correct option
    for (let i = 1; i < optionNo!; i++) {
      secondOptionUiHandler.processInput(Button.DOWN);
    }

    // Select the option
    secondOptionUiHandler.processInput(Button.ACTION);
  }
}

/**
 * For any {@linkcode MysteryEncounter} that has a battle, can call this to skip battle and proceed to {@linkcode MysteryEncounterRewardsPhase}
 * @param game
 * @param runRewardsPhase
 */
export async function skipBattleRunMysteryEncounterRewardsPhase(game: GameManager, runRewardsPhase = true) {
  game.scene.clearPhaseQueue();
  game.scene.clearPhaseQueueSplice();
  game.scene.getEnemyParty().forEach(p => {
    p.hp = 0;
    p.status = new Status(StatusEffect.FAINT);
    game.scene.field.remove(p);
  });
  game.scene.pushPhase(new VictoryPhase(0));
  game.phaseInterceptor.superEndPhase();
  game.setMode(Mode.MESSAGE);
  await game.phaseInterceptor.to(MysteryEncounterRewardsPhase, runRewardsPhase);
}