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,
    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, 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, socket);
                    },
                        () => {
                            TempLoader(intersectPoint!, isTempLoader, tempLoader, itemsGroup);
                        });
                } else {
                    // console.log(`Added ${selectedItem.name} from Backend`);

                    loader.load(`${url_Backend_dwinzo}/api/v1/AssetFile/${selectedItem.id}`, async (gltf) => {
                        const modelBlob = await fetch(`${url_Backend_dwinzo}/api/v1/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, 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,
    socket: Socket<any>
) {
    const model = gltf.scene.clone();
    model.userData = { name: selectedItem.name, modelId: selectedItem.id };
    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] : "";

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

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

            const eventData: Extract<Types.FloorItemType['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: false
                    }],
                    triggers: [],
                    connections: {
                        source: { pathUUID: model.uuid, pointUUID: pointUUIDs[index] },
                        targets: []
                    }
                })),
                speed: 'Inherit'
            };

            // console.log('eventData: ', eventData);
            newFloorItem.eventData = eventData;
        }

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

        // 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,
        //     newFloorItem.eventData
        // );

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