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

/**
 * AudioWaveformVisualizer
 *
 * Animated waveform visualizer with integrated audio controls.
 * The waveform is decorative — multiple overlapping sine waves
 * create an organic display while audio plays. Animation is
 * paused on the Framer editor canvas.
 */

// ── Types ────────────────────────────────────────────────────

interface WaveformVisualizerProps {
    audioUrl: string
    audioFile?: string
    title: string
    description: string
    titleFont: Record<string, any>
    descriptionFont: Record<string, any>
    textColor: string
    bassColor: string
    lowMidColor: string
    highMidColor: string
    trebleColor: string
    backgroundColor: string
    accentColor: string
    barCount: number
    barRadius: number
}

// ── Helpers ──────────────────────────────────────────────────

function formatTime(seconds: number): string {
    if (!seconds || isNaN(seconds) || seconds < 0) return "0:00"
    const m = Math.floor(seconds / 60)
    const s = String(Math.floor(seconds % 60)).padStart(2, "0")
    return `${m}:${s}`
}

// Resolve any CSS color (hex, rgb, rgba, hsl, hsla, named) to [r, g, b, a]
const colorCache = new Map<string, [number, number, number, number]>()
let resolverCtx: CanvasRenderingContext2D | null = null

function parseColor(color: string): [number, number, number, number] {
    const cached = colorCache.get(color)
    if (cached) return cached

    if (!resolverCtx) {
        const c = document.createElement("canvas")
        c.width = 1
        c.height = 1
        resolverCtx = c.getContext("2d", { willReadFrequently: true })!
    }

    resolverCtx.clearRect(0, 0, 1, 1)
    resolverCtx.fillStyle = color
    resolverCtx.fillRect(0, 0, 1, 1)
    const [r, g, b, a] = resolverCtx.getImageData(0, 0, 1, 1).data
    const result: [number, number, number, number] = [r, g, b, a / 255]
    colorCache.set(color, result)
    return result
}

function lerpColor(a: string, b: string, t: number): string {
    const [r1, g1, b1, a1] = parseColor(a)
    const [r2, g2, b2, a2] = parseColor(b)
    const r = Math.round(r1 + (r2 - r1) * t)
    const g = Math.round(g1 + (g2 - g1) * t)
    const bl = Math.round(b1 + (b2 - b1) * t)
    const al = a1 + (a2 - a1) * t
    return `rgba(${r},${g},${bl},${al.toFixed(2)})`
}

function getBarColor(
    t: number,
    stops: [string, string, string, string]
): string {
    const seg = t * 3
    const idx = Math.min(Math.floor(seg), 2)
    const frac = seg - idx
    return lerpColor(stops[idx], stops[idx + 1], frac)
}

// ── Component ────────────────────────────────────────────────

