//////////  Three and React Three Fiber Imports  //////////

import * as THREE from "three";
import { useEffect, useRef, useState } from "react";
import { useThree, useFrame } from "@react-three/fiber";

//////////  Component Imports  //////////

import DistanceText from "./geomentries/lines/distanceText/distanceText";
import ReferenceDistanceText from "./geomentries/lines/distanceText/referenceDistanceText";

//////////  Assests Imports  //////////

import arch from "../../assets/gltf-glb/arch.glb";
import door from "../../assets/gltf-glb/door.glb";
import Window from "../../assets/gltf-glb/window.glb";

//////////  Zustand State Imports  //////////

import {
    useToggleView,
    useDeletePointOrLine,
    useActiveLayer,
    useWallVisibility,
    useRoofVisibility,
    useShadows,
    useUpdateScene,
    useWalls,
    useToolMode,
    useRefTextUpdate,
    useRenderDistance,
    useLimitDistance,
} from "../../store/store";

//////////  3D Function Imports  //////////

import loadWalls from "./geomentries/walls/loadWalls";

import * as Types from "../../types/world/worldTypes";

import SocketResponses from "../collaboration/socket/socketResponses.dev";
import FloorItemsGroup from "./groups/floorItemsGroup";
import FloorPlanGroup from "./groups/floorPlanGroup";
import FloorGroup from "./groups/floorGroup";
import FloorGroupAilse from "./groups/floorGroupAisle";
import Draw from "./functions/draw";
import WallsAndWallItems from "./groups/wallsAndWallItems";
import Ground from "../scene/environment/ground";
import { findEnvironment } from "../../services/factoryBuilder/environment/findEnvironment";
import Layer2DVisibility from "./geomentries/layers/layer2DVisibility";
import ZoneGroup from "./groups/zoneGroup";
import MeasurementTool from "../scene/tools/measurementTool";
import NavMesh from "../simulation/vehicle/navMesh/navMesh";
import CalculateAreaGroup from "./groups/calculateAreaGroup";

