import * as THREE from 'three';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
import gsap from 'gsap';
import { toast } from 'react-toastify';
import TempLoader from './tempLoader';
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader';
import * as Types from "../../../../types/world/worldTypes";
import { retrieveGLTF, storeGLTF } from '../../../../utils/indexDB/idbUtils';
// import { setFloorItemApi } from '../../../../services/factoryBuilder/assest/floorAsset/setFloorItemApi';
import { Socket } from 'socket.io-client';
import * as CONSTANTS from '../../../../types/world/worldConstants';
import { setFloorItemApi } from '../../../../services/factoryBuilder/assest/floorAsset/setFloorItemApi';
import PointsCalculator from '../../../simulation/events/points/functions/pointsCalculator';

async function addAssetModel(
    raycaster: THREE.Raycaster,
    camera: THREE.Camera,
    pointer: THREE.Vector2,
    floorGroup: Types.RefGroup,
    setFloorItems: Types.setFloorItemSetState,
    itemsGroup: Types.RefGroup,
    isTempLoader: Types.RefBoolean,
    tempLoader: Types.RefMesh,
    socket: Socket<any>,
    selectedItem: any,
    setSelectedItem: any,
    addEvent: (event: EventsSchema) => void,
    plane: Types.RefMesh,
): Promise<void> {

    //////////  Load Floor GLtf's and set the positions, rotation, type etc. in state and store in localstorage  //////////

    let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_MARKETPLACE_URL}`;

    try {
        isTempLoader.current = true;
        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);

        raycaster.setFromCamera(pointer, camera);
        const floorIntersections = raycaster.intersectObjects(floorGroup.current.children, true);
        const intersectedFloor = floorIntersections.find(intersect => intersect.object.name.includes("Floor"));

        const planeIntersections = raycaster.intersectObject(plane.current!, true);
        const intersectedPlane = planeIntersections[0];

        let intersectPoint: THREE.Vector3 | null = null;

        if (intersectedFloor && intersectedPlane) {
            intersectPoint = intersectedFloor.distance < intersectedPlane.distance ? (new THREE.Vector3(intersectedFloor.point.x, Math.round(intersectedFloor.point.y), intersectedFloor.point.z)) : (new THREE.Vector3(intersectedPlane.point.x, 0, intersectedPlane.point.z));
        } else if (intersectedFloor) {
            intersectPoint = new THREE.Vector3(intersectedFloor.point.x, Math.round(intersectedFloor.point.y), intersectedFloor.point.z);
        } else if (intersectedPlane) {
            intersectPoint = new THREE.Vector3(intersectedPlane.point.x, 0, intersectedPlane.point.z);
        }

        if (intersectPoint) {
            if (intersectPoint.y < 0) {
                intersectPoint = new THREE.Vector3(intersectPoint.x, 0, intersectPoint.z);
            }
            // console.log('selectedItem: ', selectedItem);
            const cachedModel = THREE.Cache.get(selectedItem.id);
            if (cachedModel) {
                // console.log(`[Cache] Fetching ${selectedItem.name}`);
                handleModelLoad(cachedModel, intersectPoint!, selectedItem, itemsGroup, tempLoader, isTempLoader, setFloorItems, addEvent, socket);
                return;
            } else {
                const cachedModelBlob = await retrieveGLTF(selectedItem.id);

                if (cachedModelBlob) {
                    // console.log(`Added ${selectedItem.name} from indexDB`);

                    const blobUrl = URL.createObjectURL(cachedModelBlob);
                    loader.load(blobUrl, (gltf) => {
                        URL.revokeObjectURL(blobUrl);
                        THREE.Cache.remove(blobUrl);
                        THREE.Cache.add(selectedItem.id, gltf);
                        handleModelLoad(gltf, intersectPoint!, selectedItem, itemsGroup, tempLoader, isTempLoader, setFloorItems, addEvent, socket);
                    },
                        () => {
                            TempLoader(intersectPoint!, isTempLoader, tempLoader, itemsGroup);
                        });
                } else {
                    // console.log(`Added ${selectedItem.name} from Backend`);

                    loader.load(`${url_Backend_dwinzo}/api/v2/AssetFile/${selectedItem.id}`, async (gltf) => {
                        const modelBlob = await fetch(`${url_Backend_dwinzo}/api/v2/AssetFile/${selectedItem.id}`).then((res) => res.blob());
                        await storeGLTF(selectedItem.id, modelBlob);
                        THREE.Cache.add(selectedItem.id, gltf);
                        await handleModelLoad(gltf, intersectPoint!, selectedItem, itemsGroup, tempLoader, isTempLoader, setFloorItems, addEvent, socket);
                    },
                        () => {
                            TempLoader(intersectPoint!, isTempLoader, tempLoader, itemsGroup);
                        });
                }
            }
        }
    } catch (error) {
        console.error('Error fetching asset model:', error);
    } finally {
        setSelectedItem({});
    }
}

async function handleModelLoad(
    gltf: any,
    intersectPoint: THREE.Vector3,
    selectedItem: any,
    itemsGroup: Types.RefGroup,
    tempLoader: Types.RefMesh,
    isTempLoader: Types.RefBoolean,
    setFloorItems: Types.setFloorItemSetState,
    addEvent: (event: EventsSchema) => void,
    socket: Socket<any>
) {
    const model = gltf.scene.clone();
    model.userData = { name: selectedItem.name, modelId: selectedItem.id, modeluuid: model.uuid };
    model.position.set(intersectPoint!.x, 3 + intersectPoint!.y, intersectPoint!.z);
    model.scale.set(...CONSTANTS.assetConfig.defaultScaleBeforeGsap);

    model.traverse((child: any) => {
        if (child) {
            child.castShadow = true;
            child.receiveShadow = true;
        }
    });

    itemsGroup.current.add(model);
    if (tempLoader.current) {
        (<any>tempLoader.current.material).dispose();
        (<any>tempLoader.current.geometry).dispose();
        itemsGroup.current.remove(tempLoader.current);
        tempLoader.current = undefined;
    }

    const newFloorItem: Types.FloorItemType = {
        modeluuid: model.uuid,
        modelname: selectedItem.name,
        modelfileID: selectedItem.id,
        position: [intersectPoint!.x, intersectPoint!.y, intersectPoint!.z],
        rotation: { x: model.rotation.x, y: model.rotation.y, z: model.rotation.z },
        isLocked: false,
        isVisible: true
    };

    const email = localStorage.getItem("email");
    const organization = email ? email.split("@")[1].split(".")[0] : "";

    // API

    // await setFloorItemApi(
    //     organization,
    //     newFloorItem.modeluuid,
    //     newFloorItem.modelname,
    //     newFloorItem.modelfileID,
    //     newFloorItem.position,
    //     { x: model.rotation.x, y: model.rotation.y, z: model.rotation.z },
    //     false,
    //     true,
    // );

    // SOCKET

    const data = {
        organization,
        modeluuid: newFloorItem.modeluuid,
        modelname: newFloorItem.modelname,
        modelfileID: newFloorItem.modelfileID,
        position: newFloorItem.position,
        rotation: { x: model.rotation.x, y: model.rotation.y, z: model.rotation.z },
        isLocked: false,
        isVisible: true,
        socketId: socket.id
    };

    if (selectedItem.type) {
        const data = PointsCalculator(
            selectedItem.type,
            gltf.scene.clone(),
            new THREE.Vector3(...model.rotation)
        );

        if (!data || !data.points) return;

        if (selectedItem.type === "Conveyor") {
            const ConveyorEvent: ConveyorEventSchema = {
                modelUuid: newFloorItem.modeluuid,
                modelName: newFloorItem.modelname,
                position: newFloorItem.position,
                rotation: [newFloorItem.rotation.x, newFloorItem.rotation.y, newFloorItem.rotation.z],
                state: "idle",
                type: 'transfer',
                speed: 1,
                points: data.points.map((point: THREE.Vector3, index: number) => ({
                    uuid: THREE.MathUtils.generateUUID(),
                    position: [point.x, point.y, point.z],
                    rotation: [0, 0, 0],
                    action: {
                        actionUuid: THREE.MathUtils.generateUUID(),
                        actionName: `Action ${index}`,
                        actionType: 'default',
                        material: 'Default Material',
                        delay: 0,
                        spawnInterval: 5,
                        spawnCount: 1,
                        triggers: []
                    }
                }))
            }
            addEvent(ConveyorEvent);
        } else if (selectedItem.type === "Vehicle") {
            const vehicleEvent: VehicleEventSchema = {
                modelUuid: newFloorItem.modeluuid,
                modelName: newFloorItem.modelname,
                position: newFloorItem.position,
                rotation: [newFloorItem.rotation.x, newFloorItem.rotation.y, newFloorItem.rotation.z],
                state: "idle",
                type: "vehicle",
                speed: 1,
                point: {
                    uuid: THREE.MathUtils.generateUUID(),
                    position: [data.points[0].x, data.points[0].y, data.points[0].z],
                    rotation: [0, 0, 0],
                    action: {
                        actionUuid: THREE.MathUtils.generateUUID(),
                        actionName: "Action 1",
                        actionType: "travel",
                        unLoadDuration: 5,
                        loadCapacity: 10,
                        pickUpPoint: null,
                        unLoadPoint: null,
                        triggers: []
                    }
                }
            };
            addEvent(vehicleEvent);
        } else if (selectedItem.type === "ArmBot") {
            const roboticArmEvent: RoboticArmEventSchema = {
                modelUuid: newFloorItem.modeluuid,
                modelName: newFloorItem.modelname,
                position: newFloorItem.position,
                rotation: [newFloorItem.rotation.x, newFloorItem.rotation.y, newFloorItem.rotation.z],
                state: "idle",
                type: "roboticArm",
                speed: 1,
                point: {
                    uuid: THREE.MathUtils.generateUUID(),
                    position: [data.points[0].x, data.points[0].y, data.points[0].z],
                    rotation: [0, 0, 0],
                    actions: [
                        {
                            actionUuid: THREE.MathUtils.generateUUID(),
                            actionName: "Action 1",
                            actionType: "pickAndPlace",
                            process: {
                                startPoint: null,
                                endPoint: null
                            },
                            triggers: []
                        }
                    ]
                }
            };
            addEvent(roboticArmEvent);
        } else if (selectedItem.type === "StaticMachine") {
            const machineEvent: MachineEventSchema = {
                modelUuid: newFloorItem.modeluuid,
                modelName: newFloorItem.modelname,
                position: newFloorItem.position,
                rotation: [newFloorItem.rotation.x, newFloorItem.rotation.y, newFloorItem.rotation.z],
                state: "idle",
                type: "machine",
                point: {
                    uuid: THREE.MathUtils.generateUUID(),
                    position: [data.points[0].x, data.points[0].y, data.points[0].z],
                    rotation: [0, 0, 0],
                    action: {
                        actionUuid: THREE.MathUtils.generateUUID(),
                        actionName: "Action 1",
                        actionType: "process",
                        processTime: 10,
                        swapMaterial: "Default Material",
                        triggers: []
                    }
                }
            };
            addEvent(machineEvent);
        }
    }

    setFloorItems((prevItems) => {
        const updatedItems = [...(prevItems || []), newFloorItem];
        localStorage.setItem("FloorItems", JSON.stringify(updatedItems));
        return updatedItems;
    });

    socket.emit("v2:model-asset:add", data);

    gsap.to(model.position, { y: newFloorItem.position[1], duration: 1.5, ease: "power2.out" });
    gsap.to(model.scale, { x: 1, y: 1, z: 1, duration: 1.5, ease: "power2.out", onComplete: () => { toast.success("Model Added!"); } });
}

export default addAssetModel;