export default function AudioWaveformVisualizer({
    audioUrl = "",
    audioFile,
    title = "",
    description = "",
    titleFont = {},
    descriptionFont = {},
    textColor = "rgba(255,255,255,0.9)",
    bassColor = "#e53935",
    lowMidColor = "#fb8c00",
    highMidColor = "#43a047",
    trebleColor = "#1e88e5",
    backgroundColor = "#0a0a0a",
    accentColor = "#e53935",
    barCount = 48,
    barRadius = 3,
}: WaveformVisualizerProps) {
    const isCanvas = RenderTarget.current() === RenderTarget.canvas

    const canvasRef = useRef<HTMLCanvasElement>(null)
    const audioRef = useRef<HTMLAudioElement | null>(null)
    const frameRef = useRef<number>(0)
    const playingRef = useRef(false)
    const barsRef = useRef<Float32Array>(new Float32Array(barCount).fill(0.06))

    const [playing, setPlaying] = useState(false)
    const [current, setCurrent] = useState(0)
    const [dur, setDur] = useState(0)
    const [vol, setVol] = useState(0.8)
    const [muted, setMuted] = useState(false)
    const [error, setError] = useState<string | null>(null)

    // Unique ID for the volume slider style
    const uid = useMemo(() => `wv-${Math.random().toString(36).slice(2, 8)}`, [])

    // Resolve audio source — single derivation, no duplication
    const resolvedSrc = useMemo(() => {
        const raw = audioFile || audioUrl
        if (!raw) return ""
        if (raw.startsWith("http") || raw.startsWith("data:")) return raw
        return (typeof window !== "undefined" ? window.location.origin : "") +
            (raw.startsWith("/") ? raw : "/" + raw)
    }, [audioFile, audioUrl])

    // Resize bar array when barCount changes
    useEffect(() => {
        barsRef.current = new Float32Array(barCount).fill(0.06)
    }, [barCount])

    // ── Audio element lifecycle ──────────────────────────────

    useEffect(() => {
        if (isCanvas) return

        const el = new Audio()
        el.preload = "metadata"
        el.volume = 0.8

        const onTime = () => setCurrent(el.currentTime)
        const onMeta = () => { setDur(el.duration); setError(null) }
        const onEnd = () => { setPlaying(false); playingRef.current = false }
        const onError = () => {
            setError("Unable to load audio")
            setPlaying(false)
            playingRef.current = false
        }

        el.addEventListener("timeupdate", onTime)
        el.addEventListener("loadedmetadata", onMeta)
        el.addEventListener("ended", onEnd)
        el.addEventListener("error", onError)

        audioRef.current = el

        return () => {
            el.pause()
            el.removeEventListener("timeupdate", onTime)
            el.removeEventListener("loadedmetadata", onMeta)
            el.removeEventListener("ended", onEnd)
            el.removeEventListener("error", onError)
            el.src = ""
            audioRef.current = null
        }
    }, [isCanvas])

    // ── Update source ────────────────────────────────────────

    useEffect(() => {
        if (isCanvas || !audioRef.current || !resolvedSrc) return
        audioRef.current.pause()
        setPlaying(false)
        playingRef.current = false
        setCurrent(0)
        setDur(0)
        setError(null)
        audioRef.current.src = resolvedSrc
        audioRef.current.load()
    }, [resolvedSrc, isCanvas])

    // ── Volume sync ──────────────────────────────────────────

    useEffect(() => {
        if (audioRef.current) audioRef.current.volume = muted ? 0 : vol
    }, [vol, muted])

    // ── Canvas waveform animation (no React re-renders) ─────

    useEffect(() => {
        // Clear cached color resolutions when colors change
        colorCache.clear()

        const canvas = canvasRef.current
        if (!canvas) return

        // On the editor canvas, draw a static idle state and stop
        if (isCanvas) {
            const ctx = canvas.getContext("2d")
            if (!ctx) return
            const draw = () => {
                const dpr = window.devicePixelRatio || 1
                const w = canvas.clientWidth
                const h = canvas.clientHeight
                canvas.width = w * dpr
                canvas.height = h * dpr
                ctx.scale(dpr, dpr)
                ctx.clearRect(0, 0, w, h)

                const stops: [string, string, string, string] = [
                    bassColor, lowMidColor, highMidColor, trebleColor,
                ]
                const gap = w / barCount
                const barW = gap * 0.75

                for (let i = 0; i < barCount; i++) {
                    const t = i / (barCount - 1)
                    const barH = h * 0.06
                    const x = gap * i + gap * 0.125
                    const y = (h - barH) / 2
                    ctx.fillStyle = getBarColor(t, stops)
                    ctx.globalAlpha = 0.35
                    ctx.beginPath()
                    ctx.roundRect(x, y, barW, barH, barRadius)
                    ctx.fill()
                }
                ctx.globalAlpha = 1
            }
            draw()
            // Redraw on resize
            const ro = new ResizeObserver(draw)
            ro.observe(canvas)
            return () => ro.disconnect()
        }

        // Live site: run animation loop writing directly to canvas
        const ctx = canvas.getContext("2d")
        if (!ctx) return

        const stops: [string, string, string, string] = [
            bassColor, lowMidColor, highMidColor, trebleColor,
        ]

        const animate = (ts: number) => {
            const dpr = window.devicePixelRatio || 1
            const w = canvas.clientWidth
            const h = canvas.clientHeight
            canvas.width = w * dpr
            canvas.height = h * dpr
            ctx.scale(dpr, dpr)
            ctx.clearRect(0, 0, w, h)

            const t = ts / 1000
            const bars = barsRef.current
            const gap = w / barCount
            const barW = gap * 0.75
            const isPlaying = playingRef.current

            for (let i = 0; i < barCount; i++) {
                const pos = i / (barCount - 1)

                if (isPlaying) {
                    const w1 = Math.sin(t * 2.5 + pos * Math.PI * 6)
                    const w2 = Math.sin(t * 3.7 + pos * Math.PI * 4)
                    const w3 = Math.sin(t * 1.4 + pos * Math.PI * 2)
                    const env = 1 - pos * 0.3
                    bars[i] = Math.max(0.05, ((w1 * 0.4 + w2 * 0.35 + w3 * 0.25) * 0.5 + 0.5) * env)
                } else {
                    // Ease bars down to idle
                    bars[i] += (0.06 - bars[i]) * 0.08
                }

                const barH = bars[i] * h * 0.85
                const x = gap * i + gap * 0.125
                const y = (h - barH) / 2
                const color = getBarColor(pos, stops)
                const opacity = isPlaying ? 0.8 + bars[i] * 0.2 : 0.3 + bars[i] * 0.3

                ctx.fillStyle = color
                ctx.globalAlpha = opacity
                ctx.beginPath()
                ctx.roundRect(x, y, barW, barH, barRadius)
                ctx.fill()
            }

            ctx.globalAlpha = 1
            frameRef.current = requestAnimationFrame(animate)
        }

        frameRef.current = requestAnimationFrame(animate)
        return () => cancelAnimationFrame(frameRef.current)
    }, [barCount, barRadius, bassColor, lowMidColor, highMidColor, trebleColor, isCanvas])

    // ── Handlers ─────────────────────────────────────────────

    const handlePlayPause = useCallback(() => {
        if (!audioRef.current || !resolvedSrc) return
        if (playing) {
            audioRef.current.pause()
            setPlaying(false)
            playingRef.current = false
        } else {
            audioRef.current
                .play()
                .then(() => {
                    setPlaying(true)
                    playingRef.current = true
                })
                .catch(() => setError("Playback blocked — tap again"))
        }
    }, [playing, resolvedSrc])

    const handleSeek = useCallback(
        (e: React.MouseEvent<HTMLDivElement> | React.TouchEvent<HTMLDivElement>) => {
            if (!audioRef.current || !dur) return
            const rect = e.currentTarget.getBoundingClientRect()
            const clientX = "touches" in e ? e.touches[0].clientX : e.clientX
            const pct = Math.max(0, Math.min(1, (clientX - rect.left) / rect.width))
            audioRef.current.currentTime = pct * dur
            setCurrent(pct * dur)
        },
        [dur]
    )

    const progress = dur > 0 ? current / dur : 0

    // ── Volume slider thumb style ────────────────────────────

    const thumbStyle = useMemo(
        () => `
        #${uid}::-webkit-slider-thumb{-webkit-appearance:none;width:14px;height:14px;border-radius:50%;background:${accentColor};cursor:pointer}
        #${uid}::-moz-range-thumb{width:14px;height:14px;border-radius:50%;background:${accentColor};border:none;cursor:pointer}
    `,
        [accentColor, uid]
    )

    // ── Render ────────────────────────────────────────────────

    return (
        <div
            style={{
                width: "100%",
                height: "100%",
                display: "flex",
                flexDirection: "column",
                background: backgroundColor,
                borderRadius: 12,
                overflow: "hidden",
                position: "relative",
            }}
        >
            <style>{thumbStyle}</style>

            {/* Title & Description */}
            {(title || description) && (
                <div
                    style={{
                        padding: "14px 16px 0",
                        flexShrink: 0,
                    }}
                >
                    {title && (
                        <div
                            style={{
                                ...titleFont,
                                color: textColor,
                                overflow: "hidden",
                                textOverflow: "ellipsis",
                                whiteSpace: "nowrap",
                            }}
                        >
                            {title}
                        </div>
                    )}
                    {description && (
                        <div
                            style={{
                                ...descriptionFont,
                                color: textColor,
                                opacity: 0.55,
                                marginTop: title ? 2 : 0,
                                overflow: "hidden",
                                textOverflow: "ellipsis",
                                whiteSpace: "nowrap",
                            }}
                        >
                            {description}
                        </div>
                    )}
                </div>
            )}

            {/* Waveform canvas */}
            <div
                style={{
                    flex: 1,
                    minHeight: 0,
                    padding: "0 12px",
                    display: "flex",
                    alignItems: "center",
                }}
            >
                <canvas
                    ref={canvasRef}
                    style={{ width: "100%", height: "100%", display: "block" }}
                />
            </div>

            {/* Error banner */}
            {error && (
                <div
                    style={{
                        padding: "4px 16px",
                        fontSize: 11,
                        color: "rgba(255,255,255,0.7)",
                        background: "rgba(255,80,80,0.2)",
                        textAlign: "center",
                    }}
                    role="alert"
                >
                    {error}
                </div>
            )}

            {/* Controls */}
            <div
                style={{
                    padding: "4px 16px 10px",
                    display: "flex",
                    flexDirection: "column",
                    gap: 6,
                    flexShrink: 0,
                }}
            >
                    {/* Seek bar — tall invisible hit zone, thin visible track */}
                    <div
                        style={{
                            display: "flex",
                            alignItems: "center",
                            gap: 10,
                        }}
                    >
                        <div
                            onClick={handleSeek}
                            onTouchStart={handleSeek}
                            role="slider"
                            aria-label="Seek"
                            aria-valuenow={Math.round(current)}
                            aria-valuemin={0}
                            aria-valuemax={Math.round(dur)}
                            tabIndex={0}
                            style={{
                                flex: 1,
                                height: 24,
                                display: "flex",
                                alignItems: "center",
                                cursor: "pointer",
                            }}
                        >
                            <div
                                style={{
                                    width: "100%",
                                    height: 5,
                                    borderRadius: 3,
                                    background: "rgba(255,255,255,0.2)",
                                    position: "relative",
                                    overflow: "hidden",
                                }}
                            >
                                <div
                                    style={{
                                        width: `${progress * 100}%`,
                                        height: "100%",
                                        borderRadius: 3,
                                        background: accentColor,
                                        pointerEvents: "none",
                                    }}
                                />
                            </div>
                        </div>
                        <span
                            style={{
                                fontFamily:
                                    "ui-monospace, SFMono-Regular, Menlo, monospace",
                                fontSize: "clamp(9px, 2.5vw, 11px)",
                                color: "rgba(255,255,255,0.45)",
                                whiteSpace: "nowrap",
                            }}
                        >
                            {formatTime(current)} / {formatTime(dur)}
                        </span>
                    </div>

                    {/* Play + Volume */}
                    <div
                        style={{
                            display: "flex",
                            alignItems: "center",
                            gap: 12,
                            flexWrap: "wrap",
                        }}
                    >
                        <button
                            onClick={handlePlayPause}
                            aria-label={playing ? "Pause" : "Play"}
                            style={{
                                width: 44,
                                height: 44,
                                borderRadius: "50%",
                                background: accentColor,
                                border: "none",
                                cursor: "pointer",
                                display: "flex",
                                alignItems: "center",
                                justifyContent: "center",
                                flexShrink: 0,
                            }}
                        >
                            {playing ? (
                                <svg
                                    width="14"
                                    height="14"
                                    viewBox="0 0 13 13"
                                    fill="white"
                                >
                                    <rect
                                        x="2"
                                        y="1"
                                        width="3.5"
                                        height="11"
                                        rx="1"
                                    />
                                    <rect
                                        x="7.5"
                                        y="1"
                                        width="3.5"
                                        height="11"
                                        rx="1"
                                    />
                                </svg>
                            ) : (
                                <svg
                                    width="14"
                                    height="14"
                                    viewBox="0 0 13 13"
                                    fill="white"
                                >
                                    <polygon points="3,1 12,6.5 3,12" />
                                </svg>
                            )}
                        </button>

                        <div
                            style={{
                                display: "flex",
                                alignItems: "center",
                                gap: 8,
                                flex: 1,
                                justifyContent: "flex-end",
                                minWidth: 0,
                            }}
                        >
                            <button
                                onClick={() => setMuted(!muted)}
                                aria-label={muted ? "Unmute" : "Mute"}
                                style={{
                                    background: "none",
                                    border: "none",
                                    cursor: "pointer",
                                    padding: 4,
                                    display: "flex",
                                    flexShrink: 0,
                                }}
                            >
                                <svg
                                    width="16"
                                    height="16"
                                    viewBox="0 0 15 15"
                                    fill="none"
                                >
                                    <path
                                        d="M2 5.5H5L8.5 2.5V12.5L5 9.5H2V5.5Z"
                                        fill={
                                            muted
                                                ? "rgba(255,255,255,0.25)"
                                                : "rgba(255,255,255,0.6)"
                                        }
                                    />
                                    {!muted && (
                                        <path
                                            d="M10 5C11.2 5.8 12 6.8 12 7.5C12 8.2 11.2 9.2 10 10"
                                            stroke="rgba(255,255,255,0.6)"
                                            strokeWidth="1.2"
                                            strokeLinecap="round"
                                            fill="none"
                                        />
                                    )}
                                </svg>
                            </button>
                            <input
                                type="range"
                                min="0"
                                max="1"
                                step="0.01"
                                value={muted ? 0 : vol}
                                onChange={(e) => {
                                    setVol(parseFloat(e.target.value))
                                    setMuted(false)
                                }}
                                aria-label="Volume"
                                id={uid}
                                style={{
                                    width: "clamp(60px, 20%, 100px)",
                                    height: 3,
                                    borderRadius: 2,
                                    appearance: "none",
                                    WebkitAppearance: "none",
                                    background: `linear-gradient(to right, rgba(255,255,255,0.8) ${Math.round((muted ? 0 : vol) * 100)}%, rgba(255,255,255,0.15) ${Math.round((muted ? 0 : vol) * 100)}%)`,
                                    outline: "none",
                                    cursor: "pointer",
                                }}
                            />
                        </div>
                    </div>
                </div>
        </div>
    )
}

