import * as THREE from "three";
import { useMemo, useRef, useState } from "react";
import { useThree } from "@react-three/fiber";
import { useToolMode } from "../../../../store/builder/store";
import { useSceneContext } from "../../../scene/sceneContext";
import { useVersionContext } from "../../../builder/version/versionContext";
import { useProductContext } from "../../products/productContext";
import { useParams } from "react-router-dom";
import { upsertProductOrEventApi } from "../../../../services/simulation/products/UpsertProductOrEventApi";

interface ConnectionLine {
    id: string;
    startPointUuid: string;
    endPointUuid: string;
    trigger: TriggerSchema;
}

export function Arrows({ connections }: { readonly connections: ConnectionLine[] }) {
    const [hoveredArrowTrigger, setHoveredArrowTrigger] = useState<string | null>(null);
    const groupRef = useRef<THREE.Group>(null);
    const { scene } = useThree();
    const { toolMode } = useToolMode();
    const { productStore } = useSceneContext();
    const { removeTrigger } = productStore();
    const { selectedVersionStore } = useVersionContext();
    const { selectedVersion } = selectedVersionStore();
    const { selectedProductStore } = useProductContext();
    const { selectedProduct } = selectedProductStore();
    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 || '',
        })
    }

    const getWorldPositionFromScene = (uuid: string): THREE.Vector3 | null => {
        const obj = scene.getObjectByProperty("uuid", uuid);
        if (!obj) return null;
        const pos = new THREE.Vector3();
        obj.getWorldPosition(pos);
        return pos;
    };

    const processedArrows = new Set<string>();

    const createArrow = (
        key: string,
        fullCurve: THREE.QuadraticBezierCurve3,
        centerT: number,
        segmentSize: number,
        scale: number,
        reverse: boolean,
        trigger: TriggerSchema
    ) => {
        if (processedArrows.has(trigger.triggerUuid)) return [];
        processedArrows.add(trigger.triggerUuid);

        const t1 = Math.max(0, centerT - segmentSize / 2);
        const t2 = Math.min(1, centerT + segmentSize / 2);
        const subCurve = getSubCurve(fullCurve, t1, t2, reverse);

        const shaftGeometry = new THREE.TubeGeometry(subCurve, 8, 0.01 * scale, 8, false);

        const end = subCurve.getPoint(1);
        const tangent = subCurve.getTangent(1).normalize();

        const arrowHeadLength = 0.15 * scale;
        const arrowRadius = 0.01 * scale;
        const arrowHeadRadius = arrowRadius * 2.5;

        const headGeometry = new THREE.ConeGeometry(arrowHeadRadius, arrowHeadLength, 8);
        headGeometry.translate(0, arrowHeadLength / 2, 0);

        const rotation = new THREE.Quaternion().setFromUnitVectors(
            new THREE.Vector3(0, 1, 0),
            tangent
        );

        const removeConnection = (trigger: TriggerSchema) => {
            if (trigger.triggerUuid) {
                const event = removeTrigger(selectedProduct.productUuid, trigger.triggerUuid);
                if (event) {
                    updateBackend(
                        selectedProduct.productName,
                        selectedProduct.productUuid,
                        projectId || '',
                        event
                    );
                }
            }
        };

        return (
            <group
                key={key}
                onPointerOver={() => {
                    if (toolMode === '3D-Delete') {
                        setHoveredArrowTrigger(trigger.triggerUuid)
                    }
                }}
                onPointerOut={() => {
                    if (toolMode === '3D-Delete') {
                        setHoveredArrowTrigger(null)
                    }
                }}
                onClick={() => {
                    if (toolMode === '3D-Delete') {
                        removeConnection(trigger)
                    }
                }}
            >
                <mesh
                    geometry={shaftGeometry}
                >
                    <meshStandardMaterial
                        color={toolMode === '3D-Delete' && hoveredArrowTrigger === trigger.triggerUuid ? "red" : "#42a5f5"}
                    />
                </mesh>
                <mesh
                    position={end}
                    quaternion={rotation}
                    geometry={headGeometry}
                >
                    <meshStandardMaterial
                        color={toolMode === '3D-Delete' && hoveredArrowTrigger === trigger.triggerUuid ? "red" : "#42a5f5"}
                    />
                </mesh>
            </group>
        );
    };

    const getSubCurve = (
        curve: THREE.Curve<THREE.Vector3>,
        t1: number,
        t2: number,
        reverse = false
    ) => {
        const divisions = 10;
        const subPoints = Array.from({ length: divisions + 1 }, (_, i) => {
            const t = THREE.MathUtils.lerp(t1, t2, i / divisions);
            return curve.getPoint(t);
        });

        if (reverse) subPoints.reverse();

        return new THREE.CatmullRomCurve3(subPoints);
    };

    const arrowGroups = connections.flatMap((connection) => {
        const { startPointUuid, endPointUuid } = connection;
        const start = getWorldPositionFromScene(startPointUuid);
        const end = getWorldPositionFromScene(endPointUuid);
        if (!start || !end) return [];

        const isBidirectional = connections.some((other) => other.startPointUuid === endPointUuid && other.endPointUuid === startPointUuid);

        const distance = start.distanceTo(end);
        const heightFactor = Math.max(0.5, distance * 0.2);
        const control = new THREE.Vector3((start.x + end.x) / 2, Math.max(start.y, end.y) + heightFactor, (start.z + end.z) / 2);
        const curve = new THREE.QuadraticBezierCurve3(start, control, end);
        const scale = THREE.MathUtils.clamp(distance * 0.75, 0.5, 5);

        if (isBidirectional) {
            return [
                createArrow(connection.id + "-fwd", curve, 0.33, 0.25, scale, true, connection.trigger),
                createArrow(connection.id + "-bwd", curve, 0.66, 0.25, scale, false, connection.trigger),
            ];
        } else {
            return [
                createArrow(connection.id, curve, 0.5, 0.3, scale, false, connection.trigger)
            ];
        }
    });

    return <group ref={groupRef} name="connectionArrows">{arrowGroups}</group>;
}

