// SPDX-FileCopyrightText: 2022 Johannes Loher
//
// SPDX-License-Identifier: MIT

import { packageId } from '../../constants';

export const registerCombatFunctionality = () => {
  CONFIG.Combat.documentClass = CombatMixin(CONFIG.Combat.documentClass);
};

/**
 * Enhance a combat class with functionality for the Tickwerk.
 * @param {typeof Combat} BaseCombat  The combat class to enhance
 * @returns A combat class, adapted for the Tickwerk
 */
const CombatMixin = (BaseCombat) => {
  return class TickwerkCombat extends BaseCombat {
    /** @override */
    get combatant() {
      return this.turns?.[0];
    }

    /** @override */
    get nextCombatant() {
      return this.turns?.[1];
    }

    /** @override */
    get started() {
      return (this.turns?.length ?? 0) > 0 && (this.getFlag(packageId, 'started') ?? false);
    }

    /**
     * The current tick value of the Combat encounter.
     * @type {number}
     */
    get tickValue() {
      const tickValues = this.combatants
        .filter((combatant) => !combatant.isDefeated && !combatant.waiting)
        .map((combatant) => combatant.initiative)
        .filter((tickValue) => tickValue !== null);
      const tickValue = Math.min(...tickValues);
      return tickValue === Infinity ? 0 : tickValue;
    }

    /** @override */
    prepareDerivedData() {
      super.prepareDerivedData();
      this.turn = this.started ? 0 : null;
    }

    /** @override */
    async nextRound() {
      throw new Error('Not implemented!');
    }

    /** @override */
    async nextTurn() {
      await this.combatant?.advanceTicksDialog();
      return this;
    }

    /** @override */
    previousRound() {
      throw new Error('Not implemented!');
    }

    /** @override */
    previousTurn() {
      throw new Error('Not implemented!');
    }

    /** @override */
    async resetAll() {
      for (const c of this.combatants) {
        c.updateSource({ initiative: null });
      }
      return this.update(
        {
          turn: 0,
          combatants: this.combatants.toObject().map((combatant) => {
            if (combatant.flags.tickwerk.tiebreaker) delete combatant.flags.tickwerk.tiebreaker;
            return { ...combatant, [`flags.${packageId}.-=tiebreaker`]: null };
          }),
          flags: { [packageId]: { started: false } },
        },
        { diff: false },
      );
    }

    /** @override */
    setupTurns() {
      const turns = this.combatants.contents.sort(this._sortCombatants);

      const c = turns[0];
      this.current = {
        round: this.round,
        turn: 0,
        combatantId: c?.id ?? null,
        tokenId: c?.tokenId ?? null,
      };
      return (this.turns = turns);
    }

    /** @override */
    async startCombat() {
      const hasCombatantWithTickValue = this.combatants.find(
        (combatant) => !combatant.isDefeated && combatant.initiative !== null,
      );
      if (!hasCombatantWithTickValue) {
        ui.notifications?.warn('TICKWERK.WarningCannotStartCombat', { localize: true });
        return this;
      }
      this._playCombatSound('startEncounter');
      const updateData = { flags: { [packageId]: { started: true } } };
      Hooks.callAll('combatStart', this, updateData);
      return this.update(updateData);
    }

    /** @override */
    _sortCombatants(a, b) {
      const da = a.isDefeated ? 1 : 0;
      const db = b.isDefeated ? 1 : 0;
      const cd = da - db;
      if (cd !== 0) return cd;

      const wa = a.waiting ? 1 : 0;
      const wb = b.waiting ? 1 : 0;
      const cw = wa - wb;
      if (cw !== 0) return cw;

      const ia = a.initiative ?? Infinity;
      const ib = b.initiative ?? Infinity;
      const ci = ia - ib;
      if (ci !== 0) return ci;

      const tba = a.getFlag(packageId, 'tiebreaker') ?? 0;
      const tbb = b.getFlag(packageId, 'tiebreaker') ?? 0;
      const ctb = tba - tbb;
      if (ctb !== 0) return ctb;

      return (b.id ?? '') > (a.id ?? '') ? 1 : -1;
    }

    /** @override */
    async _preUpdate(changed, options, user) {
      delete changed.round;
      delete changed.turn;
      options.tickwerk = {
        combatantBeforeUpdate: this.combatant?.id,
        nextCombatantBeforeUpdate: this.nextCombatant?.id,
      };
      return super._preUpdate(changed, options, user);
    }

    /** @override */
    _onUpdate(data, options, userId) {
      super._onUpdate(data, options, userId);
      if (game.user.character) {
        const { combatantBeforeUpdate, nextCombatantBeforeUpdate } = options.tickwerk ?? {};
        if (
          combatantBeforeUpdate !== undefined &&
          combatantBeforeUpdate !== this.combatant &&
          this.combatant?.actorId === game.user.character._id
        ) {
          this._playCombatSound('yourTurn');
        } else if (
          nextCombatantBeforeUpdate !== undefined &&
          nextCombatantBeforeUpdate !== this.nextCombatant &&
          this.nextCombatant?.actorId === game.user.character._id
        )
          this._playCombatSound('nextUp');
      }
    }
  };
};
