import { Layers, Object3D } from "three";

import { serializable } from "../engine/engine_serialization_decorator.js";
import { getParam } from "../engine/engine_utils.js";
import { BoxHelperComponent } from "./BoxHelperComponent.js"
import { Behaviour, GameObject } from "./Component.js";
import { EventList } from "./EventList.js";

const debug = getParam("debugspatialtrigger");

const layer1 = new Layers();
const layer2 = new Layers();
function testMask(mask1, mask2) {
    layer1.mask = mask1;
    layer2.mask = mask2;
    return layer1.test(layer2);
}

/**
 * @category Interactivity
 * @group Components
 */
export class SpatialTriggerReceiver extends Behaviour {

    // currently Layers in unity but maybe this should be a string or plane number? Or should it be a bitmask to allow receivers use multiple triggers?
    @serializable()
    triggerMask: number = 0;
    @serializable(EventList)
    onEnter?: EventList<any>;
    @serializable(EventList)
    onStay?: EventList<any>;
    @serializable(EventList)
    onExit?: EventList<any>;

    start() {
        if (debug) console.log(this.name, this.triggerMask, this);
    }

    update(): void {
        this.currentIntersected.length = 0;
        for (const trigger of SpatialTrigger.triggers) {
            if (testMask(trigger.triggerMask, this.triggerMask)) {
                if (trigger.test(this.gameObject)) {
                    this.currentIntersected.push(trigger);
                }
            }
        }
        for (let i = this.lastIntersected.length - 1; i >= 0; i--) {
            const last = this.lastIntersected[i]
            if (this.currentIntersected.indexOf(last) < 0) {
                this.onExitTrigger(last);
                this.lastIntersected.splice(i, 1);
            }
        }
        for (const cur of this.currentIntersected) {
            if (this.lastIntersected.indexOf(cur) < 0)
                this.onEnterTrigger(cur);
            this.onStayTrigger(cur);
        }
        this.lastIntersected.length = 0;
        this.lastIntersected.push(...this.currentIntersected);

    }

    currentIntersected: SpatialTrigger[] = [];
    lastIntersected: SpatialTrigger[] = [];

    onEnterTrigger(trigger: SpatialTrigger): void {
        if(debug) console.log("ENTER TRIGGER", this.name, trigger.name, this, trigger);
        trigger.raiseOnEnterEvent(this);
        this.onEnter?.invoke(this, trigger);
    }
    onExitTrigger(trigger: SpatialTrigger): void {
        if(debug) console.log("EXIT TRIGGER", this.name, trigger.name, this, trigger);
        trigger.raiseOnExitEvent(this);
        this.onExit?.invoke(this, trigger);
    }

    onStayTrigger(trigger: SpatialTrigger): void {
        trigger.raiseOnStayEvent(this);
        this.onStay?.invoke(this, trigger);
    }
}

/**
 * A trigger that can be used to detect if an object is inside a box.
 * @category Interactivity
 * @group Components
 */
export class SpatialTrigger extends Behaviour {

    static triggers: SpatialTrigger[] = [];

    @serializable()
    triggerMask?: number;

    private boxHelper?: BoxHelperComponent;

    start() {
        if (debug)
            console.log(this.name, this.triggerMask, this);
    }

    onEnable(): void {
        SpatialTrigger.triggers.push(this);
        if (!this.boxHelper) {
            this.boxHelper = GameObject.addComponent(this.gameObject, BoxHelperComponent);
            this.boxHelper?.showHelper(null, debug as boolean);
        }
    }
    onDisable(): void {
        SpatialTrigger.triggers.splice(SpatialTrigger.triggers.indexOf(this), 1);
    }

    test(obj: Object3D): boolean {
        if (!this.boxHelper) return false;
        return this.boxHelper.isInBox(obj) ?? false;
    }

    // private args: SpatialTriggerEventArgs = new SpatialTriggerEventArgs();

    raiseOnEnterEvent(rec: SpatialTriggerReceiver) {
        // this.args.trigger = this;
        // this.args.source = rec;
        GameObject.foreachComponent(this.gameObject, c => {
            if (c === rec) return;
            if(c instanceof SpatialTriggerReceiver) {
                c.onEnterTrigger(this);
            }
        }, false);
    }

    raiseOnStayEvent(rec: SpatialTriggerReceiver) {
        // this.args.trigger = this;
        // this.args.source = rec;
        GameObject.foreachComponent(this.gameObject, c => {
            if (c === rec) return;
            if(c instanceof SpatialTriggerReceiver) {
                c.onStayTrigger(this);
            }
        }, false);
    }

    raiseOnExitEvent(rec: SpatialTriggerReceiver) {
        GameObject.foreachComponent(this.gameObject, c => {
            if (c === rec) return;
            if(c instanceof SpatialTriggerReceiver) {
                c.onExitTrigger(this);
            }
        }, false);
    }
}
