import { useEffect, useRef, useState } from 'react'
import * as THREE from 'three'
import { Tube, useGLTF } from '@react-three/drei';
import { useFrame, useThree } from '@react-three/fiber';
import { useIsDragging, useIsRotating, useSelectedAction, useSelectedEventSphere } from '../../../../../store/simulation/useSimulationStore';
import { useProductContext } from '../../../products/productContext';
import { useSceneContext } from '../../../../scene/sceneContext';
import { Group, Plane, Vector2, Vector3 } from 'three';
import { useVersionContext } from '../../../../builder/version/versionContext';
import { useParams } from 'react-router-dom';
import startPoint from "../../../../../assets/gltf-glb/ui/human-ui-green.glb";
import startEnd from "../../../../../assets/gltf-glb/ui/human-ui-orange.glb";
import manufacture from "../../../../../assets/gltf-glb/ui/human-ui-manufacture.glb";
import { upsertProductOrEventApi } from '../../../../../services/simulation/products/UpsertProductOrEventApi';

function HumanUi() {
    const { scene: startScene } = useGLTF(startPoint) as any;
    const { scene: endScene } = useGLTF(startEnd) as any;
    const { scene: manufactureScene } = useGLTF(manufacture) as any;
    const startMarker = useRef<Group>(null);
    const endMarker = useRef<Group>(null);
    const manufactureMarker = useRef<Group>(null);
    const outerGroup = useRef<Group>(null);
    const prevMousePos = useRef({ x: 0, y: 0 });
    const { controls, raycaster, camera } = useThree();
    const { selectedEventSphere } = useSelectedEventSphere();
    const { selectedProductStore } = useProductContext();
    const { humanStore, productStore } = useSceneContext();
    const { selectedProduct } = selectedProductStore();
    const { humans, getHumanById } = humanStore();
    const { updateEvent, getActionByUuid } = productStore();
    const [startPosition, setStartPosition] = useState<[number, number, number]>([0, 1, 0]);
    const [endPosition, setEndPosition] = useState<[number, number, number]>([0, 1, 0]);
    const [manufacturePosition, setManufacturePosition] = useState<[number, number, number]>([0, 1, 0]);
    const [startRotation, setStartRotation] = useState<[number, number, number]>([0, Math.PI, 0]);
    const [manufactureRotation, setManufactureRotation] = useState<[number, number, number]>([0, 0, 0]);
    const [endRotation, setEndRotation] = useState<[number, number, number]>([0, 0, 0]);
    const { isDragging, setIsDragging } = useIsDragging();
    const { isRotating, setIsRotating } = useIsRotating();
    const plane = useRef(new Plane(new Vector3(0, 1, 0), 0));
    const dragOffset = useRef(new Vector3());

    const [selectedHumanData, setSelectedHumanData] = useState<{
        position: [number, number, number];
        rotation: [number, number, number];
    }>({ position: [0, 0, 0], rotation: [0, 0, 0] });

    const { selectedAction } = useSelectedAction();
    const { selectedVersionStore } = useVersionContext();
    const { selectedVersion } = selectedVersionStore();
    const { projectId } = useParams();

    const currentAction = getActionByUuid(selectedProduct.productUuid, selectedAction.actionId || '');
    const isManufacture = currentAction?.actionType === 'manufacturer';

    const updateBackend = (
        productName: string,
        productUuid: string,
        projectId: string,
        eventData: EventsSchema
    ) => {
        upsertProductOrEventApi({
            productName: productName,
            productUuid: productUuid,
            projectId: projectId,
            eventDatas: eventData,
            versionId: selectedVersion?.versionId || '',
        });
    };

    useEffect(() => {
        if (!selectedEventSphere || !selectedAction?.actionId) return;

        const selectedHuman = getHumanById(selectedEventSphere.userData.modelUuid);
        if (!selectedHuman || !selectedHuman.point?.actions) return;

        setSelectedHumanData({
            position: selectedHuman.position,
            rotation: selectedHuman.rotation,
        });

        if (outerGroup.current) {
            outerGroup.current.position.set(
                selectedHuman.position[0],
                selectedHuman.position[1],
                selectedHuman.position[2]
            );
        }

        const action = selectedHuman.point.actions.find(a => a.actionUuid === selectedAction.actionId);
        if (!action) return;

        if (isManufacture) {
            if (action.manufacturePoint?.position && outerGroup.current) {
                const worldPos = new Vector3(...action.manufacturePoint.position);
                const localPosition = outerGroup.current.worldToLocal(worldPos.clone());
                setManufacturePosition([localPosition.x, 1, localPosition.z]);
                setManufactureRotation(action.manufacturePoint.rotation || [0, 0, 0]);
            } else {
                setManufacturePosition([0, 1, 0]);
                setManufactureRotation([0, 0, 0]);
            }
        } else {
            if (action.pickUpPoint?.position && outerGroup.current) {
                const worldPos = new Vector3(...action.pickUpPoint.position);
                const localPosition = outerGroup.current.worldToLocal(worldPos.clone());
                setStartPosition([localPosition.x, 1, localPosition.z]);
                setStartRotation(action.pickUpPoint.rotation || [0, 0, 0]);
            } else {
                setStartPosition([0, 1, 0]);
                setStartRotation([0, Math.PI, 0]);
            }

            if (action.dropPoint?.position && outerGroup.current) {
                const worldPos = new Vector3(...action.dropPoint.position);
                const localPosition = outerGroup.current.worldToLocal(worldPos.clone());
                setEndPosition([localPosition.x, 1, localPosition.z]);
                setEndRotation(action.dropPoint.rotation || [0, Math.PI, 0]);
            } else {
                setEndPosition([0, 1, 0]);
                setEndRotation([0, 0, 0]);
            }
        }
    }, [selectedEventSphere, outerGroup.current, selectedAction, humans]);

    const handlePointerDown = (
        e: any,
        state: "start" | "end" | "manufacture",
        rotation: "start" | "end" | "manufacture"
    ) => {
        e.stopPropagation();
        const intersection = new Vector3();
        const pointer = new Vector2((e.clientX / window.innerWidth) * 2 - 1, -(e.clientY / window.innerHeight) * 2 + 1);
        raycaster.setFromCamera(pointer, camera);
        const intersects = raycaster.ray.intersectPlane(plane.current, intersection);

        if (e.object.parent.name === "handle") {
            const normalizedX = (e.clientX / window.innerWidth) * 2 - 1;
            const normalizedY = -(e.clientY / window.innerHeight) * 2 + 1;
            prevMousePos.current = { x: normalizedX, y: normalizedY };
            setIsRotating(rotation);
            setIsDragging(null);
        } else {
            setIsDragging(state);
            setIsRotating(null);
        }

        if (intersects) {
            let localPoint: Vector3 | null = null;
            if (outerGroup.current) {
                localPoint = outerGroup.current.worldToLocal(intersection.clone());
            }
            const marker =
                state === "start" ? startMarker.current :
                    state === "end" ? endMarker.current :
                        manufactureMarker.current;
            if (marker && localPoint) {
                const markerPos = new Vector3().copy(marker.position);
                dragOffset.current.copy(markerPos.sub(localPoint));
            }
        }

        if (controls) (controls as any).enabled = false;
    };

    const handlePointerUp = () => {
        (controls as any).enabled = true;
        setIsDragging(null);
        setIsRotating(null);

        if (!selectedEventSphere?.userData.modelUuid || !selectedAction?.actionId) return;

        const selectedHuman = getHumanById(selectedEventSphere.userData.modelUuid);
        if (!selectedHuman || !outerGroup.current || !currentAction) return;

        const updatedActions = selectedHuman.point.actions.map(action => {
            if (action.actionUuid !== currentAction.actionUuid) return action;

            if (isManufacture) {
                if (!manufactureMarker.current || !outerGroup.current) return action;

                const worldPosManufacture = new Vector3(...manufacturePosition);
                const globalManufacturePosition = outerGroup.current.localToWorld(worldPosManufacture.clone());

                return {
                    ...action,
                    manufacturePoint: {
                        position: [globalManufacturePosition.x, globalManufacturePosition.y, globalManufacturePosition.z] as [number, number, number],
                        rotation: manufactureRotation
                    },
                };
            } else {
                if (!startMarker.current || !endMarker.current || !outerGroup.current) return action;

                const worldPosStart = new Vector3(...startPosition);
                const globalStartPosition = outerGroup.current.localToWorld(worldPosStart.clone());

                const worldPosEnd = new Vector3(...endPosition);
                const globalEndPosition = outerGroup.current.localToWorld(worldPosEnd.clone());

                return {
                    ...action,
                    pickUpPoint: {
                        position: [globalStartPosition.x, globalStartPosition.y, globalStartPosition.z] as [number, number, number],
                        rotation: startRotation,
                    },
                    dropPoint: {
                        position: [globalEndPosition.x, globalEndPosition.y, globalEndPosition.z] as [number, number, number],
                        rotation: endRotation,
                    },
                };
            }
        });

        const event = updateEvent(
            selectedProduct.productUuid,
            selectedEventSphere.userData.modelUuid,
            {
                ...selectedHuman,
                point: {
                    ...selectedHuman.point,
                    actions: updatedActions,
                },
            }
        );

        if (event) {
            updateBackend(
                selectedProduct.productName,
                selectedProduct.productUuid,
                projectId || '',
                event
            );
        }
    };

    useFrame(() => {
        if (!isDragging || !plane.current || !raycaster || !outerGroup.current) return;
        const intersectPoint = new Vector3();
        const intersects = raycaster.ray.intersectPlane(plane.current, intersectPoint);
        if (!intersects) return;

        const localPoint = outerGroup.current.worldToLocal(intersectPoint.clone()).add(dragOffset.current);

        if (isDragging === "start") {
            setStartPosition([localPoint.x, 1, localPoint.z]);
        } else if (isDragging === "end") {
            setEndPosition([localPoint.x, 1, localPoint.z]);
        } else if (isDragging === "manufacture") {
            setManufacturePosition([localPoint.x, 1, localPoint.z]);
        }
    });

    useFrame((state) => {
        if (!isRotating) return;
        const currentPointerX = state.pointer.x;
        const deltaX = currentPointerX - prevMousePos.current.x;
        prevMousePos.current.x = currentPointerX;

        const marker =
            isRotating === "start" ? startMarker.current :
                isRotating === "end" ? endMarker.current :
                    manufactureMarker.current;

        if (marker) {
            const rotationSpeed = 10;
            marker.rotation.y += deltaX * rotationSpeed;

            if (isRotating === "start") {
                setStartRotation([
                    marker.rotation.x,
                    marker.rotation.y,
                    marker.rotation.z,
                ]);
            } else if (isRotating === "end") {
                setEndRotation([
                    marker.rotation.x,
                    marker.rotation.y,
                    marker.rotation.z,
                ]);
            } else {
                setManufactureRotation([
                    marker.rotation.x,
                    marker.rotation.y,
                    marker.rotation.z,
                ]);
            }
        }
    });

    useEffect(() => {
        const handleGlobalPointerUp = () => {
            setIsDragging(null);
            setIsRotating(null);
            if (controls) (controls as any).enabled = true;
            handlePointerUp();
        };

        if (isDragging || isRotating) {
            window.addEventListener("pointerup", handleGlobalPointerUp);
        }

        return () => {
            window.removeEventListener("pointerup", handleGlobalPointerUp);
        };
    }, [isDragging, isRotating, startPosition, startRotation, endPosition, endRotation, manufacturePosition, manufactureRotation]);

    return (
        <>
            {selectedHumanData && (
                <group
                    position={selectedHumanData.position}
                    ref={outerGroup}
                    rotation={[0, Math.PI, 0]}
                >
                    {isManufacture ? (
                        <MarkerPrimitive
                            name="manufactureMarker"
                            refProp={manufactureMarker}
                            object={manufactureScene}
                            position={manufacturePosition}
                            rotation={manufactureRotation}
                            outerGroupRef={outerGroup}
                            type="manufacture"
                            subtype="manufacture"
                            color="#0f87f7"
                            setIsDragging={setIsDragging}
                            setIsRotating={setIsRotating}
                            handlePointerDown={handlePointerDown}
                        />
                    ) : (
                        <>
                            <MarkerPrimitive
                                name="startMarker"
                                refProp={startMarker}
                                object={startScene}
                                position={startPosition}
                                rotation={startRotation}
                                outerGroupRef={outerGroup}
                                type="start"
                                subtype="start"
                                color="lightgreen"
                                setIsDragging={setIsDragging}
                                setIsRotating={setIsRotating}
                                handlePointerDown={handlePointerDown}
                            />
                            <MarkerPrimitive
                                name="endMarker"
                                refProp={endMarker}
                                object={endScene}
                                position={endPosition}
                                rotation={endRotation}
                                outerGroupRef={outerGroup}
                                type="end"
                                subtype="end"
                                color="darkorange"
                                setIsDragging={setIsDragging}
                                setIsRotating={setIsRotating}
                                handlePointerDown={handlePointerDown}
                            />
                        </>
                    )}
                </group>
            )}
        </>
    );
}

