//////////  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,
	useMovePoint,
	useActiveLayer,
	useSocketStore,
	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 ZoneGroup from "../groups/zoneGroup1";
import { findEnvironment } from "../../services/factoryBuilder/environment/findEnvironment";
import Layer2DVisibility from "./geomentries/layers/layer2DVisibility";
import DrieHtmlTemp from "../visualization/mqttTemp/drieHtmlTemp";
import ZoneGroup from "./groups/zoneGroup";
import useModuleStore from "../../store/useModuleStore";
import MeasurementTool from "../scene/tools/measurementTool";
import NavMesh from "../simulation/vehicle/navMesh/navMesh";

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, setActiveLayer } = useActiveLayer(); // State that changes based on which layer the user chooses in Layers.jsx.
	const { toggleView, setToggleView } = useToggleView(); // State for toggling between 2D and 3D.
	const { toolMode, setToolMode } = useToolMode();
	const { movePoint, setMovePoint } = useMovePoint(); // State that stores a boolean which represents whether the move mode is active or not.
	const { deletePointOrLine, setDeletePointOrLine } = useDeletePointOrLine();
	const { socket } = useSocketStore();
	const { roofVisibility, setRoofVisibility } = useRoofVisibility();
	const { wallVisibility, setWallVisibility } = useWallVisibility();
	const { shadows, setShadows } = useShadows();
	const { renderDistance, setRenderDistance } = useRenderDistance();
	const { limitDistance, setLimitDistance } = useLimitDistance();
	const { updateScene, setUpdateScene } = useUpdateScene();
	const { walls, setWalls } = useWalls();
	const { refTextupdate, setRefTextUpdate } = useRefTextUpdate();
	const { activeModule } = useModuleStore();

	// 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 (dragPointControls.current) {
			dragPointControls.current.enabled = false;
		}
		if (toggleView) {
			Layer2DVisibility(
				activeLayer,
				floorPlanGroup,
				floorPlanGroupLine,
				floorPlanGroupPoint,
				currentLayerPoint,
				dragPointControls
			);
		} else {
			setToolMode(null);
			setDeletePointOrLine(false);
			setMovePoint(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 />

			<NavMesh lines={lines} />

		</>
	);
}
