import { CameraControls } from '@react-three/drei';
import { useThree, useFrame } from '@react-three/fiber';
import { CollisionPayload, RapierRigidBody, RigidBody } from '@react-three/rapier';
import { useEffect, useRef, useState } from 'react'
import * as THREE from 'three';
import { useSceneContext } from '../../../sceneContext';
import { ColliderArrow } from './colliderArrow';

function ColliderInstance({ collider }: { collider: Collider }) {
    const { camera, gl, pointer, controls } = useThree();
    const { colliderStore } = useSceneContext();
    const { colliders, selectedCollider, setSelectedCollider, updateCollider, getArrowByArrowId } = colliderStore();
    const [draggedId, setDraggedId] = useState<string | null>(null);
    const dragOffset = useRef(new THREE.Vector3());
    const initialDepth = useRef(0);
    const ref = useRef<RapierRigidBody>(null);
    const [objectsOnCollider, setObjectsOnCollider] = useState<Set<RapierRigidBody>>(new Set());
    const isSelected = selectedCollider?.id === collider.id;
    const objectCounterRef = useRef(0);

    const handlePointerDown = (id: string) => {
        if (controls) {
            (controls as CameraControls).enabled = false;
        }
        setDraggedId(id);

        const collider = colliders.find(c => c.id === id);
        if (!collider || !ref.current) return;

        const pos = ref.current.translation();
        const screenPos = new THREE.Vector3(pos.x, pos.y, pos.z).project(camera);

        dragOffset.current = new THREE.Vector3(screenPos.x - pointer.x, 0, screenPos.y - pointer.y);
        initialDepth.current = new THREE.Vector3(pos.x, pos.y, pos.z).sub(camera.position).length();

        ref.current.setGravityScale(0, true);
        ref.current.setLinearDamping(10);
        ref.current.setAngularDamping(10);
    };

    const handlePointerMove = (e: PointerEvent) => {
        if (!draggedId) return;

        const collider = colliders.find(c => c.id === draggedId);
        if (!collider || !ref.current) return;
        if (e.altKey) {
            const rotation = ref.current.rotation();
            const currentQuaternion = new THREE.Quaternion(rotation.x, rotation.y, rotation.z, rotation.w);
            const deltaQuaternion = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), 0.01);
            const newQuaternion = currentQuaternion.multiply(deltaQuaternion);
            ref.current.setRotation({ x: newQuaternion.x, y: newQuaternion.y, z: newQuaternion.z, w: newQuaternion.w }, true);
            return;
        };

        const screenTarget = new THREE.Vector3(pointer.x + dragOffset.current.x, 0, pointer.y + dragOffset.current.z);
        const worldTarget = new THREE.Vector3(screenTarget.x, screenTarget.z, 0.5).unproject(camera);
        const dir = worldTarget.clone().sub(camera.position).normalize();
        const finalPos = camera.position.clone().add(dir.multiplyScalar(initialDepth.current));
        ref.current.setTranslation({ x: finalPos.x, y: finalPos.y, z: finalPos.z }, true);
    }

    const handlePointerUp = () => {
        if (controls) {
            (controls as CameraControls).enabled = true;
        }
        if (!draggedId) return;

        if (ref.current) {
            ref.current.setGravityScale(1, true);
            ref.current.setLinearDamping(0.5);
            ref.current.setAngularDamping(0.5);

            const pos = ref.current.translation();
            const rot = ref.current.rotation();

            updateCollider(draggedId, {
                position: [pos.x, pos.y, pos.z],
                rotation: [rot.x, rot.y, rot.z],
            });
        }

        setDraggedId(null);
    };

    useEffect(() => {
        const canvas = gl.domElement;
        canvas.addEventListener('pointermove', handlePointerMove);
        canvas.addEventListener('pointerup', handlePointerUp);

        return () => {
            canvas.removeEventListener('pointermove', handlePointerMove);
            canvas.removeEventListener('pointerup', handlePointerUp);
        };
    }, [colliders, draggedId, controls]);

    const handleMaterialEnter = (e: CollisionPayload) => {
        const body = e.other.rigidBody;
        if (body && (body.userData as any)?.materialType) {
            setObjectsOnCollider(prev => {
                const newSet = new Set(prev);
                newSet.add(body);
                return newSet;
            });

            if (collider.colliderCondition.conditionType === "count") {
                objectCounterRef.current += 1;
            }
        }
    };

    const handleMaterialExit = (e: CollisionPayload) => {
        const body = e.other.rigidBody;
        if (body) {
            setObjectsOnCollider(prev => {
                const newSet = new Set(prev);
                newSet.delete(body);
                return newSet;
            });
        }
    };

    useFrame(() => {
        objectsOnCollider.forEach(rigidBody => {
            if (!rigidBody) return;

            if (collider.colliderCondition.conditionType === "material") {
                const bodyMaterial = (rigidBody as any).userData.materialType;
                if (!bodyMaterial) return;
                let matchedCondition = collider.colliderCondition.arrowCondition?.find((material) => material.materialType === bodyMaterial);
                if (!matchedCondition) {
                    matchedCondition = collider.colliderCondition.arrowCondition?.find((material) => material.materialType === "Any");
                }
                if (matchedCondition) {
                    let arrowDetails = getArrowByArrowId(matchedCondition.arrowId);
                    if (arrowDetails?.position && ref.current) {
                        const arrowPos = new THREE.Vector3(...arrowDetails.position);
                        const colliderPos = new THREE.Vector3(...(collider.position || [0, 0, 0]));
                        const direction = new THREE.Vector3();
                        direction.subVectors(arrowPos, colliderPos).normalize();
                        rigidBody.setLinvel({ x: direction.x, y: direction.y, z: direction.z }, true);
                    }
                }
            } else if (collider.colliderCondition.conditionType === "count") {
                let { count, arrowsOrder } = collider.colliderCondition;
                if (!arrowsOrder || arrowsOrder.length === 0) return;
                const totalProcessed = objectCounterRef.current;
                const arrowIndex = Math.floor(totalProcessed / count) % arrowsOrder.length;
                const arrowId = arrowsOrder[arrowIndex];
                const arrowDetails = getArrowByArrowId(arrowId);
                if (arrowDetails?.position && ref.current) {
                    const arrowPos = new THREE.Vector3(...arrowDetails.position);
                    const colliderPos = new THREE.Vector3(...(collider.position || [0, 0, 0]));
                    const direction = new THREE.Vector3();
                    direction.subVectors(arrowPos, colliderPos).normalize();
                    rigidBody.setLinvel({ x: direction.x, y: direction.y, z: direction.z }, true);
                }
            }
        });
    });

    return (
        <RigidBody
            name='Sensor-Collider'
            key={collider.id}
            ref={ref}
            type="fixed"
            sensor
            position={collider.position}
            rotation={collider.rotation}
            colliders="cuboid"
            includeInvisible
            gravityScale={0}
            onIntersectionEnter={handleMaterialEnter}
            onIntersectionExit={handleMaterialExit}
        >
            <mesh
                onPointerDown={(e) => {
                    e.stopPropagation();
                    handlePointerDown(collider.id);
                }}
                onClick={(e) => {
                    e.stopPropagation();
                    setSelectedCollider(collider);

                }}
            >
                <boxGeometry args={[0.1, 1, 1]} />
                <meshStandardMaterial color={isSelected ? 'green' : 'white'} transparent opacity={0.3} />
            </mesh>
            {isSelected && collider.arrows.map((arrow) => (
                <ColliderArrow
                    key={arrow.arrowId}
                    startPosition={collider.position}
                    endPosition={arrow.position}
                    colliderRotation={collider.rotation}
                    colliderId={collider.id}
                    arrowId={arrow.arrowId}
                />
            ))}

        </RigidBody>
    );
}

export default ColliderInstance;