export default HumanUi;

const MarkerPrimitive = ({
    name,
    refProp,
    object,
    position,
    rotation,
    outerGroupRef,
    type,
    subtype,
    color,
    setIsDragging,
    setIsRotating,
    handlePointerDown,
}: {
    name: string;
    refProp: any;
    object: THREE.Object3D;
    position: [number, number, number];
    rotation: [number, number, number];
    outerGroupRef: React.RefObject<THREE.Group>;
    type: string;
    subtype: string;
    color: string;
    setIsDragging: (val: any) => void;
    setIsRotating: (val: any) => void;
    handlePointerDown: any;
}) => {
    const { controls, scene, camera } = useThree();
    const [hitPoint, setHitPoint] = useState<THREE.Vector3 | null>(null);
    const [curve, setCurve] = useState<THREE.CatmullRomCurve3 | null>(null);

    useFrame(() => {
        if (!refProp.current || !outerGroupRef.current || !scene) return;

        const worldPos = new THREE.Vector3();
        refProp.current.getWorldPosition(worldPos);
        const localMarkerPos = outerGroupRef.current.worldToLocal(worldPos.clone());

        const rayOrigin = worldPos.clone();
        const direction = new THREE.Vector3(0, -1, 0);
        const raycaster = new THREE.Raycaster(rayOrigin, direction, 0.1, 1000);
        raycaster.camera = camera;
        const intersects = raycaster.intersectObjects(scene.children, true);

        const hit = intersects.find(i => i.object.name !== name);

        if (hit) {
            const localHit = outerGroupRef.current.worldToLocal(hit.point.clone());
            setHitPoint(localHit);

            const newCurve = new THREE.CatmullRomCurve3([
                localMarkerPos.clone(),
                localHit.clone(),
            ]);
            setCurve(newCurve);
        } else {
            setHitPoint(null);
            setCurve(null);
        }
    });

    return (
        <>
            <primitive
                name={name}
                ref={refProp}
                object={object}
                position={position}
                rotation={rotation}
                onPointerDown={(e: any) => {
                    handlePointerDown(e, type, subtype);
                }}
                onPointerMissed={() => {
                    setIsDragging(null);
                    setIsRotating(null);
                    if (controls) (controls as any).enabled = true;
                }}
            />

            {hitPoint && (
                <>
                    <mesh name={name} position={hitPoint} rotation={[-Math.PI / 2, 0, 0]}>
                        <torusGeometry args={[0.15, 0.02, 3, 32]} />
                        <meshBasicMaterial color={color} depthWrite={false} />
                    </mesh>

                    {curve && (
                        <Tube args={[curve, 20, 0.01, 8, true]} >
                            <meshBasicMaterial color={color} depthWrite={false} />
                        </Tube>
                    )}
                </>
            )}
        </>
    );
};