import * as THREE from "three";
import { useMemo, useRef, useState } from "react";
import { useThree } from "@react-three/fiber";
import { useToolMode } from "../../../../store/builder/store";

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

export function Arrows({ connections }: { readonly connections: ConnectionLine[] }) {
    const [hoveredLineKey, setHoveredLineKey] = useState<string | null>(null);
    const groupRef = useRef<THREE.Group>(null);
    const { scene } = useThree();
    const { toolMode } = useToolMode();

    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 createArrow = (
        key: string,
        fullCurve: THREE.QuadraticBezierCurve3,
        centerT: number,
        segmentSize: number,
        scale: number,
        reverse = false
    ) => {
        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
        );

        return (
            <group key={key}>
                <mesh
                    geometry={shaftGeometry}
                    onPointerOver={() => setHoveredLineKey(key)}
                    onPointerOut={() => setHoveredLineKey(null)}
                >
                    <meshStandardMaterial
                        color={toolMode === '2D-Delete' && hoveredLineKey === key ? "red" : "#42a5f5"}
                    />
                </mesh>
                <mesh
                    position={end}
                    quaternion={rotation}
                    geometry={headGeometry}
                    onPointerOver={() => setHoveredLineKey(key)}
                    onPointerOut={() => setHoveredLineKey(null)}
                >
                    <meshStandardMaterial
                        color={toolMode === '2D-Delete' && hoveredLineKey === key ? "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 start = getWorldPositionFromScene(connection.startPointUuid);
        const end = getWorldPositionFromScene(connection.endPointUuid);
        if (!start || !end) return [];

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

        if (isBidirectional && connection.startPointUuid < connection.endPointUuid) {
            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, 3);

            return [
                createArrow(connection.id + "-fwd", curve, 0.33, 0.25, scale, true),
                createArrow(connection.id + "-bwd", curve, 0.66, 0.25, scale, false),
            ];
        }

        if (!isBidirectional) {
            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);

            return [
                createArrow(connection.id, curve, 0.5, 0.3, scale)
            ];
        }

        return [];
    });

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