import { Box3, MeshBasicMaterial } from 'three';
import { HTMLMesh } from 'three/examples/jsm/interactive/HTMLMesh.js';
import { InteractiveGroup } from 'three/examples/jsm/interactive/InteractiveGroup.js';

import { serializable } from '../../engine/engine_serialization.js';
import { getWorldRotation, setWorldRotationXYZ } from '../../engine/engine_three_utils.js';
import { Behaviour } from '../Component.js';

/**
 * [SpatialHtml](https://engine.needle.tools/docs/api/SpatialHtml) is a component that allows you to integrate HTML elements into a 3D scene.
 * By specifying the ID of an existing HTML element, you can render it as a 3D object within the scene.
 * @summary Render HTML elements as 3D objects in the scene
 * @category User Interface
 * @group Components
 */
export class SpatialHtml extends Behaviour {

    @serializable()
    id: string | null = null;
    @serializable()
    keepAspect: boolean = false;

    private _object: InteractiveGroup | null = null;

    onEnable() {
        if (this._object) {
            this.gameObject.add(this._object);
            return;
        }

        if (!this.id || !this.context.mainCamera) return;
        const div = document.getElementById(this.id);
        if (!div) {
            console.warn("Could not find element with id \"" + this.id + "\"");
            return;
        }
        div.style.display = "block";
        div.style.visibility = "hidden";

        const group = new InteractiveGroup();
        group.listenToPointerEvents(this.context.renderer, this.context.mainCamera!);
        // TODO listen to controller events?
        this.gameObject.add(group);

        const mesh = new HTMLMesh(div);
        group.add(mesh);
        mesh.visible = false;

        const mat = mesh.material as MeshBasicMaterial;
        mat.transparent = true;

        // need to wait one frame for it to render to get bounds
        setTimeout(() => {
            mesh.visible = true;
            // align box to get bounding box
            const rot = getWorldRotation(this.gameObject).clone();
            setWorldRotationXYZ(this.gameObject, 0, 0, 0);
            this.gameObject.updateMatrixWorld();
            const aabb = new Box3();
            aabb.setFromObject(group);
            this.setWorldRotation(rot.x, rot.y, rot.z);
            // apply bounds
            const width = aabb.max.x - aabb.min.x;
            const height = aabb.max.y - aabb.min.y;
            if (this.keepAspect) {
                const aspect = width / height;
                if (width > height) {
                    mesh.scale.set(1 / width, 1 / height / aspect, 1);
                }
                else {
                    mesh.scale.set(1 / width * aspect, 1 / height, 1);
                }
            }
            else {
                mesh.scale.set(1 / width, 1 / height, 1);
            }
            // TODO: replace with world scale once we have that
            const factor = this.gameObject.scale;
            mesh.scale.multiply(factor);
        }, 1);
    }

    onDisable(): void {
        this._object?.removeFromParent();
    }
}
