import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import * as THREE from "three";
import { useThree } from "@react-three/fiber";
import { SelectionHelper } from "../selectionHelper";
import { SelectionBox } from "three/examples/jsm/interactive/SelectionBox";

import useModuleStore from "../../../../../store/useModuleStore";
import { useParams } from "react-router-dom";
import { getUserData } from "../../../../../functions/getUserData";
import { useSceneContext } from "../../../sceneContext";
import { useVersionContext } from "../../../../builder/version/versionContext";
import { useSocketStore, useToggleView, useToolMode, } from "../../../../../store/builder/store";
import { useSelectedPoints } from "../../../../../store/simulation/useSimulationStore";
import { useBuilderStore } from "../../../../../store/builder/useBuilderStore";
import MoveControls2D from "./moveControls2D";

import { deleteAisleApi } from "../../../../../services/factoryBuilder/aisle/deleteAisleApi";
import { deleteWallApi } from "../../../../../services/factoryBuilder/wall/deleteWallApi";
import { deleteFloorApi } from "../../../../../services/factoryBuilder/floor/deleteFloorApi";
import { upsertFloorApi } from "../../../../../services/factoryBuilder/floor/upsertFloorApi";
import { deleteZoneApi } from "../../../../../services/factoryBuilder/zone/deleteZoneApi";
import { upsertZoneApi } from "../../../../../services/factoryBuilder/zone/upsertZoneApi";
import { deleteWallAssetApi } from "../../../../../services/factoryBuilder/asset/wallAsset/deleteWallAssetApi";

