import { ExtensionType } from 'pixi.js';
import { EventSystem } from '@pixi/events';
import Locator from '../services/Locator';

type NativePointerEvent = PointerEvent | MouseEvent | TouchEvent;
type NormalizedPointerData = Parameters<EventSystem['bootstrapEvent']>[1] & { isNormalized?: boolean };
type FederatedPointer = ReturnType<EventSystem['bootstrapEvent']>;

/** Broadcasts global pointer events through the Locator event bus. */
//@ts-expect-error type
export class GlobalPointerEvents extends EventSystem
{
    static override extension = {
        name: 'globalEvents',
        type: [
            ExtensionType.RendererSystem,
            ExtensionType.CanvasRendererSystem,
        ],
    } as const;

    override onPointerDown(nativeEvent: NativePointerEvent): void
    {
        if (!this.features.click)
        {
            return;
        }

        //@ts-expect-error type
        const events = this.normalizeToPointerData(nativeEvent) as NormalizedPointerData[];

        if (this.autoPreventDefault && events.length > 0 && events[0].isNormalized)
        {
            const cancelable = nativeEvent.cancelable || !('cancelable' in nativeEvent);

            if (cancelable)
            {
                nativeEvent.preventDefault();
            }
        }

        for (const eventData of events)
        {
            //@ts-expect-error type
            const federatedEvent = this.bootstrapEvent(this.rootPointerEvent, eventData) as FederatedPointer;

            Locator.events.emit('globalpointerdown', federatedEvent);
        }
    }

    override onPointerMove(nativeEvent: NativePointerEvent): void
    {
        //@ts-expect-error type
        this.rootBoundary.rootTarget = this.renderer.lastObjectRendered;

        //@ts-expect-error type
        const events = this.normalizeToPointerData(nativeEvent) as NormalizedPointerData[];

        for (const eventData of events)
        {
            //@ts-expect-error type
            const event = this.bootstrapEvent(this.rootPointerEvent, eventData) as FederatedPointer;

            Locator.events.emit('globalpointermove', event);
        }
    }

    override onPointerUp(nativeEvent: NativePointerEvent): void
    {
        if (!this.features.click)
        {
            return;
        }

        //@ts-expect-error type
        this.rootBoundary.rootTarget = this.renderer.lastObjectRendered;

        let target = nativeEvent.target as EventTarget | null;

        if ('composedPath' in nativeEvent && typeof nativeEvent.composedPath === 'function')
        {
            const composedPath = nativeEvent.composedPath();

            if (Array.isArray(composedPath) && composedPath.length > 0)
            {
                target = composedPath[0] as EventTarget | null;
            }
        }

        const outside = target !== this.domElement ? 'outside' : '';
        //@ts-expect-error type
        const events = this.normalizeToPointerData(nativeEvent) as NormalizedPointerData[];

        for (const eventData of events)
        {
            //@ts-expect-error type
            const event = this.bootstrapEvent(this.rootPointerEvent, eventData) as FederatedPointer;

            event.type += outside;

            Locator.events.emit('globalpointerup', event);
        }
    }

    override onPointerOverOut(_nativeEvent: NativePointerEvent): void
    {
        // PixiJS requires this override even when unused
    }

    override onWheel(_nativeEvent: WheelEvent): void
    {
        // PixiJS requires this override even when unused
    }

    override init(options?: Record<string, unknown>): void
    {
        super.init(options);
    }
}
