import * as THREE from 'three';
import { useRef, useEffect, useState, useCallback } from 'react';
import { useThree } from '@react-three/fiber';
import { TransformControls } from '@react-three/drei';
import { useSocketStore, useToolMode } from '../../../../../store/builder/store';
import { useProductContext } from '../../../../simulation/products/productContext';
import { useVersionContext } from '../../../../builder/version/versionContext';
import { useSceneContext } from '../../../sceneContext';
import { useParams } from 'react-router-dom';

import { getUserData } from '../../../../../functions/getUserData';
import { upsertProductOrEventApi } from '../../../../../services/simulation/products/UpsertProductOrEventApi';
import { detectModifierKeys } from '../../../../../utils/shortcutkeys/detectModifierKeys';
import { setAssetsApi } from '../../../../../services/factoryBuilder/asset/floorAsset/setAssetsApi';

function TransformControls3D() {
    const { toolMode } = useToolMode();
    const { camera, scene, gl } = useThree();
    const transformControlsRef = useRef<any>(null);
    const [visible, setVisible] = useState(false);
    const [keyEvent, setKeyEvent] = useState<"Ctrl" | "Shift" | "Ctrl+Shift" | "">("");
    const { socket } = useSocketStore();
    const { selectedProductStore } = useProductContext();
    const { selectedProduct } = selectedProductStore();
    const { assetStore, eventStore, productStore, undoRedo3DStore } = useSceneContext();
    const { push3D, subscribeUndoRedo } = undoRedo3DStore();
    const { updateAsset, getAssetById, selectedAssets, setSelectedAssets } = assetStore();
    const { userId, organization } = getUserData();
    const { selectedVersionStore } = useVersionContext();
    const { selectedVersion } = selectedVersionStore();
    const { projectId } = useParams();
    const pivotPointRef = useRef<THREE.Vector3>(new THREE.Vector3());
    const initialPositionsRef = useRef<Map<string, THREE.Vector3>>(new Map());
    const initialRotationsRef = useRef<Map<string, THREE.Euler>>(new Map());
    const initialPivotPositionRef = useRef<THREE.Vector3>(new THREE.Vector3());
    const initialQuaternionsRef = useRef<Map<string, THREE.Quaternion>>(new Map());
    const tempObjectRef = useRef<THREE.Object3D>(new THREE.Object3D());

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

    const recalcGizmo = useCallback(() => {
        if (selectedAssets.length === 0) return;

        const bbox = new THREE.Box3();
        selectedAssets.forEach(obj => bbox.expandByObject(obj));
        bbox.getCenter(pivotPointRef.current);
        initialPivotPositionRef.current.copy(pivotPointRef.current);

        tempObjectRef.current.position.copy(pivotPointRef.current);
        tempObjectRef.current.rotation.set(0, 0, 0);
        tempObjectRef.current.scale.set(1, 1, 1);

        initialPositionsRef.current.clear();
        initialRotationsRef.current.clear();
        initialQuaternionsRef.current.clear();

        selectedAssets.forEach(obj => {
            initialPositionsRef.current.set(obj.uuid, obj.position.clone());
            initialRotationsRef.current.set(obj.uuid, obj.rotation.clone());
            initialQuaternionsRef.current.set(obj.uuid, obj.quaternion.clone());
        });
    }, [selectedAssets]);

    useEffect(() => {
        const unsubscribe = subscribeUndoRedo(() => {
            setTimeout(() => {
                recalcGizmo();
            }, 10);
        });
        return unsubscribe;
    }, [subscribeUndoRedo, recalcGizmo]);

    const handleTransformationComplete = useCallback(() => {
        if (selectedAssets.length === 0) return;

        const updatedAssets = [...selectedAssets];
        setSelectedAssets(updatedAssets);

        selectedAssets.forEach(obj => {
            const asset = getAssetById(obj.uuid);
            if (!asset) return;

            updateAsset(asset.modelUuid, {
                position: [obj.position.x, obj.position.y, obj.position.z],
                rotation: [obj.rotation.x, obj.rotation.y, obj.rotation.z],
            });

            if (asset.eventData) {
                const eventData = eventStore.getState().getEventByModelUuid(asset.modelUuid);
                const productData = productStore.getState().getEventByModelUuid(
                    selectedProduct.productUuid,
                    asset.modelUuid
                );

                if (eventData) {
                    eventStore.getState().updateEvent(asset.modelUuid, {
                        position: [obj.position.x, obj.position.y, obj.position.z],
                        rotation: [obj.rotation.x, obj.rotation.y, obj.rotation.z],
                    });
                }

                if (productData) {
                    const event = productStore
                        .getState()
                        .updateEvent(
                            selectedProduct.productUuid,
                            asset.modelUuid,
                            {
                                position: [obj.position.x, obj.position.y, obj.position.z],
                                rotation: [obj.rotation.x, obj.rotation.y, obj.rotation.z],
                            }
                        );

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


            if (!socket?.active) {
                // API

                setAssetsApi({
                    modelUuid: asset.modelUuid,
                    modelName: asset.modelName,
                    assetId: asset.assetId,
                    position: [obj.position.x, obj.position.y, obj.position.z],
                    rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z },
                    isLocked: false,
                    isVisible: true,
                    versionId: selectedVersion?.versionId || '',
                    projectId: projectId,
                });
            } else {

                // SOCKET

                const data = {
                    organization,
                    modelUuid: asset.modelUuid,
                    modelName: asset.modelName,
                    assetId: asset.assetId,
                    position: [obj.position.x, obj.position.y, obj.position.z],
                    rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z },
                    isLocked: false,
                    isVisible: true,
                    socketId: socket.id,
                    versionId: selectedVersion?.versionId || '',
                    userId,
                    projectId
                };

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

        });
    }, [selectedAssets, setSelectedAssets, getAssetById, updateAsset, eventStore, productStore, selectedProduct, updateBackend, projectId, organization, socket, selectedVersion, userId, push3D]);

    useEffect(() => {
        const temp = tempObjectRef.current;
        scene.add(temp);
        return () => {
            scene.remove(temp);
        };
    }, [scene]);

    useEffect(() => {
        if (selectedAssets.length === 0) {
            setVisible(false);
            return;
        }

        const bbox = new THREE.Box3();
        selectedAssets.forEach(obj => bbox.expandByObject(obj));
        bbox.getCenter(pivotPointRef.current);
        initialPivotPositionRef.current.copy(pivotPointRef.current);

        tempObjectRef.current.position.copy(pivotPointRef.current);
        tempObjectRef.current.rotation.set(0, 0, 0);
        tempObjectRef.current.scale.set(1, 1, 1);

        initialPositionsRef.current.clear();
        initialRotationsRef.current.clear();
        initialQuaternionsRef.current.clear();
        selectedAssets.forEach(obj => {
            initialPositionsRef.current.set(obj.uuid, obj.position.clone());
            initialRotationsRef.current.set(obj.uuid, obj.rotation.clone());
            initialQuaternionsRef.current.set(obj.uuid, obj.quaternion.clone());
        });

        setVisible(true);
    }, [selectedAssets]);

    useEffect(() => {
        const controls = transformControlsRef.current;
        if (!controls || !visible) return;

        controls.attach(tempObjectRef.current);
        controls.setMode(toolMode === 'Move-Asset' ? 'translate' : 'rotate');

        const onObjectChange = () => {
            const temp = tempObjectRef.current;
            if (!temp) return;

            selectedAssets.forEach(obj => {
                const initialPos = initialPositionsRef.current.get(obj.uuid);
                const initialQuat = initialQuaternionsRef.current.get(obj.uuid);
                if (!initialPos || !initialQuat) return;

                if (controls.mode === 'translate') {
                    const delta = new THREE.Vector3().copy(temp.position).sub(initialPivotPositionRef.current);
                    obj.position.copy(initialPos).add(delta);
                } else if (controls.mode === 'rotate') {
                    const deltaQuat = temp.quaternion.clone();
                    const relPos = initialPos.clone().sub(initialPivotPositionRef.current);
                    relPos.applyQuaternion(deltaQuat);
                    obj.position.copy(initialPivotPositionRef.current).add(relPos);
                    obj.quaternion.copy(deltaQuat).multiply(initialQuat);
                }
            });
        };

        const onMouseUp = () => {
            const assetsToUpdate: AssetData[] = [];
            const undoActions: UndoRedo3DAction[] = [];

            selectedAssets.forEach((obj) => {
                const asset = getAssetById(obj.uuid);
                if (!asset) return;

                const initialPos = initialPositionsRef.current.get(obj.uuid);
                const initialRot = initialRotationsRef.current.get(obj.uuid);
                if (!initialPos || !initialRot) return;

                const assetData: Asset = {
                    ...asset,
                    position: [initialPos.x, initialPos.y, initialPos.z],
                    rotation: [initialRot.x, initialRot.y, initialRot.z],
                };

                const newData: Asset = {
                    ...asset,
                    position: [obj.position.x, obj.position.y, obj.position.z],
                    rotation: [obj.rotation.x, obj.rotation.y, obj.rotation.z],
                };

                assetsToUpdate.push({
                    type: "Asset",
                    assetData,
                    newData,
                    timeStap: new Date().toISOString(),
                });

                updateAsset(asset.modelUuid, {
                    position: [obj.position.x, obj.position.y, obj.position.z],
                    rotation: [obj.rotation.x, obj.rotation.y, obj.rotation.z],
                });
            });

            if (assetsToUpdate.length > 0) {
                if (assetsToUpdate.length === 1) {
                    undoActions.push({
                        module: "builder",
                        actionType: "Asset-Update",
                        asset: assetsToUpdate[0],
                    });
                } else {
                    undoActions.push({
                        module: "builder",
                        actionType: "Assets-Update",
                        assets: assetsToUpdate,
                    });
                }

                push3D({
                    type: "Scene",
                    actions: undoActions,
                });
            }

            selectedAssets.forEach(obj => {
                initialPositionsRef.current.set(obj.uuid, obj.position.clone());
                initialRotationsRef.current.set(obj.uuid, obj.rotation.clone());
                initialQuaternionsRef.current.set(obj.uuid, obj.quaternion.clone());
            });

            if (selectedAssets.length > 0) {
                const bbox = new THREE.Box3();
                selectedAssets.forEach(obj => bbox.expandByObject(obj));
                bbox.getCenter(pivotPointRef.current);
                initialPivotPositionRef.current.copy(pivotPointRef.current);

                tempObjectRef.current.position.copy(pivotPointRef.current);
                tempObjectRef.current.rotation.set(0, 0, 0);
            }

            recalcGizmo();
            handleTransformationComplete();
        };

        controls.addEventListener('objectChange', onObjectChange);
        controls.addEventListener('mouseUp', onMouseUp);

        return () => {
            controls.removeEventListener('objectChange', onObjectChange);
            controls.removeEventListener('mouseUp', onMouseUp);
            controls.detach();
        };
    }, [selectedAssets, toolMode, visible, handleTransformationComplete]);

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

        const handleKeyDown = (event: KeyboardEvent) => {
            const keyCombination = detectModifierKeys(event);

            if (keyCombination !== keyEvent) {
                if (keyCombination === "Ctrl" || keyCombination === "Ctrl+Shift" || keyCombination === "Shift") {
                    setKeyEvent(keyCombination);
                } else {
                    setKeyEvent("");
                }
            }
        };

        const onKeyUp = (event: KeyboardEvent) => {
            const keyCombination = detectModifierKeys(event);

            if (keyCombination === "") {
                setKeyEvent("");
            } else if (keyCombination === "Ctrl" || keyCombination === "Ctrl+Shift" || keyCombination === "Shift") {
                setKeyEvent(keyCombination);
            }
        }

        if (visible) {
            canvasElement.addEventListener('keydown', handleKeyDown);
            canvasElement.addEventListener('keyup', onKeyUp);
        }

        return () => {
            canvasElement.removeEventListener('keydown', handleKeyDown);
            canvasElement.removeEventListener('keyup', onKeyUp);
        }
    }, [gl, visible, keyEvent]);

    if (!visible || (toolMode !== 'Move-Asset' && toolMode !== 'Rotate-Asset')) {
        return null;
    }

    return (
        <TransformControls
            space="world"
            translationSnap={keyEvent === 'Ctrl' ? 0.5 : keyEvent === 'Ctrl+Shift' ? 0.1 : 0}
            rotationSnap={keyEvent === 'Ctrl' ? THREE.MathUtils.degToRad(15) : keyEvent === 'Ctrl+Shift' ? THREE.MathUtils.degToRad(5) : 0}
            ref={transformControlsRef}
            camera={camera}
            showX
            showY
            showZ
        />
    );
}

export default TransformControls3D;