import * as THREE from 'three';
import { useThree } from '@react-three/fiber';
import { useEffect, useMemo, useState } from "react";
import { DragControls, Tube } from '@react-three/drei';
import { useSocketStore, useToolMode } from '../../../store/builder/store';
import { useBuilderStore } from '../../../store/builder/useBuilderStore';
import { useSceneContext } from '../../scene/sceneContext';
import * as Constants from '../../../types/world/worldConstants';
import { useVersionContext } from '../version/versionContext';
import { useParams } from 'react-router-dom';
import { getUserData } from '../../../functions/getUserData';
import { handleCanvasCursors } from '../../../utils/mouseUtils/handleCanvasCursors';
import { useSelectedPoints } from '../../../store/simulation/useSimulationStore';
import { calculateAssetTransformationOnWall } from '../wallAsset/Instances/Instance/functions/calculateAssetTransformationOnWall';

import { upsertWallApi } from '../../../services/factoryBuilder/wall/upsertWallApi';
import { deleteWallApi } from '../../../services/factoryBuilder/wall/deleteWallApi';
import { upsertFloorApi } from '../../../services/factoryBuilder/floor/upsertFloorApi';
import { deleteFloorApi } from '../../../services/factoryBuilder/floor/deleteFloorApi';
import { deleteZoneApi } from '../../../services/factoryBuilder/zone/deleteZoneApi';
import { upsertZoneApi } from '../../../services/factoryBuilder/zone/upsertZoneApi';
import { upsertWallAssetApi } from '../../../services/factoryBuilder/asset/wallAsset/upsertWallAssetApi';
import { deleteWallAssetApi } from '../../../services/factoryBuilder/asset/wallAsset/deleteWallAssetApi';

interface LineProps {
    points: [Point, Point];
}

