import * as THREE from "three";
import { useCallback, useEffect, useMemo, useState } from "react";
import { useFrame, useThree } from "@react-three/fiber";
import { useSocketStore, useToggleView, useToolMode, } from "../../../../../store/builder/store";
import { detectModifierKeys } from "../../../../../utils/shortcutkeys/detectModifierKeys";
import { useParams } from "react-router-dom";
import { getUserData } from "../../../../../functions/getUserData";
import { useSceneContext } from "../../../sceneContext";
import { useVersionContext } from "../../../../builder/version/versionContext";
import { useSelectedPoints } from "../../../../../store/simulation/useSimulationStore";
import useModuleStore from "../../../../../store/useModuleStore";

// import { upsertAisleApi } from "../../../../../services/factoryBuilder/aisle/upsertAisleApi";
// import { upsertWallApi } from "../../../../../services/factoryBuilder/wall/upsertWallApi";
// import { upsertFloorApi } from "../../../../../services/factoryBuilder/floor/upsertFloorApi";
// import { upsertZoneApi } from "../../../../../services/factoryBuilder/zone/upsertZoneApi";

function MoveControls2D({
    movedObjects,
    setMovedObjects,
    pastedObjects,
    setpastedObjects,
    duplicatedObjects,
    setDuplicatedObjects,
    rotatedObjects,
    setRotatedObjects,
}: any) {
    const { camera, controls, gl, scene, pointer, raycaster } = useThree();
    const plane = useMemo(() => new THREE.Plane(new THREE.Vector3(0, 1, 0), 0), []);
    const { toolMode } = useToolMode();
    const { toggleView } = useToggleView();
    const { activeModule } = useModuleStore();
    const { selectedPoints, clearSelectedPoints } = useSelectedPoints();
    const { socket } = useSocketStore();
    const { userId, organization } = getUserData();
    const { projectId } = useParams();
    const { selectedVersionStore } = useVersionContext();
    const { selectedVersion } = selectedVersionStore();
    const { aisleStore, wallStore, floorStore, zoneStore } = useSceneContext();
    const { setPosition: setAislePosition, getAislesByPointId } = aisleStore();
    const { setPosition: setWallPosition, getWallsByPointId } = wallStore();
    const { setPosition: setFloorPosition, getFloorsByPointId } = floorStore();
    const { setPosition: setZonePosition, getZonesByPointId } = zoneStore();
    const [dragOffset, setDragOffset] = useState<THREE.Vector3 | null>(null);
    const [initialPositions, setInitialPositions] = useState<Record<string, THREE.Vector3>>({});
    const [initialStates, setInitialStates] = useState<Record<string, { position: THREE.Vector3; rotation?: THREE.Euler; }>>({});
    const [isMoving, setIsMoving] = useState(false);

    useEffect(() => {
        if (!camera || !scene || !toggleView) return;

        const canvasElement = gl.domElement;
        canvasElement.tabIndex = 0;

        let isMoving = false;

        const onPointerDown = () => {
            isMoving = false;
        };

        const onPointerMove = () => {
            isMoving = true;
        };

        const onPointerUp = (event: PointerEvent) => {
            if (!isMoving && movedObjects.length > 0 && event.button === 0) {
                event.preventDefault();
                placeMovedAssets();
            }
            if (!isMoving && movedObjects.length > 0 && event.button === 2) {
                event.preventDefault();

                clearSelection();
                setMovedObjects([]);
                resetToInitialPositions();
            }
        };

        const onKeyDown = (event: KeyboardEvent) => {
            const keyCombination = detectModifierKeys(event);

            if (pastedObjects.length > 0 || duplicatedObjects.length > 0 || rotatedObjects.length > 0) return;

            if (keyCombination === "G") {
                if (selectedPoints.length > 0) {
                    moveAssets();
                }
            }

            if (keyCombination === "ESCAPE") {
                event.preventDefault();

                clearSelection();
                setMovedObjects([]);
                resetToInitialPositions();
            }
        };

        if (toggleView && selectedPoints.length > 0) {
            canvasElement.addEventListener("pointerdown", onPointerDown);
            canvasElement.addEventListener("pointermove", onPointerMove);
            canvasElement.addEventListener("pointerup", onPointerUp);
            canvasElement.addEventListener("keydown", onKeyDown);
        }

        return () => {
            canvasElement.removeEventListener("pointerdown", onPointerDown);
            canvasElement.removeEventListener("pointermove", onPointerMove);
            canvasElement.removeEventListener("pointerup", onPointerUp);
            canvasElement.removeEventListener("keydown", onKeyDown);
        };
    }, [camera, controls, scene, toggleView, selectedPoints, socket, pastedObjects, duplicatedObjects, movedObjects, rotatedObjects]);

    useEffect(() => {
        if (toolMode !== 'move' || !toggleView) {
            if (movedObjects.length > 0) {
                resetToInitialPositions();
            }
            clearSelection();
        }
    }, [activeModule, toolMode, toggleView, movedObjects]);

    useFrame(() => {
        if (!isMoving || movedObjects.length === 0 || !dragOffset) return;

        raycaster.setFromCamera(pointer, camera);
        const intersectionPoint = new THREE.Vector3();
        const hit = raycaster.ray.intersectPlane(plane, intersectionPoint);

        if (hit) {
            const baseNewPosition = new THREE.Vector3().addVectors(hit, dragOffset);

            movedObjects.forEach((movedPoint: THREE.Object3D) => {
                if (movedPoint.userData.pointUuid) {
                    const point: Point = movedPoint.userData as Point;
                    const initialPosition = initialPositions[movedPoint.uuid];

                    if (initialPosition) {
                        const relativeOffset = new THREE.Vector3().subVectors(
                            initialPosition,
                            initialPositions[movedObjects[0].uuid]
                        );

                        const newPosition = new THREE.Vector3().addVectors(baseNewPosition, relativeOffset);
                        const positionArray: [number, number, number] = [newPosition.x, newPosition.y, newPosition.z];

                        if (point.pointType === 'Aisle') {
                            setAislePosition(point.pointUuid, positionArray);
                        } else if (point.pointType === 'Wall') {
                            setWallPosition(point.pointUuid, positionArray);
                        } else if (point.pointType === 'Floor') {
                            setFloorPosition(point.pointUuid, positionArray);
                        } else if (point.pointType === 'Zone') {
                            setZonePosition(point.pointUuid, positionArray);
                        }
                    }
                }
            });
        }
    });

    const calculateDragOffset = useCallback((point: THREE.Object3D, hitPoint: THREE.Vector3) => {
        const pointPosition = new THREE.Vector3().copy(point.position);
        return new THREE.Vector3().subVectors(pointPosition, hitPoint);
    }, []);

    const moveAssets = useCallback(() => {
        if (selectedPoints.length === 0) return;

        const states: Record<string, { position: THREE.Vector3; rotation?: THREE.Euler; }> = {};

        selectedPoints.forEach((point: THREE.Object3D) => {
            states[point.uuid] = {
                position: new THREE.Vector3().copy(point.position),
                rotation: point.rotation ? new THREE.Euler().copy(point.rotation) : undefined
            };
        });
        setInitialStates(states);

        const positions: Record<string, THREE.Vector3> = {};
        selectedPoints.forEach((point: THREE.Object3D) => { positions[point.uuid] = new THREE.Vector3().copy(point.position); });
        setInitialPositions(positions);

        raycaster.setFromCamera(pointer, camera);
        const intersectionPoint = new THREE.Vector3();
        const hit = raycaster.ray.intersectPlane(plane, intersectionPoint);

        if (hit && selectedPoints[0]) {
            const offset = calculateDragOffset(selectedPoints[0], hit);
            setDragOffset(offset);
        }

        setMovedObjects(selectedPoints);
        setIsMoving(true);
    }, [selectedPoints, camera, pointer, plane, raycaster, calculateDragOffset]);

    const resetToInitialPositions = useCallback(() => {
        setTimeout(() => {
            movedObjects.forEach((movedPoint: THREE.Object3D) => {
                if (movedPoint.userData.pointUuid && initialStates[movedPoint.uuid]) {
                    const point: Point = movedPoint.userData as Point;
                    const initialState = initialStates[movedPoint.uuid];
                    const positionArray: [number, number, number] = [
                        initialState.position.x,
                        initialState.position.y,
                        initialState.position.z
                    ];

                    if (point.pointType === 'Aisle') {
                        setAislePosition(point.pointUuid, positionArray);
                    } else if (point.pointType === 'Wall') {
                        setWallPosition(point.pointUuid, positionArray);
                    } else if (point.pointType === 'Floor') {
                        setFloorPosition(point.pointUuid, positionArray);
                    } else if (point.pointType === 'Zone') {
                        setZonePosition(point.pointUuid, positionArray);
                    }
                }
            });
        }, 0)
    }, [movedObjects, initialStates, setAislePosition, setWallPosition, setFloorPosition, setZonePosition]);

    const placeMovedAssets = () => {
        if (movedObjects.length === 0) return;

        movedObjects.forEach((movedObject: THREE.Object3D) => {
            if (movedObject.userData.pointUuid) {
                const point: Point = movedObject.userData as Point;

                if (point.pointType === 'Aisle') {
                    const updatedAisles = getAislesByPointId(point.pointUuid);
                    if (updatedAisles.length > 0 && projectId) {
                        updatedAisles.forEach((updatedAisle) => {

                            // API

                            // upsertAisleApi(updatedAisle.aisleUuid, updatedAisle.points, updatedAisle.type, projectId, selectedVersion?.versionId || '');

                            // SOCKET 

                            socket.emit('v1:model-aisle:add', {
                                projectId: projectId,
                                versionId: selectedVersion?.versionId || '',
                                userId: userId,
                                organization: organization,
                                aisleUuid: updatedAisle.aisleUuid,
                                points: updatedAisle.points,
                                type: updatedAisle.type
                            })
                        })
                    }
                } else if (point.pointType === 'Wall') {
                    const updatedWalls = getWallsByPointId(point.pointUuid);
                    if (updatedWalls && updatedWalls.length > 0 && projectId) {
                        updatedWalls.forEach((updatedWall) => {

                            // API

                            // upsertWallApi(projectId, selectedVersion?.versionId || '', updatedWall);

                            // SOCKET 

                            const data = {
                                wallData: updatedWall,
                                projectId: projectId,
                                versionId: selectedVersion?.versionId || '',
                                userId: userId,
                                organization: organization
                            }

                            socket.emit('v1:model-Wall:add', data);
                        });
                    }
                } else if (point.pointType === 'Floor') {
                    const updatedFloors = getFloorsByPointId(point.pointUuid);
                    if (updatedFloors && updatedFloors.length > 0 && projectId) {
                        updatedFloors.forEach((updatedFloor) => {

                            // API

                            // upsertFloorApi(projectId, selectedVersion?.versionId || '', updatedFloor);

                            // SOCKET

                            const data = {
                                floorData: updatedFloor,
                                projectId: projectId,
                                versionId: selectedVersion?.versionId || '',
                                userId: userId,
                                organization: organization
                            }

                            socket.emit('v1:model-Floor:add', data);
                        });
                    }
                } else if (point.pointType === 'Zone') {
                    const updatedZones = getZonesByPointId(point.pointUuid);
                    if (updatedZones && updatedZones.length > 0 && projectId) {
                        updatedZones.forEach((updatedZone) => {

                            // API

                            // upsertZoneApi(projectId, selectedVersion?.versionId || '', updatedZone);

                            // SOCKET

                            const data = {
                                zoneData: updatedZone,
                                projectId: projectId,
                                versionId: selectedVersion?.versionId || '',
                                userId: userId,
                                organization: organization
                            }

                            socket.emit('v1:zone:add', data);
                        });
                    }
                }
            }
        })

        echo.success("Object moved!");

        clearSelection();
    };

    const clearSelection = () => {
        setpastedObjects([]);
        setDuplicatedObjects([]);
        setMovedObjects([]);
        setRotatedObjects([]);
        clearSelectedPoints();
    };

    return (
        <>
        </>
    );
}

export default MoveControls2D;