export function ArrowOnQuadraticBezier({
    start,
    mid,
    end,
    color = "#42a5f5",
}: {
    start: number[];
    mid: number[];
    end: number[];
    color?: string;
}) {
    const minScale = 0.5;
    const maxScale = 5;
    const segmentSize = 0.3;

    const startVec = useMemo(() => new THREE.Vector3(...start), [start]);
    const midVec = useMemo(() => new THREE.Vector3(...mid), [mid]);
    const endVec = useMemo(() => new THREE.Vector3(...end), [end]);

    const fullCurve = useMemo(
        () => new THREE.QuadraticBezierCurve3(startVec, midVec, endVec),
        [startVec, midVec, endVec]
    );

    const distance = useMemo(() => startVec.distanceTo(endVec), [startVec, endVec]);
    const scale = useMemo(() => THREE.MathUtils.clamp(distance * 0.75, minScale, maxScale), [distance]);

    const arrowHeadLength = 0.15 * scale;
    const arrowRadius = 0.01 * scale;
    const arrowHeadRadius = arrowRadius * 2.5;

    const subCurve = useMemo(() => {
        const centerT = 0.5;
        const t1 = Math.max(0, centerT - segmentSize / 2);
        const t2 = Math.min(1, centerT + segmentSize / 2);

        const divisions = 10;
        const subPoints = Array.from({ length: divisions + 1 }, (_, i) => {
            const t = THREE.MathUtils.lerp(t1, t2, i / divisions);
            return fullCurve.getPoint(t);
        });
        return new THREE.CatmullRomCurve3(subPoints);
    }, [fullCurve, segmentSize]);

    const tubeGeometry = useMemo(
        () => new THREE.TubeGeometry(subCurve, 20, arrowRadius, 8, false),
        [subCurve, arrowRadius]
    );

    const arrowPosition = useMemo(() => subCurve.getPoint(1), [subCurve]);
    const arrowTangent = useMemo(() => subCurve.getTangent(1).normalize(), [subCurve]);

    const arrowRotation = useMemo(() => {
        return new THREE.Quaternion().setFromUnitVectors(new THREE.Vector3(0, 1, 0), arrowTangent);
    }, [arrowTangent]);

    const coneGeometry = useMemo(() => {
        const geom = new THREE.ConeGeometry(arrowHeadRadius, arrowHeadLength, 8);
        geom.translate(0, arrowHeadLength / 2, 0);
        return geom;
    }, [arrowHeadRadius, arrowHeadLength]);

    return (
        <group name="ArrowWithTube">
            <mesh name="ArrowWithTube" geometry={tubeGeometry}>
                <meshStandardMaterial color={color} />
            </mesh>
            <mesh name="ArrowWithTube" position={arrowPosition} quaternion={arrowRotation} geometry={coneGeometry}>
                <meshStandardMaterial color={color} />
            </mesh>
        </group>
    );
}