// import { Canvas } from './Canvas.js';
import { AxesHelper, Object3D } from 'three';
import * as ThreeMeshUI from 'three-mesh-ui';

import { showGizmos } from '../../engine/engine_default_parameters.js';
import { ComponentInit } from '../../engine/engine_types.js';
import { getParam } from '../../engine/engine_utils.js';
import { Behaviour, GameObject } from "../Component.js";
import { EventSystem } from "./EventSystem.js";
import type { ICanvas } from './Interfaces.js';
import { $shadowDomOwner } from './Symbols.js';
export const includesDir = "./include";

const debug = getParam("debugshadowcomponents");

// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy


ThreeMeshUI.Block.prototype["interactable"] = {
    get() {
        return this.interactive;
    },
    set(value) {
        this.interactable = value;
    }
}


/**
 * Derive from this class if you want to implement your own UI components.   
 * It provides utility methods and simplifies managing the underlying three-mesh-ui hierarchy.
 * @category User Interface
 * @group Components
*/
export class BaseUIComponent extends Behaviour {

    /** Is this object on the root of the UI hierarchy ? */
    isRoot() { return this.Root?.gameObject === this.gameObject; }

    /** Access the parent canvas component */
    get canvas() {
        const cv = this.Root as any as ICanvas;
        if (cv?.isCanvas) return cv;
        return null;
    }
    /** @deprecated use `canvas` */
    protected get Canvas() {
        return this.canvas;
    }

    /** Mark the UI dirty which will trigger an THREE-Mesh-UI update */
    markDirty() {
        EventSystem.markUIDirty(this.context);
    }

    /** the underlying three-mesh-ui */
    get shadowComponent() { return this._shadowComponent }
    private set shadowComponent(val: Object3D | null) {
        this._shadowComponent = val;
    }

    private _shadowComponent: Object3D | null = null;

    private _controlsChildLayout = true;
    get controlsChildLayout(): boolean { return this._controlsChildLayout; }
    set controlsChildLayout(val: boolean) {
        this._controlsChildLayout = val;
        if (this.shadowComponent) {
            //@ts-ignore
            (this.shadowComponent as ThreeMeshUI.MeshUIComponent).autoLayout = val;
        }
    }

    private _root?: UIRootComponent | null = undefined;
    protected get Root(): UIRootComponent | null {
        if (this._root === undefined) {
            this._root = GameObject.getComponentInParent(this.gameObject, UIRootComponent);
        }
        return this._root;
    }

    // private _intermediate?: Object3D;
    protected _parentComponent?: BaseUIComponent | null = undefined;

    __internalNewInstanceCreated(args: ComponentInit<this>) {
        super.__internalNewInstanceCreated(args);
        this.shadowComponent = null;
        this._root = undefined;
        this._parentComponent = undefined;
        return this;
    }

    onEnable() {
        super.onEnable();
    }

    /** Add a three-mesh-ui object to the UI hierarchy 
     * @param container the three-mesh-ui object to add
     * @param parent the parent component to add the object to
    */
    protected addShadowComponent(container: any, parent?: BaseUIComponent) {

        if (!container) return;
        this.removeShadowComponent();

        // instead of inserting here, we attach to the matching shadow hierarchy starting with the Canvas component.
        const searchFrom = this.isRoot() ? this.gameObject : this.gameObject.parent;
        this._parentComponent = GameObject.getComponentInParent(searchFrom!, BaseUIComponent);
        if (!this._parentComponent) {
            console.warn(`Component \"${this.name}\" doesn't have a UI parent anywhere. Do you have an UI element outside a Canvas? UI components must be a child of a Canvas component`, this);
            return;
        }

        container.name = this.name + " (" + (this.constructor.name ?? "UI") + ")";
        container.autoLayout = this._parentComponent.controlsChildLayout;
        container[$shadowDomOwner] = this;

        // TODO: raycastTarget doesnt work anymore -> i think we need to set the gameObject layer and then check in the raycaster if the shadowComponentOwner is on the correct layer?!
        // const raycastTarget = (this as unknown as IGraphic).raycastTarget;
        // this.gameObject.layers.set(2)


        this.setShadowComponentOwner(container);

        let needsUpdate = false;

        if (this.Root?.gameObject === this.gameObject) {
            this.gameObject.add(container);
        }
        else {
            const targetShadowComponent = this._parentComponent.shadowComponent;
            if (targetShadowComponent) {
                // console.log("ADD", this.name, "to", this._parentComponent.name, targetShadowComponent);
                targetShadowComponent?.add(container);
                needsUpdate = true;
            }
        }
        this.shadowComponent = container;
        if (parent && parent.shadowComponent && this.shadowComponent) {
            parent.shadowComponent.add(this.shadowComponent);
        }
        // this.applyTransform();

        if (showGizmos) {
            container.add(new AxesHelper(.5));
        }

        this.onAfterAddedToScene();

        // make sure to update the layout when adding content
        // otherwise it will fail when object are enabled at runtime
        if (needsUpdate)
            ThreeMeshUI.update();

        if (debug) console.warn("Added shadow component", this.shadowComponent);
    }

    protected setShadowComponentOwner(current: ThreeMeshUI.MeshUIBaseElement | Object3D | null | undefined) {
        if (!current) return;
        // TODO: only traverse our own hierarchy, we can stop if we find another owner
        if (current[$shadowDomOwner] === undefined || current[$shadowDomOwner] === this) {
            current[$shadowDomOwner] = this;
            if (current.children) {
                for (const ch of current.children) {
                    this.setShadowComponentOwner(ch);
                }
            }
        }
    }

    private traverseOwnedShadowComponents(current: Object3D, owner: any, callback: (obj: any) => void) {
        if (!current) return;
        if (current[$shadowDomOwner] === owner) {
            callback(current);
            for (const ch of current.children) {
                this.traverseOwnedShadowComponents(ch, owner, callback);
            }
        }
    }

    /** Remove the underlying UI object from the hierarchy */
    protected removeShadowComponent() {
        if (this.shadowComponent) {
            this.shadowComponent.removeFromParent();
        }
    }

    protected onAfterAddedToScene() {

    }

    setInteractable(value: boolean) {
        if (this.shadowComponent) {
            //@ts-ignore
            this.shadowComponent.interactable = value;
        }
    }
}

export class UIRootComponent extends BaseUIComponent {
    awake() {
        super.awake();
    }
}