import ExpandedSymbol from "../entities/ExpandedSymbol";
import BaseSystem from "./BaseSystem";
import AnimationProgressHook from "../utils/hooks/AnimationProgressHook";
import Hook from "../utils/hooks/Hook";
import BaseMachine from "../slotMachines/BaseMachine";

/** Configuration settings for symbols class. */
export interface WinningExpandedSymbolsAnimationSystemSettings
{
  entityClass: typeof ExpandedSymbol,
}

/**
 * @class WinningExpandedSymbolsAnimationSystem
 * @extends BaseSystem<WinningExpandedSymbolsAnimationSystemSettings>
 * @description Handles the winning behavior of ExpandedSymbol entities.
 */
export default class WinningExpandedSymbolsAnimationSystem extends BaseSystem<WinningExpandedSymbolsAnimationSystemSettings>
{
  static override requiredMaps: string[] = ['winningSymbols'];
  static override requiredUtils: string[] = ['grid'];

  static defaultSettings = {
    entityClass: ExpandedSymbol,
  };

  public state: 'checking' | 'animating' | 'stopped' = 'stopped';

  protected expandedSymbols: ExpandedSymbol[] = [];

  private hooks: Hook[] = [];

  /**
   * Initializes machine settings.
   * @class
   * @param {BaseMachine} machine - The slot machine instance.
   */
  constructor(machine: BaseMachine)
  {
    super(machine);
    this.setSettings(WinningExpandedSymbolsAnimationSystem.defaultSettings);
  }

  override onEnter()
  {
    this.state = 'checking';
    this.expandedSymbols = this.machine.children.filter(c => c instanceof this.settings.entityClass);
  }

  override onLeave()
  {
    // Do nothing
  }

  /**
   * Updates the animation system per frame.
   * - If in `"checking"` state, scans `winningSymbols` and sets up animation hooks.
   * - If in `"animating"` state, processes animation progress and checks for completion.
   * @param {number} dt - Delta time since the last update (in milliseconds).
   */
  override update(dt: number)
  {
    if (this.state === 'checking')
    {
      this.checkWinningSymbolsMap();
    }

    if (this.state === 'animating')
    {
      this.processAnimations(dt);
    }
  }

  /**
   * Scans the `winningSymbols` map to determine which symbols need animations.
   * - If no symbols have a winning flag, system transitions to `"stopped"`.
   * - Otherwise, creates `AnimationProgressHook` instances for each winning symbol.
   * - Transitions state to `"animating"` if animations are needed.
   * @private
   */
  private checkWinningSymbolsMap()
  {
    const winningSymbolsReelIndexes = this.machine.maps.winningSymbols.reduce((acc: number[], flag: boolean, index: number) =>
    {
      if (!flag) return acc;
      const reelIndex = this.machine.utils.grid.fromMapIndexToGridCoords(index).x;
      if (!acc.includes(reelIndex))
      {
        acc.push(reelIndex);
      }
      return acc;
    }, []);
    const expandedSymbolsToAnimate = this.expandedSymbols.filter(expandedSymbol => winningSymbolsReelIndexes.includes(expandedSymbol.reelIndex));
    if (!expandedSymbolsToAnimate.length)
    {
      this.state = 'stopped';
      return;
    }
    this.animateSymbols(expandedSymbolsToAnimate);
  }

  /**
   * Animates the winning expanded symbols.
   * - Sets the system state to `"animating"`.
   * - Creates `AnimationProgressHook` instances for each winning symbol.
   * @private
   * @param {ExpandedSymbol[]} expandedSymbols - The expanded symbols to animate.
   * @returns {void}
   */
  private animateSymbols(expandedSymbols: ExpandedSymbol[]): void
  {
    this.state = 'animating';
    this.hooks = expandedSymbols.map((expandedSymbol: ExpandedSymbol) => {
      return new AnimationProgressHook(() => expandedSymbol.win());
    });
  }

  /**
   * Processes active animation hooks.
   * - Calls `update(dt)` on each hook to progress animations.
   * - Filters out completed animations.
   * - Transitions system to `"stopped"` once all animations are finished.
   * @param {number} dt - Delta time since the last update (in milliseconds).
   * @private
   */
  private processAnimations(dt: number): void
  {
    this.hooks = this.hooks.filter((hook) =>
    {
      hook.update(dt);

      return !hook.isComplete();
    });

    if (this.hooks.length === 0)
    {
      this.state = 'stopped';
    }
  }
}
