import * as THREE from "three";
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
import * as Types from "../../../../types/world/worldTypes";
import { retrieveGLTF, storeGLTF } from "../../../../utils/indexDB/idbUtils";
import { Socket } from "socket.io-client";
import * as CONSTANTS from "../../../../types/world/worldConstants";
import PointsCalculator from "../../../simulation/events/points/functions/pointsCalculator";

import { getUserData } from "../../../../functions/getUserData";
// import { setAssetsApi } from '../../../../services/factoryBuilder/asset/floorAsset/setAssetsApi';

async function addAssetModel(
    scene: THREE.Scene,
    raycaster: THREE.Raycaster,
    camera: THREE.Camera,
    pointer: THREE.Vector2,
    socket: Socket<any>,
    selectedItem: any,
    setSelectedItem: any,
    addEvent: (event: EventsSchema) => void,
    addAsset: (asset: Asset) => void,
    plane: Types.RefMesh,
    loader: GLTFLoader,
    selectedVersion?: Version | null,
    projectId?: string,
    userId?: string
): 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 {
        raycaster.setFromCamera(pointer, camera);
        const wallFloorsGroup = scene.getObjectByName("Walls-Floors-Group") as Types.Group | null;
        const floorsGroup = scene.getObjectByName("Floors-Group") as Types.Group | null;
        const floorChildren = floorsGroup?.children ?? [];
        const wallFloorChildren = wallFloorsGroup?.children ?? [];
        const floorIntersections = raycaster.intersectObjects([...floorChildren, ...wallFloorChildren], 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);
            if (intersectedFloor.distance < intersectedPlane.distance) {
                if (intersectedFloor.object.userData.floorUuid) {
                    intersectPoint = new THREE.Vector3(intersectedFloor.point.x, intersectedFloor.object.userData.floorDepth, intersectedFloor.point.z);
                } else {
                    intersectPoint = new THREE.Vector3(intersectedFloor.point.x, 0, intersectedFloor.point.z);
                }
            } else {
                intersectPoint = 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);
            }
            const cachedModel = THREE.Cache.get(selectedItem.id);
            if (cachedModel) {
                handleModelLoad(cachedModel, intersectPoint!, selectedItem, addEvent, addAsset, socket, selectedVersion?.versionId || '', projectId, userId);
                return;
            } else {
                const cachedModelBlob = await retrieveGLTF(selectedItem.id);
                if (cachedModelBlob) {
                    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, addEvent, addAsset, socket, selectedVersion?.versionId || '', projectId, userId);
                    });
                } else {
                    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, addEvent, addAsset, socket, selectedVersion?.versionId || '', projectId, userId);
                        }
                    );
                }
            }
        }
    } catch (error) {
        echo.error("Failed to add asset");
    } finally {
        setSelectedItem({});
    }
}

