import { BaseMachine } from 'src/index';
import { CanPlayWin, Entity } from 'src/types/Entity';
import AnimationProgressHook from '../utils/hooks/AnimationProgressHook';
import Hook from '../utils/hooks/Hook';
import ImmediateHook from '../utils/hooks/ImmediateHook';
import BaseSystem from './BaseSystem';

/**
 * @description
 * Represents an entity that is compatible with the `WinningSymbolAnimationsSystem`.
 * This entity combines the base `Entity` properties with the ability to trigger a win animation (`CanPlayWin`).
 *
 * Used within `WinningSymbolAnimationsSystem` to identify symbols that should play win animations.
 */
export type WinningSymbolAnimationsSystemCompatibleEntity = Entity & CanPlayWin;

export interface WinningSymbolAnimationsSystemSettings
{
    zIndex: number;
}

/**
 * @class WinningSymbolAnimationsSystem
 * @description
 * System responsible for triggering win animations on symbols that are marked as winning.
 *
 * This system:
 * - Reads from the `winningSymbols` map to identify symbols that need animations.
 * - Uses animation hooks (`AnimationProgressHook`) to manage animation lifecycle.
 * - Transitions through the states: `"checking"` → `"animating"` → `"stopped"`.
 * - Stops execution once all animations are completed.
 * @extends BaseSystem
 * @property {"checking" | "animating" | "stopped"} state - Current system state.
 * @property {Hook[]} hooks - List of hooks managing animation progress.
 */
export default class WinningSymbolAnimationsSystem extends BaseSystem<WinningSymbolAnimationsSystemSettings>
{
    /**
     * System state transitions:
     * - "checking": Scans `winningSymbols` map to determine which symbols need animations.
     * - "animating": Executes animations using hooks.
     * - "stopped": Ends the animation process once all animations are complete.
     */
    public state: 'checking' | 'animating' | 'stopped' = 'stopped';

    /** Stores hooks for tracking animation progress */
    private hooks: Hook[] = [];

    /** Required maps used in this system */
    static override requiredMaps: string[] = ['tinting', 'winningSymbols', 'symbols'];

    static defaultSettings = {
        zIndex: 10,
    };

    constructor(machine: BaseMachine)
    {
        super(machine);

        this.setSettings(WinningSymbolAnimationsSystem.defaultSettings);
    }

    /** Called when the system becomes active. Transitions state to `"checking"` and starts scanning for winning symbols. */
    override onEnter()
    {
        this.state = 'checking';
    }

    /** Called when the system exits. Currently, does nothing but can be extended in the future. */
    override onLeave()
    {
        this.machine.maps.symbols.forEach((symbolID: string) =>
        {
            const entity: WinningSymbolAnimationsSystemCompatibleEntity = this.machine.getChildByName(symbolID);

            entity.reset();
        });
    }

    /**
     * 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()
    {
        if (this.machine.maps.winningSymbols.every((flag: boolean) => flag === false))
        {
            this.state = 'stopped';

            return;
        }

        this.animateSymbols();
    }

    private animateSymbols()
    {
        this.state = 'animating';
        this.hooks = this.machine.maps.winningSymbols.map((flag: boolean, i: number) =>
        {
            if (!flag) return new ImmediateHook();
            const entityId = this.machine.maps.symbols[i];

            if (!entityId) return new ImmediateHook();
            const entity: WinningSymbolAnimationsSystemCompatibleEntity = this.machine.getChildByName(entityId);

            entity.zIndex += this.settings.zIndex;

            return new AnimationProgressHook(() => entity.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.resetZIndex();
            this.state = 'stopped';
        }
    }

    resetZIndex()
    {
        this.machine.maps.winningSymbols.map((flag: boolean, i: number) =>
        {
            if (!flag) return;
            const entityId = this.machine.maps.symbols[i];

            if (!entityId) return;
            const entity = this.machine.getChildByName(entityId);

            entity.zIndex -= this.settings.zIndex;
        });
    }
}
