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

import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import HoverPopover from './HoverPopover'
import LiveVideo from './LiveVideo'
import {
  XMarkIcon,
  SpeakerXMarkIcon,
  SpeakerWaveIcon,
} from '@heroicons/react/24/outline'
import LoadingSpinner from './LoadingSpinner'
import { apiUrl, apiFetch } from '../../lib/api'

type Props = {
  jobId: string
  enableHoverLive?: boolean
  refreshKey?: number
  blur?: boolean
  className?: string
  fit?: 'cover' | 'contain'
  roomStatus?: string
  fallbackSrc?: string
  initialSrc?: string
}

function buildChaturbateCookieHeader(): string {
  try {
    const raw = window.localStorage.getItem('record_cookies')
    if (!raw) return ''

    const obj = JSON.parse(raw) as Record<string, string>
    return Object.entries(obj ?? {})
      .map(([k, v]) => [String(k ?? '').trim(), String(v ?? '').trim()] as const)
      .filter(([k, v]) => k && v)
      .map(([k, v]) => `${k}=${v}`)
      .join('; ')
  } catch {
    return ''
  }
}

export default function ModelPreview({
  jobId,
  enableHoverLive = false,
  refreshKey,
  blur = false,
  className,
  fit = 'cover',
  roomStatus,
  fallbackSrc,
  initialSrc,
}: Props) {
  const rootRef = useRef<HTMLDivElement | null>(null)
  const [inView, setInView] = useState(false)
  const [imgError, setImgError] = useState(false)
  const fadeTimerRef = useRef<number | null>(null)
  
  const [displaySrc, setDisplaySrc] = useState('')
  const [pendingDisplaySrc, setPendingDisplaySrc] = useState('')
  const [incomingSrc, setIncomingSrc] = useState('')
  const [showIncoming, setShowIncoming] = useState(false)
  const [liveSrc, setLiveSrc] = useState('')
  const [hoverOpen, setHoverOpen] = useState(false)

  const [localRefreshNonce, setLocalRefreshNonce] = useState(0)
  const [usingFallback, setUsingFallback] = useState(false)

  const [livePreparing, setLivePreparing] = useState(false)
  const [liveReady, setLiveReady] = useState(false)
  const [liveUnavailable, setLiveUnavailable] = useState(false)

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

  const handleLiveLoadingStart = useCallback(() => {
    setLiveReady(false)
    setLiveUnavailable(false)
  }, [])

  const handleLiveReady = useCallback(() => {
    setLiveReady(true)
    setLiveUnavailable(false)
  }, [])

  const handleLiveUnavailable = useCallback(() => {
    setLiveUnavailable(true)
  }, [])

  const handleLiveVolumeChange = useCallback((nextVolume: number, nextMuted: boolean) => {
    setPopupVolume(nextVolume)
    setPopupMuted(nextMuted)
  }, [])

  const blurCls = blur ? 'blur-md' : ''
  const objectFitCls = fit === 'contain' ? 'object-contain' : 'object-cover'

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

  const previewSrc = useMemo(() => {
    const explicit = String(initialSrc ?? '').trim()
    const base = explicit || `/api/preview?id=${encodeURIComponent(jobId)}&file=preview.jpg`

    const version =
      typeof refreshKey === 'number' && Number.isFinite(refreshKey)
        ? `${refreshKey}-${localRefreshNonce}`
        : localRefreshNonce > 0
          ? String(localRefreshNonce)
          : ''

    if (!version) return base

    return base.includes('?')
      ? `${base}&v=${encodeURIComponent(version)}`
      : `${base}?v=${encodeURIComponent(version)}`
  }, [initialSrc, jobId, refreshKey, localRefreshNonce])

  const fallbackImgSrc = useMemo(() => {
    const s = String(fallbackSrc ?? '').trim()
    const base = s || `/api/preview?id=${encodeURIComponent(jobId)}&fallbackOnly=1`

    const version =
      typeof refreshKey === 'number' && Number.isFinite(refreshKey)
        ? `${refreshKey}-${localRefreshNonce}`
        : localRefreshNonce > 0
          ? String(localRefreshNonce)
          : ''

    if (!version) return base

    return base.includes('?')
      ? `${base}&v=${encodeURIComponent(version)}`
      : `${base}?v=${encodeURIComponent(version)}`
  }, [fallbackSrc, jobId, refreshKey, localRefreshNonce])

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

  useEffect(() => {
    if (!inView) return

    const id = window.setInterval(() => {
      setLocalRefreshNonce((n) => n + 1)
    }, 20000)

    return () => window.clearInterval(id)
  }, [inView, jobId])

  useEffect(() => {
    const ac = new AbortController()

    const loadLive = async () => {
      if (!enableHoverLive || !hoverOpen) {
        setLiveSrc('')
        setLivePreparing(false)
        setLiveReady(false)
        setLiveUnavailable(false)
        return
      }

      setLivePreparing(true)
      setLiveReady(false)
      setLiveUnavailable(false)

      const cookieHeader = buildChaturbateCookieHeader()

      try {
        let prepared = false

        for (let attempt = 0; attempt < 2; attempt += 1) {
          const prepareUrl = apiUrl(
            `/api/preview/live?id=${encodeURIComponent(jobId)}&hover=1&play=1&prepare=1&attempt=${attempt + 1}`
          )

          const prepareRes = await apiFetch(prepareUrl, {
            method: 'GET',
            cache: 'no-store',
            signal: ac.signal,
            headers: cookieHeader
              ? { 'X-Chaturbate-Cookie': cookieHeader }
              : undefined,
          })

          if (ac.signal.aborted) return

          const prepareData = await prepareRes.json().catch(() => null)
          if (ac.signal.aborted) return

          if (prepareRes.ok && prepareData?.ok !== false) {
            prepared = true
            break
          }

          if (attempt === 0) {
            await new Promise((resolve) => window.setTimeout(resolve, 500))
            if (ac.signal.aborted) return
          }
        }

        if (!prepared) {
          throw new Error('Live preview preparation failed')
        }

        setLiveSrc(
          apiUrl(`/api/preview/live?id=${encodeURIComponent(jobId)}&hover=1&play=1&ts=${Date.now()}`)
        )
      } catch {
        if (ac.signal.aborted) return
        setLiveSrc(
          apiUrl(`/api/preview/live?id=${encodeURIComponent(jobId)}&hover=1&play=1&ts=${Date.now()}`)
        )
      } finally {
        if (!ac.signal.aborted) setLivePreparing(false)
      }
    }

    void loadLive()
    return () => ac.abort()
  }, [enableHoverLive, hoverOpen, jobId])

  useEffect(() => {
    const el = rootRef.current
    if (!el) return

    const obs = new IntersectionObserver(
      (entries) => {
        const entry = entries[0]
        setInView(Boolean(entry?.isIntersecting || entry?.intersectionRatio > 0))
      },
      {
        root: null,
        threshold: 0.01,
      }
    )

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

  useEffect(() => {
    if (fadeTimerRef.current) window.clearTimeout(fadeTimerRef.current)

    setImgError(false)
    setUsingFallback(false)
    setPendingDisplaySrc('')
    setDisplaySrc('')
    setIncomingSrc('')
    setShowIncoming(false)

    setLiveSrc('')
    setLivePreparing(false)
    setLiveReady(false)
    setLiveUnavailable(false)
  }, [jobId])

  useEffect(() => {
    if (!inView) return
    if (!previewSrc) return

    if (!displaySrc) {
      setDisplaySrc(previewSrc)
      return
    }

    if (previewSrc === displaySrc || previewSrc === incomingSrc) return

    setIncomingSrc(previewSrc)
    setShowIncoming(false)
  }, [inView, previewSrc, displaySrc, incomingSrc])

  useEffect(() => {
    if (!inView) return
    if (!usingFallback) return
    if (!fallbackImgSrc) return
    if (!displaySrc) return

    if (fallbackImgSrc === displaySrc || fallbackImgSrc === incomingSrc) return

    setIncomingSrc(fallbackImgSrc)
    setShowIncoming(false)
  }, [inView, usingFallback, fallbackImgSrc, displaySrc, incomingSrc])

  const handleDisplayLoad = useCallback(() => {
    setImgError(false)

    const current = String(displaySrc || '').trim()
    const fallback = String(fallbackImgSrc || '').trim()

    setUsingFallback(Boolean(fallback && current === fallback))

    if (pendingDisplaySrc && current === pendingDisplaySrc) {
      requestAnimationFrame(() => {
        requestAnimationFrame(() => {
          setIncomingSrc('')
          setShowIncoming(false)
          setPendingDisplaySrc('')
        })
      })
    }
  }, [displaySrc, fallbackImgSrc, pendingDisplaySrc])

  const handleDisplayError = useCallback(() => {
    const current = String(displaySrc || '').trim()
    const fallback = String(fallbackImgSrc || '').trim()

    if (fallback && current !== fallback) {
      setUsingFallback(true)
      setImgError(false)
      setDisplaySrc(fallback)
      return
    }

    setImgError(true)
  }, [displaySrc, fallbackImgSrc])

  const handleIncomingLoad = useCallback(() => {
    const next = String(incomingSrc || '').trim()
    if (!next) return

    if (fadeTimerRef.current) window.clearTimeout(fadeTimerRef.current)

    requestAnimationFrame(() => {
      setShowIncoming(true)
    })

    fadeTimerRef.current = window.setTimeout(() => {
      setPendingDisplaySrc(next)
      setDisplaySrc(next)
    }, 320)
  }, [incomingSrc])

  const handleIncomingError = useCallback(() => {
    const next = String(incomingSrc || '').trim()
    const fallback = String(fallbackImgSrc || '').trim()

    if (fallback && next !== fallback) {
      setIncomingSrc(fallback)
      setShowIncoming(false)
      return
    }

    setIncomingSrc('')
    setShowIncoming(false)

    if (!displaySrc) {
      setImgError(true)
    }
  }, [incomingSrc, fallbackImgSrc, displaySrc])

  return (
    <HoverPopover
      onOpenChange={(open) => {
        const nextOpen = enableHoverLive && open

        setHoverOpen(nextOpen)

        if (!nextOpen) {
          setLiveSrc('')
          setLivePreparing(false)
          setLiveReady(false)
          setLiveUnavailable(false)
        }
      }}
      content={(open, { close }) =>
        enableHoverLive && open ? (
          <div className="w-[420px] max-w-[calc(100vw-1.5rem)]">
            <div
              className="relative rounded-lg overflow-hidden bg-black"
              style={{ paddingBottom: '56.25%' }}
            >
              <div className="absolute inset-0">
                <LiveVideo
                  src={liveSrc}
                  muted={popupMuted}
                  volume={popupVolume}
                  roomStatus={roomStatus}
                  onLoadingStart={handleLiveLoadingStart}
                  onReady={handleLiveReady}
                  onUnavailable={handleLiveUnavailable}
                  onVolumeChange={handleLiveVolumeChange}
                  className="w-full h-full object-contain object-center relative z-0"
                />

                {!liveUnavailable && (livePreparing || !liveReady) ? (
                  <div className="absolute inset-0 z-10 flex flex-col items-center justify-center gap-3 bg-black/85 text-white">
                    <LoadingSpinner />
                    <div className="text-sm font-medium">
                      {livePreparing ? 'Live-Vorschau wird vorbereitet…' : 'Live-Stream wird geladen…'}
                    </div>
                  </div>
                ) : null}

                {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-white/90 px-2.5 py-1.5 text-gray-900 shadow-sm ring-1 ring-black/10 hover:bg-white focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gray-500/70 dark:bg-black/55 dark:text-white dark:ring-white/10 dark:hover:bg-black/70 dark: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)
                      }
                    }}
                  >
                    {popupMuted ? (
                      <SpeakerXMarkIcon className="h-4 w-4" />
                    ) : (
                      <SpeakerWaveIcon className="h-4 w-4" />
                    )}
                  </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>
        ) : null
      }
    >
      <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()}
      >
        {inView ? (
          !imgError ? (
            <div className="relative w-full h-full">
              {displaySrc ? (
                <img
                  src={displaySrc}
                  loading="lazy"
                  decoding="async"
                  alt=""
                  referrerPolicy="no-referrer"
                  className={[
                    'block w-full h-full',
                    objectFitCls,
                    'object-center',
                    blurCls,
                  ]
                    .filter(Boolean)
                    .join(' ')}
                  onLoad={handleDisplayLoad}
                  onError={handleDisplayError}
                />
              ) : null}

              {incomingSrc ? (
                <img
                  src={incomingSrc}
                  loading="lazy"
                  decoding="async"
                  alt=""
                  referrerPolicy="no-referrer"
                  className={[
                    'absolute inset-0 block w-full h-full transition-opacity duration-300 ease-in-out',
                    objectFitCls,
                    'object-center',
                    blurCls,
                    showIncoming ? 'opacity-100' : 'opacity-0',
                  ]
                    .filter(Boolean)
                    .join(' ')}
                  onLoad={handleIncomingLoad}
                  onError={handleIncomingError}
                />
              ) : null}
            </div>
          ) : (
            <div className={['block w-full h-full bg-gray-100 dark:bg-white/5', blurCls].filter(Boolean).join(' ')} />
          )
        ) : (
          <div className={['block w-full h-full bg-gray-100 dark:bg-white/5', blurCls].filter(Boolean).join(' ')} />
        )}
      </div>
    </HoverPopover>
  )
}
