import * as THREE from 'three';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
import URDFLoader from './urdf/URDFLoader.js';

// Setup scene, camera, renderer
const canvas = document.getElementById('urdf-canvas');
const renderer = new THREE.WebGLRenderer({ canvas, antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);

const scene = new THREE.Scene();
scene.background = new THREE.Color(0xaaaaaa);

const camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 100);
camera.position.set(2, 2, 2);
camera.lookAt(0, 0, 0);

// Lights
scene.add(new THREE.AmbientLight(0xffffff, 0.6));
const directional = new THREE.DirectionalLight(0xffffff, 0.8);
directional.position.set(15, 10, 7);
const directional2 = new THREE.DirectionalLight(0xffffff, 0.8);
directional2.position.set(-15, 10, -7);
scene.add(directional2);

// Controls
const controls = new OrbitControls(camera, renderer.domElement);
controls.target.set(0, 0.5, 0);
controls.update();

// Helpers
scene.add(new THREE.AxesHelper(1));
const gridHelper = new THREE.GridHelper(2, 10);
//gridHelper.rotation.x = Math.PI / 2;
gridHelper.position.z = 0;
scene.add(gridHelper);

// Raycasting
const raycaster = new THREE.Raycaster();
const mouse = new THREE.Vector2();
let hoveredJoint = null;
let isDragging = false;
let lastX = null;
let worldAxis = null;

let draggedJoint = null;
let arc = null;
const jointAngles = {}; // key: jointName, value: current angle in radians




function findJointAncestor(object) {
    while (object && object.parent) {
        if (object.type === 'URDFJoint') return object;
        object = object.parent;
    }
    return null;
}

function createRotationArc(axis, lower, upper, radius = 0.1, segments = 32) {
    const points = [];
    console.log(axis);
    console.log(axis.clone());
    // ✅ Ensure axis is normalized
    const normalizedAxis = axis.clone().normalize();

    // ✅ Find a perpendicular vector
    let perp = new THREE.Vector3(0, 1, 0);
    if (Math.abs(normalizedAxis.dot(perp)) > 0.99) {
        perp = new THREE.Vector3(1, 0, 0); // fallback if axis is nearly parallel to Y
    }
    const tangent = perp.cross(normalizedAxis).normalize();

    for (let i = 0; i <= segments; i++) {
        const angle = lower + (upper - lower) * (i / segments);
        const point = tangent.clone().applyAxisAngle(normalizedAxis, angle).multiplyScalar(radius);
        points.push(point);
    }

    const geometry = new THREE.BufferGeometry().setFromPoints(points);
    const material = new THREE.LineBasicMaterial({ color: 0xffff00 });
    return new THREE.Line(geometry, material);
}

function createRotationSector(axis, lower, upper, radius = 0.1, segments = 64) {
    const motorMargin = 0.4; // hardcoded extra range beyond joint limits
    const motorLower = lower - motorMargin;
    const motorUpper = upper + motorMargin;

    const redMaterial = new THREE.MeshBasicMaterial({
        color: 0xff0000,
        transparent: true,
        opacity: 0.2,
        side: THREE.DoubleSide,
        depthTest: false,
        depthWrite: false
    });

    const whiteMaterial = new THREE.MeshBasicMaterial({
        color: 0xffffff,
        transparent: true,
        opacity: 0.6,
        side: THREE.DoubleSide,
        depthTest: false,
        depthWrite: false
    });

    function createArc(start, end, material) {
        const shape = new THREE.Shape();
        shape.moveTo(0, 0);
        for (let i = 0; i <= segments; i++) {
            const angle = start + (end - start) * (i / segments);
            shape.lineTo(Math.cos(angle) * radius, Math.sin(angle) * radius);
        }
        shape.lineTo(0, 0);
        const geometry = new THREE.ShapeGeometry(shape);
        return new THREE.Mesh(geometry, material);
    }

    const redLeft = createArc(motorLower, lower, redMaterial);
    const whiteArc = createArc(lower, upper, whiteMaterial);
    const redRight = createArc(upper, motorUpper, redMaterial);

    // Orient all arcs to match the axis
    const basis = new THREE.Matrix4();
    const up = new THREE.Vector3(0, 0, 1);
    const quaternion = new THREE.Quaternion().setFromUnitVectors(up, axis.clone().normalize());
    basis.makeRotationFromQuaternion(quaternion);

    redLeft.applyMatrix4(basis);
    whiteArc.applyMatrix4(basis);
    redRight.applyMatrix4(basis);

    const group = new THREE.Group();
    group.add(redLeft);
    group.add(whiteArc);
    group.add(redRight);

    return group;
}


function createAngleIndicator(axis, angle, radius = 0.1) {
    const dir = new THREE.Vector3(Math.cos(angle), Math.sin(angle), 0).multiplyScalar(radius);

    const geometry = new THREE.BufferGeometry().setFromPoints([
        new THREE.Vector3(0, 0, 0),
        dir,
    ]);

    const material = new THREE.LineBasicMaterial({
        color: 0xffffff,         // white
        linewidth: 2,            // note: linewidth only works in some renderers
        depthTest: false,        // ✅ always on top
        depthWrite: false
    });

    const line = new THREE.Line(geometry, material);

    // Rotate into axis plane
    const up = new THREE.Vector3(0, 0, 1);
    const quaternion = new THREE.Quaternion().setFromUnitVectors(up, axis.clone().normalize());
    line.applyQuaternion(quaternion);

    return line;
}



