import BaseMachine from '../slotMachines/BaseMachine';

/**
 * @class BaseSystem
 * @description Base class that defines an interface for all systems within the slot machine. Systems that extend
 * BaseSystem should implement specific logic in the `onEnter`, `onLeave`, and `update` methods.
 */
abstract class BaseSystem<SettingsType = Record<string, unknown>>
{
    machine: BaseMachine;
    settings: SettingsType;

    /** Static list of flags that the machine should provide. Subclasses can override to specify their own requirements. */
    static requiredFlags: string[] = [];

    /** Static list of maps that the machine should provide. Subclasses can override to specify their own requirements. */
    static requiredMaps: string[] = [];

    /** Static list of inputs that the machine should provide. Subclasses can override to specify their own requirements. */
    static requiredInputs: string[] = [];

    /** Static list of methods that the machine should provide. Subclasses can override to specify their own requirements. */
    static requiredMethods: string[] = [];

    /** Static list of utils that the machine should provide. Subclasses can override to specify their own requirements. */
    static requiredUtils: string[] = [];

    static sharedSystemData: string[] = [];

    /**
     * @class
     * @param {BaseMachine} machine - The slot machine instance that the system will operate on.
     */
    constructor(machine: BaseMachine)
    {
        this.machine = machine;
        this.settings = {} as SettingsType;
        this._validateMachineRequirements();
    }

    /**
     * Validates that the machine instance provides all required flags, maps, inputs, methods, and utilities.
     * Throws an error if any requirements are missing.
     * @private
     */
    private _validateMachineRequirements(): void
    {
        const missingFlags = (this.constructor as typeof BaseSystem).requiredFlags
            .filter((flag) => !(flag in this.machine.flags));
        const missingMaps = (this.constructor as typeof BaseSystem)
            .requiredMaps.filter((map) => !(map in this.machine.maps));
        const missingInputs = (this.constructor as typeof BaseSystem)
            .requiredInputs.filter((input) => !(input in this.machine.inputs));
        const missingMethods: string[] = (this.constructor as typeof BaseSystem)
            .requiredMethods.filter((method: string) =>
            {
                const fn = (this.machine as any)[method];

                return typeof fn !== 'function';
            });
        const missingUtils = (this.constructor as typeof BaseSystem)
            .requiredUtils.filter((util) => !(util in this.machine.utils));
        const missingSharedSystemData = (this.constructor as typeof BaseSystem)
            .sharedSystemData.filter((sharedSystemData) => !(sharedSystemData in this.machine.sharedSystemData));

        const errors: string[] = [];

        if (missingFlags.length > 0) errors.push(`Missing flags: ${missingFlags.join(', ')}`);
        if (missingMaps.length > 0) errors.push(`Missing maps: ${missingMaps.join(', ')}`);
        if (missingInputs.length > 0) errors.push(`Missing inputs: ${missingInputs.join(', ')}`);
        if (missingMethods.length > 0) errors.push(`Missing methods: ${missingMethods.join(', ')}`);
        if (missingUtils.length > 0) errors.push(`Missing utilities: ${missingUtils.join(', ')}`);
        if (missingSharedSystemData.length > 0) errors.push(`Missing shared system data: ${missingSharedSystemData.join(', ')}`);

        if (errors.length > 0)
        {
            throw new Error(`Machine does not meet system requirements:\n${errors.join('\n')}`);
        }
    }

    /**
     * @method onEnter
     * @description Called when the system is activated or enters scope. Should be overridden in subclasses.
     */
    abstract onEnter(): void;

    /**
     * @method onLeave
     * @description Called when the system is deactivated or leaves scope. Should be overridden in subclasses.
     */
    abstract onLeave(): void;

    /**
     * @method update
     * @description Called on each update cycle to perform the system's primary functionality.
     * Should be overridden in subclasses to add specific update logic.
     */
    abstract update(dt: number): void;

    public destroy() {

    }

    /**
     * Merges provided settings into the existing settings.
     * @param newSettings - New settings to merge with existing settings.
     */
    public setSettings(newSettings: Partial<SettingsType>): void
    {
        Object.assign(this.settings, newSettings);
    }
}

export default BaseSystem;
