import * as THREE from 'three';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { ThreeEvent, useThree } from '@react-three/fiber';
import { Base, Geometry, Subtraction } from '@react-three/csg';
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 useModuleStore from '../../../../../store/useModuleStore';
import { useSceneContext } from '../../../../scene/sceneContext';
import { useBuilderStore } from '../../../../../store/builder/useBuilderStore';
import { useActiveTool, useSocketStore, useToggleView } from '../../../../../store/builder/store';
import { useParams } from 'react-router-dom';
import { useVersionContext } from '../../../version/versionContext';
import { getUserData } from '../../../../../functions/getUserData';
import closestPointOnLineSegment from '../../../line/helpers/getClosestPointOnLineSegment';

// import { upsertWallAssetApi } from '../../../../../services/factoryBuilder/asset/wallAsset/upsertWallAssetApi';
// import { deleteWallAssetApi } from '../../../../../services/factoryBuilder/asset/wallAsset/deleteWallAssetApi';

function WallAssetInstance({ wallAsset }: { wallAsset: WallAsset }) {
    const url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_MARKETPLACE_URL}`;
    const { socket } = useSocketStore();
    const { raycaster, pointer, camera, scene, controls, gl } = useThree();
    const { wallStore, wallAssetStore } = useSceneContext();
    const { walls, getWallById } = wallStore();
    const { updateWallAsset, removeWallAsset } = wallAssetStore();
    const { toggleView } = useToggleView();
    const { activeTool } = useActiveTool();
    const { activeModule } = useModuleStore();
    const { selectedWallAsset, setSelectedWallAsset, setDeletableWallAsset, deletableWallAsset } = useBuilderStore();
    const [gltfScene, setGltfScene] = useState<GLTF["scene"] | null>(null);
    const [boundingBox, setBoundingBox] = useState<THREE.Box3 | null>(null);
    const groupRef = useRef<THREE.Group>(null);
    const wall = useMemo(() => getWallById(wallAsset.wallUuid), [getWallById, wallAsset.wallUuid, walls]);
    const draggingRef = useRef(false);
    const { selectedVersionStore } = useVersionContext();
    const { selectedVersion } = selectedVersionStore();
    const { userId, organization } = getUserData();
    const { projectId } = useParams();

    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 = wallAsset.assetId;
                const cachedModel = THREE.Cache.get(assetId);
                if (cachedModel) {
                    setGltfScene(cachedModel.scene.clone());
                    calculateBoundingBox(cachedModel.scene);
                    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 ${wallAsset.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 ${wallAsset.modelName}:`, error);
                    }
                };
                loader.load(
                    modelUrl,
                    handleBackendLoad,
                    undefined,
                    (error) => {
                        echo.error(`[Backend] Error loading ${wallAsset.modelName}:`);
                    }
                );
            } catch (err) {
                console.error("Failed to load model:", wallAsset.assetId, err);
            }
        };

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

        loadModel();

    }, []);

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

        const onPointerUp = () => {
            draggingRef.current = false;
            if (controls) {
                (controls as any).enabled = true;
            }
        };

        const onPointerMove = (e: any) => {
            if (!draggingRef.current || !wall || !selectedWallAsset) return;
            if (controls) {
                (controls as any).enabled = false;
            }
            pointer.x = (e.clientX / window.innerWidth) * 2 - 1;
            pointer.y = -(e.clientY / window.innerHeight) * 2 + 1;

            raycaster.setFromCamera(pointer, camera);
            const intersects = raycaster.intersectObjects(scene.children, true);
            const intersect = intersects.find((i: any) => i.object.name.includes('WallReference'));

            if (intersect && intersect.object.userData.wallUuid) {
                const newPoint = closestPointOnLineSegment(
                    new THREE.Vector3(intersect.point.x, 0, intersect.point.z),
                    new THREE.Vector3(...intersect.object.userData.points[0].position),
                    new THREE.Vector3(...intersect.object.userData.points[1].position)
                );

                const wallRotation = intersect.object.rotation.clone();

                const updatedWallAsset = updateWallAsset(wallAsset.modelUuid, {
                    wallUuid: intersect.object.userData.wallUuid,
                    position: [newPoint.x, wallAsset.wallAssetType === 'fixed-move' ? 0 : intersect.point.y, newPoint.z],
                    rotation: [wallRotation.x, wallRotation.y, wallRotation.z],
                });

                if (projectId && updatedWallAsset) {

                    // API

                    // upsertWallAssetApi(projectId, selectedVersion?.versionId || '', updatedWallAsset);

                    // SOCKET

                    const data = {
                        wallAssetData: updatedWallAsset,
                        projectId: projectId,
                        versionId: selectedVersion?.versionId || '',
                        userId: userId,
                        organization: organization
                    }

                    socket.emit('v1:wall-asset:add', data);

                }
            }
        };

        if (selectedWallAsset && !toggleView && activeModule === 'builder') {
            canvasElement.addEventListener('mousemove', onPointerMove);
            canvasElement.addEventListener('pointerup', onPointerUp);
        }

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

    }, [gl, camera, toggleView, activeModule, selectedWallAsset, socket])

    const handlePointerClick = useCallback((wallAsset: WallAsset) => {
        if (activeTool === 'delete' && deletableWallAsset && deletableWallAsset.userData.modelUuid === wallAsset.modelUuid) {
            const removedWallAsset = removeWallAsset(wallAsset.modelUuid);

            if (projectId && removedWallAsset) {

                // API

                // deleteWallAssetApi(projectId, selectedVersion?.versionId || '', removedWallAsset.modelUuid, removedWallAsset.wallUuid);

                // SOCKET

                const data = {
                    projectId: projectId,
                    versionId: selectedVersion?.versionId || '',
                    userId: userId,
                    organization: organization,
                    modelUuid: removedWallAsset.modelUuid,
                    wallUuid: removedWallAsset.wallUuid
                }

                socket.emit('v1:wall-asset:delete', data);

            }
        }
    }, [activeTool, activeModule, deletableWallAsset, socket])

    const handlePointerOver = useCallback((wallAsset: WallAsset, currentObject: THREE.Object3D) => {
        if (activeTool === "delete" && activeModule === 'builder') {
            if (deletableWallAsset && deletableWallAsset.userData.modelUuid === wallAsset.modelUuid) {
                return;
            } else {
                setDeletableWallAsset(currentObject);
            }
        }
    }, [activeTool, activeModule, deletableWallAsset]);

    const handlePointerOut = useCallback((evt: ThreeEvent<MouseEvent>, wallAsset: WallAsset) => {
        if (evt.intersections.length === 0 && activeTool === "delete" && deletableWallAsset && deletableWallAsset.userData.modelUuid === wallAsset.modelUuid) {
            setDeletableWallAsset(null);
        }
    }, [activeTool, deletableWallAsset]);

    if (!gltfScene || !boundingBox || !wall) { return null }
    const size = new THREE.Vector3();
    boundingBox.getSize(size);
    const center = new THREE.Vector3();
    boundingBox.getCenter(center);

    return (
        <>
            <group
                key={wallAsset.modelUuid}
                name={wallAsset.modelName}
                ref={groupRef}
                position={wallAsset.position}
                rotation={wallAsset.rotation}
                visible={wallAsset.isVisible}
                userData={wallAsset}
            >
                <Subtraction position={[center.x, center.y, center.z]} scale={[size.x, size.y, wall.wallThickness + 0.05]}>
                    <Geometry>
                        <Base geometry={new THREE.BoxGeometry()} />
                    </Geometry>
                </Subtraction>
                {gltfScene && (
                    <mesh
                        onPointerDown={(e) => {
                            if (!toggleView && activeModule === 'builder' && selectedWallAsset && selectedWallAsset.userData.modelUuid === wallAsset.modelUuid) {
                                draggingRef.current = true;
                                e.stopPropagation();
                                setSelectedWallAsset(groupRef.current);
                                if (controls) {
                                    (controls as any).enabled = false;
                                }
                            }
                        }}
                        onClick={(e) => {
                            if (!toggleView && activeModule === 'builder' && activeTool !== 'delete') {
                                if (e.object) {
                                    e.stopPropagation();
                                    let currentObject = e.object as THREE.Object3D;
                                    while (currentObject) {
                                        if (currentObject.name === "Scene") {
                                            break;
                                        }
                                        currentObject = currentObject.parent as THREE.Object3D;
                                    }
                                    setSelectedWallAsset(currentObject);
                                }
                            } else if (!toggleView && activeModule === 'builder' && activeTool === 'delete') {
                                handlePointerClick(wallAsset);
                            }
                        }}
                        onPointerMissed={() => {
                            if (selectedWallAsset && selectedWallAsset.userData.modelUuid === wallAsset.modelUuid) {
                                setSelectedWallAsset(null);
                            }
                        }}
                        onPointerOver={(e) => {
                            if (!toggleView) {
                                e.stopPropagation();
                                let currentObject = e.object as THREE.Object3D;
                                while (currentObject) {
                                    if (currentObject.name === "Scene") {
                                        break;
                                    }
                                    currentObject = currentObject.parent as THREE.Object3D;
                                }
                                handlePointerOver(wallAsset, currentObject);
                            }
                        }}
                        onPointerLeave={(e) => {
                            if (!toggleView) {
                                e.stopPropagation();
                                handlePointerOut(e, wallAsset);
                            }
                        }}
                        userData={wallAsset}
                    >
                        <primitive userData={wallAsset} object={gltfScene} />
                    </mesh>
                )}
            </group>
        </>
    )
}

export default WallAssetInstance