import BaseMachine from '../slotMachines/BaseMachine';
import SlotMultiplierEntity from '../entities/SlotMultiplierEntity';
import GridUtils from '../utils/GridUtils';
import BaseSystem from './BaseSystem';

/** Factory signature for SlotMultiplierEntity derivatives. */
export type SlotMultiplierEntityConstructor = new () => SlotMultiplierEntity;

/** Runtime configuration for {@link SlotMultipliersSystem}. */
export interface SlotMultipliersSystemSettings
{
    entityClass: SlotMultiplierEntityConstructor;
}

/**
 * Projects multiplier overlays on top of slots based on the `slotMultipliers` map.
 */
export default class SlotMultipliersSystem extends BaseSystem<SlotMultipliersSystemSettings>
{
    public static defaultSettings: SlotMultipliersSystemSettings = {
        entityClass: SlotMultiplierEntity,
    };

    public static override requiredMaps: string[] = ['slotMultipliers'];
    public static override requiredUtils: string[] = ['grid'];

    /** Tracks whether the system currently has pending work for the state machine. */
    public state: 'active' | 'stopped' = 'stopped';

    private entities: SlotMultiplierEntity[] = [];
    private warnedAboutLength = false;

    constructor(machine: BaseMachine)
    {
        super(machine);
        this.setSettings(SlotMultipliersSystem.defaultSettings);
    }

    public override onEnter(): void
    {
        this.state = 'active';
        this.ensureEntityPool();
        this.refreshVisibility();
        this.state = 'stopped';
    }

    /**
     * Leaves overlays untouched; visibility is fully driven by the multiplier map.
     */
    public override onLeave(): void
    {
        this.state = 'stopped';
    }

    public override update(_dt: number): void
    {
        this.state = 'active';
        this.ensureEntityPool();
        this.refreshVisibility();
        this.state = 'stopped';
    }

    /**
     * Ensures there is exactly one overlay entity per slot and keeps positions in sync with the grid.
     */
    private ensureEntityPool(): void
    {
        const total = this.machine.config.reels * this.machine.config.slots;
        const grid = this.grid();

        for (let index = 0; index < total; index++)
        {
            if (this.entities[index])
            {
                const position = grid.fromMapIndexToXY(index);
                if (position)
                {
                    this.entities[index].position.copyFrom(position);
                }

                continue;
            }

            const entity = new this.settings.entityClass();
            entity.name = `slotMultiplier-${index}`;
            entity.visible = false;
            entity.zIndex = 10_000;
            const position = grid.fromMapIndexToXY(index);
            if (position)
            {
                entity.position.copyFrom(position);
            }
            this.machine.addChild(entity);
            this.entities[index] = entity;
        }

        if (this.entities.length > total)
        {
            for (let i = total; i < this.entities.length; i++)
            {
                const entity = this.entities[i];
                if (entity)
                {
                    entity.destroy();
                }
            }
            this.entities.length = total;
        }
    }

    /**
     * Shows or hides entities based on the current multiplier map.
     */
    private refreshVisibility(): void
    {
        const total = this.machine.config.reels * this.machine.config.slots;
        const multipliers = this.normaliseMultipliers(total);

        for (let i = 0; i < total; i++)
        {
            const entity = this.entities[i];
            if (!entity) continue;

            const value = multipliers[i];
            entity.value = value;

            if (value > 0)
            {
                if (!entity.isShown())
                {
                    entity.in();
                }
            }
            else if (entity.isShown())
            {
                entity.out();
            }
        }
    }

    /**
     * Validates and normalises the raw multiplier map against the expected cell count.
     */
    private normaliseMultipliers(total: number): number[]
    {
        const map = this.machine.maps.slotMultipliers;
        if (!Array.isArray(map))
        {
            if (!this.warnedAboutLength)
            {
                console.warn('SlotMultipliersSystem: slotMultipliers map is missing.');
                this.warnedAboutLength = true;
            }

            return new Array(total).fill(0);
        }

        if (map.length !== total)
        {
            if (!this.warnedAboutLength)
            {
                console.warn(`SlotMultipliersSystem: slotMultipliers length ${map.length} does not match expected ${total}.`);
                this.warnedAboutLength = true;
            }
        }
        else
        {
            this.warnedAboutLength = false;
        }

        const result = new Array<number>(total).fill(0);
        const limit = Math.min(total, map.length);

        for (let index = 0; index < limit; index++)
        {
            const value = map[index];
            result[index] = typeof value === 'number' && value > 0 ? value : 0;
        }

        return result;
    }

    /**
     * Provides typed access to the machine grid utility.
     */
    private grid(): GridUtils
    {
        return this.machine.utils.grid as GridUtils;
    }
}
