import React, {FC, useCallback, useLayoutEffect, useRef, useState} from "react";
import {motion} from "motion/react";
import {__} from "../globals";
import {cn} from "../utils/cn";
import {autoUpdate, offset, shift, useFloating} from "@floating-ui/react";
import type {Placement} from "@floating-ui/react";
import rough from "roughjs/bin/rough";

type AnchorSide = 'top' | 'right' | 'bottom' | 'left';

type Point = {
    x: number,
    y: number
}

type ArrowGeometry = {
    path: string,
    connectorPath: string,
    headPath: string
}

type SuggestedArrowProps = {
    show: boolean,
    children: React.ReactNode,
    label?: string,
    className?: string,
    labelPlacement?: Placement,
    startAnchor?: AnchorSide,
    endAnchor?: AnchorSide
}

const getAnchorPoint = (rect: { left: number, top: number, width: number, height: number }, anchor: AnchorSide): Point => {
    if (anchor === 'top') {
        return {x: rect.left + rect.width / 2, y: rect.top};
    }
    if (anchor === 'right') {
        return {x: rect.left + rect.width, y: rect.top + rect.height / 2};
    }
    if (anchor === 'left') {
        return {x: rect.left, y: rect.top + rect.height / 2};
    }

    return {x: rect.left + rect.width / 2, y: rect.top + rect.height};
};

const getArrowGeometry = (start: Point, end: Point): ArrowGeometry => {
    const dx = end.x - start.x;
    const dy = end.y - start.y;
    const absDx = Math.abs(dx);
    const absDy = Math.max(1, Math.abs(dy));
    const direction = dx >= 0 ? 1 : -1;
    const verticalDirection = dy >= 0 ? 1 : -1;
    const outwardSwing = Math.max(28, Math.min(76, absDx * 1.15));
    const inwardSwing = Math.max(16, Math.min(44, absDx * 0.72));
    const control1 = {
        x: start.x + direction * outwardSwing,
        y: start.y + Math.max(18, absDy * 0.3) * verticalDirection
    };
    const control2 = {
        x: end.x - direction * inwardSwing,
        y: end.y - Math.max(22, absDy * 0.52) * verticalDirection
    };
    const tangentX = end.x - control2.x;
    const tangentY = end.y - control2.y;
    const tangentLength = Math.hypot(tangentX, tangentY) || 1;
    const directionX = tangentX / tangentLength;
    const directionY = tangentY / tangentLength;
    const normalX = -directionY;
    const normalY = directionX;
    const tipGap = 12;
    const tip = {
        x: end.x - directionX * tipGap,
        y: end.y - directionY * tipGap
    };
    const headLength = 12;
    const headHalfWidth = 6.5;
    const shaftEnd = {
        x: tip.x - directionX * headLength,
        y: tip.y - directionY * headLength
    };
    const path = `M ${start.x} ${start.y} C ${control1.x} ${control1.y} ${control2.x} ${control2.y} ${shaftEnd.x} ${shaftEnd.y}`;
    const connectorPath = `M ${shaftEnd.x} ${shaftEnd.y} L ${tip.x} ${tip.y}`;
    const leftX = shaftEnd.x + normalX * headHalfWidth;
    const leftY = shaftEnd.y + normalY * headHalfWidth;
    const rightX = shaftEnd.x - normalX * headHalfWidth;
    const rightY = shaftEnd.y - normalY * headHalfWidth;
    const headPath = `M ${leftX} ${leftY} L ${tip.x} ${tip.y} L ${rightX} ${rightY}`;

    return {path, connectorPath, headPath};
};

