import * as THREE from 'three';
import { useEffect, useMemo, useRef, useState } from 'react';
import { Base, Geometry, Subtraction } from '@react-three/csg';
import { GLTF, GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader';
import { retrieveGLTF, storeGLTF } from '../../../../../utils/indexDB/idbUtils';
import { useSceneContext } from '../../../../scene/sceneContext';

import { useWallAssetEventHandler } from './eventHandler/useWallAssetEventHandler';

function WallAssetInstance({ wallAsset }: { readonly wallAsset: WallAsset }) {
    const url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_MARKETPLACE_URL}`;
    const { wallStore } = useSceneContext();
    const { walls, getWallById } = wallStore();
    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 { handlePointerDown, handleClick, handlePointerMissed, handlePointerEnter, handlePointerLeave } = useWallAssetEventHandler({ wallAsset, groupRef, wall });

    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();

    }, []);

    if (!gltfScene || !boundingBox) { 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, 0]} scale={[size.x, size.y, (wall?.wallThickness ?? 0) + 0.05]}>
                <Geometry>
                    <Base geometry={new THREE.BoxGeometry()} />
                </Geometry>
            </Subtraction>
            {gltfScene && (
                <mesh
                    onPointerDown={handlePointerDown}
                    onClick={handleClick}
                    onPointerMissed={handlePointerMissed}
                    onPointerEnter={handlePointerEnter}
                    onPointerLeave={handlePointerLeave}
                    userData={wallAsset}
                >
                    <primitive userData={wallAsset} object={gltfScene} />
                </mesh>
            )}
        </group>
    )
}

export default WallAssetInstance