// Load URDF
const loader = new URDFLoader();
loader.packages = { '': './urdf/' };

function findObjectByName(root, name) {
    let result = null;
    root.traverse(child => {
        if (child.name === name) result = child;
    });
    return result;
}

loader.load('./urdf/sample.urdf', robot => {
    //scene.rotation.x = -Math.PI / 2;
    robot.rotation.x = -Math.PI / 2;
    scene.add(robot);
    document.getElementById('status').textContent = 'URDF Loaded';

    loader.parseVisual = true;
    loader.parseCollision = false;

    // Add AxesHelpers to all meshes
    robot.traverse(obj => {
        if (obj.isMesh) obj.add(new THREE.AxesHelper(0.05));
    });

    // Setup pointer events
    canvas.addEventListener('pointermove', event => {
        mouse.x = (event.clientX / canvas.clientWidth) * 2 - 1;
        mouse.y = -(event.clientY / canvas.clientHeight) * 2 + 1;

        raycaster.setFromCamera(mouse, camera);
        const intersects = raycaster.intersectObjects(robot.children, true);

        if (isDragging && draggedJoint && worldAxis) {
            const deltaX = event.clientX - lastX;
            lastX = event.clientX;
            const angleDelta = deltaX * 0.005;

            const jointName = draggedJoint.name;
            const limits = robot.joints[jointName]?.limit;
            const lower = limits?.lower ?? -Math.PI;
            const upper = limits?.upper ?? Math.PI;

            const currentAngle = jointAngles[jointName] ?? 0;
            const proposedAngle = currentAngle + angleDelta;

            // ✅ Clamp to limits
            const clampedAngle = Math.max(lower, Math.min(upper, proposedAngle));
            const actualDelta = clampedAngle - currentAngle;

            // ✅ Apply rotation
            draggedJoint.rotateOnAxis(worldAxis, actualDelta);
            jointAngles[jointName] = clampedAngle;


            // Remove old indicator
            if (draggedJoint.userData.gizmo?.indicator) {
                draggedJoint.parent.remove(draggedJoint.userData.gizmo.indicator);
            }

            // Add updated indicator
            const newIndicator = createAngleIndicator(worldAxis, jointAngles[jointName]);
            newIndicator.position.copy(draggedJoint.position);
            draggedJoint.parent.add(newIndicator);
            draggedJoint.userData.gizmo.indicator = newIndicator;

        }



        if (intersects.length > 0) {
            const target = intersects[0].object;

            // Reset previous highlight
            if (
                hoveredJoint &&
                hoveredJoint.material &&
                'emissive' in hoveredJoint.material &&
                typeof hoveredJoint.material.emissive.setHex === 'function'
            ) {
                hoveredJoint.material.emissive.setHex(0x000000);
            }

            hoveredJoint = target;

            // Highlight new joint
            if (
                hoveredJoint.material &&
                'emissive' in hoveredJoint.material &&
                typeof hoveredJoint.material.emissive.setHex === 'function'
            ) {
                hoveredJoint.material.emissive.setHex(0x333333);
            }

        } else {
            if (
                hoveredJoint &&
                hoveredJoint.material &&
                'emissive' in hoveredJoint.material &&
                typeof hoveredJoint.material.emissive.setHex === 'function'
            ) {
                hoveredJoint.material.emissive.setHex(0x000000);
            }
            hoveredJoint = null;
        }

    });

    canvas.addEventListener('pointerdown', event => {
        if (!hoveredJoint) return;

        isDragging = true;
        lastX = event.clientX;
        controls.enabled = false;

        draggedJoint = findJointAncestor(hoveredJoint);


        if (!draggedJoint) return;

        const jointName = draggedJoint.name;
        const jointData = robot.joints?.[jointName];

        // ✅ Check if axis is a valid THREE.Vector3
        if (!(jointData?.axis instanceof THREE.Vector3)) {
            console.warn(`Invalid axis for joint "${jointName}":`, jointData?.axis);
            return;
        }

        const urdfAxis = jointData.axis.clone();
        if (urdfAxis.lengthSq() === 0) {
            console.warn(`Zero-length axis for joint "${jointName}"`);
            return;
        }

        worldAxis = urdfAxis.normalize();
        const limits = robot.joints[jointName]?.limit;
        const lower = limits?.lower ?? -Math.PI;
        const upper = limits?.upper ?? Math.PI;

        const sector = createRotationSector(worldAxis, lower, upper);
        const indicator = createAngleIndicator(worldAxis, jointAngles[jointName] ?? 0);

        sector.position.copy(draggedJoint.position);
        indicator.position.copy(draggedJoint.position);

        draggedJoint.parent.add(sector);
        draggedJoint.parent.add(indicator);

        // Store for cleanup
        draggedJoint.userData.gizmo = { sector, indicator };




        console.log(`Dragging joint "${jointName}" on axis`, worldAxis.toArray());
    });






    canvas.addEventListener('pointerup', () => {
        isDragging = false;
        controls.enabled = true;
        const gizmo = draggedJoint?.userData.gizmo;
        if (gizmo) {
            draggedJoint.parent.remove(gizmo.sector);
            draggedJoint.parent.remove(gizmo.indicator);
        }


        draggedJoint = null;
        worldAxis = null;
    });


    animate();
});

function animate() {
    requestAnimationFrame(animate);
    renderer.render(scene, camera);
}

