import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import {
  generateModifierType,
  initBattleWithEnemyConfig,
  leaveEncounterWithoutBattle,
  setEncounterRewards,
  transitionMysteryEncounterIntroVisuals,
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import type Pokemon from "#app/field/pokemon";
import { EnemyPokemon, PokemonMove } from "#app/field/pokemon";
import type { BerryModifierType, PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
import { modifierTypes } from "#app/modifier/modifier-type";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { Species } from "#enums/species";
import { globalScene } from "#app/global-scene";
import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import { PersistentModifierRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements";
import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { BerryModifier, PokemonInstantReviveModifier } from "#app/modifier/modifier";
import { getPokemonSpecies } from "#app/data/pokemon-species";
import { Moves } from "#enums/moves";
import { BattlerTagType } from "#enums/battler-tag-type";
import { randInt } from "#app/utils";
import { BattlerIndex } from "#app/battle";
import {
  applyModifierTypeToPlayerPokemon,
  catchPokemon,
  getHighestLevelPlayerPokemon,
} from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import { TrainerSlot } from "#app/data/trainer-config";
import { PokeballType } from "#enums/pokeball";
import type HeldModifierConfig from "#app/interfaces/held-modifier-config";
import type { BerryType } from "#enums/berry-type";
import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase";
import { Stat } from "#enums/stat";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
import i18next from "i18next";

/** the i18n namespace for this encounter */
const namespace = "mysteryEncounters/absoluteAvarice";

/**
 * Absolute Avarice encounter.
 * @see {@link https://github.com/pagefaultgames/pokerogue/issues/3805 | GitHub Issue #3805}
 * @see For biome requirements check {@linkcode mysteryEncountersByBiome}
 */
export const AbsoluteAvariceEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(
  MysteryEncounterType.ABSOLUTE_AVARICE,
)
  .withEncounterTier(MysteryEncounterTier.GREAT)
  .withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
  .withSceneRequirement(new PersistentModifierRequirement("BerryModifier", 4)) // Must have at least 4 berries to spawn
  .withFleeAllowed(false)
  .withIntroSpriteConfigs([
    {
      // This sprite has the shadow
      spriteKey: "",
      fileRoot: "",
      species: Species.GREEDENT,
      hasShadow: true,
      alpha: 0.001,
      repeat: true,
      x: -5,
    },
    {
      spriteKey: "",
      fileRoot: "",
      species: Species.GREEDENT,
      hasShadow: false,
      repeat: true,
      x: -5,
    },
    {
      spriteKey: "lum_berry",
      fileRoot: "items",
      isItem: true,
      x: 7,
      y: -14,
      hidden: true,
      disableAnimation: true,
    },
    {
      spriteKey: "salac_berry",
      fileRoot: "items",
      isItem: true,
      x: 2,
      y: 4,
      hidden: true,
      disableAnimation: true,
    },
    {
      spriteKey: "lansat_berry",
      fileRoot: "items",
      isItem: true,
      x: 32,
      y: 5,
      hidden: true,
      disableAnimation: true,
    },
    {
      spriteKey: "liechi_berry",
      fileRoot: "items",
      isItem: true,
      x: 6,
      y: -5,
      hidden: true,
      disableAnimation: true,
    },
    {
      spriteKey: "sitrus_berry",
      fileRoot: "items",
      isItem: true,
      x: 7,
      y: 8,
      hidden: true,
      disableAnimation: true,
    },
    {
      spriteKey: "enigma_berry",
      fileRoot: "items",
      isItem: true,
      x: 26,
      y: -4,
      hidden: true,
      disableAnimation: true,
    },
    {
      spriteKey: "leppa_berry",
      fileRoot: "items",
      isItem: true,
      x: 16,
      y: -27,
      hidden: true,
      disableAnimation: true,
    },
    {
      spriteKey: "petaya_berry",
      fileRoot: "items",
      isItem: true,
      x: 30,
      y: -17,
      hidden: true,
      disableAnimation: true,
    },
    {
      spriteKey: "ganlon_berry",
      fileRoot: "items",
      isItem: true,
      x: 16,
      y: -11,
      hidden: true,
      disableAnimation: true,
    },
    {
      spriteKey: "apicot_berry",
      fileRoot: "items",
      isItem: true,
      x: 14,
      y: -2,
      hidden: true,
      disableAnimation: true,
    },
    {
      spriteKey: "starf_berry",
      fileRoot: "items",
      isItem: true,
      x: 18,
      y: 9,
      hidden: true,
      disableAnimation: true,
    },
  ])
  .withHideWildIntroMessage(true)
  .withAutoHideIntroVisuals(false)
  .withIntroDialogue([
    {
      text: `${namespace}:intro`,
    },
  ])
  .setLocalizationKey(`${namespace}`)
  .withTitle(`${namespace}:title`)
  .withDescription(`${namespace}:description`)
  .withQuery(`${namespace}:query`)
  .withOnInit(() => {
    const encounter = globalScene.currentBattle.mysteryEncounter!;

    globalScene.loadSe("PRSFX- Bug Bite", "battle_anims", "PRSFX- Bug Bite.wav");
    globalScene.loadSe("Follow Me", "battle_anims", "Follow Me.mp3");

    // Get all player berry items, remove from party, and store reference
    const berryItems = globalScene.findModifiers(m => m instanceof BerryModifier) as BerryModifier[];

    // Sort berries by party member ID to more easily re-add later if necessary
    const berryItemsMap = new Map<number, BerryModifier[]>();
    globalScene.getPlayerParty().forEach(pokemon => {
      const pokemonBerries = berryItems.filter(b => b.pokemonId === pokemon.id);
      if (pokemonBerries?.length > 0) {
        berryItemsMap.set(pokemon.id, pokemonBerries);
      }
    });

    encounter.misc = { berryItemsMap };

    // Generates copies of the stolen berries to put on the Greedent
    const bossModifierConfigs: HeldModifierConfig[] = [];
    berryItems.forEach(berryMod => {
      // Can't define stack count on a ModifierType, have to just create separate instances for each stack
      // Overflow berries will be "lost" on the boss, but it's un-catchable anyway
      for (let i = 0; i < berryMod.stackCount; i++) {
        const modifierType = generateModifierType(modifierTypes.BERRY, [
          berryMod.berryType,
        ]) as PokemonHeldItemModifierType;
        bossModifierConfigs.push({ modifier: modifierType });
      }
    });

    // Do NOT remove the real berries yet or else it will be persisted in the session data

    // SpDef buff below wave 50, +1 to all stats otherwise
    const statChangesForBattle: (Stat.ATK | Stat.DEF | Stat.SPATK | Stat.SPDEF | Stat.SPD | Stat.ACC | Stat.EVA)[] =
      globalScene.currentBattle.waveIndex < 50 ? [Stat.SPDEF] : [Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD];

    // Calculate boss mon
    const config: EnemyPartyConfig = {
      levelAdditiveModifier: 1,
      pokemonConfigs: [
        {
          species: getPokemonSpecies(Species.GREEDENT),
          isBoss: true,
          bossSegments: 3,
          shiny: false, // Shiny lock because of consistency issues between the different options
          moveSet: [Moves.THRASH, Moves.BODY_PRESS, Moves.STUFF_CHEEKS, Moves.CRUNCH],
          modifierConfigs: bossModifierConfigs,
          tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON],
          mysteryEncounterBattleEffects: (pokemon: Pokemon) => {
            queueEncounterMessage(`${namespace}:option.1.boss_enraged`);
            globalScene.unshiftPhase(
              new StatStageChangePhase(pokemon.getBattlerIndex(), true, statChangesForBattle, 1),
            );
          },
        },
      ],
    };

    encounter.enemyPartyConfigs = [config];
    encounter.setDialogueToken("greedentName", getPokemonSpecies(Species.GREEDENT).getName());

    return true;
  })
  .withOnVisualsStart(() => {
    doGreedentSpriteSteal();
    doBerrySpritePile();

    // Remove the berries from the party
    // Session has been safely saved at this point, so data won't be lost
    const berryItems = globalScene.findModifiers(m => m instanceof BerryModifier) as BerryModifier[];
    berryItems.forEach(berryMod => {
      globalScene.removeModifier(berryMod);
    });

    globalScene.updateModifiers(true);

    return true;
  })
  .withOption(
    MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
      .withDialogue({
        buttonLabel: `${namespace}:option.1.label`,
        buttonTooltip: `${namespace}:option.1.tooltip`,
        selected: [
          {
            text: `${namespace}:option.1.selected`,
          },
        ],
      })
      .withOptionPhase(async () => {
        // Pick battle
        const encounter = globalScene.currentBattle.mysteryEncounter!;

        // Provides 1x Reviver Seed to each party member at end of battle
        const revSeed = generateModifierType(modifierTypes.REVIVER_SEED);
        encounter.setDialogueToken(
          "foodReward",
          revSeed?.name ?? i18next.t("modifierType:ModifierType.REVIVER_SEED.name"),
        );
        const givePartyPokemonReviverSeeds = () => {
          const party = globalScene.getPlayerParty();
          party.forEach(p => {
            const heldItems = p.getHeldItems();
            if (revSeed && !heldItems.some(item => item instanceof PokemonInstantReviveModifier)) {
              const seedModifier = revSeed.newModifier(p);
              globalScene.addModifier(seedModifier, false, false, false, true);
            }
          });
          queueEncounterMessage(`${namespace}:option.1.food_stash`);
        };

        setEncounterRewards({ fillRemaining: true }, undefined, givePartyPokemonReviverSeeds);
        encounter.startOfBattleEffects.push({
          sourceBattlerIndex: BattlerIndex.ENEMY,
          targets: [BattlerIndex.ENEMY],
          move: new PokemonMove(Moves.STUFF_CHEEKS),
          ignorePp: true,
        });

        await transitionMysteryEncounterIntroVisuals(true, true, 500);
        await initBattleWithEnemyConfig(encounter.enemyPartyConfigs[0]);
      })
      .build(),
  )
  .withOption(
    MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
      .withDialogue({
        buttonLabel: `${namespace}:option.2.label`,
        buttonTooltip: `${namespace}:option.2.tooltip`,
        selected: [
          {
            text: `${namespace}:option.2.selected`,
          },
        ],
      })
      .withOptionPhase(async () => {
        const encounter = globalScene.currentBattle.mysteryEncounter!;
        const berryMap = encounter.misc.berryItemsMap;

        // Returns 2/5 of the berries stolen to each Pokemon
        const party = globalScene.getPlayerParty();
        party.forEach(pokemon => {
          const stolenBerries: BerryModifier[] = berryMap.get(pokemon.id);
          const berryTypesAsArray: BerryType[] = [];
          stolenBerries?.forEach(bMod => berryTypesAsArray.push(...new Array(bMod.stackCount).fill(bMod.berryType)));
          const returnedBerryCount = Math.floor(((berryTypesAsArray.length ?? 0) * 2) / 5);

          if (returnedBerryCount > 0) {
            for (let i = 0; i < returnedBerryCount; i++) {
              // Shuffle remaining berry types and pop
              Phaser.Math.RND.shuffle(berryTypesAsArray);
              const randBerryType = berryTypesAsArray.pop();

              const berryModType = generateModifierType(modifierTypes.BERRY, [randBerryType]) as BerryModifierType;
              applyModifierTypeToPlayerPokemon(pokemon, berryModType);
            }
          }
        });
        await globalScene.updateModifiers(true);

        await transitionMysteryEncounterIntroVisuals(true, true, 500);
        leaveEncounterWithoutBattle(true);
      })
      .build(),
  )
  .withOption(
    MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
      .withDialogue({
        buttonLabel: `${namespace}:option.3.label`,
        buttonTooltip: `${namespace}:option.3.tooltip`,
        selected: [
          {
            text: `${namespace}:option.3.selected`,
          },
        ],
      })
      .withPreOptionPhase(async () => {
        // Animate berries being eaten
        doGreedentEatBerries();
        doBerrySpritePile(true);
        return true;
      })
      .withOptionPhase(async () => {
        // Let it have the food
        // Greedent joins the team, level equal to 2 below highest party member (shiny locked)
        const level = getHighestLevelPlayerPokemon(false, true).level - 2;
        const greedent = new EnemyPokemon(getPokemonSpecies(Species.GREEDENT), level, TrainerSlot.NONE, false, true);
        greedent.moveset = [
          new PokemonMove(Moves.THRASH),
          new PokemonMove(Moves.BODY_PRESS),
          new PokemonMove(Moves.STUFF_CHEEKS),
          new PokemonMove(Moves.SLACK_OFF),
        ];
        greedent.passive = true;

        await transitionMysteryEncounterIntroVisuals(true, true, 500);
        await catchPokemon(greedent, null, PokeballType.POKEBALL, false);
        leaveEncounterWithoutBattle(true);
      })
      .build(),
  )
  .build();

function doGreedentSpriteSteal() {
  const shakeDelay = 50;
  const slideDelay = 500;

  const greedentSprites = globalScene.currentBattle.mysteryEncounter!.introVisuals?.getSpriteAtIndex(1);

  globalScene.playSound("battle_anims/Follow Me");
  globalScene.tweens.chain({
    targets: greedentSprites,
    tweens: [
      {
        // Slide Greedent diagonally
        duration: slideDelay,
        ease: "Cubic.easeOut",
        y: "+=75",
        x: "-=65",
        scale: 1.1,
      },
      {
        // Shake
        duration: shakeDelay,
        ease: "Cubic.easeOut",
        yoyo: true,
        x: (randInt(2) > 0 ? "-=" : "+=") + 5,
        y: (randInt(2) > 0 ? "-=" : "+=") + 5,
      },
      {
        // Shake
        duration: shakeDelay,
        ease: "Cubic.easeOut",
        yoyo: true,
        x: (randInt(2) > 0 ? "-=" : "+=") + 5,
        y: (randInt(2) > 0 ? "-=" : "+=") + 5,
      },
      {
        // Shake
        duration: shakeDelay,
        ease: "Cubic.easeOut",
        yoyo: true,
        x: (randInt(2) > 0 ? "-=" : "+=") + 5,
        y: (randInt(2) > 0 ? "-=" : "+=") + 5,
      },
      {
        // Shake
        duration: shakeDelay,
        ease: "Cubic.easeOut",
        yoyo: true,
        x: (randInt(2) > 0 ? "-=" : "+=") + 5,
        y: (randInt(2) > 0 ? "-=" : "+=") + 5,
      },
      {
        // Shake
        duration: shakeDelay,
        ease: "Cubic.easeOut",
        yoyo: true,
        x: (randInt(2) > 0 ? "-=" : "+=") + 5,
        y: (randInt(2) > 0 ? "-=" : "+=") + 5,
      },
      {
        // Shake
        duration: shakeDelay,
        ease: "Cubic.easeOut",
        yoyo: true,
        x: (randInt(2) > 0 ? "-=" : "+=") + 5,
        y: (randInt(2) > 0 ? "-=" : "+=") + 5,
      },
      {
        // Slide Greedent diagonally
        duration: slideDelay,
        ease: "Cubic.easeOut",
        y: "-=75",
        x: "+=65",
        scale: 1,
      },
      {
        // Bounce at the end
        duration: 300,
        ease: "Cubic.easeOut",
        yoyo: true,
        y: "-=20",
        loop: 1,
      },
    ],
  });
}

function doGreedentEatBerries() {
  const greedentSprites = globalScene.currentBattle.mysteryEncounter!.introVisuals?.getSpriteAtIndex(1);
  let index = 1;
  globalScene.tweens.add({
    targets: greedentSprites,
    duration: 150,
    ease: "Cubic.easeOut",
    yoyo: true,
    y: "-=8",
    loop: 5,
    onStart: () => {
      globalScene.playSound("battle_anims/PRSFX- Bug Bite");
    },
    onLoop: () => {
      if (index % 2 === 0) {
        globalScene.playSound("battle_anims/PRSFX- Bug Bite");
      }
      index++;
    },
  });
}

/**
 * @param isEat Default false. Will "create" pile when false, and remove pile when true.
 */
function doBerrySpritePile(isEat = false) {
  const berryAddDelay = 150;
  let animationOrder = [
    "starf",
    "sitrus",
    "lansat",
    "salac",
    "apicot",
    "enigma",
    "liechi",
    "ganlon",
    "lum",
    "petaya",
    "leppa",
  ];
  if (isEat) {
    animationOrder = animationOrder.reverse();
  }
  const encounter = globalScene.currentBattle.mysteryEncounter!;
  animationOrder.forEach((berry, i) => {
    const introVisualsIndex = encounter.spriteConfigs.findIndex(config => config.spriteKey?.includes(berry));
    let sprite: Phaser.GameObjects.Sprite, tintSprite: Phaser.GameObjects.Sprite;
    const sprites = encounter.introVisuals?.getSpriteAtIndex(introVisualsIndex);
    if (sprites) {
      sprite = sprites[0];
      tintSprite = sprites[1];
    }
    globalScene.time.delayedCall(berryAddDelay * i + 400, () => {
      if (sprite) {
        sprite.setVisible(!isEat);
      }
      if (tintSprite) {
        tintSprite.setVisible(!isEat);
      }

      // Animate Petaya berry falling off the pile
      if (berry === "petaya" && sprite && tintSprite && !isEat) {
        globalScene.time.delayedCall(200, () => {
          doBerryBounce([sprite, tintSprite], 30, 500);
        });
      }
    });
  });
}

function doBerryBounce(berrySprites: Phaser.GameObjects.Sprite[], yd: number, baseBounceDuration: number) {
  let bouncePower = 1;
  let bounceYOffset = yd;

  const doBounce = () => {
    globalScene.tweens.add({
      targets: berrySprites,
      y: "+=" + bounceYOffset,
      x: { value: "+=" + bouncePower * bouncePower * 10, ease: "Linear" },
      duration: bouncePower * baseBounceDuration,
      ease: "Cubic.easeIn",
      onComplete: () => {
        bouncePower = bouncePower > 0.01 ? bouncePower * 0.5 : 0;

        if (bouncePower) {
          bounceYOffset = bounceYOffset * bouncePower;

          globalScene.tweens.add({
            targets: berrySprites,
            y: "-=" + bounceYOffset,
            x: { value: "+=" + bouncePower * bouncePower * 10, ease: "Linear" },
            duration: bouncePower * baseBounceDuration,
            ease: "Cubic.easeOut",
            onComplete: () => doBounce(),
          });
        }
      },
    });
  };

  doBounce();
}