import { PerspectiveCamera, PointerLockControls } from "@react-three/drei";
import { useFrame, useThree } from "@react-three/fiber";
import { useContext, useEffect, useRef } from "react";
import { AppContext } from "../App";
import { CapsuleCollider, RigidBody, useRapier, vec3 } from "@react-three/rapier";
import { Vector3 } from "three";
import { useBeforePhysicsStep } from "@react-three/rapier";
import { Raycaster } from "three";

const _movespeed = 3.0;

export default function Player() {
    const controlsRef = useRef();
    const { keys, setMessages, apiToken } = useContext(AppContext);
    const rapier = useRapier();
    const controller = useRef();
    const collider = useRef();
    const rigidbody = useRef();
    const camera = useRef();
    const raycaster = useRef(new Raycaster());
    const { scene, pointer } = useThree();

    const refState = useRef({
        grounded: false,
        velocity: vec3(),
    });

    const onClick = (_) => {
        raycaster.current.setFromCamera(pointer, camera.current);
        const intersect = raycaster.current.intersectObjects(scene.children).at(0);
        if (intersect && intersect.object.name === 'ground' && intersect.distance < 10 && controlsRef.current.isLocked) {
            const message = prompt();
            if (message) {
                let data = new FormData();
                data.append("position", JSON.stringify(intersect.point.toArray()));
                data.append("message", message);
                data.append("token", apiToken.current);
                fetch('/api/new_message', {
                    method: "POST",
                    body: data,
                }).then(() => {
                    fetch('/api/message').then((res) => res.json()).then((data) => {
                        console.log(data);
                        setMessages(data);
                    });
                });
            }
            controlsRef.current.lock();
        }
    };

    useEffect(() => {
        window.addEventListener("click", onClick);
        return () => window.removeEventListener("click", onClick);
    }, []);

    useEffect(() => {
        const c = rapier.world.createCharacterController(0.1);
        c.setApplyImpulsesToDynamicBodies(true);
        c.setCharacterMass(0.2);
        controller.current = c;
    }, []);

    useFrame((_, delta) => {
        const fov_axis = +(keys.current.includes('Minus')) - +(keys.current.includes('Equal'));
        if (fov_axis != 0) {
            camera.current.fov += 45 * fov_axis * delta;
            camera.current.updateProjectionMatrix();
        }
    });

    useBeforePhysicsStep((world) => {
        if (controller.current && rigidbody.current && collider.current) {
            const move_axis_x = +(keys.current.includes('KeyD')) - +(keys.current.includes('KeyA'));
            const move_axis_z = +(keys.current.includes('KeyW')) - +(keys.current.includes('KeyS'));

            const { velocity } = refState.current;
            const position = vec3(rigidbody.current.translation());
            const movement = vec3();

            const forward = new Vector3();
            camera.current.getWorldDirection(forward);
            const left = new Vector3().crossVectors(forward, camera.current.up);

            movement.x += move_axis_z * world.timestep * _movespeed * forward.x;
            movement.z += move_axis_z * world.timestep * _movespeed * forward.z;
            movement.x += move_axis_x * world.timestep * _movespeed * left.x;
            movement.z += move_axis_x * world.timestep * _movespeed * left.z;

            if (refState.current.grounded) {
                velocity.y = 0;
            } else {
                velocity.y -= 9.81 * world.timestep * world.timestep;
            }

            movement.add(velocity);

            controller.current.computeColliderMovement(collider.current, movement);
            refState.current.grounded = controller.current.computedGrounded();

            let correctedMovement = controller.current.computedMovement();
            position.add(vec3(correctedMovement));

            rigidbody.current.setNextKinematicTranslation(position);
        }
    });

    return (
        <RigidBody type="kinematicPosition" colliders={false} ref={rigidbody} position={[0, 2, 0]}>
            <PerspectiveCamera makeDefault position={[0, .9, 0]} ref={camera} fov={80} />
            <PointerLockControls ref={controlsRef} />
            <CapsuleCollider ref={collider} args={[1, 0.5]} />
        </RigidBody>
    );
}