async function handleModelLoad(
    gltf: any,
    intersectPoint: THREE.Vector3,
    selectedItem: any,
    addEvent: (event: EventsSchema) => void,
    addAsset: (asset: Asset) => void,
    socket: Socket<any>,
    versionId: string,
    projectId?: string,
    userId?: string
) {
    const { organization } = getUserData();
    const model = gltf.scene.clone();
    model.userData = {
        name: selectedItem.name,
        modelId: selectedItem.id,
        modelUuid: model.uuid,
    };
    model.position.set(intersectPoint!.x, intersectPoint!.y, intersectPoint!.z);
    model.scale.set(...CONSTANTS.assetConfig.defaultScaleAfterGsap);

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

    const newFloorItem: Asset = {
        modelUuid: model.uuid,
        modelName: selectedItem.name,
        assetId: selectedItem.id,
        position: [intersectPoint!.x, intersectPoint!.y, intersectPoint!.z],
        rotation: [0, 0, 0],
        isLocked: false,
        isVisible: true,
        isCollidable: false,
        opacity: 1,
    };


    // API

    // await setAssetsApi(
    //     organization,
    //     newFloorItem.modelUuid,
    //     newFloorItem.modelName,
    //     newFloorItem.assetId,
    //     newFloorItem.position,
    //     { x: 0, y: 0, z: 0 },
    //     false,
    //     true,
    // );

    // SOCKET

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

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

        const eventData: any = { type: selectedItem.type, subType: selectedItem.subType };

        if (selectedItem.type === "Conveyor") {
            const ConveyorEvent: ConveyorEventSchema = {
                modelUuid: newFloorItem.modelUuid,
                modelName: newFloorItem.modelName,
                position: newFloorItem.position,
                rotation: newFloorItem.rotation,
                state: "idle",
                type: "transfer",
                subType: selectedItem.subType || '',
                speed: 1,
                points: data.points.map((point: THREE.Vector3, index: number) => {
                    const triggers: TriggerSchema[] = [];

                    if (data.points && index < data.points.length - 1) {
                        triggers.push({
                            triggerUuid: THREE.MathUtils.generateUUID(),
                            triggerName: `Trigger 1`,
                            triggerType: "onComplete",
                            delay: 0,
                            triggeredAsset: {
                                triggeredModel: {
                                    modelName: newFloorItem.modelName,
                                    modelUuid: newFloorItem.modelUuid,
                                },
                                triggeredPoint: {
                                    pointName: `Point`,
                                    pointUuid: "",
                                },
                                triggeredAction: {
                                    actionName: `Action 1`,
                                    actionUuid: "",
                                },
                            },
                        });
                    }

                    return {
                        uuid: THREE.MathUtils.generateUUID(),
                        position: [point.x, point.y, point.z],
                        rotation: [0, 0, 0],
                        action: {
                            actionUuid: THREE.MathUtils.generateUUID(),
                            actionName: `Action 1`,
                            actionType: "default",
                            material: "Default Material",
                            delay: 0,
                            spawnInterval: 5,
                            spawnCount: 1,
                            triggers: triggers,
                        },
                    };
                }),
            };

            for (let i = 0; i < ConveyorEvent.points.length - 1; i++) {
                const currentPoint = ConveyorEvent.points[i];
                const nextPoint = ConveyorEvent.points[i + 1];

                if (currentPoint.action.triggers.length > 0) {
                    currentPoint.action.triggers[0].triggeredAsset!.triggeredPoint!.pointUuid = nextPoint.uuid;
                    currentPoint.action.triggers[0].triggeredAsset!.triggeredAction!.actionUuid = nextPoint.action.actionUuid;
                }
            }
            addEvent(ConveyorEvent);
            eventData.points = ConveyorEvent.points.map((point) => ({
                uuid: point.uuid,
                position: point.position,
                rotation: point.rotation,
            }));
        } else if (selectedItem.type === "Vehicle") {
            const vehicleEvent: VehicleEventSchema = {
                modelUuid: newFloorItem.modelUuid,
                modelName: newFloorItem.modelName,
                position: newFloorItem.position,
                rotation: newFloorItem.rotation,
                state: "idle",
                type: "vehicle",
                subType: selectedItem.subType || '',
                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: 1,
                        steeringAngle: 0,
                        pickUpPoint: null,
                        unLoadPoint: null,
                        paths: {
                            initPickup: [],
                            pickupDrop: [],
                            dropPickup: [],
                        },
                        triggers: [],
                    },
                },
            };
            addEvent(vehicleEvent);
            eventData.point = {
                uuid: vehicleEvent.point.uuid,
                position: vehicleEvent.point.position,
                rotation: vehicleEvent.point.rotation,
            };
        } else if (selectedItem.type === "ArmBot") {
            const roboticArmEvent: RoboticArmEventSchema = {
                modelUuid: newFloorItem.modelUuid,
                modelName: newFloorItem.modelName,
                position: newFloorItem.position,
                rotation: newFloorItem.rotation,
                state: "idle",
                type: "roboticArm",
                subType: selectedItem.subType || '',
                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);
            eventData.point = {
                uuid: roboticArmEvent.point.uuid,
                position: roboticArmEvent.point.position,
                rotation: roboticArmEvent.point.rotation,
            };
        } else if (selectedItem.type === "StaticMachine") {
            const machineEvent: MachineEventSchema = {
                modelUuid: newFloorItem.modelUuid,
                modelName: newFloorItem.modelName,
                position: newFloorItem.position,
                rotation: newFloorItem.rotation,
                state: "idle",
                type: "machine",
                subType: selectedItem.subType || '',
                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);
            eventData.point = {
                uuid: machineEvent.point.uuid,
                position: machineEvent.point.position,
                rotation: machineEvent.point.rotation,
            };
        } else if (selectedItem.type === "Storage") {
            const storageEvent: StorageEventSchema = {
                modelUuid: newFloorItem.modelUuid,
                modelName: newFloorItem.modelName,
                position: newFloorItem.position,
                rotation: newFloorItem.rotation,
                state: "idle",
                type: "storageUnit",
                storageCapacity: 10,
                storageCount: 10,
                materialType: "Default material",
                subType: selectedItem.subType || '',
                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: "store",
                            triggers: [],
                        }
                    ],
                },
            };
            addEvent(storageEvent);
            eventData.point = {
                uuid: storageEvent.point.uuid,
                position: storageEvent.point.position,
                rotation: storageEvent.point.rotation,
            };
        } else if (selectedItem.type === "Human") {
            const humanEvent: HumanEventSchema = {
                modelUuid: newFloorItem.modelUuid,
                modelName: newFloorItem.modelName,
                position: newFloorItem.position,
                rotation: newFloorItem.rotation,
                state: "idle",
                type: "human",
                subType: selectedItem.subType || '',
                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: "worker",
                            loadCount: 1,
                            assemblyCount: 1,
                            assemblyCondition: {
                                conditionType: 'material',
                                materialType: "Default material"
                            },
                            manufactureCount: 1,
                            loadCapacity: 1,
                            processTime: 10,
                            triggers: []
                        }
                    ]
                }
            }
            addEvent(humanEvent);
            eventData.point = {
                uuid: humanEvent.point.uuid,
                position: humanEvent.point.position,
                rotation: humanEvent.point.rotation,
            }
        } else if (selectedItem.type === "Crane") {
            const craneEvent: CraneEventSchema = {
                modelUuid: newFloorItem.modelUuid,
                modelName: newFloorItem.modelName,
                position: newFloorItem.position,
                rotation: newFloorItem.rotation,
                state: "idle",
                type: "crane",
                subType: selectedItem.subType || '',
                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: "pickAndDrop",
                            maxPickUpCount: 1,
                            triggers: []
                        }
                    ]
                }
            }
            addEvent(craneEvent);
            eventData.point = {
                uuid: craneEvent.point.uuid,
                position: craneEvent.point.position,
                rotation: craneEvent.point.rotation,
            }
        }

        const completeData = {
            organization,
            modelUuid: newFloorItem.modelUuid,
            modelName: newFloorItem.modelName,
            assetId: newFloorItem.assetId,
            position: newFloorItem.position,
            rotation: { x: model.rotation.x, y: model.rotation.y, z: model.rotation.z, },
            isLocked: false,
            isVisible: true,
            socketId: socket.id,
            eventData: eventData,
            versionId: versionId,
            projectId: projectId,
            userId: userId,
        };

        socket.emit("v1:model-asset:add", completeData);

        const asset: Asset = {
            modelUuid: completeData.modelUuid,
            modelName: completeData.modelName,
            assetId: completeData.assetId,
            position: completeData.position,
            rotation: [completeData.rotation.x, completeData.rotation.y, completeData.rotation.z,] as [number, number, number],
            isLocked: completeData.isLocked,
            isCollidable: false,
            isVisible: completeData.isVisible,
            opacity: 1,
            eventData: completeData.eventData,
        };

        addAsset(asset);
    } else {
        const data = {
            organization,
            modelUuid: newFloorItem.modelUuid,
            modelName: newFloorItem.modelName,
            assetId: newFloorItem.assetId,
            position: newFloorItem.position,
            rotation: { x: model.rotation.x, y: model.rotation.y, z: model.rotation.z, },
            isLocked: false,
            isVisible: true,
            socketId: socket.id,
            versionId: versionId,
            projectId: projectId,
            userId: userId,
        };

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

        const asset = {
            modelUuid: data.modelUuid,
            modelName: data.modelName,
            assetId: data.assetId,
            position: data.position,
            rotation: [data.rotation.x, data.rotation.y, data.rotation.z] as [number, number, number],
            isLocked: data.isLocked,
            isCollidable: false,
            isVisible: data.isVisible,
            opacity: 1,
        };

        addAsset(asset);
    }
}

export default addAssetModel;
