import { addPropertyControls, ControlType, RenderTarget } from "framer"
import { useEffect, useRef, useMemo, useCallback } from "react"

/**
 * AnimatedGradientButton — v16
 *
 * Changes from v15:
 * 1. Removed custom onClick scroll interception that broke Framer's
 *    ControlType.Link routing for internal pages and scroll targets.
 *    All link types (pages, scroll targets, external URLs) now pass
 *    through to the native <a> so Framer's router handles them.
 * 2. Removed scrollBehavior prop (no longer needed — Framer's scroll
 *    target system controls smooth/instant behavior natively).
 * 3. Fixed duplicate fontSize / lineHeight declarations in the live
 *    sub-component that would cause build errors.
 */

// ---------------------------------------------------------------------------
// Pure colour parser — no DOM canvas needed
// ---------------------------------------------------------------------------
function colorToRgb(color: string): [number, number, number] {
    color = color.trim()

    const hex6 = color.match(/^#([0-9a-f]{6})$/i)
    if (hex6) {
        const n = parseInt(hex6[1], 16)
        return [(n >> 16) & 255, (n >> 8) & 255, n & 255]
    }
    const hex3 = color.match(/^#([0-9a-f]{3})$/i)
    if (hex3) {
        const [r, g, b] = hex3[1].split("").map((c) => parseInt(c + c, 16))
        return [r, g, b]
    }

    const rgb = color.match(/rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)/)
    if (rgb) return [+rgb[1], +rgb[2], +rgb[3]]

    const hsl = color.match(
        /hsla?\(\s*([\d.]+)\s*,\s*([\d.]+)%\s*,\s*([\d.]+)%/
    )
    if (hsl) {
        const h = +hsl[1] / 360,
            s = +hsl[2] / 100,
            l = +hsl[3] / 100
        const q = l < 0.5 ? l * (1 + s) : l + s - l * s
        const p = 2 * l - q
        const hue = (t: number) => {
            t = ((t % 1) + 1) % 1
            if (t < 1 / 6) return p + (q - p) * 6 * t
            if (t < 1 / 2) return q
            if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6
            return p
        }
        return [
            Math.round(hue(h + 1 / 3) * 255),
            Math.round(hue(h) * 255),
            Math.round(hue(h - 1 / 3) * 255),
        ]
    }

    return [79, 70, 229]
}

// ---------------------------------------------------------------------------
// Arc-length parameterised pill points
// ---------------------------------------------------------------------------
function pillPoints(w: number, h: number, r: number, count: number) {
    const DENSE = count * 16
    const raw: [number, number][] = []
    const straightW = w - 2 * r
    const straightH = h - 2 * r
    const cornerLen = (Math.PI / 2) * r
    const totalPerim = 2 * straightW + 2 * straightH + 2 * Math.PI * r

    for (let i = 0; i < DENSE; i++) {
        let d = (i / DENSE) * totalPerim
        if (d < straightW) {
            raw.push([r + d, 0])
            continue
        }
        d -= straightW
        if (d < cornerLen) {
            const a = -Math.PI / 2 + d / r
            raw.push([w - r + Math.cos(a) * r, r + Math.sin(a) * r])
            continue
        }
        d -= cornerLen
        if (d < straightH) {
            raw.push([w, r + d])
            continue
        }
        d -= straightH
        if (d < cornerLen) {
            const a = d / r
            raw.push([w - r + Math.cos(a) * r, h - r + Math.sin(a) * r])
            continue
        }
        d -= cornerLen
        if (d < straightW) {
            raw.push([w - r - d, h])
            continue
        }
        d -= straightW
        if (d < cornerLen) {
            const a = Math.PI / 2 + d / r
            raw.push([r + Math.cos(a) * r, h - r + Math.sin(a) * r])
            continue
        }
        d -= cornerLen
        if (d < straightH) {
            raw.push([0, h - r - d])
            continue
        }
        d -= straightH
        const a = Math.PI + d / r
        raw.push([r + Math.cos(a) * r, r + Math.sin(a) * r])
    }

    const cum = [0]
    for (let i = 1; i < raw.length; i++) {
        const dx = raw[i][0] - raw[i - 1][0]
        const dy = raw[i][1] - raw[i - 1][1]
        cum.push(cum[i - 1] + Math.sqrt(dx * dx + dy * dy))
    }
    const totalArc = cum[cum.length - 1]

    const pts: { x: number; y: number }[] = []
    let ri = 0
    for (let i = 0; i < count; i++) {
        const target = (i / count) * totalArc
        while (ri < cum.length - 2 && cum[ri + 1] < target) ri++
        const span = cum[ri + 1] - cum[ri]
        const t = span > 0 ? (target - cum[ri]) / span : 0
        pts.push({
            x: raw[ri][0] + t * (raw[ri + 1][0] - raw[ri][0]),
            y: raw[ri][1] + t * (raw[ri + 1][1] - raw[ri][1]),
        })
    }
    return pts
}

// ---------------------------------------------------------------------------
// Component
// ---------------------------------------------------------------------------
export default function AnimatedGradientButton({
    text = "Dive into space",
    link = "",
    openInNewTab = false,
    font = {
        fontSize: 15,
        fontWeight: 500,
        fontFamily: "Inter, sans-serif",
        lineHeight: 1.5,
    },
    buttonColor = "#0a0a0a",
    textColor = "#ffffff",
    hoverColor = "#1c1c1c",
    gradientColor1 = "#4f46e5",
    gradientColor2 = "#06b6d4",
    gradientSize = 1,
    duration = 4,
    paddingTop = 16,
    paddingRight = 32,
    paddingBottom = 16,
    paddingLeft = 32,
}: {
    text?: string
    link?: string
    openInNewTab?: boolean
    font?: {
        fontSize: number
        fontWeight: number | string
        fontFamily: string
        lineHeight?: string | number
    }
    buttonColor?: string
    textColor?: string
    hoverColor?: string
    gradientColor1?: string
    gradientColor2?: string
    gradientSize?: number
    duration?: number
    paddingTop?: number
    paddingRight?: number
    paddingBottom?: number
    paddingLeft?: number
}) {
    // Detect Framer editor canvas once on mount
    const isCanvas = useMemo(
        () => RenderTarget.current() === RenderTarget.canvas,
        []
    )

    const fontSize = font?.fontSize ?? 15
    const lineHeight = font?.lineHeight ?? 1.5
    const lhMultiplier = typeof lineHeight === "number" ? lineHeight : 1.5

    // ── Editor canvas: pure CSS fallback (no <canvas>, no pixelation) ──
    if (isCanvas) {
        const borderWidth = Math.max(gradientSize * 2, 2)
        return (
            <div
                style={{
                    display: "flex",
                    alignItems: "center",
                    justifyContent: "center",
                    width: "100%",
                    height: "100%",
                    minHeight:
                        paddingTop + paddingBottom + fontSize * lhMultiplier,
                    position: "relative",
                }}
            >
                {/* Gradient border layer — vector, never pixelated */}
                <div
                    style={{
                        position: "absolute",
                        inset: gradientSize,
                        borderRadius: 9999,
                        padding: borderWidth,
                        background: `conic-gradient(from 180deg, ${gradientColor1}, ${gradientColor2}, transparent 75%)`,
                        WebkitMask:
                            "linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0)",
                        WebkitMaskComposite: "xor",
                        maskComposite: "exclude",
                    }}
                />
                {/* Button fill */}
                <div
                    style={{
                        position: "absolute",
                        inset: gradientSize + borderWidth,
                        borderRadius: 9999,
                        background: buttonColor,
                    }}
                />
                {/* Text */}
                <span
                    style={{
                        position: "relative",
                        zIndex: 2,
                        display: "inline-flex",
                        alignItems: "center",
                        justifyContent: "center",
                        paddingTop,
                        paddingBottom,
                        paddingLeft,
                        paddingRight,
                        fontSize,
                        fontWeight: font?.fontWeight ?? 500,
                        fontFamily: font?.fontFamily ?? "Inter, sans-serif",
                        lineHeight,
                        color: textColor,
                        whiteSpace: "nowrap" as const,
                        maxWidth: "100%",
                        overflow: "hidden",
                        textOverflow: "ellipsis",
                    }}
                >
                    {text}
                </span>
            </div>
        )
    }

    // ── Live site: full canvas animation ────────────────────────────────
    return (
        <AnimatedGradientButtonLive
            text={text}
            link={link}
            openInNewTab={openInNewTab}
            font={font}
            buttonColor={buttonColor}
            textColor={textColor}
            hoverColor={hoverColor}
            gradientColor1={gradientColor1}
            gradientColor2={gradientColor2}
            gradientSize={gradientSize}
            duration={duration}
            paddingTop={paddingTop}
            paddingRight={paddingRight}
            paddingBottom={paddingBottom}
            paddingLeft={paddingLeft}
        />
    )
}

// ---------------------------------------------------------------------------
// Live-site sub-component — canvas animation only runs here
// ---------------------------------------------------------------------------
function AnimatedGradientButtonLive({
    text,
    link,
    openInNewTab,
    font,
    buttonColor,
    textColor,
    hoverColor,
    gradientColor1,
    gradientColor2,
    gradientSize,
    duration,
    paddingTop,
    paddingRight,
    paddingBottom,
    paddingLeft,
}: {
    text: string
    link: string
    openInNewTab: boolean
    font: {
        fontSize: number
        fontWeight: number | string
        fontFamily: string
        lineHeight?: string | number
    }
    buttonColor: string
    textColor: string
    hoverColor: string
    gradientColor1: string
    gradientColor2: string
    gradientSize: number
    duration: number
    paddingTop: number
    paddingRight: number
    paddingBottom: number
    paddingLeft: number
}) {
    const containerRef = useRef<HTMLAnchorElement>(null)
    const canvasRef = useRef<HTMLCanvasElement>(null)
    const frameRef = useRef<number>(0)
    const progressRef = useRef(0)
    const animatingRef = useRef(false)
    const sizeRef = useRef({ w: 0, h: 0, dpr: 1 })

    const hoveredRef = useRef(false)
    const hoverColorRef = useRef(hoverColor)
    const buttonColorRef = useRef(buttonColor)

    useEffect(() => {
        hoverColorRef.current = hoverColor
    }, [hoverColor])
    useEffect(() => {
        buttonColorRef.current = buttonColor
    }, [buttonColor])

    const drawOnce = useCallback(() => {
        const canvas = canvasRef.current
        const container = containerRef.current
        if (!canvas || !container) return

        const { w, h } = sizeRef.current
        const dpr = window.devicePixelRatio || 1

        const bw = Math.round(w * dpr)
        const bh = Math.round(h * dpr)
        if (canvas.width !== bw || canvas.height !== bh) {
            canvas.width = bw
            canvas.height = bh
            sizeRef.current.dpr = dpr
        }

        const ctx = canvas.getContext("2d")
        if (!ctx || w <= 0 || h <= 0) return

        ctx.setTransform(dpr, 0, 0, dpr, 0, 0)
        ctx.clearRect(0, 0, w, h)

        const margin = gradientSize * 3
        const pw = w - margin * 2
        const ph = h - margin * 2
        if (pw <= 0 || ph <= 0) return
        const r = ph / 2

        const SEGMENTS = 480
        const ARC_FRAC = 0.75
        const arcLen = Math.floor(ARC_FRAC * SEGMENTS)

        const rgb1 = colorToRgb(gradientColor1)
        const rgb2 = colorToRgb(gradientColor2)
        const lut: string[] = []
        for (let i = 0; i < arcLen; i++) {
            const t = i / arcLen
            const alpha = Math.sin(t * Math.PI) * 0.95
            const ri2 = Math.round(rgb1[0] + (rgb2[0] - rgb1[0]) * t)
            const gi = Math.round(rgb1[1] + (rgb2[1] - rgb1[1]) * t)
            const bi = Math.round(rgb1[2] + (rgb2[2] - rgb1[2]) * t)
            lut.push(`rgba(${ri2},${gi},${bi},${alpha.toFixed(3)})`)
        }

        const allPts = pillPoints(pw, ph, r, SEGMENTS).map((p) => ({
            x: p.x + margin,
            y: p.y + margin,
        }))

        const total = allPts.length
        const start = Math.floor(progressRef.current * total)

        ctx.lineWidth = gradientSize * 2.5
        ctx.lineCap = "round"

        // Batched draw — group contiguous same-color segments
        let batchStart = 0
        while (batchStart < arcLen - 1) {
            const color = lut[batchStart]
            ctx.beginPath()
            const idx0 = (start + batchStart) % total
            ctx.moveTo(allPts[idx0].x, allPts[idx0].y)

            let batchEnd = batchStart
            while (batchEnd < arcLen - 1 && lut[batchEnd] === color) {
                const idxNext = (start + batchEnd + 1) % total
                ctx.lineTo(allPts[idxNext].x, allPts[idxNext].y)
                batchEnd++
            }

            ctx.strokeStyle = color
            ctx.stroke()
            batchStart = batchEnd
        }

        // Pill fill
        ctx.beginPath()
        ctx.roundRect(margin, margin, pw, ph, r)
        ctx.fillStyle = hoveredRef.current
            ? hoverColorRef.current
            : buttonColorRef.current
        ctx.fill()
    }, [gradientColor1, gradientColor2, gradientSize])

    useEffect(() => {
        const container = containerRef.current
        if (!container) return

        const rect = container.getBoundingClientRect()
        sizeRef.current = {
            w: rect.width,
            h: rect.height,
            dpr: window.devicePixelRatio || 1,
        }

        const ro = new ResizeObserver((entries) => {
            for (const entry of entries) {
                const { width, height } = entry.contentRect
                sizeRef.current.w = width
                sizeRef.current.h = height
            }
            if (!animatingRef.current) drawOnce()
        })
        ro.observe(container)

        const onVisibility = () => {
            if (document.visibilityState === "visible") {
                if (!animatingRef.current) {
                    animatingRef.current = true
                    lastTime = null
                    frameRef.current = requestAnimationFrame(loop)
                }
            } else {
                animatingRef.current = false
            }
        }

        let lastTime: number | null = null

        const loop = (timestamp: number) => {
            if (!animatingRef.current) return

            if (lastTime !== null) {
                const delta = (timestamp - lastTime) / 1000
                progressRef.current =
                    (progressRef.current + delta / duration) % 1
            }
            lastTime = timestamp

            drawOnce()
            frameRef.current = requestAnimationFrame(loop)
        }

        animatingRef.current = document.visibilityState === "visible"
        if (animatingRef.current) {
            frameRef.current = requestAnimationFrame(loop)
        } else {
            drawOnce()
        }

        document.addEventListener("visibilitychange", onVisibility)
        return () => {
            animatingRef.current = false
            cancelAnimationFrame(frameRef.current)
            ro.disconnect()
            document.removeEventListener("visibilitychange", onVisibility)
        }
    }, [duration, drawOnce])

    useEffect(() => {
        if (!animatingRef.current) drawOnce()
    }, [buttonColor, hoverColor, drawOnce])

    const onEnter = useCallback(() => {
        hoveredRef.current = true
        if (!animatingRef.current) drawOnce()
    }, [drawOnce])
    const onLeave = useCallback(() => {
        hoveredRef.current = false
        if (!animatingRef.current) drawOnce()
    }, [drawOnce])

    const fontSize = font?.fontSize ?? 15
    const lineHeight = font?.lineHeight ?? 1.5

    return (
        <a
            ref={containerRef}
            href={link || undefined}
            target={openInNewTab ? "_blank" : undefined}
            rel={openInNewTab ? "noopener noreferrer" : undefined}
            onMouseEnter={onEnter}
            onMouseLeave={onLeave}
            style={{
                display: "flex",
                alignItems: "center",
                justifyContent: "center",
                width: "100%",
                height: "100%",
                minHeight:
                    paddingTop +
                    paddingBottom +
                    fontSize *
                        (typeof lineHeight === "number" ? lineHeight : 1.5),
                position: "relative",
                textDecoration: "none",
                cursor: "pointer",
            }}
        >
            <canvas
                ref={canvasRef}
                style={{
                    position: "absolute",
                    top: 0,
                    left: 0,
                    width: "100%",
                    height: "100%",
                    pointerEvents: "none",
                }}
            />
            <span
                style={{
                    display: "inline-flex",
                    alignItems: "center",
                    justifyContent: "center",
                    borderRadius: 9999,
                    paddingTop,
                    paddingBottom,
                    paddingLeft,
                    paddingRight,
                    background: "transparent",
                    fontSize,
                    fontWeight: font?.fontWeight ?? 500,
                    fontFamily: font?.fontFamily ?? "Inter, sans-serif",
                    lineHeight,
                    color: textColor,
                    whiteSpace: "nowrap",
                    position: "relative",
                    zIndex: 2,
                    border: "none",
                    maxWidth: "100%",
                    overflow: "hidden",
                    textOverflow: "ellipsis",
                }}
            >
                {text}
            </span>
        </a>
    )
}

// Framer canvas default size
;(AnimatedGradientButton as any).defaultSize = { width: 200, height: 56 }

// ---------------------------------------------------------------------------
// Property controls
// ---------------------------------------------------------------------------
addPropertyControls(AnimatedGradientButton, {
    text: {
        type: ControlType.String,
        title: "Text",
        defaultValue: "Dive into space",
        description: "The button label displayed to users.",
    },
    link: {
        type: ControlType.Link,
        title: "Link",
        description:
            "Supports pages, scroll targets, and external URLs.\nFramer handles navigation and smooth scrolling natively.",
    },
    openInNewTab: {
        type: ControlType.Boolean,
        title: "New Tab",
        defaultValue: false,
        description: "Open the link in a new browser tab.",
    },
    font: {
        type: ControlType.Font,
        title: "Font",
        controls: "extended",
    },
    buttonColor: {
        type: ControlType.Color,
        title: "Button",
        defaultValue: "#0a0a0a",
        description: "Background fill color of the button.",
    },
    hoverColor: {
        type: ControlType.Color,
        title: "Hover",
        defaultValue: "#1c1c1c",
        description: "Button fill color on mouse hover.",
    },
    textColor: {
        type: ControlType.Color,
        title: "Text",
        defaultValue: "#ffffff",
        description: "Color of the button label.",
    },
    gradientColor1: {
        type: ControlType.Color,
        title: "Gradient Start",
        defaultValue: "#4f46e5",
        description: "Leading color of the animated gradient arc.",
    },
    gradientColor2: {
        type: ControlType.Color,
        title: "Gradient End",
        defaultValue: "#06b6d4",
        description: "Trailing color of the animated gradient arc.",
    },
    gradientSize: {
        type: ControlType.Number,
        title: "Gradient Size",
        defaultValue: 1,
        min: 1,
        max: 12,
        step: 1,
        displayStepper: true,
        description:
            "Thickness of the gradient stroke. 1–3 is subtle; 8–12 is dramatic.",
    },
    duration: {
        type: ControlType.Number,
        title: "Speed (s)",
        defaultValue: 4,
        min: 0.5,
        max: 20,
        step: 0.5,
        displayStepper: true,
        description: "Seconds per full orbit. Lower values are faster.",
    },
    paddingTop: {
        type: ControlType.Number,
        title: "Pad Top",
        defaultValue: 16,
        min: 0,
        step: 1,
        displayStepper: true,
        description: "Inner spacing above the text.",
    },
    paddingRight: {
        type: ControlType.Number,
        title: "Pad Right",
        defaultValue: 32,
        min: 0,
        step: 1,
        displayStepper: true,
        description: "Inner spacing to the right of the text.",
    },
    paddingBottom: {
        type: ControlType.Number,
        title: "Pad Bottom",
        defaultValue: 16,
        min: 0,
        step: 1,
        displayStepper: true,
        description: "Inner spacing below the text.",
    },
    paddingLeft: {
        type: ControlType.Number,
        title: "Pad Left",
        defaultValue: 32,
        min: 0,
        step: 1,
        displayStepper: true,
        description: "Inner spacing to the left of the text.",
    },
})
