import { DisplayObject, Graphics } from 'pixi.js';
import BaseSystem from './BaseSystem';
import BaseMachine from '../slotMachines/BaseMachine';
import { CanBeMasked } from '../types/Entity';

/**
 * Configuration settings for the masking system.
 */
export interface MaskingSystemSettings {
    width: number;
    height: number;
    enabled: boolean;
}

/**
 * Coordinates masking behaviour across systems.
 */
export default class MaskingSystem extends BaseSystem<MaskingSystemSettings>
{
    /** Current lifecycle state for the masking system. */
    public state = 'stopped';

    private mask: Graphics;

    constructor(machine: BaseMachine)
    {
        super(machine);
        this.setSettings({
            width: machine.config.width,
            height: machine.config.height,
            enabled: true,
        });
        this.mask = this.createMask();
    }

    /**
     * Updates the masking settings and redraws the mask accordingly.
     */
    public override setSettings(newSettings: Partial<MaskingSystemSettings>): void
    {
        const previousSettings = { ...this.settings };
        super.setSettings(newSettings);

        if (!this.mask)
        {
            return;
        }

        const enabledChanged = typeof newSettings.enabled === 'boolean'
            && newSettings.enabled !== previousSettings.enabled;
        const dimensionsChanged = typeof newSettings.width === 'number'
            || typeof newSettings.height === 'number';

        if (enabledChanged && !this.settings.enabled)
        {
            this.removeMaskFromChildren();
        }

        if (dimensionsChanged || (enabledChanged && this.settings.enabled))
        {
            this.refreshMaskShape();
        }
    }

    /** @inheritdoc */
    override onEnter(): void
    {
        this.state = 'stopped';
        if (this.settings.enabled)
        {
            this.refreshMaskShape();
            return;
        }

        this.removeMaskFromChildren();
    }

    /** @inheritdoc */
    override onLeave(): void
    {
        this.state = 'stopped';
        this.removeMaskFromChildren();
    }

    /** @inheritdoc */
    override update(_dt: number): void
    {
        if (!this.settings.enabled)
        {
            this.removeMaskFromChildren();
            return;
        }

        this.machine.children.forEach((child) =>
        {
            if (child === this.mask) return;
            if (!this.isMaskableEntity(child)) return;
            if (child.isMasked)
            {
                this.applyMask(child);
                return;
            }
            this.clearMask(child);
        });
        this.mask.worldTransform.copyFrom(this.machine.worldTransform)
    }

    /**
     * Creates the reusable mask graphics with the configured size.
     */
    private createMask(): Graphics
    {
        const mask = new Graphics();
        mask.name = 'masking-system-mask';
        mask.visible = true;
        this.drawMask(mask);
        return mask;
    }

    /**
     * Redraws the mask using the current settings.
     */
    private refreshMaskShape(): void
    {
        this.drawMask(this.mask);
    }

    /**
     * Applies the configured mask to the entity when no other mask is set.
     */
    private applyMask(entity: DisplayObject & CanBeMasked): void
    {
        if (entity.mask) return;
        entity.mask = this.mask;
    }

    /**
     * Removes the configured mask from the entity if it is currently applied.
     */
    private clearMask(entity: DisplayObject & CanBeMasked): void
    {
        if (entity.mask !== this.mask) return;
        entity.mask = null;
    }

    /**
     * Ensures entities are unmasked when the system leaves the scene.
     */
    private removeMaskFromChildren(): void
    {
        this.machine.children.forEach((child) =>
        {
            if (!this.isMaskableEntity(child)) return;
            this.clearMask(child);
        });
    }

    /**
     * Type guard verifying that the display object supports masking control.
     */
    private isMaskableEntity(child: DisplayObject): child is DisplayObject & CanBeMasked
    {
        return typeof (child as Partial<CanBeMasked>).isMasked === 'boolean';
    }

    /**
     * Draws a rectangle mask using the provided graphics instance.
     */
    private drawMask(graphics: Graphics): void
    {
        graphics.clear();
        graphics.beginFill(0xff0000, 1);
        graphics.drawRect(0, 0, this.settings.width, this.settings.height);
        graphics.endFill();
    }
}