export default function Builder() {
  const state = useThree<Types.ThreeState>(); // Importing the state from the useThree hook, which contains the scene, camera, and other Three.js elements.
  const csg = useRef(); // Reference for CSG object, used for 3D modeling.
  const CSGGroup = useRef() as Types.RefMesh; // Reference to a group of CSG objects.
  const scene = useRef() as Types.RefScene; // Reference to the scene.
  const camera = useRef() as Types.RefCamera; // Reference to the camera object.
  const controls = useRef<any>(); // Reference to the controls object.
  const raycaster = useRef() as Types.RefRaycaster; // Reference for raycaster used for detecting objects being pointed at in the scene.
  const dragPointControls = useRef() as Types.RefDragControl; // Reference for drag point controls, an array for drag control.

  // Assigning the scene and camera from the Three.js state to the references.

  scene.current = state.scene;
  camera.current = state.camera;
  controls.current = state.controls;
  raycaster.current = state.raycaster;

  const plane = useRef<THREE.Mesh>(null); // Reference for a plane object for raycaster reference.
  const grid = useRef() as any; // Reference for a grid object for raycaster reference.
  const snappedPoint = useRef() as Types.RefVector3; // Reference for storing a snapped point at the (end = isSnapped) and (start = ispreSnapped) of the line.
  const isSnapped = useRef(false) as Types.RefBoolean; // Boolean reference to indicate if an object is snapped at the (end).
  const anglesnappedPoint = useRef() as Types.RefVector3; // Reference for storing an angle-snapped point when the line is in 90 degree etc...
  const isAngleSnapped = useRef(false) as Types.RefBoolean; // Boolean to indicate if angle snapping is active.
  const isSnappedUUID = useRef() as Types.RefString; // UUID reference to identify the snapped point.
  const ispreSnapped = useRef(false) as Types.RefBoolean; // Boolean reference to indicate if an object is snapped at the (start).
  const tempLoader = useRef() as Types.RefMesh; // Reference for a temporary loader for the floor items.
  const isTempLoader = useRef() as Types.RefBoolean; // Reference to check if a temporary loader is active.
  const Tube = useRef() as Types.RefTubeGeometry; // Reference for tubes used for reference line creation and updation.
  const line = useRef([]) as Types.RefLine; // Reference for line which stores the current line that is being drawn.
  const lines = useRef([]) as Types.RefLines; // Reference for lines which stores all the lines that are ever drawn.
  const onlyFloorline = useRef<Types.OnlyFloorLine>([]); // Reference for floor lines which does not have walls or roof and have only floor used to store the current line that is being drawn.
  const onlyFloorlines = useRef<Types.OnlyFloorLines>([]); // Reference for all the floor lines that are ever drawn.
  const ReferenceLineMesh = useRef() as Types.RefMesh; // Reference for storing the mesh of the reference line for moving it during draw.
  const LineCreated = useRef(false) as Types.RefBoolean; // Boolean to track whether the reference line is created or not.
  const referencePole = useRef() as Types.RefMesh; // Reference for a pole that is used as the reference for the user to show where it is placed.
  const itemsGroup = useRef() as Types.RefGroup; // Reference to the THREE.Group that has the floor items (Gltf).
  const floorGroup = useRef() as Types.RefGroup; // Reference to the THREE.Group that has the roofs and the floors.
  const AttachedObject = useRef() as Types.RefMesh; // Reference for an object that is attached using dbl click for transform controls rotation.
  const floorPlanGroup = useRef() as Types.RefGroup; // Reference for a THREE.Group that has the lines group and the points group.
  const floorPlanGroupLine = useRef() as Types.RefGroup; // Reference for a THREE.Group that has the lines that are drawn.
  const floorPlanGroupPoint = useRef() as Types.RefGroup; // Reference for a THREE.Group that has the points that are created.
  const floorGroupAisle = useRef() as Types.RefGroup;
  const zoneGroup = useRef() as Types.RefGroup;
  const currentLayerPoint = useRef([]) as Types.RefMeshArray; // Reference for points that re in the current layer used to update the points in drag controls.
  const hoveredDeletablePoint = useRef() as Types.RefMesh; // Reference for the currently hovered point that can be deleted.
  const hoveredDeletableLine = useRef() as Types.RefMesh; // Reference for the currently hovered line that can be deleted.
  const hoveredDeletableFloorItem = useRef() as Types.RefMesh; // Reference for the currently hovered floor item that can be deleted.
  const hoveredDeletableWallItem = useRef() as Types.RefMesh; // Reference for the currently hovered wall item that can be deleted.
  const hoveredDeletablePillar = useRef() as Types.RefMesh; // Reference for the currently hovered pillar that can be deleted.
  const currentWallItem = useRef() as Types.RefMesh; // Reference for the currently selected wall item that can be scaled, dragged etc...

  const cursorPosition = new THREE.Vector3(); // 3D vector for storing the cursor position.

    const [selectedItemsIndex, setSelectedItemsIndex] = useState<Types.Number | null>(null); // State for tracking the index of the selected item.
    const { activeLayer } = useActiveLayer(); // State that changes based on which layer the user chooses in Layers.jsx.
    const { toggleView } = useToggleView(); // State for toggling between 2D and 3D.
    const { toolMode, setToolMode } = useToolMode();
    const { setDeletePointOrLine } = useDeletePointOrLine();
    const { setRoofVisibility } = useRoofVisibility();
    const { setWallVisibility } = useWallVisibility();
    const { setShadows } = useShadows();
    const { setRenderDistance } = useRenderDistance();
    const { setLimitDistance } = useLimitDistance();
    const { setUpdateScene } = useUpdateScene();
    const { setWalls } = useWalls();
    const { refTextupdate, setRefTextUpdate } = useRefTextUpdate();

  // const loader = new GLTFLoader();
  // const dracoLoader = new DRACOLoader();

  // dracoLoader.setDecoderPath('https://cdn.jsdelivr.net/npm/three@0.160.0/examples/jsm/libs/draco/gltf/');
  // loader.setDRACOLoader(dracoLoader);

  //////////  Assest Configuration Values  //////////

  const AssetConfigurations: Types.AssetConfigurations = {
    arch: {
      modelUrl: arch,
      scale: [0.75, 0.75, 0.75],
      csgscale: [2, 4, 0.5],
      csgposition: [0, 2, 0],
      positionY: () => 0,
      type: "Fixed-Move",
    },
    door: {
      modelUrl: door,
      scale: [0.75, 0.75, 0.75],
      csgscale: [2, 4, 0.5],
      csgposition: [0, 2, 0],
      positionY: () => 0,
      type: "Fixed-Move",
    },
    window: {
      modelUrl: Window,
      scale: [0.75, 0.75, 0.75],
      csgscale: [5, 3, 0.5],
      csgposition: [0, 1.5, 0],
      positionY: (intersectionPoint) => intersectionPoint.point.y,
      type: "Free-Move",
    },
  };

  //////////  All Toggle's //////////

    useEffect(() => {
        setRefTextUpdate((prevUpdate: number) => prevUpdate - 1);
        if (toggleView) {
            Layer2DVisibility(
                activeLayer,
                floorPlanGroup,
                floorPlanGroupLine,
                floorPlanGroupPoint,
                currentLayerPoint,
                dragPointControls
            );
        } else {
            setToolMode(null);
            setDeletePointOrLine(false);
            loadWalls(lines, setWalls);
            setUpdateScene(true);
            line.current = [];
        }
    }, [toggleView]);

  useEffect(() => {
    THREE.Cache.clear();
    THREE.Cache.enabled = true;
  }, []);

  useEffect(() => {
    const email = localStorage.getItem("email");
    const organization = email!.split("@")[1].split(".")[0];

    async function fetchVisibility() {
      const visibility = await findEnvironment(
        organization,
        localStorage.getItem("userId")!
      );
      if (visibility) {
        setRoofVisibility(visibility.roofVisibility);
        setWallVisibility(visibility.wallVisibility);
        setShadows(visibility.shadowVisibility);
        setRenderDistance(visibility.renderDistance);
        setLimitDistance(visibility.limitDistance);
      }
    }
    fetchVisibility();
  }, []);

  //////////  UseFrame is Here  //////////

  useFrame(() => {
    if (toolMode) {
      Draw(
        state,
        plane,
        cursorPosition,
        floorPlanGroupPoint,
        floorPlanGroupLine,
        snappedPoint,
        isSnapped,
        isSnappedUUID,
        line,
        lines,
        ispreSnapped,
        floorPlanGroup,
        ReferenceLineMesh,
        LineCreated,
        setRefTextUpdate,
        Tube,
        anglesnappedPoint,
        isAngleSnapped,
        toolMode
      );
    }
  });

  //////////  Return  //////////

  return (
    <>
      <Ground grid={grid} plane={plane} />

      <DistanceText key={toggleView} />

      <ReferenceDistanceText
        key={refTextupdate}
        line={ReferenceLineMesh.current}
      />

      <SocketResponses
        floorPlanGroup={floorPlanGroup}
        lines={lines}
        floorGroup={floorGroup}
        floorGroupAisle={floorGroupAisle}
        scene={scene}
        onlyFloorlines={onlyFloorlines}
        AssetConfigurations={AssetConfigurations}
        itemsGroup={itemsGroup}
        isTempLoader={isTempLoader}
        tempLoader={tempLoader}
        currentLayerPoint={currentLayerPoint}
        floorPlanGroupPoint={floorPlanGroupPoint}
        floorPlanGroupLine={floorPlanGroupLine}
        zoneGroup={zoneGroup}
        dragPointControls={dragPointControls}
      />

      <WallsAndWallItems
        CSGGroup={CSGGroup}
        AssetConfigurations={AssetConfigurations}
        setSelectedItemsIndex={setSelectedItemsIndex}
        selectedItemsIndex={selectedItemsIndex}
        currentWallItem={currentWallItem}
        csg={csg}
        lines={lines}
        hoveredDeletableWallItem={hoveredDeletableWallItem}
      />

      <FloorItemsGroup
        itemsGroup={itemsGroup}
        hoveredDeletableFloorItem={hoveredDeletableFloorItem}
        AttachedObject={AttachedObject}
        floorGroup={floorGroup}
        tempLoader={tempLoader}
        isTempLoader={isTempLoader}
        plane={plane}
      />

      <FloorGroup
        floorGroup={floorGroup}
        lines={lines}
        referencePole={referencePole}
        hoveredDeletablePillar={hoveredDeletablePillar}
      />

      <FloorPlanGroup
        floorPlanGroup={floorPlanGroup}
        floorPlanGroupLine={floorPlanGroupLine}
        floorPlanGroupPoint={floorPlanGroupPoint}
        floorGroup={floorGroup}
        currentLayerPoint={currentLayerPoint}
        dragPointControls={dragPointControls}
        hoveredDeletablePoint={hoveredDeletablePoint}
        hoveredDeletableLine={hoveredDeletableLine}
        plane={plane}
        line={line}
        lines={lines}
        onlyFloorline={onlyFloorline}
        onlyFloorlines={onlyFloorlines}
        ReferenceLineMesh={ReferenceLineMesh}
        LineCreated={LineCreated}
        isSnapped={isSnapped}
        ispreSnapped={ispreSnapped}
        snappedPoint={snappedPoint}
        isSnappedUUID={isSnappedUUID}
        isAngleSnapped={isAngleSnapped}
        anglesnappedPoint={anglesnappedPoint}
      />

      <ZoneGroup />

      <FloorGroupAilse
        floorGroupAisle={floorGroupAisle}
        plane={plane}
        floorPlanGroupLine={floorPlanGroupLine}
        floorPlanGroupPoint={floorPlanGroupPoint}
        line={line}
        lines={lines}
        currentLayerPoint={currentLayerPoint}
        dragPointControls={dragPointControls}
        floorPlanGroup={floorPlanGroup}
        ReferenceLineMesh={ReferenceLineMesh}
        LineCreated={LineCreated}
        isSnapped={isSnapped}
        ispreSnapped={ispreSnapped}
        snappedPoint={snappedPoint}
        isSnappedUUID={isSnappedUUID}
        isAngleSnapped={isAngleSnapped}
        anglesnappedPoint={anglesnappedPoint}
      />

      <MeasurementTool />
      <CalculateAreaGroup />
      <NavMesh lines={lines} />
    </>
  );
}
