import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader';
import gsap from 'gsap';
import * as THREE from 'three';
import * as CONSTANTS from '../../../types/world/worldConstants';
import * as Types from "../../../types/world/worldTypes";
import { initializeDB, retrieveGLTF, storeGLTF } from '../../../utils/indexDB/idbUtils';
import { getCamera } from '../../../services/factoryBuilder/camera/getCameraApi';
import { getFloorAssets } from '../../../services/factoryBuilder/assest/floorAsset/getFloorItemsApi';

async function loadInitialFloorItems(
    itemsGroup: Types.RefGroup,
    setFloorItems: Types.setFloorItemSetState,
    addEvent: (event: EventsSchema) => void,
    renderDistance: number,
    projectId?:string
): Promise<void> {
    if (!itemsGroup.current) return;
    let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_MARKETPLACE_URL}`;
    const email = localStorage.getItem('email');
    const organization = (email!.split("@")[1]).split(".")[0];

    const items = await getFloorAssets(organization,projectId);
    localStorage.setItem("FloorItems", JSON.stringify(items));
    await initializeDB();

    if (items.message === "floorItems not found") return;

    if (items) {
        const storedFloorItems: Types.FloorItems = items;
        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);

        let modelsLoaded = 0;
        const modelsToLoad = storedFloorItems.length;

        const camData = await getCamera(organization, localStorage.getItem('userId')!,projectId);
        let storedPosition;
        if (camData && camData.position) {
            storedPosition = camData?.position;
        } else {
            storedPosition = new THREE.Vector3(0, 40, 30);
        }
        if (!storedPosition) return;
        const cameraPosition = new THREE.Vector3(storedPosition.x, storedPosition.y, storedPosition.z);

        storedFloorItems.sort((a, b) => {
            const aPosition = new THREE.Vector3(a.position[0], a.position[1], a.position[2]);
            const bPosition = new THREE.Vector3(b.position[0], b.position[1], b.position[2]);
            return cameraPosition.distanceTo(aPosition) - cameraPosition.distanceTo(bPosition);
        });

        for (const item of storedFloorItems) {
            if (!item.modelfileID) return;
            const itemPosition = new THREE.Vector3(item.position[0], item.position[1], item.position[2]);
            let storedPosition;
            if (localStorage.getItem("cameraPosition")) {
                storedPosition = JSON.parse(localStorage.getItem("cameraPosition")!);
            } else {
                storedPosition = new THREE.Vector3(0, 40, 30);
            }

            const cameraPosition = new THREE.Vector3(storedPosition.x, storedPosition.y, storedPosition.z);

            if (cameraPosition.distanceTo(itemPosition) < renderDistance) {
                await new Promise<void>(async (resolve) => {

                    // Check Three.js Cache
                    const cachedModel = THREE.Cache.get(item.modelfileID!);
                    if (cachedModel) {
                        processLoadedModel(cachedModel.scene.clone(), item, itemsGroup, setFloorItems, addEvent);
                        modelsLoaded++;
                        checkLoadingCompletion(modelsLoaded, modelsToLoad, dracoLoader, resolve);
                        return;
                    }

                    // Check IndexedDB
                    const indexedDBModel = await retrieveGLTF(item.modelfileID!);
                    if (indexedDBModel) {
                        const blobUrl = URL.createObjectURL(indexedDBModel);
                        loader.load(blobUrl, (gltf) => {
                            URL.revokeObjectURL(blobUrl);
                            THREE.Cache.remove(blobUrl);
                            THREE.Cache.add(item.modelfileID!, gltf);
                            processLoadedModel(gltf.scene.clone(), item, itemsGroup, setFloorItems, addEvent);
                            modelsLoaded++;
                            checkLoadingCompletion(modelsLoaded, modelsToLoad, dracoLoader, resolve);
                        },
                            undefined,
                            (error) => {
                                echo.error(`[IndexedDB] Error loading ${item.modelName}:`);
                                URL.revokeObjectURL(blobUrl);
                                resolve();
                            }
                        );
                        return;
                    }

                    // Fetch from Backend
                    const modelUrl = `${url_Backend_dwinzo}/api/v2/AssetFile/${item.modelfileID!}`;
                    loader.load(modelUrl, async (gltf) => {
                        const modelBlob = await fetch(modelUrl).then((res) => res.blob());
                        await storeGLTF(item.modelfileID!, modelBlob);
                        THREE.Cache.add(item.modelfileID!, gltf);
                        processLoadedModel(gltf.scene.clone(), item, itemsGroup, setFloorItems, addEvent);
                        modelsLoaded++;
                        checkLoadingCompletion(modelsLoaded, modelsToLoad, dracoLoader, resolve);
                    },
                        undefined,
                        (error) => {
                            echo.error(`[Backend] Error loading ${item.modelName}:`);
                            resolve();
                        }
                    );
                });
            } else {
                // 
                setFloorItems((prevItems) => [
                    ...(prevItems || []),
                    {
                        modelUuid: item.modelUuid,
                        modelName: item.modelName,
                        position: item.position,
                        rotation: item.rotation,
                        modelfileID: item.modelfileID,
                        isLocked: item.isLocked,
                        isVisible: item.isVisible,
                    },
                ]);

                modelsLoaded++;
                checkLoadingCompletion(modelsLoaded, modelsToLoad, dracoLoader, () => { });
            }
        }

        // Dispose loader after all models
        dracoLoader.dispose();
    }
}


function processLoadedModel(
    gltf: any,
    item: Types.FloorItemType,
    itemsGroup: Types.RefGroup,
    setFloorItems: Types.setFloorItemSetState,
    addEvent: (event: EventsSchema) => void,
) {
    const model = gltf.clone();
    model.uuid = item.modelUuid;
    model.scale.set(...CONSTANTS.assetConfig.defaultScaleBeforeGsap);
    model.userData = { name: item.modelName, modelId: item.modelfileID, modelUuid: item.modelUuid, eventData: item.eventData };
    model.position.set(...item.position);
    model.rotation.set(item.rotation.x, item.rotation.y, item.rotation.z);

    model.traverse((child: any) => {
        if (child.isMesh) {
            // Clone the material to ensure changes are independent
            // child.material = child.material.clone();

            child.castShadow = true;
            child.receiveShadow = true;
        }
    });

    itemsGroup?.current?.add(model);

    if (item.eventData) {
        setFloorItems((prevItems) => [
            ...(prevItems || []),
            {
                modelUuid: item.modelUuid,
                modelName: item.modelName,
                position: item.position,
                rotation: item.rotation,
                modelfileID: item.modelfileID,
                isLocked: item.isLocked,
                isVisible: item.isVisible,
                eventData: item.eventData,
            },
        ]);

        if (item.eventData.type === "Vehicle") {
            const vehicleEvent: VehicleEventSchema = {
                modelUuid: item.modelUuid,
                modelName: item.modelName,
                position: item.position,
                rotation: [item.rotation.x, item.rotation.y, item.rotation.z],
                state: "idle",
                type: "vehicle",
                speed: 1,
                point: {
                    uuid: item.eventData.point?.uuid || THREE.MathUtils.generateUUID(),
                    position: [item.eventData.point?.position[0] || 0, item.eventData.point?.position[1] || 0, item.eventData.point?.position[2] || 0],
                    rotation: [item.eventData.point?.rotation[0] || 0, item.eventData.point?.rotation[1] || 0, item.eventData.point?.rotation[2] || 0],
                    action: {
                        actionUuid: THREE.MathUtils.generateUUID(),
                        actionName: "Action 1",
                        actionType: "travel",
                        unLoadDuration: 5,
                        loadCapacity: 1,
                        steeringAngle: 0,
                        pickUpPoint: null,
                        unLoadPoint: null,
                        triggers: []
                    }
                }
            };
            addEvent(vehicleEvent);
        } else if (item.eventData.type === "Conveyor") {
            const ConveyorEvent: ConveyorEventSchema = {
                modelUuid: item.modelUuid,
                modelName: item.modelName,
                position: item.position,
                rotation: [item.rotation.x, item.rotation.y, item.rotation.z],
                state: "idle",
                type: "transfer",
                speed: 1,
                points: item.eventData.points?.map((point: any, index: number) => ({
                    uuid: point.uuid || THREE.MathUtils.generateUUID(),
                    position: [point.position[0], point.position[1], point.position[2]],
                    rotation: [point.rotation[0], point.rotation[1], point.rotation[2]],
                    action: {
                        actionUuid: THREE.MathUtils.generateUUID(),
                        actionName: `Action 1`,
                        actionType: 'default',
                        material: 'Default material',
                        delay: 0,
                        spawnInterval: 5,
                        spawnCount: 1,
                        triggers: []
                    }
                })) || [],
            };
            addEvent(ConveyorEvent);
        } else if (item.eventData.type === "StaticMachine") {
            const machineEvent: MachineEventSchema = {
                modelUuid: item.modelUuid,
                modelName: item.modelName,
                position: item.position,
                rotation: [item.rotation.x, item.rotation.y, item.rotation.z],
                state: "idle",
                type: "machine",
                point: {
                    uuid: item.eventData.point?.uuid || THREE.MathUtils.generateUUID(),
                    position: [item.eventData.point?.position[0] || 0, item.eventData.point?.position[1] || 0, item.eventData.point?.position[2] || 0],
                    rotation: [item.eventData.point?.rotation[0] || 0, item.eventData.point?.rotation[1] || 0, item.eventData.point?.rotation[2] || 0],
                    action: {
                        actionUuid: THREE.MathUtils.generateUUID(),
                        actionName: "Action 1",
                        actionType: "process",
                        processTime: 10,
                        swapMaterial: "material-id",
                        triggers: []
                    }
                }
            };
            addEvent(machineEvent);
        } else if (item.eventData.type === "ArmBot") {
            const roboticArmEvent: RoboticArmEventSchema = {
                modelUuid: item.modelUuid,
                modelName: item.modelName,
                position: item.position,
                rotation: [item.rotation.x, item.rotation.y, item.rotation.z],
                state: "idle",
                type: "roboticArm",
                speed: 1,
                point: {
                    uuid: item.eventData.point?.uuid || THREE.MathUtils.generateUUID(),
                    position: [item.eventData.point?.position[0] || 0, item.eventData.point?.position[1] || 0, item.eventData.point?.position[2] || 0],
                    rotation: [item.eventData.point?.rotation[0] || 0, item.eventData.point?.rotation[1] || 0, item.eventData.point?.rotation[2] || 0],
                    actions: [
                        {
                            actionUuid: THREE.MathUtils.generateUUID(),
                            actionName: "Action 1",
                            actionType: "pickAndPlace",
                            process: {
                                startPoint: null,
                                endPoint: null
                            },
                            triggers: []
                        }
                    ]
                }
            };
            addEvent(roboticArmEvent);
        } else if (item.eventData.type === 'Storage') {
            const storageEvent: StorageEventSchema = {
                modelUuid: item.modelUuid,
                modelName: item.modelName,
                position: item.position,
                rotation: [item.rotation.x, item.rotation.y, item.rotation.z], state: "idle",
                type: "storageUnit",
                point: {
                    uuid: item.eventData.point?.uuid || THREE.MathUtils.generateUUID(),
                    position: [item.eventData.point?.position[0] || 0, item.eventData.point?.position[1] || 0, item.eventData.point?.position[2] || 0],
                    rotation: [item.eventData.point?.rotation[0] || 0, item.eventData.point?.rotation[1] || 0, item.eventData.point?.rotation[2] || 0],
                    action: {
                        actionUuid: THREE.MathUtils.generateUUID(),
                        actionName: "Action 1",
                        actionType: "store",
                        storageCapacity: 10,
                        triggers: []
                    }
                }
            };
            addEvent(storageEvent);
        }
    } else {
        setFloorItems((prevItems) => [
            ...(prevItems || []),
            {
                modelUuid: item.modelUuid,
                modelName: item.modelName,
                position: item.position,
                rotation: item.rotation,
                modelfileID: item.modelfileID,
                isLocked: item.isLocked,
                isVisible: item.isVisible,
            },
        ]);
    }

    gsap.to(model.position, { y: item.position[1], duration: 1.5, ease: 'power2.out' });
    gsap.to(model.scale, { x: 1, y: 1, z: 1, duration: 1.5, ease: 'power2.out' });
}

function checkLoadingCompletion(
    modelsLoaded: number,
    modelsToLoad: number,
    dracoLoader: DRACOLoader,
    resolve: () => void
) {
    if (modelsLoaded === modelsToLoad) {
        echo.success("Models Loaded!");
        dracoLoader.dispose();
    }
    resolve();
}

export default loadInitialFloorItems;