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 { calculateAssetTransformationOnWall } from "../../../../builder/wallAsset/Instances/Instance/functions/calculateAssetTransformationOnWall";

// 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, undoRedo2DStore, wallAssetStore } = useSceneContext();
    const { getWallAssetsByWall, updateWallAsset } = wallAssetStore();
    const { push2D } = undoRedo2DStore();
    const { setPosition: setAislePosition, getAislesByPointId, getAisleById } = aisleStore();
    const { setPosition: setWallPosition, getWallsByPointId, getWallById } = wallStore();
    const { setPosition: setFloorPosition, getFloorsByPointId, getFloorById } = floorStore();
    const { setPosition: setZonePosition, getZonesByPointId, getZoneById } = 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 [initial, setInitial] = useState<{
        aisles?: Aisle[],
        walls?: Wall[],
        floors?: Floor[],
        zones?: Zone[]
    }>({});
    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();
                placeMovedPoints();
            }
            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) {
                    movePoints();
                }
            }

            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, initial]);

    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 movePoints = (() => {
        if (selectedPoints.length === 0) return;

        const states: Record<string, { position: THREE.Vector3; rotation?: THREE.Euler; }> = {};
        const initials: {
            aisles?: Aisle[] | undefined;
            walls?: Wall[];
            floors?: Floor[];
            zones?: Zone[];
        } = {}

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

            if (point.userData.pointType === "Aisle") {
                const aisles = getAislesByPointId(point.userData.pointUuid);
                initials.aisles = [...(initials.aisles ?? []), ...aisles,].filter((aisle, index, self) => index === self.findIndex((a) => a.aisleUuid === aisle.aisleUuid));
            } else if (point.userData.pointType === "Wall") {
                const walls = getWallsByPointId(point.userData.pointUuid);
                initials.walls = [...(initials.walls ?? []), ...walls,].filter((wall, index, self) => index === self.findIndex((w) => w.wallUuid === wall.wallUuid));
            } else if (point.userData.pointType === "Floor") {
                const floors = getFloorsByPointId(point.userData.pointUuid);
                initials.floors = [...(initials.floors ?? []), ...floors,].filter((floor, index, self) => index === self.findIndex((f) => f.floorUuid === floor.floorUuid));
            } else if (point.userData.pointType === "Zone") {
                const zones = getZonesByPointId(point.userData.pointUuid);
                initials.zones = [...(initials.zones ?? []), ...zones,].filter((zone, index, self) => index === self.findIndex((z) => z.zoneUuid === zone.zoneUuid));
            }
        });

        setInitial(initials)
        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);
    });

    const resetToInitialPositions = () => {
        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)
    };

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

        const undoPoints: UndoRedo2DDataTypeSchema[] = [];
        const processedAisles: UndoRedo2DDataTypeSchema[] = [];
        const processedWalls: UndoRedo2DDataTypeSchema[] = [];
        const processedFloors: UndoRedo2DDataTypeSchema[] = [];
        const processedZones: UndoRedo2DDataTypeSchema[] = [];
        const wallAssetUpdates: WallAsset[] = [];

        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,
                                versionId: selectedVersion?.versionId || '',
                                userId,
                                organization,
                                aisleUuid: updatedAisle.aisleUuid,
                                points: updatedAisle.points,
                                type: updatedAisle.type
                            });

                            const old = initialStates[movedObject.uuid];
                            if (old) {
                                processedAisles.push({
                                    type: 'Aisle',
                                    lineData: {
                                        ...updatedAisle,
                                        points: [
                                            initialStates[updatedAisle.points[0].pointUuid] ?
                                                { ...updatedAisle.points[0], position: initialStates[updatedAisle.points[0].pointUuid].position }
                                                : updatedAisle.points[0],
                                            initialStates[updatedAisle.points[1].pointUuid] ?
                                                { ...updatedAisle.points[1], position: initialStates[updatedAisle.points[1].pointUuid].position }
                                                : updatedAisle.points[1]
                                        ] as [Point, Point],
                                    },
                                    newData: updatedAisle,
                                    timeStamp: new Date().toISOString(),
                                });
                            }
                        });
                    }
                } else if (point.pointType === 'Wall') {
                    const updatedWalls = getWallsByPointId(point.pointUuid);
                    if (updatedWalls?.length && projectId) {
                        updatedWalls.forEach(updatedWall => {

                            const initialWall = initial.walls?.find(w => w.wallUuid === updatedWall.wallUuid);

                            if (initialWall) {
                                const assetsOnWall = getWallAssetsByWall(updatedWall.wallUuid);

                                assetsOnWall.forEach((asset) => {
                                    const { position, rotation } = calculateAssetTransformationOnWall(asset, initialWall, updatedWall);

                                    const updatedWallAsset: WallAsset = {
                                        ...asset,
                                        position: [position[0], asset.position[1], position[2]],
                                        rotation,
                                    };

                                    wallAssetUpdates.push(updatedWallAsset);
                                });
                            }

                            // API

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

                            // SOCKET

                            socket.emit('v1:model-Wall:add', {
                                wallData: updatedWall,
                                projectId,
                                versionId: selectedVersion?.versionId || '',
                                userId,
                                organization
                            });

                            const old = initialStates[movedObject.uuid];
                            if (old) {
                                processedWalls.push({
                                    type: 'Wall',
                                    lineData: {
                                        ...updatedWall,
                                        points: [
                                            initialStates[updatedWall.points[0].pointUuid] ?
                                                { ...updatedWall.points[0], position: initialStates[updatedWall.points[0].pointUuid].position }
                                                : updatedWall.points[0],
                                            initialStates[updatedWall.points[1].pointUuid] ?
                                                { ...updatedWall.points[1], position: initialStates[updatedWall.points[1].pointUuid].position }
                                                : updatedWall.points[1]
                                        ] as [Point, Point],
                                    },
                                    newData: updatedWall,
                                    timeStamp: new Date().toISOString(),
                                });
                            }
                        });
                    }
                } else if (point.pointType === 'Floor') {
                    const Floors = getFloorsByPointId(point.pointUuid);
                    const updatedFloors = getFloorsByPointId(point.pointUuid);
                    if (updatedFloors?.length && projectId) {
                        updatedFloors.forEach(updatedFloor => {

                            // API

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

                            // SOCKET

                            socket.emit('v1:model-Floor:add', {
                                floorData: updatedFloor,
                                projectId,
                                versionId: selectedVersion?.versionId || '',
                                userId,
                                organization
                            });

                            const updatedFloorsData = updatedFloors.map((floor) => {
                                const originalFloor = Floors.find(f => f.floorUuid === floor.floorUuid) || floor;

                                const updatedPoints = originalFloor.points.map((pt: Point) => {
                                    const init = initialStates[pt.pointUuid];
                                    return init ? { ...pt, position: [init.position.x, init.position.y, init.position.z] } : pt;
                                }) as [Point, Point];

                                return {
                                    type: "Floor" as const,
                                    lineData: { ...originalFloor, points: updatedPoints },
                                    newData: floor,
                                    timeStamp: new Date().toISOString(),
                                };
                            });

                            processedFloors.push(...updatedFloorsData);
                        });
                    }
                } else if (point.pointType === 'Zone') {
                    const Zones = getZonesByPointId(point.pointUuid);
                    const updatedZones = getZonesByPointId(point.pointUuid);
                    if (updatedZones?.length && projectId) {
                        updatedZones.forEach(updatedZone => {

                            // API

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

                            // SOCKET

                            socket.emit('v1:zone:add', {
                                zoneData: updatedZone,
                                projectId,
                                versionId: selectedVersion?.versionId || '',
                                userId,
                                organization
                            });

                            const updatedZonesData = updatedZones.map((zone) => {
                                const originalZone = Zones.find(z => z.zoneUuid === zone.zoneUuid) || zone;

                                const updatedPoints = originalZone.points.map((pt: Point) => {
                                    const init = initialStates[pt.pointUuid];
                                    return init ? { ...pt, position: [init.position.x, init.position.y, init.position.z] } : pt;
                                }) as [Point, Point];

                                return {
                                    type: "Zone" as const,
                                    lineData: { ...originalZone, points: updatedPoints },
                                    newData: zone,
                                    timeStamp: new Date().toISOString(),
                                };
                            });

                            processedZones.push(...updatedZonesData);
                        });
                    }
                }
            }
        });

        setTimeout(() => {

            if (wallAssetUpdates.length > 0) {
                wallAssetUpdates.filter((wallAssets, index, self) => index === self.findIndex((w) => w.modelUuid === wallAssets.modelUuid));
                wallAssetUpdates.forEach((updatedWallAsset) => {
                    if (projectId && updatedWallAsset) {

                        updateWallAsset(updatedWallAsset.modelUuid, {
                            position: updatedWallAsset.position,
                            rotation: updatedWallAsset.rotation
                        });

                        // API

                        // upsertWallAssetApi(projectId, selectedVersion?.versionId || '', updatedWallAsset);

                        // SOCKET

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

                        socket.emit('v1:wall-asset:add', data);
                    }
                });
            }

            if (processedWalls.length > 0) {
                const wallMap = new Map<string, UndoRedo2DDataTypeSchema[]>();

                for (const wall of processedWalls) {
                    if (wall.type !== 'Wall' || !wall.lineData.wallUuid) continue;
                    const uuid = wall.lineData.wallUuid;
                    if (!wallMap.has(uuid)) wallMap.set(uuid, []);
                    wallMap.get(uuid)!.push(wall);
                }

                wallMap.forEach((actions, uuid) => {
                    const hasUpdate = actions.some(action => 'newData' in action);
                    if (hasUpdate) {
                        const wallData = getWallById(uuid);
                        if (wallData) {
                            undoPoints.push({
                                type: 'Wall',
                                lineData: actions[0].lineData as Wall,
                                newData: wallData as Wall,
                                timeStamp: new Date().toISOString()
                            });
                        }
                    }
                });
            }

            if (processedAisles.length > 0) {
                const aisleMap = new Map<string, UndoRedo2DDataTypeSchema[]>();

                for (const aisle of processedAisles) {
                    if (aisle.type !== 'Aisle' || !aisle.lineData.aisleUuid) continue;
                    const uuid = aisle.lineData.aisleUuid;
                    if (!aisleMap.has(uuid)) aisleMap.set(uuid, []);
                    aisleMap.get(uuid)!.push(aisle);
                }

                aisleMap.forEach((actions, uuid) => {
                    const hasUpdate = actions.some(action => 'newData' in action);
                    if (hasUpdate) {
                        const aisleData = getAisleById(uuid);
                        if (aisleData) {
                            undoPoints.push({
                                type: 'Aisle',
                                lineData: actions[0].lineData as Aisle,
                                newData: aisleData as Aisle,
                                timeStamp: new Date().toISOString()
                            });
                        }
                    }
                });
            }

            if (processedFloors.length > 0) {
                const floorMap = new Map<string, UndoRedo2DDataTypeSchema[]>();

                for (const floor of processedFloors) {
                    if (floor.type !== 'Floor' || !floor.lineData.floorUuid) continue;
                    const uuid = floor.lineData.floorUuid;
                    if (!floorMap.has(uuid)) {
                        floorMap.set(uuid, []);
                    }
                    floorMap.get(uuid)!.push(floor);
                }

                floorMap.forEach((actions, uuid) => {
                    const hasUpdate = actions.some(action => 'newData' in action);
                    if (hasUpdate) {
                        const floorData = getFloorById(uuid);
                        if (floorData) {
                            undoPoints.push({
                                type: 'Floor',
                                lineData: actions[0].lineData as Floor,
                                newData: floorData as Floor,
                                timeStamp: new Date().toISOString()
                            });
                        }
                    }
                });
            }

            if (processedZones.length > 0) {
                const zoneMap = new Map<string, UndoRedo2DDataTypeSchema[]>();

                for (const zone of processedZones) {
                    if (zone.type !== 'Zone' || !zone.lineData.zoneUuid) continue;
                    const uuid = zone.lineData.zoneUuid;
                    if (!zoneMap.has(uuid)) {
                        zoneMap.set(uuid, []);
                    }
                    zoneMap.get(uuid)!.push(zone);
                }

                zoneMap.forEach((actions, uuid) => {
                    const hasUpdate = actions.some(action => 'newData' in action);
                    if (hasUpdate) {
                        const zoneData = getZoneById(uuid);
                        if (zoneData) {
                            undoPoints.push({
                                type: 'Zone',
                                lineData: actions[0].lineData as Zone,
                                newData: zoneData as Zone,
                                timeStamp: new Date().toISOString()
                            });
                        }
                    }
                });
            }

            if (undoPoints.length > 0) {
                push2D({
                    type: 'Draw',
                    actions: [
                        {
                            actionType: 'Lines-Update',
                            points: undoPoints
                        }
                    ]
                });
            }
        }, 0);

        echo.success("Object moved!");
        clearSelection();
    };

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

    return (
        <>
        </>
    );
}

export default MoveControls2D;
