// frontend/src/components/ui/ModelPreview.tsx
'use client'

import { useEffect, useMemo, useRef, useState } from 'react'
import HoverPopover from './HoverPopover'
import LiveVideo from './LiveVideo'
import {
  XMarkIcon,
  SpeakerXMarkIcon,
  SpeakerWaveIcon,
} from '@heroicons/react/24/outline'

type Props = {
  jobId: string
  thumbTick?: number
  autoTickMs?: number
  blur?: boolean
  className?: string
  fit?: 'cover' | 'contain'
  roomStatus?: string

  alignStartAt?: string | number | Date
  alignEndAt?: string | number | Date | null
  alignEveryMs?: number

  fastRetryMs?: number
  fastRetryMax?: number
  fastRetryWindowMs?: number

  thumbsWebpUrl?: string | null
  thumbsCandidates?: Array<string | null | undefined>
}

export default function ModelPreview({
  jobId,
  thumbTick,
  autoTickMs = 10_000,
  blur = false,
  className,
  roomStatus,
  alignStartAt,
  alignEndAt = null,
  alignEveryMs,
  fastRetryMs,
  fastRetryMax,
  fastRetryWindowMs,
  thumbsWebpUrl,
  thumbsCandidates,
}: Props) {
  const blurCls = blur ? 'blur-md' : ''
  const CONTROLBAR_H = 0

  const normalizedRoomStatus = String(roomStatus ?? '').trim().toLowerCase()
  const showLiveBadge = normalizedRoomStatus !== '' && normalizedRoomStatus !== 'offline'

  const rootRef = useRef<HTMLDivElement | null>(null)

  // ✅ page visibility als REF (kein Rerender-Fanout bei visibilitychange)
  const pageVisibleRef = useRef(true)

  // inView als State (brauchen wir für eager/lazy + fetchPriority + UI)
  const [inView, setInView] = useState(false)
  const inViewRef = useRef(false)

  const [localTick, setLocalTick] = useState(0)
  const [directImgError, setDirectImgError] = useState(false)
  const [apiImgError, setApiImgError] = useState(false)

  const retryT = useRef<number | null>(null)
  const fastTries = useRef(0)
  const hadSuccess = useRef(false)
  const enteredViewOnce = useRef(false)

  const [pageVisible, setPageVisible] = useState(true)
  const [popupMuted, setPopupMuted] = useState(true)
  const [popupVolume, setPopupVolume] = useState(1)

  const toMs = (v: any): number => {
    if (typeof v === 'number' && Number.isFinite(v)) return v
    if (v instanceof Date) return v.getTime()
    const ms = Date.parse(String(v ?? ''))
    return Number.isFinite(ms) ? ms : NaN
  }

  const normalizeUrl = (u?: string | null): string => {
    const s = String(u ?? '').trim()
    if (!s) return ''
    if (/^https?:\/\//i.test(s)) return s
    if (s.startsWith('/')) return s
    return `/${s}`
  }

  const thumbsCandidatesKey = useMemo(() => {
    const list = [
      thumbsWebpUrl,
      ...(Array.isArray(thumbsCandidates) ? thumbsCandidates : []),
    ]
      .map(normalizeUrl)
      .filter(Boolean)

    // Reihenfolge behalten, nur dedupe
    return Array.from(new Set(list)).join('|')
  }, [thumbsWebpUrl, thumbsCandidates])

  // ✅ visibilitychange -> nur REF updaten
  useEffect(() => {
    const onVis = () => {
      const vis = !document.hidden
      pageVisibleRef.current = vis
      setPageVisible(vis) // ✅ sorgt dafür, dass Tick-Effect neu aufgebaut wird
    }
    const vis = !document.hidden
    pageVisibleRef.current = vis
    setPageVisible(vis)

    document.addEventListener('visibilitychange', onVis)
    return () => document.removeEventListener('visibilitychange', onVis)
  }, [])

  useEffect(() => {
    return () => {
      if (retryT.current) window.clearTimeout(retryT.current)
    }
  }, [])

  // ✅ IntersectionObserver: setState nur bei tatsächlichem Wechsel
  useEffect(() => {
    const el = rootRef.current
    if (!el) return

    const obs = new IntersectionObserver(
      (entries) => {
        const entry = entries[0]
        const next = Boolean(entry && (entry.isIntersecting || entry.intersectionRatio > 0))
        if (next === inViewRef.current) return
        inViewRef.current = next
        setInView(next)
      },
      {
        root: null,
        threshold: 0,
        rootMargin: '300px 0px',
      }
    )

    obs.observe(el)
    return () => obs.disconnect()
  }, [])

  // ✅ einmaliger Tick beim ersten Sichtbarwerden (nur wenn Parent nicht tickt)
  useEffect(() => {
    if (typeof thumbTick === 'number') return
    if (!inView) return
    if (!pageVisibleRef.current) return
    if (enteredViewOnce.current) return
    enteredViewOnce.current = true
    setLocalTick((x) => x + 1)
  }, [inView, thumbTick])

  // ✅ lokales Ticken nur wenn nötig (kein Timer wenn Parent tickt / offscreen / tab hidden)
  useEffect(() => {
    if (typeof thumbTick === 'number') return
    if (!inView) return
    if (!pageVisibleRef.current) return

    const period = Number(alignEveryMs ?? autoTickMs ?? 10_000)
    if (!Number.isFinite(period) || period <= 0) return

    const startMs = alignStartAt ? toMs(alignStartAt) : NaN
    const endMs = alignEndAt ? toMs(alignEndAt) : NaN

    // aligned schedule
    if (Number.isFinite(startMs)) {
      let t: number | undefined

      const schedule = () => {
        // ✅ wenn tab inzwischen hidden wurde, keine neuen timeouts schedulen
        if (!pageVisibleRef.current) return
        const now = Date.now()
        if (Number.isFinite(endMs) && now >= endMs) return

        const elapsed = Math.max(0, now - startMs)
        const rem = elapsed % period
        const wait = rem === 0 ? period : period - rem

        t = window.setTimeout(() => {
          // ✅ nochmal checken, falls inzwischen offscreen/hidden
          if (!inViewRef.current) return
          if (!pageVisibleRef.current) return
          setLocalTick((x) => x + 1)
          schedule()
        }, wait)
      }

      schedule()
      return () => {
        if (t) window.clearTimeout(t)
      }
    }

    // fallback interval
    const id = window.setInterval(() => {
      if (!inViewRef.current) return
      if (!pageVisibleRef.current) return
      setLocalTick((x) => x + 1)
    }, period)

    return () => window.clearInterval(id)
  }, [thumbTick, autoTickMs, inView, pageVisible, alignStartAt, alignEndAt, alignEveryMs])

  // ✅ tick Quelle
  const rawTick = typeof thumbTick === 'number' ? thumbTick : localTick

  // ✅ WICHTIG: Offscreen NICHT ständig src ändern (sonst trotzdem Requests!)
  // Wir "freezen" den Tick, solange inView=false oder tab hidden
  const frozenTickRef = useRef(0)
  const [frozenTick, setFrozenTick] = useState(0)

  useEffect(() => {
    if (!inView) return
    if (!pageVisibleRef.current) return
    frozenTickRef.current = rawTick
    setFrozenTick(rawTick)
  }, [rawTick, inView])

  // bei neuem *sichtbaren* Tick Error-Flag zurücksetzen
  useEffect(() => {
    setDirectImgError(false)
    setApiImgError(false)
  }, [frozenTick])

  // bei Job-Wechsel reset
  useEffect(() => {
    hadSuccess.current = false
    fastTries.current = 0
    enteredViewOnce.current = false
    setDirectImgError(false)
    setApiImgError(false)

    if (inViewRef.current && pageVisibleRef.current) {
      setLocalTick((x) => x + 1)
    }
  }, [jobId, thumbsCandidatesKey])

  const thumb = useMemo(
    () => `/api/preview?id=${encodeURIComponent(jobId)}&v=${frozenTick}`,
    [jobId, frozenTick]
  )

  const hq = useMemo(
    () => `/api/preview/live?id=${encodeURIComponent(jobId)}&hover=1`,
    [jobId]
  )

  const directThumbCandidates = useMemo(() => {
    if (!thumbsCandidatesKey) return []
    return thumbsCandidatesKey.split('|')
  }, [thumbsCandidatesKey])

  const directThumb = directThumbCandidates[0] || ''
  const useDirectThumb = Boolean(directThumb) && !directImgError
  const currentImgSrc = useMemo(() => {
    if (useDirectThumb) {
      const sep = directThumb.includes('?') ? '&' : '?'
      return `${directThumb}${sep}v=${encodeURIComponent(String(frozenTick))}`
    }
    return thumb
  }, [useDirectThumb, directThumb, frozenTick, thumb])

  return (
    <HoverPopover
      content={(open, { close }) =>
        open && (
          <div className="w-[420px] max-w-[calc(100vw-1.5rem)]">
            <div
              className="relative rounded-lg overflow-hidden bg-black"
              style={{
                // 16:9 Videofläche + feste 30px Controlbar
                paddingBottom: `calc(56.25% + ${CONTROLBAR_H}px)`,
              }}
            >
              <div className="absolute inset-0">
                <LiveVideo
                  src={hq}
                  muted={popupMuted}
                  volume={popupVolume}
                  roomStatus={roomStatus}
                  onVolumeChange={(nextVolume, nextMuted) => {
                    setPopupVolume(nextVolume)
                    setPopupMuted(nextMuted)
                  }}
                  className="w-full h-full object-contain object-bottom relative z-0"
                />

                {showLiveBadge ? (
                  <div className="absolute left-2 top-2 z-[20] inline-flex items-center gap-1.5 rounded-full bg-red-600/90 px-2 py-1 text-[11px] font-semibold text-white shadow-sm">
                    <span className="inline-block size-1.5 rounded-full bg-white animate-pulse" />
                    Live
                  </div>
                ) : null}

                <div className="absolute right-2 bottom-2 z-[60]">
                  <button
                    type="button"
                    className="pointer-events-auto inline-flex items-center justify-center rounded-full bg-black/65 px-2.5 py-1.5 text-white shadow-sm ring-1 ring-white/10 hover:bg-black/75 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-white/70"
                    title={popupMuted ? 'Ton an' : 'Stumm'}
                    aria-label={popupMuted ? 'Ton an' : 'Stumm'}
                    onClick={(e) => {
                      e.preventDefault()
                      e.stopPropagation()

                      if (popupMuted) {
                        setPopupMuted(false)
                        if (popupVolume <= 0) setPopupVolume(1)
                      } else {
                        setPopupMuted(true)
                      }
                    }}
                  >
                    <span className="text-[13px] leading-none">
                      {popupMuted ? (
                        <SpeakerXMarkIcon className="h-4 w-4" />
                      ) : (
                        <SpeakerWaveIcon className="h-4 w-4" />
                      )}
                    </span>
                  </button>
                </div>

                <button
                  type="button"
                  className="absolute right-2 top-2 z-20 inline-flex items-center justify-center rounded-md p-1.5 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-white/70 bg-white/75 text-gray-900 ring-1 ring-black/10 hover:bg-white/90 dark:bg-black/40 dark:text-white dark:ring-white/10 dark:hover:bg-black/55"
                  aria-label="Live-Vorschau schließen"
                  title="Vorschau schließen"
                  onClick={(e) => {
                    e.preventDefault()
                    e.stopPropagation()
                    close()
                  }}
                >
                  <XMarkIcon className="h-4 w-4" />
                </button>
              </div>
            </div>
          </div>
        )
      }
    >
      <div
        ref={rootRef}
        className={[
          'block relative rounded bg-gray-100 dark:bg-white/5 overflow-hidden',
          className || 'w-full h-full',
        ].join(' ')}
        onClick={(e) => {
          e.stopPropagation()
        }}
        onMouseDown={(e) => {
          e.stopPropagation()
        }}
        onTouchStart={(e) => {
          e.stopPropagation()
        }}
        onPointerDown={(e) => {
          e.stopPropagation()
        }}
      >
        {!apiImgError ? (
          <img
            src={currentImgSrc}
            loading={inView ? 'eager' : 'lazy'}
            fetchPriority={inView ? 'high' : 'auto'}
            decoding="async"
            alt=""
            className={['block w-full h-full object-cover object-center', blurCls].filter(Boolean).join(' ')}
            onLoad={() => {
              hadSuccess.current = true
              fastTries.current = 0
              if (retryT.current) window.clearTimeout(retryT.current)

              // nur den aktuell genutzten Pfad als "ok" markieren
              if (useDirectThumb) setDirectImgError(false)
              else setApiImgError(false)
            }}
            onError={() => {
              // 1) Wenn direkte preview.webp fehlschlägt -> auf API-Fallback umschalten
              if (useDirectThumb) {
                setDirectImgError(true)
                return
              }

              // 2) API-Fallback fehlschlägt -> bisherige Retry-Logik
              setApiImgError(true)

              if (!fastRetryMs) return
              if (!inViewRef.current || !pageVisibleRef.current) return
              if (hadSuccess.current) return

              const startMs = alignStartAt ? toMs(alignStartAt) : NaN
              const windowMs = Number(fastRetryWindowMs ?? 60_000)
              const withinWindow = !Number.isFinite(startMs) || Date.now() - startMs < windowMs
              if (!withinWindow) return

              const max = Number(fastRetryMax ?? 25)
              if (fastTries.current >= max) return

              if (retryT.current) window.clearTimeout(retryT.current)
              retryT.current = window.setTimeout(() => {
                fastTries.current += 1
                setApiImgError(false)     // API erneut probieren
                setLocalTick((x) => x + 1)
              }, fastRetryMs)
            }}
          />
        ) : (
          <div className="absolute inset-0 grid place-items-center px-1 text-center text-[10px] text-gray-500 dark:text-gray-400">
            keine Vorschau
          </div>
        )}
      </div>
    </HoverPopover>
  )
}