const SelectionControls2D: React.FC = () => {
    const { camera, controls, gl, scene, pointer } = useThree();
    const { toggleView } = useToggleView();
    const { selectedPoints, setSelectedPoints, clearSelectedPoints } = useSelectedPoints();
    const [movedObjects, setMovedObjects] = useState<THREE.Object3D[]>([]);
    const [rotatedObjects, setRotatedObjects] = useState<THREE.Object3D[]>([]);
    const [copiedObjects, setCopiedObjects] = useState<THREE.Object3D[]>([]);
    const [pastedObjects, setPastedObjects] = useState<THREE.Object3D[]>([]);
    const [duplicatedObjects, setDuplicatedObjects] = useState<THREE.Object3D[]>([]);
    const { activeModule } = useModuleStore();
    const { socket } = useSocketStore();
    const selectionBox = useMemo(() => new SelectionBox(camera, scene), [camera, scene]);
    const { toolMode } = useToolMode();
    const { selectedVersionStore } = useVersionContext();
    const { selectedVersion } = selectedVersionStore();
    const { projectId } = useParams();
    const { hoveredLine, hoveredPoint } = useBuilderStore();
    const { aisleStore, wallStore, floorStore, zoneStore, undoRedo2DStore, wallAssetStore } = useSceneContext();
    const { getWallAssetsByWall, removeWallAsset } = wallAssetStore();
    const { push2D } = undoRedo2DStore();
    const { removePoint: removeAislePoint } = aisleStore();
    const { removePoint: removeWallPoint } = wallStore();
    const { removePoint: removeFloorPoint, getFloorsByPointId, getFloorById } = floorStore();
    const { removePoint: removeZonePoint, getZonesByPointId, getZoneById } = zoneStore();

    const isDragging = useRef(false);
    const isLeftMouseDown = useRef(false);
    const isSelecting = useRef(false);
    const isRightClick = useRef(false);
    const rightClickMoved = useRef(false);
    const isCtrlSelecting = useRef(false);
    const isShiftSelecting = useRef(false);
    const { userId, organization } = getUserData();

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

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

        const helper = new SelectionHelper(gl);

        const onPointerDown = (event: PointerEvent) => {
            if (event.button === 2) {
                isRightClick.current = true;
                rightClickMoved.current = false;
            } else if (event.button === 0) {
                isSelecting.current = false;
                isCtrlSelecting.current = event.ctrlKey;
                isShiftSelecting.current = event.shiftKey;
                isLeftMouseDown.current = true;
                isDragging.current = false;
                if (event.ctrlKey && duplicatedObjects.length === 0) {
                    if (controls) (controls as any).enabled = false;
                    selectionBox.startPoint.set(pointer.x, pointer.y, 0);
                }
            }
        };

        const onPointerMove = (event: PointerEvent) => {
            if (isRightClick.current) {
                rightClickMoved.current = true;
            }
            if (isLeftMouseDown.current) {
                isDragging.current = true;
            }
            isSelecting.current = true;
            if (helper.isDown && event.ctrlKey && duplicatedObjects.length === 0 && isCtrlSelecting.current) {
                selectionBox.endPoint.set(pointer.x, pointer.y, 0);
            }
        };

        const onPointerUp = (event: PointerEvent) => {
            if (event.button === 2 && !event.ctrlKey && !event.shiftKey) {
                isRightClick.current = false;
                if (!rightClickMoved.current) {
                    clearSelection();
                }
                return;
            }

            if (isSelecting.current && isCtrlSelecting.current) {
                isCtrlSelecting.current = false;
                isSelecting.current = false;
                if (event.ctrlKey && duplicatedObjects.length === 0) {
                    selectAssets();
                }
            } else if (!isSelecting.current && selectedPoints.length > 0 && ((!event.ctrlKey && !event.shiftKey && pastedObjects.length === 0 && duplicatedObjects.length === 0 && movedObjects.length === 0 && rotatedObjects.length === 0) || event.button !== 0)) {
                clearSelection();
                helper.enabled = true;
                isCtrlSelecting.current = false;
            } else if (controls) {
                (controls as any).enabled = true;
            }

            if (!isDragging.current && isLeftMouseDown.current && isShiftSelecting.current && event.shiftKey) {
                isShiftSelecting.current = false;
                isLeftMouseDown.current = false;
                isDragging.current = false;

            } else if (controls) {
                (controls as any).enabled = true;
            }
        };

        const onKeyDown = (event: KeyboardEvent) => {
            if (movedObjects.length > 0 || rotatedObjects.length > 0) return;
            if (event.key.toLowerCase() === "escape") {
                event.preventDefault();
                clearSelection();
            }
            if (event.key.toLowerCase() === "delete") {
                event.preventDefault();
                deleteSelection();
            }
        };

        const onContextMenu = (event: MouseEvent) => {
            event.preventDefault();
            if (!rightClickMoved.current) {
                clearSelection();
            }
            rightClickMoved.current = false;
        };

        if (toggleView && toolMode === 'move') {
            helper.enabled = true;
            canvasElement.addEventListener("pointermove", onPointerMove);
            canvasElement.addEventListener("pointerup", onPointerUp);
            canvasElement.addEventListener("pointerdown", onPointerDown);
            canvasElement.addEventListener("contextmenu", onContextMenu);
            canvasElement.addEventListener("keydown", onKeyDown);
        } else {
            helper.enabled = false;
            helper.dispose();
        }

        return () => {
            canvasElement.removeEventListener("pointerdown", onPointerDown);
            canvasElement.removeEventListener("pointermove", onPointerMove);
            canvasElement.removeEventListener("contextmenu", onContextMenu);
            canvasElement.removeEventListener("pointerup", onPointerUp);
            canvasElement.removeEventListener("keydown", onKeyDown);
            helper.enabled = false;
            helper.dispose();
        };
    }, [camera, controls, scene, toggleView, selectedPoints, copiedObjects, pastedObjects, duplicatedObjects, movedObjects, socket, rotatedObjects, toolMode, hoveredLine, hoveredPoint]);

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

    const selectAssets = useCallback(() => {
        selectionBox.endPoint.set(pointer.x, pointer.y, 0);
        if (controls) (controls as any).enabled = true;

        let selectedObjects = selectionBox.select();
        let Objects = new Set<THREE.Object3D>();

        selectedObjects.forEach((object) => {
            let currentObject: THREE.Object3D | null = object;
            while (currentObject) {
                if (currentObject.userData.pointUuid) {
                    Objects.add(currentObject);
                    break;
                }
                currentObject = currentObject.parent || null;
            }
        });

        if (Objects.size === 0) {
            clearSelection();
            return;
        }

        const updatedSelections = new Set<THREE.Object3D>(selectedPoints);

        Objects.forEach((obj) => {
            const existing = Array.from(updatedSelections).find((o) => o.userData?.pointUuid === obj.userData?.pointUuid);

            if (existing) {
                updatedSelections.delete(existing);
            } else {
                updatedSelections.add(obj);
            }
        });

        const selected = Array.from(updatedSelections);

        setSelectedPoints(selected);

    }, [selectionBox, pointer, controls, selectedPoints, setSelectedPoints]);

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

    const deleteSelection = () => {
        if (selectedPoints.length > 0 && duplicatedObjects.length === 0) {

            const deletedPoints: UndoRedo2DDataTypeSchema[] = [];
            const updatedPoints: UndoRedo2DDataTypeSchema[] = [];
            const processedAisles: UndoRedo2DDataTypeSchema[] = [];
            const processedWalls: UndoRedo2DDataTypeSchema[] = [];
            const processedFloors: UndoRedo2DDataTypeSchema[] = [];
            const processedZones: UndoRedo2DDataTypeSchema[] = [];

            selectedPoints.forEach((selectedPoint) => {
                if (selectedPoint.userData.pointUuid) {
                    const point: Point = selectedPoint.userData as Point;
                    if (point.pointType === 'Aisle') {
                        const removedAisles = removeAislePoint(point.pointUuid);
                        if (removedAisles.length > 0) {
                            removedAisles.forEach(aisle => {
                                if (projectId) {
                                    if (!socket?.active) {

                                        // API

                                        deleteAisleApi(aisle.aisleUuid, projectId, selectedVersion?.versionId || '');
                                    } else {

                                        // SOCKET

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

                                        socket.emit('v1:model-aisle:delete', data);
                                    }
                                }
                            });

                            const removedAislesData = removedAisles.map((aisle) => ({
                                type: "Aisle" as const,
                                lineData: aisle,
                                timeStamp: new Date().toISOString(),
                            }));

                            processedAisles.push(...removedAislesData);
                        }
                    }
                    if (point.pointType === 'Wall') {
                        const removedWalls = removeWallPoint(point.pointUuid);
                        if (removedWalls.length > 0) {
                            removedWalls.forEach(wall => {

                                const assetsOnWall = getWallAssetsByWall(wall.wallUuid);

                                assetsOnWall.forEach((asset) => {
                                    if (projectId && asset) {

                                        removeWallAsset(asset.modelUuid);

                                        if (!socket?.active) {

                                            // API

                                            deleteWallAssetApi(projectId, selectedVersion?.versionId || '', asset.modelUuid, asset.wallUuid);
                                        } else {

                                            // SOCKET

                                            const data = {
                                                projectId: projectId,
                                                versionId: selectedVersion?.versionId || '',
                                                userId: userId,
                                                organization: organization,
                                                modelUuid: asset.modelUuid,
                                                wallUuid: asset.wallUuid
                                            }

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

                                if (projectId) {
                                    if (!socket?.active) {

                                        // API

                                        deleteWallApi(projectId, selectedVersion?.versionId || '', wall.wallUuid);
                                    } else {

                                        // SOCKET

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

                                        socket.emit('v1:model-Wall:delete', data);
                                    }
                                }
                            });

                            const removedWallsData = removedWalls.map((wall) => ({
                                type: "Wall" as const,
                                lineData: wall,
                                timeStamp: new Date().toISOString(),
                            }));

                            processedWalls.push(...removedWallsData);
                        }
                    }
                    if (point.pointType === 'Floor') {
                        const Floors = getFloorsByPointId(point.pointUuid);
                        const { removedFloors, updatedFloors } = removeFloorPoint(point.pointUuid);
                        if (removedFloors.length > 0) {
                            removedFloors.forEach(floor => {
                                if (projectId) {
                                    if (!socket?.active) {

                                        // API

                                        deleteFloorApi(projectId, selectedVersion?.versionId || '', floor.floorUuid);
                                    } else {

                                        // SOCKET

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

                                        socket.emit('v1:model-Floor:delete', data);
                                    }
                                }
                            });

                            const removedFloorsData = removedFloors.map((floor) => ({
                                type: "Floor" as const,
                                lineData: floor,
                                timeStamp: new Date().toISOString(),
                            }));

                            processedFloors.push(...removedFloorsData);
                        }
                        if (updatedFloors.length > 0) {
                            updatedFloors.forEach(floor => {
                                if (projectId) {
                                    if (!socket?.active) {

                                        // API

                                        upsertFloorApi(projectId, selectedVersion?.versionId || '', floor);
                                    } else {

                                        // SOCKET

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

                                        socket.emit('v1:model-Floor:add', data);
                                    }
                                }
                            });

                            const updatedFloorsData = updatedFloors.map((floor) => ({
                                type: "Floor" as const,
                                lineData: Floors.find(f => f.floorUuid === floor.floorUuid) || floor,
                                newData: floor,
                                timeStamp: new Date().toISOString(),
                            }));

                            processedFloors.push(...updatedFloorsData);
                        }
                    }
                    if (point.pointType === 'Zone') {
                        const Zones = getZonesByPointId(point.pointUuid);
                        const { removedZones, updatedZones } = removeZonePoint(point.pointUuid);
                        if (removedZones.length > 0) {
                            removedZones.forEach(zone => {
                                if (projectId) {
                                    if (!socket?.active) {

                                        // API

                                        deleteZoneApi(projectId, selectedVersion?.versionId || '', zone.zoneUuid);
                                    } else {

                                        // SOCKET

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

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

                            const removedZonesData = removedZones.map((zone) => ({
                                type: "Zone" as const,
                                lineData: zone,
                                timeStamp: new Date().toISOString(),
                            }));

                            processedZones.push(...removedZonesData);
                        }
                        if (updatedZones.length > 0) {
                            updatedZones.forEach(zone => {
                                if (projectId) {
                                    if (!socket?.active) {

                                        // API

                                        upsertZoneApi(projectId, selectedVersion?.versionId || '', zone);
                                    } else {

                                        // SOCKET

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

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

                            const updatedZonesData = updatedZones.map((zone) => ({
                                type: "Zone" as const,
                                lineData: Zones.find(z => z.zoneUuid === zone.zoneUuid) || zone,
                                newData: zone,
                                timeStamp: new Date().toISOString(),
                            }));

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

            setTimeout(() => {
                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) => {
                        const hasDelete = actions.some(action => !('newData' in action));
                        if (hasDelete) {
                            deletedPoints.push({
                                type: 'Wall',
                                lineData: actions[0].lineData 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) => {
                        const hasDelete = actions.some(action => !('newData' in action));
                        if (hasDelete) {
                            deletedPoints.push({
                                type: 'Aisle',
                                lineData: actions[0].lineData 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) return;
                        const uuid = floor.lineData.floorUuid;
                        if (!floorMap.has(uuid)) {
                            floorMap.set(uuid, []);
                        }
                        floorMap.get(uuid)!.push(floor);
                    }

                    floorMap.forEach((actions, uuid) => {
                        const hasDelete = actions.some(action => !('newData' in action));
                        const hasUpdate = actions.some(action => 'newData' in action);

                        if (hasDelete) {
                            deletedPoints.push({
                                type: 'Floor',
                                lineData: actions[0].lineData as Floor,
                                timeStamp: new Date().toISOString()
                            });
                        } else if (!hasDelete && hasUpdate) {
                            const floorData = getFloorById(uuid);
                            if (floorData) {
                                updatedPoints.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) return;
                        const uuid = zone.lineData.zoneUuid;
                        if (!zoneMap.has(uuid)) {
                            zoneMap.set(uuid, []);
                        }
                        zoneMap.get(uuid)!.push(zone);
                    }

                    zoneMap.forEach((actions, uuid) => {
                        const hasDelete = actions.some(action => !('newData' in action));
                        const hasUpdate = actions.some(action => 'newData' in action);

                        if (hasDelete) {
                            deletedPoints.push({
                                type: 'Zone',
                                lineData: actions[0].lineData as Zone,
                                timeStamp: new Date().toISOString()
                            });
                        } else if (!hasDelete && hasUpdate) {
                            const zoneData = getZoneById(uuid);
                            if (zoneData) {
                                updatedPoints.push({
                                    type: 'Zone',
                                    lineData: actions[0].lineData as Zone,
                                    newData: zoneData as Zone,
                                    timeStamp: new Date().toISOString()
                                });
                            }
                        }
                    });
                }

                if (deletedPoints.length > 0 && updatedPoints.length > 0) {
                    push2D({
                        type: 'Draw',
                        actions: [
                            {
                                actionType: 'Lines-Delete',
                                points: deletedPoints
                            },
                            {
                                actionType: 'Lines-Update',
                                points: updatedPoints
                            }
                        ]
                    });
                } else if (deletedPoints.length > 0 && updatedPoints.length === 0) {
                    push2D({
                        type: 'Draw',
                        actions: [
                            {
                                actionType: 'Lines-Delete',
                                points: deletedPoints
                            }
                        ]
                    });
                } else if (updatedPoints.length > 0 && deletedPoints.length === 0) {
                    push2D({
                        type: 'Draw',
                        actions: [
                            {
                                actionType: 'Lines-Update',
                                points: updatedPoints
                            }
                        ]
                    });
                }
            }, 0);
        }
        echo.success("Selected points removed!");
        clearSelection();
    };

    return (
        <>

            <MoveControls2D movedObjects={movedObjects} setMovedObjects={setMovedObjects} pastedObjects={pastedObjects} setPastedObjects={setPastedObjects} duplicatedObjects={duplicatedObjects} setDuplicatedObjects={setDuplicatedObjects} rotatedObjects={rotatedObjects} setRotatedObjects={setRotatedObjects} />

        </>
    );
};

export default SelectionControls2D;
