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 { getAssetEventType } from '../../../../services/simulation/getAssetEventType';
import { setFloorItemApi } from '../../../../services/factoryBuilder/assest/floorAsset/setFloorItemApi';

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,
    setSimulationStates: any,
    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, setSimulationStates, 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, setSimulationStates, 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, setSimulationStates, 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,
    setSimulationStates: any,
    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.EventData = {
        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] : "";

    getAssetEventType(selectedItem.id, organization).then(async (res) => {
        console.log('res: ', res);

        if (res.type === "Conveyor") {
            const pointUUIDs = res.points.map(() => THREE.MathUtils.generateUUID());

            const backendEventData: Extract<Types.EventData['eventData'], { type: 'Conveyor' }> = {
                type: 'Conveyor',
                points: res.points.map((point: any, index: number) => ({
                    uuid: pointUUIDs[index],
                    position: point.position as [number, number, number],
                    rotation: point.rotation as [number, number, number],
                    actions: [{
                        uuid: THREE.MathUtils.generateUUID(),
                        name: 'Action 1',
                        type: 'Inherit',
                        material: 'Inherit',
                        delay: 'Inherit',
                        spawnInterval: 'Inherit',
                        isUsed: true
                    }],
                    triggers: [],
                    connections: {
                        source: { modelUUID: model.uuid, pointUUID: pointUUIDs[index] },
                        targets: []
                    }
                })),
                speed: 'Inherit'
            };

            // 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,
            //     { type: backendEventData.type, points: backendEventData.points, speed: backendEventData.speed }
            // );

            // 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,
                eventData: backendEventData,
                socketId: socket.id
            };

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

            const eventData: any = backendEventData;
            eventData.modeluuid = newFloorItem.modeluuid;
            eventData.modelName = newFloorItem.modelname;
            eventData.position = newFloorItem.position;
            eventData.rotation = [model.rotation.x, model.rotation.y, model.rotation.z];

            setSimulationStates((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema | Types.ArmBotEventsSchema)[]) => [
                ...(prevEvents || []),
                eventData as Types.ConveyorEventsSchema
            ]);

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

        } else if (res.type === "Vehicle") {

            const pointUUID = THREE.MathUtils.generateUUID();

            const backendEventData: Extract<Types.EventData['eventData'], { type: 'Vehicle' }> = {
                type: "Vehicle",
                points: {
                    uuid: pointUUID,
                    position: res.points.position as [number, number, number],
                    rotation: res.points.rotation as [number, number, number],
                    actions: { uuid: THREE.MathUtils.generateUUID(), name: 'Action 1', type: '', start: {}, hitCount: 1, end: {}, buffer: 0 },
                    connections: { source: { modelUUID: model.uuid, pointUUID: pointUUID }, targets: [] },
                    speed: 2,
                }
            }

            // 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,
            //     { type: backendEventData.type, points: backendEventData.points }
            // );

            // 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,
                eventData: { type: backendEventData.type, points: backendEventData.points },
                socketId: socket.id
            };

            const eventData: any = backendEventData;
            eventData.modeluuid = newFloorItem.modeluuid;
            eventData.modelName = newFloorItem.modelname;
            eventData.position = newFloorItem.position;

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

            setSimulationStates((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema | Types.ArmBotEventsSchema)[]) => [
                ...(prevEvents || []),
                eventData as Types.VehicleEventsSchema
            ]);

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

        } else if (res.type === "StaticMachine") {

            const pointUUID = THREE.MathUtils.generateUUID();

            const backendEventData: Extract<Types.EventData['eventData'], { type: 'StaticMachine' }> = {
                type: "StaticMachine",
                points: {
                    uuid: pointUUID,
                    position: res.points.position as [number, number, number],
                    rotation: res.points.rotation as [number, number, number],
                    actions: { uuid: THREE.MathUtils.generateUUID(), name: 'Action 1', buffer: 0, material: 'Inherit' },
                    triggers: { uuid: THREE.MathUtils.generateUUID(), name: 'Trigger 1', type: 'OnComplete' },
                    connections: { source: { modelUUID: model.uuid, pointUUID: pointUUID }, targets: [] },
                }
            }

            // 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,
            //     { type: backendEventData.type, points: backendEventData.points }
            // );

            // 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,
                eventData: { type: backendEventData.type, points: backendEventData.points },
                socketId: socket.id
            };

            const eventData: any = backendEventData;
            eventData.modeluuid = newFloorItem.modeluuid;
            eventData.modelName = newFloorItem.modelname;
            eventData.position = newFloorItem.position;
            eventData.rotation = [model.rotation.x, model.rotation.y, model.rotation.z];

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

            setSimulationStates((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema | Types.ArmBotEventsSchema)[]) => [
                ...(prevEvents || []),
                eventData as Types.StaticMachineEventsSchema
            ]);

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

        } else if (res.type === "ArmBot") {

            const pointUUID = THREE.MathUtils.generateUUID();

            const backendEventData: Extract<Types.EventData['eventData'], { type: 'ArmBot' }> = {
                type: "ArmBot",
                points: {
                    uuid: pointUUID,
                    position: res.points.position as [number, number, number],
                    rotation: res.points.rotation as [number, number, number],
                    actions: { uuid: THREE.MathUtils.generateUUID(), name: 'Action 1', speed: 1, processes: [] },
                    triggers: { uuid: THREE.MathUtils.generateUUID(), name: 'Trigger 1', type: 'OnComplete' },
                    connections: { source: { modelUUID: model.uuid, pointUUID: pointUUID }, targets: [] },
                }
            }

            // 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,
            //     { type: backendEventData.type, points: backendEventData.points }
            // );

            // 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,
                eventData: { type: backendEventData.type, points: backendEventData.points },
                socketId: socket.id
            };

            const eventData: any = backendEventData;
            eventData.modeluuid = newFloorItem.modeluuid;
            eventData.modelName = newFloorItem.modelname;
            eventData.position = newFloorItem.position;
            eventData.rotation = [model.rotation.x, model.rotation.y, model.rotation.z];

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

            setSimulationStates((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema | Types.ArmBotEventsSchema)[]) => [
                ...(prevEvents || []),
                eventData as Types.ArmBotEventsSchema
            ]);

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

        } else {

            // 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
            };


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

            console.log('data: ', data);
            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;