function Line({ points }: Readonly<LineProps>) {
    const [isHovered, setIsHovered] = useState(false);
    const { raycaster, camera, pointer } = useThree();
    const plane = useMemo(() => new THREE.Plane(new THREE.Vector3(0, 1, 0), 0), []);
    const [isDeletable, setIsDeletable] = useState(false);
    const { socket } = useSocketStore();
    const { toolMode } = useToolMode();
    const { wallStore, floorStore, zoneStore, undoRedo2DStore, wallAssetStore } = useSceneContext();
    const { push2D } = undoRedo2DStore();
    const { getWallAssetsByWall, updateWallAsset, removeWallAsset } = wallAssetStore();
    const { removeWallByPoints, setPosition: setWallPosition, getWallByPoints, getConnectedWallsByWallId } = wallStore();
    const { removeFloorByPoints, setPosition: setFloorPosition, getFloorsByPointId, getFloorsByPoints } = floorStore();
    const { removeZoneByPoints, setPosition: setZonePosition, getZonesByPointId, getZonesByPoints } = zoneStore();
    const { userId, organization } = getUserData();
    const { selectedVersionStore } = useVersionContext();
    const { selectedVersion } = selectedVersionStore();
    const { projectId } = useParams();
    const [dragOffset, setDragOffset] = useState<THREE.Vector3 | null>(null);
    const { hoveredLine, setHoveredLine, hoveredPoint } = useBuilderStore();
    const { selectedPoints } = useSelectedPoints();

    const [initialPositions, setInitialPositions] = useState<{
        aisles?: Aisle[],
        walls?: Wall[],
        floors?: Floor[],
        zones?: Zone[]
    }>({});

    const path = useMemo(() => {
        const [start, end] = points.map(p => new THREE.Vector3(...p.position));
        return new THREE.LineCurve3(start, end);
    }, [points]);

    const colors = getColor(points[0]);

    function getColor(point: Point) {
        if (point.pointType === 'Aisle') {
            return {
                defaultLineColor: Constants.lineConfig.aisleColor,
                defaultDeleteColor: Constants.lineConfig.deleteColor,
            }
        } else if (point.pointType === 'Floor') {
            return {
                defaultLineColor: Constants.lineConfig.floorColor,
                defaultDeleteColor: Constants.lineConfig.deleteColor,
            }
        } else if (point.pointType === 'Wall') {
            return {
                defaultLineColor: Constants.lineConfig.wallColor,
                defaultDeleteColor: Constants.lineConfig.deleteColor,
            }
        } else if (point.pointType === 'Zone') {
            return {
                defaultLineColor: Constants.lineConfig.zoneColor,
                defaultDeleteColor: Constants.lineConfig.deleteColor,
            }
        } else {
            return {
                defaultLineColor: Constants.lineConfig.defaultColor,
                defaultDeleteColor: Constants.lineConfig.deleteColor,
            }
        }
    }

    useEffect(() => {
        if (toolMode === '2D-Delete') {
            if (isHovered && !hoveredPoint) {
                setIsDeletable(true);
            } else {
                setIsDeletable(false);
            }
        } else {
            setIsDeletable(false);
        }
    }, [isHovered, colors.defaultLineColor, colors.defaultDeleteColor, toolMode, hoveredPoint]);

    useEffect(() => {
        if (hoveredLine && (hoveredLine[0].pointUuid !== points[0].pointUuid || hoveredLine[1].pointUuid !== points[1].pointUuid)) {
            setIsHovered(false);
        }
    }, [hoveredLine])

    const handlePointClick = (points: [Point, Point]) => {
        if (toolMode === '2D-Delete') {
            if (points[0].pointType === 'Wall' && points[1].pointType === 'Wall') {
                const removedWall = removeWallByPoints(points);
                if (removedWall && projectId) {

                    const assetsOnWall = getWallAssetsByWall(removedWall.wallUuid);

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

                            removeWallAsset(asset.modelUuid);
                            if (!socket?.connected) {

                                // 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 (!socket?.connected) {

                        // API

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

                        // SOCKET

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

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

                    push2D({
                        type: 'Draw',
                        actions: [
                            {
                                actionType: 'Line-Delete',
                                point: {
                                    type: 'Wall',
                                    lineData: removedWall,
                                    timeStamp: new Date().toISOString(),
                                }
                            }
                        ]
                    });
                }

                setHoveredLine(null);
            }
            if (points[0].pointType === 'Floor' && points[1].pointType === 'Floor') {
                const Floors = getFloorsByPoints(points);
                const { removedFloors, updatedFloors } = removeFloorByPoints(points);
                if (removedFloors.length > 0) {
                    removedFloors.forEach(floor => {
                        if (projectId) {
                            if (!socket?.connected) {

                                // 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(),
                    }));

                    push2D({
                        type: 'Draw',
                        actions: [
                            {
                                actionType: 'Lines-Delete',
                                points: removedFloorsData
                            }
                        ]
                    });
                }
                if (updatedFloors.length > 0) {
                    updatedFloors.forEach(floor => {
                        if (projectId) {
                            if (!socket?.connected) {

                                // 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(),
                    }));

                    push2D({
                        type: 'Draw',
                        actions: [
                            {
                                actionType: 'Lines-Update',
                                points: updatedFloorsData
                            }
                        ]
                    });
                }

                setHoveredLine(null);
            }
            if (points[0].pointType === 'Zone' && points[1].pointType === 'Zone') {
                const Zones = getZonesByPoints(points);
                const { removedZones, updatedZones } = removeZoneByPoints(points);
                if (removedZones.length > 0) {
                    removedZones.forEach(zone => {
                        if (projectId) {
                            if (!socket?.connected) {

                                // 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(),
                    }));

                    push2D({
                        type: 'Draw',
                        actions: [
                            {
                                actionType: 'Lines-Delete',
                                points: removedZonesData
                            }
                        ]
                    });
                }
                if (updatedZones.length > 0) {
                    updatedZones.forEach(zone => {
                        if (projectId) {
                            if (!socket?.connected) {

                                // 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(),
                    }));

                    push2D({
                        type: 'Draw',
                        actions: [
                            {
                                actionType: 'Lines-Update',
                                points: updatedZonesData
                            }
                        ]
                    });
                }

                setHoveredLine(null);
            }
            handleCanvasCursors('default');
        }
    }

    const handleDrag = (points: [Point, Point]) => {
        if (toolMode === 'move' && isHovered && dragOffset) {
            raycaster.setFromCamera(pointer, camera);
            const intersectionPoint = new THREE.Vector3();
            const hit = raycaster.ray.intersectPlane(plane, intersectionPoint);

            if (hit) {
                handleCanvasCursors('grabbing');
                const positionWithOffset = new THREE.Vector3().addVectors(hit, dragOffset);

                const start = new THREE.Vector3(...points[0].position);
                const end = new THREE.Vector3(...points[1].position);
                const midPoint = new THREE.Vector3().addVectors(start, end).multiplyScalar(0.5);

                const delta = new THREE.Vector3().subVectors(positionWithOffset, midPoint);

                const newStart = new THREE.Vector3().addVectors(start, delta);
                const newEnd = new THREE.Vector3().addVectors(end, delta);

                if (points[0].pointType === 'Wall' && points[1].pointType === 'Wall') {
                    setWallPosition(points[0].pointUuid, [newStart.x, newStart.y, newStart.z]);
                    setWallPosition(points[1].pointUuid, [newEnd.x, newEnd.y, newEnd.z]);
                }
                if (points[0].pointType === 'Floor' && points[1].pointType === 'Floor') {
                    setFloorPosition(points[0].pointUuid, [newStart.x, newStart.y, newStart.z]);
                    setFloorPosition(points[1].pointUuid, [newEnd.x, newEnd.y, newEnd.z]);
                }
                if (points[0].pointType === 'Zone' && points[1].pointType === 'Zone') {
                    setZonePosition(points[0].pointUuid, [newStart.x, newStart.y, newStart.z]);
                    setZonePosition(points[1].pointUuid, [newEnd.x, newEnd.y, newEnd.z]);
                }
            }
        }
    };

    const handleDragStart = (points: [Point, Point]) => {
        raycaster.setFromCamera(pointer, camera);
        const intersectionPoint = new THREE.Vector3();
        const hit = raycaster.ray.intersectPlane(plane, intersectionPoint);

        if (hit && !hoveredPoint) {
            const start = new THREE.Vector3(...points[0].position);
            const end = new THREE.Vector3(...points[1].position);
            const midPoint = new THREE.Vector3().addVectors(start, end).multiplyScalar(0.5);

            const offset = new THREE.Vector3().subVectors(midPoint, hit);
            setDragOffset(offset);

            if (points[0].pointType === 'Wall' && points[1].pointType === 'Wall') {
                const wall = getWallByPoints(points);
                if (wall) {
                    const walls = getConnectedWallsByWallId(wall.wallUuid, false);
                    setInitialPositions({ walls });
                }
            } else if (points[0].pointType === 'Floor') {
                const floors = getFloorsByPointId(points[0].pointUuid);
                setInitialPositions({ floors });
            } else if (points[0].pointType === 'Zone') {
                const zones = getZonesByPointId(points[0].pointUuid);
                setInitialPositions({ zones });
            }
        }
    };

    const handleDragEnd = (points: [Point, Point]) => {
        if (toolMode !== 'move' || !dragOffset) return;
        handleCanvasCursors('default');
        setDragOffset(null);

        if (points[0].pointType === 'Wall' && points[1].pointType === 'Wall') {
            const wall = getWallByPoints(points);
            if (!wall) return;
            const updatedWalls = getConnectedWallsByWallId(wall.wallUuid, false);

            if (updatedWalls.length > 0 && projectId) {
                updatedWalls.forEach(updatedWall => {

                    const initialWall = initialPositions.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 = updateWallAsset(asset.modelUuid, {
                                position: [position[0], asset.position[1], position[2]],
                                rotation: rotation
                            });

                            if (projectId && updatedWallAsset) {
                                if (!socket?.connected) {
                                    // API

                                    upsertWallAssetApi(projectId, selectedVersion?.versionId || '', updatedWallAsset);
                                } else {

                                    // SOCKET

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

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

                    if (!socket?.connected) {

                        // API

                        upsertWallApi(projectId, selectedVersion?.versionId || '', updatedWall);
                    } else {

                        // SOCKET

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

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

                if (initialPositions.walls && initialPositions.walls.length > 0) {
                    const updatedPoints = initialPositions.walls.map((wall) => ({
                        type: "Wall" as const,
                        lineData: wall,
                        newData: updatedWalls.find(w => w.wallUuid === wall.wallUuid),
                        timeStamp: new Date().toISOString(),
                    }));

                    push2D({
                        type: 'Draw',
                        actions: [{
                            actionType: 'Lines-Update',
                            points: updatedPoints,
                        }]
                    });
                }
            }
        } else if (points[0].pointType === 'Floor' && points[1].pointType === 'Floor') {
            const updatedFloors1 = getFloorsByPointId(points[0].pointUuid);
            const updatedFloors2 = getFloorsByPointId(points[1].pointUuid);
            const updatedFloors = [...updatedFloors1, ...updatedFloors2].filter((floor, index, self) => index === self.findIndex((f) => f.floorUuid === floor.floorUuid));

            if (updatedFloors.length > 0 && projectId) {
                updatedFloors.forEach(updatedFloor => {
                    if (!socket?.connected) {

                        // API

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

                        // SOCKET

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

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

                if (initialPositions.floors && initialPositions.floors.length > 0) {
                    const updatedPoints = initialPositions.floors.map((floor) => ({
                        type: "Floor" as const,
                        lineData: floor,
                        newData: updatedFloors.find(f => f.floorUuid === floor.floorUuid),
                        timeStamp: new Date().toISOString(),
                    }));

                    push2D({
                        type: 'Draw',
                        actions: [{
                            actionType: 'Lines-Update',
                            points: updatedPoints,
                        }]
                    });
                }
            }
        } else if (points[0].pointType === 'Zone' && points[1].pointType === 'Zone') {
            const updatedZones1 = getZonesByPointId(points[0].pointUuid);
            const updatedZones2 = getZonesByPointId(points[1].pointUuid);
            const updatedZones = [...updatedZones1, ...updatedZones2].filter((zone, index, self) => index === self.findIndex((z) => z.zoneUuid === zone.zoneUuid));

            if (updatedZones.length > 0 && projectId) {
                updatedZones.forEach(updatedZone => {
                    if (!socket?.connected) {

                        // API

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

                        // SOCKET

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

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

                if (initialPositions.zones && initialPositions.zones.length > 0) {
                    const updatedPoints = initialPositions.zones.map((zone) => ({
                        type: "Zone" as const,
                        lineData: zone,
                        newData: updatedZones.find(z => z.zoneUuid === zone.zoneUuid),
                        timeStamp: new Date().toISOString(),
                    }));

                    push2D({
                        type: 'Draw',
                        actions: [{
                            actionType: 'Lines-Update',
                            points: updatedPoints,
                        }]
                    });
                }
            }
        }
    }

    return (
        <DragControls
            axisLock="y"
            autoTransform={false}
            onDragStart={() => handleDragStart(points)}
            onDrag={() => handleDrag(points)}
            onDragEnd={() => handleDragEnd(points)}
        >
            <Tube
                name={`${points[0].pointType}-Line`}
                key={`${points[0].pointUuid}-${points[1].pointUuid}`}
                uuid={`${points[0].pointUuid}-${points[1].pointUuid}`}
                userData={{ points, path }}
                args={[path, Constants.lineConfig.tubularSegments, Constants.lineConfig.radius, Constants.lineConfig.radialSegments, false]}
                onClick={() => {
                    handlePointClick(points);
                }}
                onPointerOver={(e) => {
                    if (selectedPoints.length === 0 && e.buttons === 0 && !e.ctrlKey) {
                        setHoveredLine(points);
                        setIsHovered(true)
                        if (toolMode === 'move' && !hoveredPoint) {
                            handleCanvasCursors('grab');
                        }
                    }
                }}
                onPointerOut={() => {
                    if (hoveredLine && isHovered) {
                        setHoveredLine(null);
                        if (!hoveredPoint) {
                            handleCanvasCursors('default');
                        }
                    }
                    setIsHovered(false)
                }}
            >
                <meshStandardMaterial color={isDeletable ? colors.defaultDeleteColor : colors.defaultLineColor} />
            </Tube>
        </DragControls >
    );
}

export default Line;