import * as THREE from 'three';
import * as Constants from '../../../types/world/worldConstants';
import { useRef, useState, useEffect, useMemo } from 'react';
import { useToolMode } from '../../../store/builder/store';
import { DragControls } from '@react-three/drei';
import { useThree } from '@react-three/fiber';
import { useBuilderStore } from '../../../store/builder/useBuilderStore';
import { usePointSnapping } from './helpers/usePointSnapping';
import { deleteAisleApi } from '../../../services/factoryBuilder/aisle/deleteAisleApi';
import { useParams } from 'react-router-dom';
import { createAisleApi } from '../../../services/factoryBuilder/aisle/createAisleApi';
import { useVersionContext } from '../version/versionContext';
import { useSceneContext } from '../../scene/sceneContext';

function Point({ point }: { readonly point: Point }) {
    const materialRef = useRef<THREE.ShaderMaterial>(null);
    const { raycaster, camera, pointer, gl } = useThree();
    const plane = useMemo(() => new THREE.Plane(new THREE.Vector3(0, 1, 0), 0), []);
    const [isHovered, setIsHovered] = useState(false);
    const [dragOffset, setDragOffset] = useState<THREE.Vector3 | null>(null);
    const { toolMode } = useToolMode();
    const { aisleStore, wallStore } = useSceneContext();
    const { setPosition: setAislePosition, removePoint: removeAislePoint, getAislesByPointId } = aisleStore();
    const { setPosition: setWallPosition, removePoint: removeWallPoint } = wallStore();
    const { snapAislePoint, snapAisleAngle, snapWallPoint, snapWallAngle } = usePointSnapping({ uuid: point.pointUuid, pointType: point.pointType, position: point.position });
    const { hoveredPoint, setHoveredPoint } = useBuilderStore();
    const { selectedVersionStore } = useVersionContext();
    const { selectedVersion } = selectedVersionStore();
    const { projectId } = useParams();
    const boxScale: [number, number, number] = Constants.pointConfig.boxScale;
    const colors = getColor(point);

    useEffect(() => {
        gl.domElement.style.cursor = 'default';
    }, [toolMode])

    function getColor(point: Point) {
        if (point.pointType === 'Aisle') {
            return {
                defaultInnerColor: Constants.pointConfig.defaultInnerColor,
                defaultOuterColor: Constants.pointConfig.aisleOuterColor,
                defaultDeleteColor: Constants.pointConfig.deleteColor,
            }
        } else if (point.pointType === 'Floor') {
            return {
                defaultInnerColor: Constants.pointConfig.defaultInnerColor,
                defaultOuterColor: Constants.pointConfig.floorOuterColor,
                defaultDeleteColor: Constants.pointConfig.deleteColor,
            }
        } else if (point.pointType === 'Wall') {
            return {
                defaultInnerColor: Constants.pointConfig.defaultInnerColor,
                defaultOuterColor: Constants.pointConfig.wallOuterColor,
                defaultDeleteColor: Constants.pointConfig.deleteColor,
            }
        } else if (point.pointType === 'Zone') {
            return {
                defaultInnerColor: Constants.pointConfig.defaultInnerColor,
                defaultOuterColor: Constants.pointConfig.zoneOuterColor,
                defaultDeleteColor: Constants.pointConfig.deleteColor,
            }
        } else {
            return {
                defaultInnerColor: Constants.pointConfig.defaultInnerColor,
                defaultOuterColor: Constants.pointConfig.defaultOuterColor,
                defaultDeleteColor: Constants.pointConfig.deleteColor,
            }
        }
    }

    useEffect(() => {
        if (materialRef.current && (toolMode === 'move' || toolMode === '2D-Delete')) {
            let innerColor;
            let outerColor;
            if (isHovered) {
                innerColor = toolMode === '2D-Delete' ? colors.defaultDeleteColor : colors.defaultOuterColor;
                outerColor = toolMode === '2D-Delete' ? colors.defaultDeleteColor : colors.defaultOuterColor;
            } else {
                innerColor = colors.defaultInnerColor;
                outerColor = colors.defaultOuterColor;
            }
            materialRef.current.uniforms.uInnerColor.value.set(innerColor);
            materialRef.current.uniforms.uOuterColor.value.set(outerColor);
            materialRef.current.uniformsNeedUpdate = true;
        } else if (materialRef.current && toolMode !== 'move') {
            materialRef.current.uniforms.uInnerColor.value.set(colors.defaultInnerColor);
            materialRef.current.uniforms.uOuterColor.value.set(colors.defaultOuterColor);
            materialRef.current.uniformsNeedUpdate = true;
        }
    }, [isHovered, colors.defaultInnerColor, colors.defaultOuterColor, colors.defaultDeleteColor, toolMode]);

    const uniforms = useMemo(() => ({
        uOuterColor: { value: new THREE.Color(colors.defaultOuterColor) },
        uInnerColor: { value: new THREE.Color(colors.defaultInnerColor) },
    }), [colors.defaultInnerColor, colors.defaultOuterColor]);

    const handleDrag = (point: Point) => {
        if (toolMode === 'move' && isHovered && dragOffset) {
            raycaster.setFromCamera(pointer, camera);
            const intersectionPoint = new THREE.Vector3();
            const hit = raycaster.ray.intersectPlane(plane, intersectionPoint);

            if (hit) {
                gl.domElement.style.cursor = 'move';
                const positionWithOffset = new THREE.Vector3().addVectors(hit, dragOffset);
                const newPosition: [number, number, number] = [positionWithOffset.x, positionWithOffset.y, positionWithOffset.z];

                if (point.pointType === 'Aisle') {
                    const aisleSnapped = snapAisleAngle(newPosition);
                    const finalSnapped = snapAislePoint(aisleSnapped.position);
                    setAislePosition(point.pointUuid, finalSnapped.position);
                } else if (point.pointType === 'Wall') {
                    const wallSnapped = snapWallAngle(newPosition);
                    const finalSnapped = snapWallPoint(wallSnapped.position);
                    setWallPosition(point.pointUuid, finalSnapped.position);
                }
            }
        }
    };

    const handleDragStart = (point: Point) => {
        raycaster.setFromCamera(pointer, camera);
        const intersectionPoint = new THREE.Vector3();
        const hit = raycaster.ray.intersectPlane(plane, intersectionPoint);

        if (hit) {
            const currentPosition = new THREE.Vector3(...point.position);
            const offset = new THREE.Vector3().subVectors(currentPosition, hit);
            setDragOffset(offset);
        }
    };

    const handleDragEnd = (point: Point) => {
        gl.domElement.style.cursor = 'default';
        setDragOffset(null);
        if (toolMode !== 'move') return;
        if (point.pointType === 'Aisle') {
            const updatedAisles = getAislesByPointId(point.pointUuid);
            if (updatedAisles.length > 0 && projectId) {
                updatedAisles.forEach((updatedAisle) => {
                    createAisleApi(updatedAisle.aisleUuid, updatedAisle.points, updatedAisle.type, projectId, selectedVersion?.versionId || '')
                })
            }
        } else if (point.pointType === 'Wall') {
            // console.log('Wall after drag: ', point);
        }
    }

    const handlePointClick = (point: Point) => {
        if (toolMode === '2D-Delete') {
            if (point.pointType === 'Aisle') {
                const removedAisles = removeAislePoint(point.pointUuid);
                if (removedAisles.length > 0) {
                    removedAisles.forEach(aisle => {
                        if (projectId)
                            deleteAisleApi(aisle.aisleUuid, projectId, selectedVersion?.versionId || '')
                    });
                    setHoveredPoint(null);
                }
            }
            if (point.pointType === 'Wall') {
                const removedAisles = removeWallPoint(point.pointUuid);
                if (removedAisles.length > 0) {
                    setHoveredPoint(null);
                }
            }
            gl.domElement.style.cursor = 'default';
        }
    }

    useEffect(() => {
        if (hoveredPoint && hoveredPoint.pointUuid !== point.pointUuid) {
            setIsHovered(false);
        }
    }, [hoveredPoint])

    if (!point) {
        return null;
    }

    return (
        <DragControls
            axisLock='y'
            autoTransform={false}
            onDragStart={() => handleDragStart(point)}
            onDrag={() => handleDrag(point)}
            onDragEnd={() => handleDragEnd(point)}
        >
            <mesh
                key={point.pointUuid}
                uuid={point.pointUuid}
                name={`${point.pointType}-Point`}
                position={new THREE.Vector3(...point.position)}
                onClick={() => {
                    handlePointClick(point);
                }}
                onPointerOver={() => {
                    if (!hoveredPoint) {
                        setHoveredPoint(point);
                        setIsHovered(true);
                        if (toolMode === 'move') {
                            gl.domElement.style.cursor = 'pointer';
                        }
                    }
                }}
                onPointerOut={() => {
                    if (hoveredPoint) {
                        setHoveredPoint(null);
                        gl.domElement.style.cursor = 'default';
                    }
                    setIsHovered(false)
                }}
                userData={point}
            >
                <boxGeometry args={boxScale} />
                <shaderMaterial
                    ref={materialRef}
                    uniforms={uniforms}
                    vertexShader={
                        `
                            varying vec2 vUv;

                            void main() {
                                vUv = uv;
                                gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
                            }
                        `
                    }
                    fragmentShader={
                        `
                            varying vec2 vUv;
                            uniform vec3 uOuterColor;
                            uniform vec3 uInnerColor;
                            
                            void main() {
                                // Define the size of the white square as a proportion of the face
                                float borderThickness = 0.2; // Adjust this value for border thickness
                                if (vUv.x > borderThickness && vUv.x < 1.0 - borderThickness && vUv.y > borderThickness && vUv.y < 1.0 - borderThickness) {
                                    gl_FragColor = vec4(uInnerColor, 1.0); // Inner square
                                } else {
                                        gl_FragColor = vec4(uOuterColor, 1.0); // Border
                                    }
                                }
                        `
                    }
                />
            </mesh>
        </DragControls>
    );
}

export default Point;