import { useId, useLayoutEffect, useRef, useState } from 'react'
import type { ReactNode } from 'react'
import { cn } from '@/lib/utils'
import { observeResize } from '@/lib/sharedResizeObserver'

export type NodeStatus = 'loading' | 'success' | 'error' | 'initial'

export type NodeStatusVariant = 'overlay' | 'border'

export type NodeStatusIndicatorProps = {
    status?: NodeStatus
    variant?: NodeStatusVariant
    children: ReactNode
    /** Optional: node width/height so the spinner can match the border exactly */
    width?: number
    height?: number
}

const R = 6 // rounded corner radius (0.375rem ≈ 6px)

const DURATION_MS = 2400

/** Ease-out with overshoot: 0→70%→85%→100% keyframe values */
function dashOffsetAtProgress(progress: number, pathLength: number): number {
    if (progress <= 0.7) return ((pathLength + 30) * progress) / 0.7
    if (progress <= 0.85)
        return pathLength + 30 + (25 * (progress - 0.7)) / 0.15
    return pathLength + 55 - (5 * (progress - 0.85)) / 0.15
}

/** One solid segment (half path length) moving along the border, with gradient opacity 0→1 along the segment.
 * Uses ResizeObserver so the border always matches the actual rendered node size (avoids min-width/min-height mismatch). */
function BorderLoadingIndicator({
    children,
    width,
    height,
}: {
    children: ReactNode
    width?: number
    height?: number
}) {
    const containerRef = useRef<HTMLDivElement>(null)
    const [measured, setMeasured] = useState({ w: 0, h: 0 })
    const hasMeasured = measured.w > 0 && measured.h > 0
    const fallbackW = (width != null && height != null && width > 0 && height > 0) ? width + 4 : 0
    const fallbackH = (width != null && height != null && width > 0 && height > 0) ? height + 4 : 0
    const w = hasMeasured ? measured.w + 4 : fallbackW
    const h = hasMeasured ? measured.h + 4 : fallbackH
    const hasSize = w > 0 && h > 0
    const pathD = hasSize
        ? `M ${R + 2} ${2} L ${w - R - 2} ${2} Q ${w - 2} ${2} ${w - 2} ${R + 2} L ${w - 2} ${h - R - 2} Q ${w - 2} ${h - 2} ${w - R - 2} ${h - 2} L ${R + 2} ${h - 2} Q ${2} ${h - 2} ${2} ${h - R - 2} L ${2} ${R + 2} Q ${2} ${2} ${R + 2} ${2} Z`
        : ''
    const gradientId = useId().replace(/:/g, '-')
    const pathRef = useRef<SVGPathElement>(null)
    const gradientRef = useRef<SVGLinearGradientElement>(null)
    const rafRef = useRef<number>(0)
    const startTimeRef = useRef<number>(0)

    useLayoutEffect(() => {
        const container = containerRef.current
        if (!container) return
        const target =
            container.firstElementChild instanceof HTMLElement
                ? container.firstElementChild
                : container
        const cw = (target as HTMLElement).offsetWidth
        const ch = (target as HTMLElement).offsetHeight
        if (cw > 0 && ch > 0) setMeasured({ w: cw, h: ch })
        const unObserve = observeResize(target, ({ width: w, height: h }) => {
            if (w > 0 && h > 0) setMeasured({ w, h })
        })
        return unObserve
    }, [])

    useLayoutEffect(() => {
        if (!hasSize) return
        const pathEl = pathRef.current
        const gradientEl = gradientRef.current
        if (!pathEl || !gradientEl) return

        const totalLen = pathEl.getTotalLength()
        const segmentLen = totalLen * 0.5
        const gapLen = totalLen * 0.5 + 80

        pathEl.style.strokeDasharray = `${segmentLen} ${gapLen}`

        const tick = () => {
            const elapsed = (performance.now() - startTimeRef.current) % DURATION_MS
            const progress = Math.min(1, elapsed / DURATION_MS)
            const dashOffset = dashOffsetAtProgress(progress, totalLen)

            pathEl.style.strokeDashoffset = String(dashOffset)

            const startLen = dashOffset % totalLen
            const endLen = (dashOffset + segmentLen) % totalLen
            const startPt = pathEl.getPointAtLength(startLen)
            const endPt = pathEl.getPointAtLength(endLen)
            gradientEl.setAttribute('x1', String(startPt.x))
            gradientEl.setAttribute('y1', String(startPt.y))
            gradientEl.setAttribute('x2', String(endPt.x))
            gradientEl.setAttribute('y2', String(endPt.y))

            rafRef.current = requestAnimationFrame(tick)
        }

        startTimeRef.current = performance.now()
        rafRef.current = requestAnimationFrame(tick)
        return () => cancelAnimationFrame(rafRef.current)
    }, [hasSize, w, h])

    return (
        <div ref={containerRef} className="relative w-full h-full overflow-visible">
            {children}
            {hasSize && (
                <svg
                    className="absolute pointer-events-none z-10"
                    style={{ top: -2, left: -2, width: w, height: h }}
                    aria-hidden
                >
                    <defs>
                        <linearGradient
                            ref={gradientRef}
                            id={gradientId}
                            gradientUnits="userSpaceOnUse"
                            x1="0"
                            y1="0"
                            x2="1"
                            y2="0"
                        >
                            <stop offset="0" stopColor="hsl(var(--primary))" stopOpacity="0" />
                            <stop offset="1" stopColor="hsl(var(--primary))" stopOpacity="1" />
                        </linearGradient>
                    </defs>
                    <path
                        ref={pathRef}
                        className="fill-none stroke-[2]"
                        d={pathD}
                        stroke={`url(#${gradientId})`}
                        style={{ strokeLinecap: 'round' }}
                    />
                </svg>
            )}
            {!hasSize && (
                <>
                    <style>{`
                        @keyframes node-status-pulse {
                            0%, 100% { opacity: 0.35; }
                            50% { opacity: 0.7; }
                        }
                    `}</style>
                    <div
                        className="absolute -inset-[2px] rounded-md border-[1.5px] border-primary pointer-events-none z-10"
                        style={{ opacity: 0.8, animation: 'node-status-pulse 2s ease-in-out infinite' }}
                        aria-hidden
                    />
                </>
            )}
        </div>
    )
}

/** Error state: red/destructive border around the node */
function ErrorStatusBorder({ children, className }: { children: ReactNode; className?: string }) {
    return (
        <div className={cn('rounded-md ring-2 ring-destructive', className)}>
            {children}
        </div>
    )
}

/** Success state: optional subtle border (e.g. green) - not required per user, keep minimal */
function SuccessStatusBorder({ children }: { children: ReactNode }) {
    return <div className="rounded-md">{children}</div>
}

export function NodeStatusIndicator({
    status,
    variant = 'border',
    children,
    width,
    height,
}: NodeStatusIndicatorProps) {
    switch (status) {
        case 'loading':
            return variant === 'border' ? (
                <BorderLoadingIndicator width={width} height={height}>{children}</BorderLoadingIndicator>
            ) : (
                <>{children}</>
            )
        case 'error':
            return <ErrorStatusBorder>{children}</ErrorStatusBorder>
        case 'success':
            return <SuccessStatusBorder>{children}</SuccessStatusBorder>
        default:
            return <>{children}</>
    }
}
