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';

function ColliderInstance({ id, colliders, setColliders, position, rotation }: {
    id: string;
    colliders: { id: string; position: [number, number, number]; rotation: [number, number, number]; colliderType: 'Default material' | 'Material 1' | 'Material 2' | 'Material 3' }[];
    setColliders: React.Dispatch<React.SetStateAction<{ id: string; position: [number, number, number]; rotation: [number, number, number]; colliderType: 'Default material' | 'Material 1' | 'Material 2' | 'Material 3' }[]>>;
    position: [number, number, number];
    rotation: [number, number, number];
}) {
    const { camera, gl, pointer, controls } = useThree();
    const [draggedId, setDraggedId] = useState<string | null>(null);
    const dragOffset = useRef(new THREE.Vector3());
    const initialDepth = useRef(0);
    const ref = useRef<RapierRigidBody>(null);
    const [color, setColor] = useState(new THREE.Color('white'));
    const [objectsOnCollider, setObjectsOnCollider] = useState<Set<RapierRigidBody>>(new Set());

    const getColorByType = (type: string) => {
        switch (type) {
            case 'Material 1':
                return new THREE.Color('blue');
            case 'Material 2':
                return new THREE.Color('green');
            case 'Material 3':
                return new THREE.Color('yellow');
            default:
                return new THREE.Color('white');
        }
    };

    useEffect(() => {
        const current = colliders.find(c => c.id === id);
        if (current) {
            setColor(getColorByType(current.colliderType));
        }
    }, [colliders, id]);


    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 = () => {
        if (!draggedId) return;

        const collider = colliders.find(c => c.id === draggedId);
        if (!collider || !ref.current) 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);
        }

        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) => {
        setColor(new THREE.Color('pink'));
        const body = e.other.rigidBody;
        const current = colliders.find(c => c.id === id);
        if (current) {
            if (body && (body.userData as any)?.materialType === current.colliderType) {
                setObjectsOnCollider(prev => {
                    const newSet = new Set(prev);
                    newSet.add(body);
                    return newSet;
                });
            }
        }
    };

    const handleMaterialExit = (e: CollisionPayload) => {
        const current = colliders.find(c => c.id === id);
        if (current)
            setColor(getColorByType(current.colliderType));
        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;

            rigidBody.setLinvel({ x: 0, y: 0, z: 2 }, true);
            rigidBody.setAngvel({ x: 0, y: 0, z: 0 }, true);
        });
    });

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

                    setColliders(prev =>
                        prev.map(c =>
                            c.id === id
                                ? {
                                    ...c,
                                    colliderType:
                                        c.colliderType === 'Default material'
                                            ? 'Material 1'
                                            : c.colliderType === 'Material 1'
                                                ? 'Material 2'
                                                : c.colliderType === 'Material 2'
                                                    ? 'Material 3'
                                                    : 'Default material',
                                }
                                : c
                        )
                    );
                }}

            >
                <boxGeometry args={[0.1, 1, 1]} />
                <meshStandardMaterial color={color} transparent opacity={0.3} />
            </mesh>
        </RigidBody>
    );
}

export default ColliderInstance;
