import React, { useState, useEffect, useMemo, useRef } from "react";
import { Line, Sphere } from "@react-three/drei";
import { useThree, useFrame } from "@react-three/fiber";
import * as THREE from "three";
import {
  useActiveLayer,
  useDeleteTool,
  useDeletePointOrLine,
  useMovePoint,
  useSocketStore,
  useToggleView,
  useToolMode,
  useRemovedLayer,
  useZones,
  useZonePoints,
} from "../../../store/store";
import { getZonesApi } from "../../../services/factoryBuilder/zones/getZonesApi";

import * as CONSTANTS from "../../../types/world/worldConstants";

const ZoneGroup: React.FC = () => {
  const { camera, pointer, gl, raycaster, scene, controls } = useThree();
  const [startPoint, setStartPoint] = useState<THREE.Vector3 | null>(null);
  const [endPoint, setEndPoint] = useState<THREE.Vector3 | null>(null);
  const { zones, setZones } = useZones();
  const { zonePoints, setZonePoints } = useZonePoints();
  const [isDragging, setIsDragging] = useState(false);
  const [draggedSphere, setDraggedSphere] = useState<THREE.Vector3 | null>(
    null
  );
  const plane = useMemo(
    () => new THREE.Plane(new THREE.Vector3(0, 1, 0), 0),
    []
  );
  const { toggleView } = useToggleView();
  const { deletePointOrLine, setDeletePointOrLine } = useDeletePointOrLine();
  const { removedLayer, setRemovedLayer } = useRemovedLayer();
  const { toolMode } = useToolMode();
  const { movePoint, setMovePoint } = useMovePoint();
  const { setDeleteTool } = useDeleteTool();
  const { activeLayer } = useActiveLayer();
  const { socket } = useSocketStore();

  const groupsRef = useRef<any>();

  const zoneMaterial = useMemo(
    () =>
      new THREE.ShaderMaterial({
        side: THREE.DoubleSide,
        vertexShader: `
        varying vec2 vUv;
        void main(){
            gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
            vUv = uv;
        }
        `,
        fragmentShader: `
        varying vec2 vUv;
        uniform vec3 uColor;
        void main(){
            float alpha = 1.0 - vUv.y;
            gl_FragColor = vec4(uColor, alpha);
        }
        `,
        uniforms: {
          uColor: { value: new THREE.Color(CONSTANTS.zoneConfig.color) },
        },
        transparent: true,
        depthWrite: false,
      }),
    []
  );

  useEffect(() => {
    const fetchZones = async () => {
      const email = localStorage.getItem("email");
      if (!email) return;

      const organization = email.split("@")[1].split(".")[0];
      const data = await getZonesApi(organization);

      if (data.data && data.data.length > 0) {
        const fetchedZones = data.data.map((zone: any) => ({
          zoneId: zone.zoneId,
          zoneName: zone.zoneName,
          points: zone.points,
          viewPortCenter: zone.viewPortCenter,
          viewPortposition: zone.viewPortposition,
          layer: zone.layer,
        }));

        setZones(fetchedZones);

        const fetchedPoints = data.data.flatMap((zone: any) =>
          zone.points
            .slice(0, 4)
            .map(
              (point: [number, number, number]) => new THREE.Vector3(...point)
            )
        );

        setZonePoints(fetchedPoints);
      }
    };

    fetchZones();
  }, []);

  useEffect(() => {
    localStorage.setItem("zones", JSON.stringify(zones));
  }, [zones]);

  useEffect(() => {
    if (removedLayer) {
      const updatedZones = zones.filter(
        (zone: any) => zone.layer !== removedLayer
      );
      setZones(updatedZones);

      const updatedzonePoints = zonePoints.filter((_: any, index: any) => {
        const zoneIndex = Math.floor(index / 4);
        return zones[zoneIndex]?.layer !== removedLayer;
      });
      setZonePoints(updatedzonePoints);

      zones
        .filter((zone: any) => zone.layer === removedLayer)
        .forEach((zone: any) => {
          deleteZoneFromBackend(zone.zoneId);
        });

      setRemovedLayer(null);
    }
  }, [removedLayer]);

  useEffect(() => {
    if (toolMode !== "Zone") {
      setStartPoint(null);
      setEndPoint(null);
    } else {
      setDeletePointOrLine(false);
      setMovePoint(false);
      setDeleteTool(false);
    }
    if (!toggleView) {
      setStartPoint(null);
      setEndPoint(null);
    }
  }, [toolMode, toggleView]);

  // eslint-disable-next-line react-hooks/exhaustive-deps
  const addZoneToBackend = async (zone: {
    zoneId: string;
    zoneName: string;
    points: [number, number, number][];
    layer: string;
  }) => {
    const email = localStorage.getItem("email");
    const userId = localStorage.getItem("userId");
    const organization = email!.split("@")[1].split(".")[0];

    const calculateCenter = (points: number[][]) => {
      if (!points || points.length === 0) return null;

      let sumX = 0,
        sumY = 0,
        sumZ = 0;
      const numPoints = points.length;

      points.forEach(([x, y, z]) => {
        sumX += x;
        sumY += y;
        sumZ += z;
      });

      return [sumX / numPoints, sumY / numPoints, sumZ / numPoints] as [
        number,
        number,
        number
      ];
    };

    const target: [number, number, number] | null = calculateCenter(
      zone.points
    );
    if (!target) return;
    const position = [target[0], 10, target[2]];

    const input = {
      userId: userId,
      organization: organization,
      zoneData: {
        zoneName: zone.zoneName,
        zoneId: zone.zoneId,
        points: zone.points,
        viewPortCenter: target,
        viewPortposition: position,
        layer: zone.layer,
      },
    };

    socket.emit("v2:zone:set", input);
  };

  // eslint-disable-next-line react-hooks/exhaustive-deps
  const updateZoneToBackend = async (zone: {
    zoneId: string;
    zoneName: string;
    points: [number, number, number][];
    layer: string;
  }) => {
    const email = localStorage.getItem("email");
    const userId = localStorage.getItem("userId");
    const organization = email!.split("@")[1].split(".")[0];

    const calculateCenter = (points: number[][]) => {
      if (!points || points.length === 0) return null;

      let sumX = 0,
        sumY = 0,
        sumZ = 0;
      const numPoints = points.length;

      points.forEach(([x, y, z]) => {
        sumX += x;
        sumY += y;
        sumZ += z;
      });

      return [sumX / numPoints, sumY / numPoints, sumZ / numPoints] as [
        number,
        number,
        number
      ];
    };

    const target: [number, number, number] | null = calculateCenter(
      zone.points
    );
    if (!target) return;
    const position = [target[0], 10, target[2]];

    const input = {
      userId: userId,
      organization: organization,
      zoneData: {
        zoneName: zone.zoneName,
        zoneId: zone.zoneId,
        points: zone.points,
        viewPortCenter: target,
        viewPortposition: position,
        layer: zone.layer,
      },
    };

    socket.emit("v2:zone:set", input);
  };

  const deleteZoneFromBackend = async (zoneId: string) => {
    const email = localStorage.getItem("email");
    const userId = localStorage.getItem("userId");
    const organization = email!.split("@")[1].split(".")[0];

    const input = {
      userId: userId,
      organization: organization,
      zoneId: zoneId,
    };

    socket.emit("v2:zone:delete", input);
  };

  // eslint-disable-next-line react-hooks/exhaustive-deps
  const handleDeleteZone = (zoneId: string) => {
    const updatedZones = zones.filter((zone: any) => zone.zoneId !== zoneId);
    setZones(updatedZones);

    const zoneIndex = zones.findIndex((zone: any) => zone.zoneId === zoneId);
    if (zoneIndex !== -1) {
      const zonePointsToRemove = zonePoints.slice(
        zoneIndex * 4,
        zoneIndex * 4 + 4
      );
      zonePointsToRemove.forEach((point: any) =>
        groupsRef.current.remove(point)
      );
      const updatedzonePoints = zonePoints.filter(
        (_: any, index: any) =>
          index < zoneIndex * 4 || index >= zoneIndex * 4 + 4
      );
      setZonePoints(updatedzonePoints);
    }
    deleteZoneFromBackend(zoneId);
  };

  useEffect(() => {
    if (!camera || !toggleView) return;
    const canvasElement = gl.domElement;

    let drag = false;
    let isLeftMouseDown = false;

    const onMouseDown = (evt: any) => {
      if (evt.button === 0) {
        isLeftMouseDown = true;
        drag = false;

        raycaster.setFromCamera(pointer, camera);
        const intersects = raycaster.intersectObjects(
          groupsRef.current.children,
          true
        );

        if (intersects.length > 0 && movePoint) {
          const clickedObject = intersects[0].object;
          const sphereIndex = zonePoints.findIndex((point: any) =>
            point.equals(clickedObject.position)
          );
          if (sphereIndex !== -1) {
            (controls as any).enabled = false;
            setDraggedSphere(zonePoints[sphereIndex]);
            setIsDragging(true);
          }
        }
      }
    };

    const onMouseUp = (evt: any) => {
      if (evt.button === 0 && !drag && !isDragging && !deletePointOrLine) {
        isLeftMouseDown = false;

        if (!startPoint && !movePoint) {
          raycaster.setFromCamera(pointer, camera);
          const intersectionPoint = new THREE.Vector3();
          const point = raycaster.ray.intersectPlane(plane, intersectionPoint);
          if (point) {
            setStartPoint(point);
            setEndPoint(null);
          }
        } else if (startPoint && !movePoint) {
          raycaster.setFromCamera(pointer, camera);
          const intersectionPoint = new THREE.Vector3();
          const point = raycaster.ray.intersectPlane(plane, intersectionPoint);
          if (!point) return;

          const points = [
            [startPoint.x, 0.15, startPoint.z],
            [point.x, 0.15, startPoint.z],
            [point.x, 0.15, point.z],
            [startPoint.x, 0.15, point.z],
            [startPoint.x, 0.15, startPoint.z],
          ] as [number, number, number][];

          const zoneName = `Zone ${zones.length + 1}`;
          const zoneId = THREE.MathUtils.generateUUID();
          const newZone = {
            zoneId,
            zoneName,
            points: points,
            layer: activeLayer,
          };

          const newZones = [...zones, newZone];

          setZones(newZones);

          const newzonePoints = [
            new THREE.Vector3(startPoint.x, 0.15, startPoint.z),
            new THREE.Vector3(point.x, 0.15, startPoint.z),
            new THREE.Vector3(point.x, 0.15, point.z),
            new THREE.Vector3(startPoint.x, 0.15, point.z),
          ];

          const updatedZonePoints = [...zonePoints, ...newzonePoints];
          setZonePoints(updatedZonePoints);

          addZoneToBackend(newZone);

          setStartPoint(null);
          setEndPoint(null);
        }
      } else if (
        evt.button === 0 &&
        !drag &&
        !isDragging &&
        deletePointOrLine
      ) {
        raycaster.setFromCamera(pointer, camera);
        const intersects = raycaster.intersectObjects(
          groupsRef.current.children,
          true
        );

        if (intersects.length > 0) {
          const clickedObject = intersects[0].object;

          const sphereIndex = zonePoints.findIndex((point: any) =>
            point.equals(clickedObject.position)
          );
          if (sphereIndex !== -1) {
            const zoneIndex = Math.floor(sphereIndex / 4);
            const zoneId = zones[zoneIndex].zoneId;
            handleDeleteZone(zoneId);
            return;
          }
        }
      }

      if (evt.button === 0) {
        if (isDragging && draggedSphere) {
          setIsDragging(false);
          setDraggedSphere(null);

          const sphereIndex = zonePoints.findIndex(
            (point: any) => point === draggedSphere
          );
          if (sphereIndex !== -1) {
            const zoneIndex = Math.floor(sphereIndex / 4);

            if (zoneIndex !== -1 && zones[zoneIndex]) {
              updateZoneToBackend(zones[zoneIndex]);
            }
          }
        }
      }
    };

    const onMouseMove = () => {
      if (isLeftMouseDown) {
        drag = true;
      }
      raycaster.setFromCamera(pointer, camera);
      const intersects = raycaster.intersectObjects(
        groupsRef.current.children,
        true
      );

      if (
        intersects.length > 0 &&
        intersects[0].object.name.includes("point")
      ) {
        gl.domElement.style.cursor = movePoint ? "pointer" : "default";
      } else {
        gl.domElement.style.cursor = "default";
      }
      if (isDragging && draggedSphere) {
        raycaster.setFromCamera(pointer, camera);
        const intersectionPoint = new THREE.Vector3();
        const point = raycaster.ray.intersectPlane(plane, intersectionPoint);
        if (point) {
          draggedSphere.set(point.x, 0.15, point.z);

          const sphereIndex = zonePoints.findIndex(
            (point: any) => point === draggedSphere
          );
          if (sphereIndex !== -1) {
            const zoneIndex = Math.floor(sphereIndex / 4);
            const cornerIndex = sphereIndex % 4;

            const updatedZones = zones.map((zone: any, index: number) => {
              if (index === zoneIndex) {
                const updatedPoints = [...zone.points];
                updatedPoints[cornerIndex] = [point.x, 0.15, point.z];
                updatedPoints[4] = updatedPoints[0];
                return { ...zone, points: updatedPoints };
              }
              return zone;
            });

            setZones(updatedZones);
          }
        }
      }
    };

    const onContext = (event: any) => {
      event.preventDefault();
      setStartPoint(null);
      setEndPoint(null);
    };

    if (toolMode === "Zone" || deletePointOrLine || movePoint) {
      canvasElement.addEventListener("mousedown", onMouseDown);
      canvasElement.addEventListener("mouseup", onMouseUp);
      canvasElement.addEventListener("mousemove", onMouseMove);
      canvasElement.addEventListener("contextmenu", onContext);
    }
    return () => {
      canvasElement.removeEventListener("mousedown", onMouseDown);
      canvasElement.removeEventListener("mouseup", onMouseUp);
      canvasElement.removeEventListener("mousemove", onMouseMove);
      canvasElement.removeEventListener("contextmenu", onContext);
    };
  }, [
    gl,
    camera,
    startPoint,
    toggleView,
    scene,
    toolMode,
    zones,
    isDragging,
    deletePointOrLine,
    zonePoints,
    draggedSphere,
    movePoint,
    activeLayer,
    raycaster,
    pointer,
    controls,
    plane,
    setZones,
    setZonePoints,
    addZoneToBackend,
    handleDeleteZone,
    updateZoneToBackend,
  ]);

  useFrame(() => {
    if (!startPoint) return;
    raycaster.setFromCamera(pointer, camera);
    const intersectionPoint = new THREE.Vector3();
    const point = raycaster.ray.intersectPlane(plane, intersectionPoint);
    if (point) {
      setEndPoint(point);
    }
  });

  return (
    <group ref={groupsRef} name="zoneGroup">
      <group name="zones" visible={!toggleView}>
        {zones.map((zone: any) => (
          <group key={zone.zoneId} name={zone.zoneName} visible={false}>
            {zone.points
              .slice(0, -1)
              .map((point: [number, number, number], index: number) => {
                const nextPoint = zone.points[index + 1];

                const point1 = new THREE.Vector3(point[0], point[1], point[2]);
                const point2 = new THREE.Vector3(
                  nextPoint[0],
                  nextPoint[1],
                  nextPoint[2]
                );

                const planeWidth = point1.distanceTo(point2);
                const planeHeight = CONSTANTS.zoneConfig.height;

                const midpoint = new THREE.Vector3(
                  (point1.x + point2.x) / 2,
                  CONSTANTS.zoneConfig.height / 2 +
                    (zone.layer - 1) * CONSTANTS.zoneConfig.height,
                  (point1.z + point2.z) / 2
                );

                const angle = Math.atan2(
                  point2.z - point1.z,
                  point2.x - point1.x
                );

                return (
                  <mesh
                    key={index}
                    position={midpoint}
                    rotation={[0, -angle, 0]}
                  >
                    <planeGeometry args={[planeWidth, planeHeight]} />
                    <primitive
                      object={zoneMaterial.clone()}
                      attach="material"
                    />
                  </mesh>
                );
              })}
          </group>
        ))}
      </group>
      <group name="zoneLines" visible={toggleView}>
        {zones
          .filter((zone: any) => zone.layer === activeLayer)
          .map((zone: any) => (
            <Line
              key={zone.zoneId}
              points={zone.points}
              color="#007BFF"
              lineWidth={3}
              onClick={(e) => {
                e.stopPropagation();
                if (deletePointOrLine) {
                  handleDeleteZone(zone.zoneId);
                }
              }}
            />
          ))}
      </group>
      <group name="zonePoints" visible={toggleView}>
        {zones
          .filter((zone: any) => zone.layer === activeLayer)
          .flatMap((zone: any) =>
            zone.points.slice(0, 4).map((point: any, pointIndex: number) => (
              <Sphere
                key={`${zone.zoneId}-point-${pointIndex}`}
                position={new THREE.Vector3(...point)}
                args={[0.3, 16, 16]}
                name={`point-${zone.zoneId}-${pointIndex}`}
              >
                <meshBasicMaterial color="red" />
              </Sphere>
            ))
          )}
      </group>
      <group name="tempGroup" visible={toggleView}>
        {startPoint && endPoint && (
          <Line
            points={[
              [startPoint.x, 0.15, startPoint.z],
              [endPoint.x, 0.15, startPoint.z],
              [endPoint.x, 0.15, endPoint.z],
              [startPoint.x, 0.15, endPoint.z],
              [startPoint.x, 0.15, startPoint.z],
            ]}
            color="#C164FF"
            lineWidth={3}
          />
        )}
      </group>
    </group>
  );
};

export default ZoneGroup;
