{
  name: "Complete Hypnosis",
  rating: 5,
  flags: { failroleplay: 1, noreceiver: 1, noentrain: 1, notrace: 1, failskillswap: 1 },
  cloneMoveSlot(slot) {
    return {
      move: slot.move,
      id: slot.id,
      pp: slot.pp,
      maxpp: slot.maxpp,
      target: slot.target,
      disabled: false,
      disabledSource: "",
      used: false
    };
  },
  pickOpponentIllusion(pokemon) {
    if (!pokemon?.side?.foe) return null;
    const foes = pokemon.side.foe.pokemon.filter(foe => foe && !foe.fainted && foe.hp > 0);
    if (!foes.length) return null;
    return this.sample(foes);
  },
  storeAizenMoves(pokemon) {
    if (pokemon.m.aizenStoredMoves) return;
    const trueMoves = {
      moveSlots: pokemon.moveSlots.map(slot => this.dex.abilities.get("completehypnosis").cloneMoveSlot.call(this, slot)),
      baseMoveSlots: pokemon.baseMoveSlots.map(slot => this.dex.abilities.get("completehypnosis").cloneMoveSlot.call(this, slot)),
      setMoves: pokemon.set?.moves ? pokemon.set.moves.slice() : null,
      setMovesInfo: pokemon.set?.movesInfo ? pokemon.set.movesInfo.map(info => Object.assign({}, info)) : null
    };
    pokemon.m.aizenStoredMoves = trueMoves;
    if (!pokemon.m.aizenTrueMoves) pokemon.m.aizenTrueMoves = trueMoves;
  },
  applyCopiedMoves(pokemon, copied) {
    if (!pokemon || !copied || !copied.moveSlots?.length) return;
    this.dex.abilities.get("completehypnosis").storeAizenMoves.call(this, pokemon);
    const copiedSlots = copied.moveSlots.map(slot => this.dex.abilities.get("completehypnosis").cloneMoveSlot.call(this, slot));
    pokemon.moveSlots = copiedSlots.map(slot => this.dex.abilities.get("completehypnosis").cloneMoveSlot.call(this, slot));
    pokemon.baseMoveSlots = copiedSlots.map(slot => this.dex.abilities.get("completehypnosis").cloneMoveSlot.call(this, slot));
    if (pokemon.set) {
      pokemon.set.moves = copiedSlots.map(slot => slot.id);
      pokemon.set.movesInfo = copiedSlots.map(slot => ({pp: slot.pp, maxPp: slot.maxpp}));
    }
    pokemon.m.aizenCopiedMoveIds = copiedSlots.map(slot => slot.id);
  },
  restoreAizenMoves(pokemon) {
    const stored = pokemon?.m?.aizenTrueMoves || pokemon?.m?.aizenStoredMoves;
    if (!stored) return;
    pokemon.moveSlots = stored.moveSlots.map(slot => this.dex.abilities.get("completehypnosis").cloneMoveSlot.call(this, slot));
    pokemon.baseMoveSlots = stored.baseMoveSlots.map(slot => this.dex.abilities.get("completehypnosis").cloneMoveSlot.call(this, slot));
    if (pokemon.set) {
      if (stored.setMoves) pokemon.set.moves = stored.setMoves.slice();
      if (stored.setMovesInfo) pokemon.set.movesInfo = stored.setMovesInfo.map(info => Object.assign({}, info));
    }
    delete pokemon.m.aizenStoredMoves;
    delete pokemon.m.aizenCopiedMoveIds;
  },
  storeAizenForm(pokemon) {
    if (pokemon.m.aizenStoredForm) return;
    pokemon.m.aizenStoredForm = {
      types: pokemon.types ? pokemon.types.slice() : null,
      baseTypes: pokemon.baseTypes ? pokemon.baseTypes.slice() : null,
      addedType: pokemon.addedType || "",
      knownType: pokemon.knownType,
      apparentType: pokemon.apparentType,
      storedStats: Object.assign({}, pokemon.storedStats),
      baseStoredStats: pokemon.baseStoredStats ? Object.assign({}, pokemon.baseStoredStats) : null,
      modifiedStats: pokemon.modifiedStats ? Object.assign({}, pokemon.modifiedStats) : null,
      boosts: pokemon.boosts ? Object.assign({}, pokemon.boosts) : null,
      speed: pokemon.speed,
      weighthg: pokemon.weighthg
    };
  },
  applyCopiedForm(pokemon, copied) {
    if (!pokemon || !copied) return;
    const ability = this.dex.abilities.get("completehypnosis");
    ability.storeAizenForm.call(this, pokemon);
    const copiedTypes = copied.getTypes ? copied.getTypes(true, true) : copied.types;
    if (copiedTypes && copiedTypes.length) pokemon.setType(copied.volatiles?.roost ? copied.volatiles.roost.typeWas : copiedTypes, true);
    pokemon.baseTypes = copied.baseTypes ? copied.baseTypes.slice() : (copied.types ? copied.types.slice() : pokemon.types.slice());
    pokemon.addedType = copied.addedType || "";
    pokemon.knownType = true;
    pokemon.apparentType = copied.apparentType || pokemon.types.join("/");
    if (copied.storedStats) {
      for (const statName in pokemon.storedStats) {
        if (typeof copied.storedStats[statName] === "number") pokemon.storedStats[statName] = copied.storedStats[statName];
        if (pokemon.modifiedStats && copied.modifiedStats && typeof copied.modifiedStats[statName] === "number") pokemon.modifiedStats[statName] = copied.modifiedStats[statName];
      }
    }
    if (pokemon.baseStoredStats && copied.baseStoredStats) {
      for (const statName in pokemon.baseStoredStats) {
        if (statName === "hp") continue;
        if (typeof copied.baseStoredStats[statName] === "number") pokemon.baseStoredStats[statName] = copied.baseStoredStats[statName];
      }
    }
    if (pokemon.boosts && copied.boosts) pokemon.boosts = Object.assign({}, copied.boosts);
    pokemon.speed = pokemon.storedStats.spe;
    pokemon.weighthg = copied.weighthg || pokemon.weighthg;
    pokemon.m.aizenCopiedTypes = pokemon.types ? pokemon.types.slice() : null;
    pokemon.m.aizenCopiedStats = Object.assign({}, pokemon.storedStats);
  },
  restoreAizenForm(pokemon) {
    const stored = pokemon?.m?.aizenStoredForm;
    if (!stored) return;
    if (stored.types && stored.types.length) pokemon.setType(stored.types, true);
    pokemon.baseTypes = stored.baseTypes ? stored.baseTypes.slice() : (stored.types ? stored.types.slice() : pokemon.baseTypes);
    pokemon.addedType = stored.addedType || "";
    pokemon.knownType = stored.knownType;
    pokemon.apparentType = stored.apparentType;
    if (stored.storedStats) {
      for (const statName in stored.storedStats) pokemon.storedStats[statName] = stored.storedStats[statName];
    }
    if (stored.baseStoredStats && pokemon.baseStoredStats) {
      for (const statName in stored.baseStoredStats) pokemon.baseStoredStats[statName] = stored.baseStoredStats[statName];
    }
    if (stored.modifiedStats && pokemon.modifiedStats) {
      for (const statName in stored.modifiedStats) pokemon.modifiedStats[statName] = stored.modifiedStats[statName];
    }
    if (stored.boosts && pokemon.boosts) pokemon.boosts = Object.assign({}, stored.boosts);
    pokemon.speed = stored.speed;
    pokemon.weighthg = stored.weighthg;
    delete pokemon.m.aizenStoredForm;
    delete pokemon.m.aizenCopiedTypes;
    delete pokemon.m.aizenCopiedStats;
  },
  confuseSeenFoes(pokemon) {
    if (!pokemon?.side?.foe) return;
    for (const foe of pokemon.foes()) {
      if (!foe || foe.fainted || foe.hp <= 0) continue;
      foe.addVolatile("kyokasuigetsu", pokemon);
      foe.addVolatile("confusion", pokemon);
    }
  },
  markIllusionBreakMove(pokemon, effect) {
    if (!pokemon || !effect || effect.effectType !== "Move") return;
    pokemon.m.aizenIllusionJustBroke = true;
    pokemon.m.aizenIllusionBreakMove = effect.id || "";
    pokemon.m.aizenIllusionBreakTurn = this.turn;
  },
  clearIllusionBreakMove(pokemon) {
    if (!pokemon?.m) return;
    delete pokemon.m.aizenIllusionJustBroke;
    delete pokemon.m.aizenIllusionBreakMove;
    delete pokemon.m.aizenIllusionBreakTurn;
  },
  ensureOpponentIllusion(pokemon) {
    if (!pokemon || pokemon.m?.aizenIllusionSpent) return;
    if (pokemon.illusion && pokemon.m?.aizenCopiedTarget) return;
    const ability = this.dex.abilities.get("completehypnosis");
    const copied = ability.pickOpponentIllusion.call(this, pokemon);
    if (!copied) return;
    pokemon.illusion = copied;
    pokemon.m.aizenCopiedTarget = copied;
    pokemon.m.aizenIllusionActive = true;
    ability.applyCopiedMoves.call(this, pokemon, copied);
    ability.applyCopiedForm.call(this, pokemon, copied);
  },
  breakOpponentIllusion(pokemon, source, effect) {
    if (!pokemon?.m?.aizenIllusionActive || pokemon.m.aizenIllusionSpent) return false;
    const ability = this.dex.abilities.get("completehypnosis");
    ability.markIllusionBreakMove.call(this, pokemon, effect);
    pokemon.m.aizenIllusionActive = false;
    pokemon.m.aizenIllusionSpent = true;
    pokemon.m.aizenBaseFormRevealed = true;
    const copied = pokemon.m.aizenCopiedTarget;
    pokemon.illusion = null;
    ability.restoreAizenMoves.call(this, pokemon);
    ability.restoreAizenForm.call(this, pokemon);
    const details = pokemon.species.name + (pokemon.level === 100 ? "" : ", L" + pokemon.level) + (pokemon.gender === "" ? "" : ", " + pokemon.gender) + (pokemon.set.shiny ? ", shiny" : "");
    this.add("replace", pokemon, details);
    this.add("-end", pokemon, "Complete Hypnosis");
    pokemon.hp = pokemon.maxhp;
    this.add("-heal", pokemon, pokemon.getHealth, "[from] ability: Complete Hypnosis");
    ability.confuseSeenFoes.call(this, pokemon);
    if (copied && copied.hp > 0 && !copied.fainted && !copied.faintQueued) {
      this.add("-activate", pokemon, "ability: Complete Hypnosis");
      copied.faint(pokemon, ability);
    }
    ability.restoreAizenMoves.call(this, pokemon);
    ability.restoreAizenForm.call(this, pokemon);
    return true;
  },
  onBeforeSwitchIn(pokemon) {
    const ability = this.dex.abilities.get("completehypnosis");
    ability.ensureOpponentIllusion.call(this, pokemon);
  },
  onStart(pokemon) {
    const ability = this.dex.abilities.get("completehypnosis");
    ability.ensureOpponentIllusion.call(this, pokemon);
    this.add("-activate", pokemon, "ability: Complete Hypnosis");
    for (const foe of pokemon.foes()) {
      if (foe && !foe.fainted) foe.addVolatile("kyokasuigetsu", pokemon);
    }
  },
  onSwitchOut(pokemon) {
    const ability = this.dex.abilities.get("completehypnosis");
    if (pokemon.m?.aizenIllusionActive && !pokemon.m?.aizenIllusionSpent) {
      pokemon.illusion = null;
      pokemon.m.aizenIllusionActive = false;
      ability.restoreAizenMoves.call(this, pokemon);
      ability.restoreAizenForm.call(this, pokemon);
    }
    ability.clearIllusionBreakMove.call(this, pokemon);
  },
  onFoeSwitchIn(target, source) {
    const aizen = this.effectState.target;
    if (aizen && aizen.hp && !aizen.fainted && target && !target.fainted) {
      target.addVolatile("kyokasuigetsu", aizen);
      if (aizen.m?.aizenBaseFormRevealed) target.addVolatile("confusion", aizen);
    }
  },
  onDamagePriority: 1,
  onDamage(damage, target, source, effect) {
    if (!target || !target.hasAbility("completehypnosis")) return;
    if (effect?.effectType !== "Move") return;
    if (typeof damage !== "number" || damage < target.hp) return;
    if (target.m?.aizenIllusionActive && !target.m?.aizenIllusionSpent) {
      const ability = this.dex.abilities.get("completehypnosis");
      ability.breakOpponentIllusion.call(this, target, source, effect);
      return 0;
    }
  },
  onFaint(pokemon) {
    const ability = this.dex.abilities.get("completehypnosis");
    pokemon.illusion = null;
    ability.restoreAizenMoves.call(this, pokemon);
    ability.restoreAizenForm.call(this, pokemon);
    ability.clearIllusionBreakMove.call(this, pokemon);
  },
  onModifySecondaries(secondaries) {
    const pokemon = this.effectState.target;
    if (!pokemon?.m?.aizenIllusionJustBroke) return;
    if (pokemon.m.aizenIllusionBreakTurn !== this.turn) {
      const ability = this.dex.abilities.get("completehypnosis");
      ability.clearIllusionBreakMove.call(this, pokemon);
      return;
    }
    this.debug("Complete Hypnosis consumed secondary effects from the reveal-breaking move");
    return [];
  },
  onAfterMoveSecondary(target, source, move) {
    const pokemon = this.effectState.target;
    if (!pokemon?.m?.aizenIllusionJustBroke) return;
    if (pokemon.m.aizenIllusionBreakTurn === this.turn) {
      const ability = this.dex.abilities.get("completehypnosis");
      ability.clearIllusionBreakMove.call(this, pokemon);
    }
  },
  onAnyModifyAccuracyPriority: -1,
  onAnyModifyAccuracy(accuracy, target, source, move) {
    if (typeof accuracy !== "number") return;
    if (!target || !source || !move) return;
    if (target.hasAbility?.("completehypnosis") && source.volatiles?.kyokasuigetsu && move.category !== "Status") {
      return this.chainModify([2731, 4096]);
    }
  },
  onModifyMove(move, pokemon, target) {
    if (target?.volatiles?.kyokasuigetsu) {
      move.ignoreEvasion = true;
      move.ignoreAccuracy = true;
    }
  },
  onTryBoost(boost, target, source, effect) {
    if (boost.accuracy && boost.accuracy < 0) {
      delete boost.accuracy;
      this.add("-fail", target, "unboost", "accuracy", "[from] ability: Complete Hypnosis");
    }
  },
  onTryAddVolatile(status, pokemon) {
    if (status.id === "confusion") return null;
  },
  desc: "On entry, randomly disguises as one living Pokemon from the opposing team and uses that Pokemon's moves, typing, non-HP battle stats, and stat stages until the illusion breaks. If the disguise would be KOed, Aizen returns at full HP, destroys the copied opponent if alive, and confuses foes that see his true form. Hypnotized foes are less accurate against Aizen, while Aizen ignores their accuracy and evasion."
}