export const SuggestedArrow: FC<SuggestedArrowProps> = ({
    show,
    children,
    label,
    className,
    labelPlacement = 'top-start',
    startAnchor = 'bottom',
    endAnchor = 'top'
}) => {
    const wrapperRef = useRef<HTMLDivElement | null>(null);
    const svgRef = useRef<SVGSVGElement | null>(null);
    const [geometry, setGeometry] = useState<ArrowGeometry | null>(null);
    const {refs, floatingStyles, x, y} = useFloating({
        placement: labelPlacement,
        middleware: [
            offset({
                mainAxis: 62,
                crossAxis: -104
            }),
            shift({padding: 8})
        ],
        whileElementsMounted: autoUpdate
    });

    const updateGeometry = useCallback(() => {
        if (!wrapperRef.current || !refs.reference.current || !refs.floating.current) {
            return;
        }

        const wrapperRect = wrapperRef.current.getBoundingClientRect();
        const sourceRect = refs.floating.current.getBoundingClientRect();
        const targetRect = refs.reference.current.getBoundingClientRect();
        const relativeSource = {
            left: sourceRect.left - wrapperRect.left,
            top: sourceRect.top - wrapperRect.top,
            width: sourceRect.width,
            height: sourceRect.height
        };
        const relativeTarget = {
            left: targetRect.left - wrapperRect.left,
            top: targetRect.top - wrapperRect.top,
            width: targetRect.width,
            height: targetRect.height
        };

        setGeometry(
            getArrowGeometry(
                getAnchorPoint(relativeSource, startAnchor),
                getAnchorPoint(relativeTarget, endAnchor)
            )
        );
    }, [endAnchor, refs.floating, refs.reference, startAnchor]);

    useLayoutEffect(() => {
        if (x == null || y == null) {
            return;
        }
        updateGeometry();
    }, [x, y, updateGeometry]);

    useLayoutEffect(() => {
        if (!svgRef.current || !geometry) {
            return;
        }

        const svg = svgRef.current;
        svg.innerHTML = '';
        const sketch = rough.svg(svg);
        const roughPath = sketch.path(geometry.path, {
            stroke: '#8E8FF2',
            strokeWidth: 2.2,
            roughness: 0.95,
            bowing: 0.9,
            preserveVertices: true,
            disableMultiStroke: true,
            fill: 'none',
        });
        const connectorPath = document.createElementNS('http://www.w3.org/2000/svg', 'path');
        connectorPath.setAttribute('d', geometry.connectorPath);
        connectorPath.setAttribute('fill', 'none');
        connectorPath.setAttribute('stroke', '#8E8FF2');
        connectorPath.setAttribute('stroke-width', '2.2');
        connectorPath.setAttribute('class', 'cp-suggested-arrow-connector');
        const headPath = document.createElementNS('http://www.w3.org/2000/svg', 'path');
        headPath.setAttribute('d', geometry.headPath);
        headPath.setAttribute('fill', 'none');
        headPath.setAttribute('stroke', '#8E8FF2');
        headPath.setAttribute('stroke-width', '2.2');
        headPath.setAttribute('class', 'cp-suggested-arrow-head');

        roughPath.querySelectorAll('path').forEach((path) => {
            path.classList.add('cp-suggested-arrow-line');
        });

        svg.appendChild(roughPath);
        svg.appendChild(connectorPath);
        svg.appendChild(headPath);
    }, [geometry]);

    if (!show) {
        return <>{children}</>;
    }

    return (
        <div className={cn("relative inline-flex", className)} ref={wrapperRef}>
            <style>{`
                @keyframes cp-suggested-arrow-dash {
                    to {
                        stroke-dashoffset: -28px;
                    }
                }
                .cp-suggested-arrow-line {
                    stroke-dasharray: 7 8;
                    animation: cp-suggested-arrow-dash 1.35s linear infinite;
                }
                .cp-suggested-arrow-line,
                .cp-suggested-arrow-connector,
                .cp-suggested-arrow-head {
                    stroke-linecap: round;
                    stroke-linejoin: round;
                }
                @keyframes cp-suggested-arrow-head-pulse {
                    0%, 100% {
                        opacity: 1;
                    }
                    50% {
                        opacity: 0.65;
                    }
                }
                .cp-suggested-arrow-head {
                    animation: cp-suggested-arrow-head-pulse 1.7s ease-in-out infinite;
                }
            `}</style>
            <div
                className="pointer-events-none z-30"
                ref={refs.setFloating}
                style={floatingStyles}
            >
                <motion.div
                    animate={{y: [0, -2, 0]}}
                    transition={{duration: 2.2, repeat: Infinity, ease: "easeInOut"}}
                >
                    <div
                        className="inline-flex items-center rounded-full bg-purple-tint-20 text-purple-normal text-smaller-2 font-semibold whitespace-nowrap px-3 py-1 shadow-sm"
                    >
                        {label || __('Suggested')}
                    </div>
                </motion.div>
            </div>
            <svg ref={svgRef} className="pointer-events-none absolute inset-0 z-20 overflow-visible"/>
            <div ref={refs.setReference} className="relative z-10">
                {children}
            </div>
        </div>
    );
};
