import { MathUtils, Mesh, MeshBasicMaterial, Object3D } 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";

/**
 * The [TransformGizmo](https://engine.needle.tools/docs/api/TransformGizmo) displays manipulation controls for translating, rotating, and scaling objects.
 * Wraps three.js {@link TransformControls} with keyboard shortcuts and snapping support.  
 *
 * **Keyboard shortcuts:**
 * - `W` - Translate mode
 * - `E` - Rotate mode
 * - `R` - Scale mode
 * - `Q` - Toggle local/world space
 * - `Shift` (hold) - Enable grid snapping
 * - `+/-` - Adjust gizmo size
 * - `X/Y/Z` - Toggle axis visibility
 * - `Space` - Toggle controls enabled
 *
 * **Snapping:**
 * Configure grid snapping with `translationSnap`, `rotationSnapAngle`, and `scaleSnap`.
 *
 * **Networking:**
 * Automatically works with {@link SyncedTransform} for multiplayer editing.
 *
 * @example Add transform gizmo to an object
 * ```ts
 * const gizmo = editableObject.addComponent(TransformGizmo);
 * gizmo.translationSnap = 0.5; // Snap to 0.5 unit grid
 * gizmo.rotationSnapAngle = 45; // Snap to 45° increments
 * ```
 *
 * @summary Object manipulation gizmo for translate/rotate/scale
 * @category Helpers
 * @group Components
 * @see {@link DragControls} for simpler drag-only interaction
 * @see {@link SyncedTransform} for network synchronization
 * @see {@link OrbitControls} - automatically disabled during gizmo drag
 * @link https://threejs.org/docs/index.html#examples/en/controls/TransformControls for underlying three.js controls and additional features
 */
export class TransformGizmo extends Behaviour {

    /**
     * When true, this is considered a helper gizmo and will only be shown if showGizmos is enabled in engine parameters.
     */
    @serializable()
    public isGizmo: boolean = false;

    /**
     * Specifies the translation grid snap value in world units.
     * Applied when holding Shift while translating an object.
     */
    @serializable()
    public translationSnap: number = 1;

    /**
     * Specifies the rotation snap angle in degrees.
     * Applied when holding Shift while rotating an object.
     */
    @serializable()
    public rotationSnapAngle: number = 15;

    /**
     * Specifies the scale snapping value.
     * Applied when holding Shift while scaling an object.
     */
    @serializable()
    public scaleSnap: number = .25;

    /**
     * Gets the underlying three.js {@link TransformControls} instance.
     * @returns The TransformControls instance or undefined if not initialized.
     */
    get control() {
        return this._control;
    }

    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.enabled = true;
            this._control.getRaycaster().layers.set(2);
            this._control.size = 1;
            const obj = ("_root" in this._control ? this._control._root : this._control) as Object3D;
            obj.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) {
            const obj = this._control.getHelper();
            this.context.scene.add(obj);
            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?.getHelper()?.removeFromParent();
        this._control?.removeEventListener('dragging-changed', this.onControlChangedEvent);
        window.removeEventListener('keydown', this.windowKeyDownListener);
        window.removeEventListener('keyup', this.windowKeyUpListener);
    }

    /**
     * Enables grid snapping for transform operations according to set snap values.
     * This applies the translationSnap, rotationSnapAngle, and scaleSnap properties to the controls.
     */
    enableSnapping() {
        if (this._control) {
            this._control.setTranslationSnap(this.translationSnap);
            this._control.setRotationSnap(MathUtils.degToRad(this.rotationSnapAngle));
            this._control.setScaleSnap(this.scaleSnap);
        }
    }

    /**
     * Disables grid snapping for transform operations.
     * Removes all snapping constraints from the transform controls.
     */
    disableSnapping() {
        if (this._control) {
            this._control.setTranslationSnap(null);
            this._control.setRotationSnap(null);
            this._control.setScaleSnap(null);
        }
    }

    /**
     * Event handler for when dragging state changes.
     * Disables orbit controls during dragging and requests ownership of the transform if it's synchronized.
     * @param event The drag change event
     */
    private onControlChangedEvent = (event) => {
        const orbit = this.orbit;
        if (orbit) orbit.enabled = !event.value;

        if (event.value) {
            // Start of interaction
            const sync = this.gameObject.getComponentInParent(SyncedTransform);
            if (sync) {
                sync.fastMode = true;
                sync.requestOwnership();
            }
        }
        else {
            // End of interaction
            const sync = this.gameObject.getComponentInParent(SyncedTransform);
            if (sync) {
                sync.fastMode = false;
            }
        }
    }

    /**
     * Handles keyboard shortcuts for transform operations:
     * - Q: Toggle local/world space
     * - W: Translation mode
     * - E: Rotation mode
     * - R: Scale mode
     * - Shift: Enable snapping (while held)
     * - +/-: Adjust gizmo size
     * - X/Y/Z: Toggle visibility of respective axis
     * - Spacebar: Toggle controls enabled state
     * @param event The keyboard event
     */
    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;
        }
    }

    /**
     * Handles keyboard key release events.
     * Currently only handles releasing Shift key to disable snapping.
     * @param event The keyboard event
     */
    private windowKeyUpListener = (event) => {
        if (!this.enabled) return;
        switch (event.keyCode) {
            case 16: // Shift
                this.disableSnapping();
                break;

        }

    }
}