import * as THREE from 'three';
import { useCallback, useEffect, useRef, useState } from 'react';
import { retrieveGLTF, storeGLTF } from '../../../../../utils/indexDB/idbUtils';
import { GLTF, GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader';
import { ThreeEvent, useFrame, useThree } from '@react-three/fiber';
import { useActiveTool, useDeletableFloorItem, useLimitDistance, useRenderDistance, useSelectedFloorItem, useSocketStore, useToggleView, useToolMode } from '../../../../../store/builder/store';
import { AssetBoundingBox } from '../../functions/assetBoundingBox';
import { CameraControls } from '@react-three/drei';
import useModuleStore, { useSubModuleStore } from '../../../../../store/useModuleStore';
import { useLeftData, useTopData } from '../../../../../store/visualization/useZone3DWidgetStore';
import { useSelectedAsset } from '../../../../../store/simulation/useSimulationStore';
import { useProductContext } from '../../../../simulation/products/productContext';
import { useParams } from 'react-router-dom';
import { getUserData } from '../../../../../functions/getUserData';
import { useSceneContext } from '../../../../scene/sceneContext';
import { useVersionContext } from '../../../version/versionContext';
import { SkeletonUtils } from 'three-stdlib';
import { useAnimationPlaySpeed, usePauseButtonStore } from '../../../../../store/usePlayButtonStore';
import { upsertProductOrEventApi } from '../../../../../services/simulation/products/UpsertProductOrEventApi';
import { getAssetIksApi } from '../../../../../services/simulation/ik/getAssetIKs';

function Model({ asset }: { readonly asset: Asset }) {
    const url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_MARKETPLACE_URL}`;
    const { camera, controls, gl } = useThree();
    const { activeTool } = useActiveTool();
    const { toolMode } = useToolMode();
    const { toggleView } = useToggleView();
    const { subModule } = useSubModuleStore();
    const { activeModule } = useModuleStore();
    const { speed } = useAnimationPlaySpeed();
    const { isPaused } = usePauseButtonStore();
    const { assetStore, eventStore, productStore } = useSceneContext();
    const { removeAsset, setAnimations, resetAnimation, setAnimationComplete } = assetStore();
    const { setTop } = useTopData();
    const { setLeft } = useLeftData();
    const { getIsEventInProduct, addPoint } = productStore();
    const { getEventByModelUuid } = eventStore();
    const { selectedProductStore } = useProductContext();
    const { selectedProduct } = selectedProductStore();
    const { setSelectedAsset, clearSelectedAsset } = useSelectedAsset();
    const { socket } = useSocketStore();
    const { deletableFloorItem, setDeletableFloorItem } = useDeletableFloorItem();
    const { selectedFloorItem, setSelectedFloorItem } = useSelectedFloorItem();
    const { limitDistance } = useLimitDistance();
    const { renderDistance } = useRenderDistance();
    const leftDrag = useRef(false);
    const isLeftMouseDown = useRef(false);
    const rightDrag = useRef(false);
    const isRightMouseDown = useRef(false);
    const [isRendered, setIsRendered] = useState(false);
    const [gltfScene, setGltfScene] = useState<GLTF["scene"] | null>(null);
    const [boundingBox, setBoundingBox] = useState<THREE.Box3 | null>(null);
    const groupRef = useRef<THREE.Group>(null);
    const mixerRef = useRef<THREE.AnimationMixer>();
    const actions = useRef<{ [name: string]: THREE.AnimationAction }>({});
    const [previousAnimation, setPreviousAnimation] = useState<string | null>(null);
    const [ikData, setIkData] = useState<any>();
    const blendFactor = useRef(0);
    const blendDuration = 0.5;
    const { selectedVersionStore } = useVersionContext();
    const { selectedVersion } = selectedVersionStore();
    const { userId, organization } = getUserData();
    const { projectId } = useParams();

    const updateBackend = (
        productName: string,
        productUuid: string,
        projectId: string,
        eventData: EventsSchema
    ) => {
        upsertProductOrEventApi({
            productName: productName,
            productUuid: productUuid,
            projectId: projectId,
            eventDatas: eventData,
            versionId: selectedVersion?.versionId || '',
        });
    };

    useEffect(() => {
        if (!ikData && asset.eventData && asset.eventData.type === 'ArmBot') {
            getAssetIksApi(asset.assetId).then((data) => {
                if (data.iks) {
                    const iks: IK[] = data.iks;
                    setIkData(iks);
                }
            })
        }
    }, [asset.modelUuid, ikData])

    useEffect(() => {
        setDeletableFloorItem(null);
        if (selectedFloorItem === null || selectedFloorItem.userData.modelUuid !== asset.modelUuid) {
            resetAnimation(asset.modelUuid);
        }
    }, [activeModule, toolMode, selectedFloorItem])

    useEffect(() => {
        if (selectedFloorItem && selectedFloorItem.userData.modelUuid === asset.modelUuid) {
            setSelectedFloorItem(groupRef.current);
        }
    }, [isRendered, selectedFloorItem])

    useEffect(() => {
        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);
        const loadModel = async () => {
            try {

                // Check Cache
                const assetId = asset.assetId;
                const cachedModel = THREE.Cache.get(assetId);
                if (cachedModel) {
                    const clone: any = SkeletonUtils.clone(cachedModel.scene);
                    clone.animations = cachedModel.animations || [];
                    setGltfScene(clone);
                    calculateBoundingBox(clone);
                    if (cachedModel.animations && clone.animations.length > 0) {
                        const animationName = clone.animations.map((clip: any) => clip.name);
                        setAnimations(asset.modelUuid, animationName)
                        mixerRef.current = new THREE.AnimationMixer(clone);

                        clone.animations.forEach((animation: any) => {
                            const action = mixerRef.current!.clipAction(animation);
                            actions.current[animation.name] = action;
                        });
                    }
                    return;
                }

                // Check IndexedDB
                const indexedDBModel = await retrieveGLTF(assetId);
                if (indexedDBModel) {
                    const blobUrl = URL.createObjectURL(indexedDBModel);
                    loader.load(blobUrl, (gltf) => {
                        URL.revokeObjectURL(blobUrl);
                        THREE.Cache.remove(blobUrl);
                        THREE.Cache.add(assetId, gltf);
                        setGltfScene(gltf.scene.clone());
                        calculateBoundingBox(gltf.scene);
                    },
                        undefined,
                        (error) => {
                            echo.error(`[IndexedDB] Error loading ${asset.modelName}:`);
                            URL.revokeObjectURL(blobUrl);
                        }
                    );
                    return;
                }

                // Fetch from Backend
                const modelUrl = `${url_Backend_dwinzo}/api/v2/AssetFile/${assetId}`;
                const handleBackendLoad = async (gltf: GLTF) => {
                    try {
                        const response = await fetch(modelUrl);
                        const modelBlob = await response.blob();
                        await storeGLTF(assetId, modelBlob);
                        THREE.Cache.add(assetId, gltf);
                        setGltfScene(gltf.scene.clone());
                        calculateBoundingBox(gltf.scene);
                    } catch (error) {
                        console.error(`[Backend] Error storing/loading ${asset.modelName}:`, error);
                    }
                };
                loader.load(
                    modelUrl,
                    handleBackendLoad,
                    undefined,
                    (error) => {
                        echo.error(`[Backend] Error loading ${asset.modelName}:`);
                    }
                );
            } catch (err) {
                console.error("Failed to load model:", asset.assetId, err);
            }
        };

        const calculateBoundingBox = (scene: THREE.Object3D) => {
            const box = new THREE.Box3().setFromObject(scene);
            setBoundingBox(box);
        };

        loadModel();

    }, []);

    useFrame(() => {
        const assetPosition = new THREE.Vector3(...asset.position);
        if (limitDistance) {
            if (!isRendered && assetPosition.distanceTo(camera.position) <= renderDistance) {
                setIsRendered(true);
            } else if (isRendered && assetPosition.distanceTo(camera.position) > renderDistance) {
                setIsRendered(false);
            }
        } else {
            if (!isRendered) {
                setIsRendered(true);
            }
        }
    })

    const handleDblClick = (asset: Asset) => {
        if (asset) {
            if (activeTool === "cursor" && boundingBox && groupRef.current && activeModule === 'builder') {
                const size = boundingBox.getSize(new THREE.Vector3());
                const center = boundingBox.getCenter(new THREE.Vector3());

                const front = new THREE.Vector3(0, 0, 1);
                groupRef.current.localToWorld(front);
                front.sub(groupRef.current.position).normalize();

                const distance = Math.max(size.x, size.y, size.z) * 2;
                const newPosition = center.clone().addScaledVector(front, distance);

                (controls as CameraControls).setPosition(
                    newPosition.x,
                    newPosition.y,
                    newPosition.z,
                    true
                );
                (controls as CameraControls).setTarget(center.x, center.y, center.z, true);
                (controls as CameraControls).fitToBox(groupRef.current, true, {
                    cover: true,
                    paddingTop: 5,
                    paddingLeft: 5,
                    paddingBottom: 5,
                    paddingRight: 5,
                });
                setSelectedFloorItem(groupRef.current);
            }
        }
    };

    const handleClick = (evt: ThreeEvent<MouseEvent>, asset: Asset) => {
        if (leftDrag.current || toggleView) return;
        if (activeTool === 'delete' && deletableFloorItem && deletableFloorItem.uuid === asset.modelUuid) {

            //REST

            // const response = await deleteFloorItem(organization, asset.modelUuid, asset.modelName);

            //SOCKET 

            const data = {
                organization,
                modelUuid: asset.modelUuid,
                modelName: asset.modelName,
                socketId: socket.id,
                userId,
                versionId: selectedVersion?.versionId || '',
                projectId
            }

            const response = socket.emit('v1:model-asset:delete', data)

            eventStore.getState().removeEvent(asset.modelUuid);
            const updatedEvents = productStore.getState().deleteEvent(asset.modelUuid);

            updatedEvents.forEach((event) => {
                updateBackend(
                    selectedProduct.productName,
                    selectedProduct.productUuid,
                    projectId || '',
                    event
                );
            })

            if (response) {

                removeAsset(asset.modelUuid);

                echo.success("Model Removed!");
            }

        } else if (activeModule === 'simulation' && subModule === "simulations" && activeTool === 'pen') {
            if (asset.eventData && asset.eventData.type === 'Conveyor') {
                const intersectedPoint = evt.point;
                const localPosition = groupRef.current?.worldToLocal(intersectedPoint.clone());
                if (localPosition) {
                    const conveyorPoint: ConveyorPointSchema = {
                        uuid: THREE.MathUtils.generateUUID(),
                        position: [localPosition?.x, localPosition?.y, localPosition?.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: []
                        }
                    }

                    const event = addPoint(selectedProduct.productUuid, asset.modelUuid, conveyorPoint);

                    if (event) {
                        updateBackend(
                            selectedProduct.productName,
                            selectedProduct.productUuid,
                            projectId || '',
                            event
                        );
                    }
                }
            }

        }
    }

    const handlePointerOver = useCallback((asset: Asset) => {
        if (activeTool === "delete" && activeModule === 'builder') {
            if (deletableFloorItem && deletableFloorItem.uuid === asset.modelUuid) {
                return;
            } else {
                setDeletableFloorItem(groupRef.current);
            }
        }
    }, [activeTool, activeModule, deletableFloorItem]);

    const handlePointerOut = useCallback((evt: ThreeEvent<MouseEvent>, asset: Asset) => {
        if (evt.intersections.length === 0 && activeTool === "delete" && deletableFloorItem && deletableFloorItem.uuid === asset.modelUuid) {
            setDeletableFloorItem(null);
        }
    }, [activeTool, deletableFloorItem]);

    const handleContextMenu = (asset: Asset, evt: ThreeEvent<MouseEvent>) => {
        if (rightDrag.current || toggleView) return;
        if (activeTool === "cursor" && subModule === 'simulations') {
            if (asset.modelUuid) {
                const canvasElement = gl.domElement;
                const isInProduct = getIsEventInProduct(selectedProduct.productUuid, asset.modelUuid);
                if (isInProduct) {
                    const event = getEventByModelUuid(asset.modelUuid);
                    if (event) {
                        setSelectedAsset(event);
                        const canvasRect = canvasElement.getBoundingClientRect();
                        const relativeX = evt.clientX - canvasRect.left;
                        const relativeY = evt.clientY - canvasRect.top;
                        setTop(relativeY);
                        setLeft(relativeX);
                    } else {
                        clearSelectedAsset();
                    }
                } else {
                    const event = getEventByModelUuid(asset.modelUuid);
                    if (event) {
                        setSelectedAsset(event)
                        const canvasRect = canvasElement.getBoundingClientRect();
                        const relativeX = evt.clientX - canvasRect.left;
                        const relativeY = evt.clientY - canvasRect.top;
                        setTop(relativeY);
                        setLeft(relativeX);
                    } else {
                        clearSelectedAsset()
                    }
                }
            } else {
                clearSelectedAsset()
            }
        } else {
            clearSelectedAsset()
        }
    }

    const handleAnimationComplete = useCallback(() => {
        if (asset.animationState) {
            setAnimationComplete(asset.modelUuid, true);
        }
    }, [asset.animationState]);

    useFrame((_, delta) => {
        if (mixerRef.current) {
            mixerRef.current.update(delta * (activeModule === 'simulation' ? speed : 1));
        }
    });

    useEffect(() => {
        if (!asset.animationState || !mixerRef.current) return;

        const { current, loopAnimation, isPlaying } = asset.animationState;
        const currentAction = actions.current[current];
        const previousAction = previousAnimation ? actions.current[previousAnimation] : null;

        if (isPlaying && currentAction && activeModule === 'simulation' && !isPaused) {
            blendFactor.current = 0;

            currentAction.reset();
            currentAction.setLoop(loopAnimation ? THREE.LoopRepeat : THREE.LoopOnce, loopAnimation ? Infinity : 1);
            currentAction.clampWhenFinished = true;

            if (previousAction && previousAction !== currentAction) {
                previousAction.crossFadeTo(currentAction, blendDuration, false);
            }

            currentAction.play();
            mixerRef.current.addEventListener('finished', handleAnimationComplete);
            setPreviousAnimation(current);
        } else {
            Object.values(actions.current).forEach((action) => action.stop());
        }

        return () => {
            if (mixerRef.current) {
                mixerRef.current.removeEventListener('finished', handleAnimationComplete);
            }
        };
    }, [asset.animationState?.current, asset.animationState?.isCompleted, asset.animationState?.isPlaying, isPaused, activeModule]);

    useEffect(() => {
        const canvasElement = gl.domElement;

        const onPointerDown = (evt: any) => {
            if (evt.button === 0) {
                isLeftMouseDown.current = true;
                leftDrag.current = false;
            }
            if (evt.button === 2) {
                isRightMouseDown.current = true;
                rightDrag.current = false;
            }
        };

        const onPointerMove = () => {
            if (isLeftMouseDown.current) {
                leftDrag.current = true;
            }
            if (isRightMouseDown.current) {
                rightDrag.current = true;
            }
        };

        const onPointerUp = (evt: any) => {
            if (evt.button === 0) {
                isLeftMouseDown.current = false;
            }
            if (evt.button === 2) {
                isRightMouseDown.current = false;
            }
        };

        canvasElement.addEventListener('pointerdown', onPointerDown);
        canvasElement.addEventListener('pointermove', onPointerMove);
        canvasElement.addEventListener('pointerup', onPointerUp);

        return () => {
            canvasElement.removeEventListener('pointerdown', onPointerDown);
            canvasElement.removeEventListener('pointermove', onPointerMove);
            canvasElement.removeEventListener('pointerup', onPointerUp);
        }

    }, [gl])

    return (
        <group
            key={asset.modelUuid}
            name='Asset Model'
            ref={groupRef}
            uuid={asset.modelUuid}
            position={asset.position}
            rotation={asset.rotation}
            visible={asset.isVisible}
            userData={{ ...asset, iks: ikData }}
            onDoubleClick={(e) => {
                e.stopPropagation();
                if (!toggleView) {
                    handleDblClick(asset);
                }
            }}
            onClick={(e) => {
                e.stopPropagation();
                if (!toggleView) {
                    handleClick(e, asset);
                }
            }}
            onPointerOver={(e) => {
                e.stopPropagation();
                if (!toggleView) {
                    handlePointerOver(asset);
                }
            }}
            onPointerLeave={(e) => {
                e.stopPropagation();
                if (!toggleView) {
                    handlePointerOut(e, asset);
                }
            }}
            onContextMenu={(e) => {
                e.stopPropagation();
                handleContextMenu(asset, e);
            }}
        >
            {gltfScene && (
                isRendered ? (
                    <primitive object={gltfScene} />
                ) : (
                    <AssetBoundingBox boundingBox={boundingBox} />
                )
            )}
        </group>
    );
}

export default Model;