// ── Default size on Framer canvas ────────────────────────────

AudioWaveformVisualizer.displayName = "Audio Waveform Visualizer"

// @ts-ignore — Framer reads this for initial canvas sizing
AudioWaveformVisualizer.defaultProps = {
    width: 400,
    height: 240,
}

// ── Property Controls ────────────────────────────────────────

addPropertyControls(AudioWaveformVisualizer, {
    // Audio
    audioUrl: {
        type: ControlType.String,
        title: "Audio URL",
        defaultValue: "",
        placeholder: "https://example.com/track.mp3",
        description: "Direct URL to a publicly accessible audio file.",
    },
    audioFile: {
        type: ControlType.File,
        title: "Audio File",
        allowedFileTypes: ["mp3", "wav", "ogg", "aac", "m4a"],
        description: "Upload an audio file. Overrides Audio URL if both are set.",
    },

    // Content
    title: {
        type: ControlType.String,
        title: "Title",
        defaultValue: "",
        placeholder: "Track name",
        description: "Track or episode title displayed above the waveform.",
    },
    description: {
        type: ControlType.String,
        title: "Description",
        defaultValue: "",
        placeholder: "Artist or subtitle",
        description: "Subtitle or artist name displayed below the title.",
    },
    titleFont: {
        // @ts-ignore
        type: ControlType.Font,
        title: "Title Font",
        controls: "extended",
        displayTextAlignment: false,
        defaultFontType: "sans-serif",
        defaultValue: {
            fontSize: 15,
            fontWeight: 600,
            lineHeight: "1.3em",
        },
        description: "Font settings for the title text.",
    },
    descriptionFont: {
        // @ts-ignore
        type: ControlType.Font,
        title: "Description Font",
        controls: "extended",
        displayTextAlignment: false,
        defaultFontType: "sans-serif",
        defaultValue: {
            fontSize: 12,
            fontWeight: 400,
            lineHeight: "1.4em",
        },
        description: "Font settings for the description text.",
    },

    // Waveform
    barCount: {
        type: ControlType.Number,
        title: "Bar Count",
        defaultValue: 48,
        min: 8,
        max: 128,
        step: 4,
        displayStepper: true,
        description: "Number of bars in the waveform display.",
    },
    barRadius: {
        type: ControlType.Number,
        title: "Bar Radius",
        defaultValue: 3,
        min: 0,
        max: 10,
        step: 1,
        displayStepper: true,
        description: "Corner rounding on each waveform bar.",
    },

    // Colors — Waveform zones
    bassColor: {
        type: ControlType.Color,
        title: "Bass",
        defaultValue: "#e53935",
        description: "Color for the leftmost (bass) frequency zone.",
    },
    lowMidColor: {
        type: ControlType.Color,
        title: "Low-Mid",
        defaultValue: "#fb8c00",
        description: "Color for the low-mid frequency zone.",
    },
    highMidColor: {
        type: ControlType.Color,
        title: "High-Mid",
        defaultValue: "#43a047",
        description: "Color for the high-mid frequency zone.",
    },
    trebleColor: {
        type: ControlType.Color,
        title: "Treble",
        defaultValue: "#1e88e5",
        description: "Color for the rightmost (treble) frequency zone.",
    },

    // Colors — Chrome
    textColor: {
        type: ControlType.Color,
        title: "Text",
        defaultValue: "rgba(255,255,255,0.9)",
        description: "Color for the title and description text.",
    },
    backgroundColor: {
        type: ControlType.Color,
        title: "Background",
        defaultValue: "#0a0a0a",
    },
    accentColor: {
        type: ControlType.Color,
        title: "Accent",
        defaultValue: "#e53935",
        description: "Used for the play button, seek fill, and volume thumb.",
    },
})
