// Global ATON and THREE

import AppState from "../state.js";

/**
 * @module Clipping
 */

/**
 * 
 * @param {THREE.Sphere} boundingSphere - The bounding sphere for the main node
 * @returns {THREE.Mesh}
 */
function createClippingPlaneMesh (boundingSphere) {
    const planeSize = boundingSphere.radius * 1.5;
    const mesh = new THREE.Mesh(
        new THREE.PlaneGeometry(planeSize, planeSize),
        new THREE.MeshBasicMaterial({ color: 0xffff00, opacity: 0.05, side: THREE.DoubleSide, transparent: true })
    );

    mesh.castShadow = false;
    mesh.receiveShadow = false;

    return mesh;
}

/**
 * 
 * @param {THREE.Mesh} planeMesh 
 * @param {THREE.ArrowHelper} arrowHelper 
 * @param {String} axis 
 */
function dragClipper (planeMesh, axis) {
    const controls = new THREE.DragControls(
        [planeMesh],
        ATON.Nav._camera,
        ATON._renderer.domElement,
    ); 

    const startPosition = new THREE.Vector3();
    // Only move along the selected axis (exlude the others)
    const excludedAxes = ['x', 'y', 'z'].filter(a => a != axis);

    if (AppState.clipping.enabled && AppState.clipping.vector) {
        controls.addEventListener('dragstart', function (event) {
            startPosition.copy(event.object.position);
            ATON.Nav.setUserControl(false);
        });

        controls.addEventListener('drag', function(event) {
            const point = event.object.position;
            updateClipper(AppState.clipping.vector, point);
            for (const a of excludedAxes) {
                event.object.position[a] = startPosition[a];
            }
        });

        controls.addEventListener('dragend', function (event) {
            ATON.Nav.setUserControl(true);
        });

        AppState.clipping.controls = controls;
    }
}

/**
 * @param {String} axis - The axis along which the plane's normal should be directed,
 *                        one of 'x', 'y', 'z'
 * @param {Number} orientation - Positive (1) or negative (-1) orientation on the axis
 */
export function addClippingPlane (axis, orientation = -1) {
    axis = axis.toLowerCase();
    const bound = AppState.clipping.boundingSphere;

    if (!bound) return;

    const vector = [
        axis === 'x' ? orientation : 0,
        axis === 'y' ? orientation : 0,
        axis === 'z' ? orientation : 0,
    ];

    AppState.clipping.vector = vector;

    // First, add a default clipping plane
    // at a default point (calculated...)
    const defaultPoint = bound.center.clone();
    activateClipper(vector, axis, defaultPoint);
}

/**
 * @todo WIP!
 * Activate clipping plane
 * @param {Number[]} vector - The vector array to direct the plane
 * @param {String} axis - The x,y,z axis
 * @param {?THREE.Vector3} point - The queried scene point
 */
function activateClipper (vector, axis, point = null) {
    ATON.enableClipPlanes();
    updateClipper(vector, point);
    dragClipper(AppState.clipping.helper, axis);
}

/**
 * 
 * @param {THREE.Vector3} vector 
 * @param {THREE.Vector3} point 
 */
function updateClipper (vector, point) {
    // Useless guard...
    if (vector) {
        // Normal of the clipping plane along the axis facing down
        const normal = new THREE.Vector3(...vector).normalize();
        const plane = AppState.clipping.plane ?? ATON.addClipPlane(normal, point);
        // Add a visible plane helper for the clipping plane
        const visiblePlane = AppState.clipping.helper ?? createClippingPlaneMesh(AppState.clipping.boundingSphere);

        if (!AppState.clipping.helper) {
            AppState.root.add(visiblePlane);
            AppState.clipping.helper = visiblePlane;
        } 

        visiblePlane.position.copy(point);
        visiblePlane.lookAt(point.clone().add(normal));

        plane.setFromNormalAndCoplanarPoint(normal, point);
        AppState.clipping.plane = plane;
    }
}
/**
 * Reset clipping state after disabling from UI
 * @todo DragControls errors!!
 */
export function resetClipping () {
    AppState.clipping.enabled = false;
    ATON.disableClipPlanes();

    if (AppState.clipping.controls) {
        AppState.clipping.controls.deactivate();
        // Manually remove event listeners from DragControls!!
        AppState.renderer.domElement.removeEventListener( 'pointermove', AppState.clipping.controls.onPointerMove );
        AppState.renderer.domElement.removeEventListener( 'pointerdown', AppState.clipping.controls.onPointerDown );
        AppState.renderer.domElement.removeEventListener( 'pointerup', AppState.clipping.controls.onPointerCancel );
        AppState.renderer.domElement.removeEventListener( 'pointerleave', AppState.clipping.controls.onPointerCancel )
        AppState.clipping.controls = null;
    }

    if (AppState.clipping.helper) {
        AppState.clipping.helper.removeFromParent();
        AppState.root.remove(AppState.clipping.helper);
        AppState.clipping.helper = null;
    }
    AppState.clipping.plane = null;
    AppState.clipping.vector = null;
    // Ensure nav controls are reactivated!
    ATON.Nav.setUserControl(true);
}
