import { MathUtils,Mesh, MeshBasicMaterial } from "three";
import { TransformControls } from "three/examples/jsm/controls/TransformControls.js";

import * as params from "../engine/engine_default_parameters.js";
import { serializable } from "../engine/engine_serialization_decorator.js";
import { Behaviour, GameObject } from "./Component.js";
import { OrbitControls } from "./OrbitControls.js";
import { SyncedTransform } from "./SyncedTransform.js";

/**
 * TransformGizmo is a component that displays a gizmo for transforming the object in the scene.
 * @category Helpers
 * @group Components
 */
export class TransformGizmo extends Behaviour {

    @serializable()
    public isGizmo: boolean = false;

    @serializable()
    public translationSnap: number = 1;

    @serializable()
    public rotationSnapAngle: number = 15;

    @serializable()
    public scaleSnap: number = .25;

    private control?: TransformControls;
    private orbit?: OrbitControls;

    /** @internal */
    onEnable() {
        if (this.isGizmo && !params.showGizmos) return;

        if (!this.context.mainCamera) return;

        if (!this.control) {
            this.control = new TransformControls(this.context.mainCamera, this.context.renderer.domElement);
            this.control.visible = true;
            this.control.enabled = true;
            this.control.getRaycaster().layers.set(2);
            this.control.size = 1;
            this.control.traverse(x => {
                const mesh = x as Mesh;
                mesh.layers.set(2);
                if (mesh) {
                    const gizmoMat = mesh.material as MeshBasicMaterial;
                    if (gizmoMat) {
                        gizmoMat.opacity = 0.3;
                    }
                }
            });
            this.orbit = GameObject.getComponentInParent(this.context.mainCamera, OrbitControls) ?? undefined;
        }

        if (this.control) {
            this.context.scene.add(this.control);
            this.control.attach(this.gameObject);

            this.control?.addEventListener('dragging-changed', this.onControlChangedEvent);
            window.addEventListener('keydown', this.windowKeyDownListener);
            window.addEventListener('keyup', this.windowKeyUpListener);
        }
    }

    /** @internal */
    onDisable() {
        this.control?.removeFromParent();
        this.control?.removeEventListener('dragging-changed', this.onControlChangedEvent);
        window.removeEventListener('keydown', this.windowKeyDownListener);
        window.removeEventListener('keyup', this.windowKeyUpListener);
    }

    enableSnapping() {
        if (this.control) {
            this.control.setTranslationSnap(this.translationSnap);
            this.control.setRotationSnap(MathUtils.degToRad(this.rotationSnapAngle));
            this.control.setScaleSnap(this.scaleSnap);
        }
    }

    disableSnapping() {
        if (this.control) {
            this.control.setTranslationSnap(null);
            this.control.setRotationSnap(null);
            this.control.setScaleSnap(null);
        }
    }

    private onControlChangedEvent = (event) => {
        const orbit = this.orbit;
        if (orbit) orbit.enabled = !event.value;
        if (event.value) {
            // request ownership on drag start
            const sync = GameObject.getComponentInParent(this.gameObject, SyncedTransform);
            if (sync) {
                sync.requestOwnership();
            }
        }
    }


    private windowKeyDownListener = (event) => {
        if (!this.enabled) return;
        if (!this.control) return;
        switch (event.keyCode) {

            case 81: // Q
                this.control.setSpace(this.control.space === 'local' ? 'world' : 'local');
                break;

            case 16: // Shift
                this.enableSnapping();
                break;

            case 87: // W
                this.control.setMode('translate');
                break;

            case 69: // E
                this.control.setMode('rotate');
                break;

            case 82: // R
                this.control.setMode('scale');
                break;
            case 187:
            case 107: // +, =, num+
                this.control.setSize(this.control.size + 0.1);
                break;

            case 189:
            case 109: // -, _, num-
                this.control.setSize(Math.max(this.control.size - 0.1, 0.1));
                break;

            case 88: // X
                this.control.showX = !this.control.showX;
                break;

            case 89: // Y
                this.control.showY = !this.control.showY;
                break;

            case 90: // Z
                this.control.showZ = !this.control.showZ;
                break;

            case 32: // Spacebar
                this.control.enabled = !this.control.enabled;
                break;
        }
    }

    private windowKeyUpListener = (event) => {
        if (!this.enabled) return;
        switch (event.keyCode) {
            case 16: // Shift
                this.disableSnapping();
                break;

        }

    }
}