import { Container, DisplayObject } from 'pixi.js';
import Events from '../events/Events';
import SpeedModes from '../speedModes/SpeedModes';
import { Updatable } from '../types/Entity';
import { SlotMachineConfig } from '../types/SlotMachineConfig';
import GridUtils from '../utils/GridUtils';

/**
 * @interface MachineInputs
 * @description Interface for machine inputs.
 */
interface MachineInputs
{
    [key: string]: any;
}

/**
 * @interface MachineFlags
 * @description Interface for machine flags.
 */
interface MachineFlags
{
    [key: string]: boolean;
}

/**
 * @interface MachineMaps
 * @description Interface for machine maps.
 */
interface MachineMaps
{
    [key: string]: any;
}

/**
 * @interface MachineUtils
 * @description Interface for machine utility objects.
 */
interface MachineUtils
{
    [key: string]: any;
}

/**
 * @class BaseMachine
 * @extends Container
 * @description A base class for creating a stateful machine with various customizable properties and systems.
 */
export default abstract class BaseMachine extends Container
{
    public config: SlotMachineConfig;
    public timeScale: number;
    public events!: Events;
    public inputs: MachineInputs;
    public flags: MachineFlags;
    public maps: MachineMaps;
    public utils: MachineUtils;
    public systems: Record<string, any>;
    public sharedSystemData: Record<string, any>;
    public speedModes: SpeedModes;

    /**
     * @class
     * @param {SlotMachineConfig} config - Configuration object for the BaseMachine instance.
     */
    protected constructor(config: SlotMachineConfig)
    {
        super();
        this.config = config;
        this.sortableChildren = true;
        this.timeScale = 1;

        this.inputs = {};
        this.flags = {};
        this.maps = {};
        this.utils = {
            grid: new GridUtils(this.config),
        };
        this.systems = {};
    }

    /**
     * Core initialization method to set up various components.
     * Subclasses should override create methods if customization is needed.
     */
    protected initialize(): void
    {
        this.createSharedData();
        this.createEvents();
        this.createInputs();
        this.createFlags();
        this.createMaps();
        this.createUtils();
        this.createSystems();
        this.createStateMachine();
        this.createSpeedModes();
    }

    protected createEvents(): void
    {
        this.events = new Events();
    }

    protected createInputs(): void
    {
        this.inputs = {};
    }

    protected createFlags(): void
    {
        this.flags = {};
    }

    protected createMaps(): void
    {
        this.maps = {};
    }

    protected createUtils(): void
    {
        this.utils = {
            grid: new GridUtils(this.config),
        };
    }

    protected createSystems(): void
    {
        this.systems = {};
    }

    protected createSharedData(): void
    {
        this.sharedSystemData = {};
    }

    /**
   Initializes the state machine (to be implemented by subclass).
     * @throws {Error} Must be implemented in subclass.
     */
    protected abstract createStateMachine(): void;

    /**
   Initializes different speed modes (to be implemented by subclass).
     * @throws {Error} Must be implemented in subclass.
     */
    protected createSpeedModes(): void
    {
        this.speedModes = new SpeedModes(this);
    }

    /**
     * Creates a symbol based on the provided key (to be implemented by subclass).
     * @param {string} key - The unique identifier for the symbol.
     * @throws {Error} Must be implemented in subclass.
     */
    public abstract createSymbol(key: string): any;

    /**
     * Updates the machine and its child entities based on a time delta.
     * @param {number} dt - The delta time for the update cycle.
     */
    public update(dt: number): void
    {
        this.speedModes.handleInput(this.inputs);
        this._updateEntities(dt);
    }

    /**
     * @private
     * @param {number} dt - The delta time to pass to each child entity's update.
     */
    private _updateEntities(dt: number): void
    {
        this.children.forEach((child) =>
        {
            if ((child as DisplayObject & Updatable).update)
            {
                (child as DisplayObject & Updatable).update(dt);
            }
        });
    }

    /**
     * Clears the specified map by setting all elements to null.
     * @param {string} mapName - The name of the map to clear.
     */
    public nullMap(mapName: string): void
    {
        if (!this.maps[mapName]) return;
        this.maps[mapName].fill(null);
    }
}
