import { useEffect, useState } from 'react'
import { motion } from 'framer-motion'
import type { BaseItemDto } from '../../api/types'
import {
  pickPrimarySource,
  getVideoStream,
  getAudioStreams,
  getSubtitleStreams,
  videoCodecLabel,
  audioFormatLabel,
  framerateLabel,
  resolutionLabel,
  videoRangeLabel,
} from '../../lib/jellyfin-meta'
import { formatBitrate } from '../../lib/format'
import { isHdrDisplayActive } from '../../lib/device-profile'

interface Props {
  item?: BaseItemDto | null
  visible: boolean
  /** PlayMethod from PlaybackInfo so users see whether they're transcoding. */
  playMethod?: 'DirectPlay' | 'DirectStream' | 'Transcode' | null
}

interface LiveStats {
  bufferAheadSec: number | null
  droppedFrames: number | null
  totalFrames: number | null
  currentFps: number | null
}

interface HwDecodeState {
  supported: boolean | null
  hwAccelerated: boolean | null
}

function readLiveStats(): LiveStats {
  const video = document.querySelector('video') as HTMLVideoElement | null
  if (!video) return { bufferAheadSec: null, droppedFrames: null, totalFrames: null, currentFps: null }
  let bufferAhead: number | null = null
  try {
    const buf = video.buffered
    if (buf && buf.length) {
      const t = video.currentTime
      for (let i = 0; i < buf.length; i++) {
        if (t >= buf.start(i) && t <= buf.end(i)) {
          bufferAhead = Math.max(0, buf.end(i) - t)
          break
        }
      }
    }
  } catch { /* timeranges throws sometimes */ }
  const q = video.getVideoPlaybackQuality?.()
  return {
    bufferAheadSec: bufferAhead,
    droppedFrames: q?.droppedVideoFrames ?? null,
    totalFrames: q?.totalVideoFrames ?? null,
    currentFps: null,
  }
}

async function checkHwDecode(stream: { Codec?: string | null; Width?: number | null; Height?: number | null; BitRate?: number | null; VideoRangeType?: string | null; VideoRange?: string | null } | null): Promise<HwDecodeState> {
  if (!stream || typeof navigator.mediaCapabilities?.decodingInfo !== 'function') {
    return { supported: null, hwAccelerated: null }
  }
  const codec = (stream.Codec || '').toLowerCase()
  const w = stream.Width ?? 1920
  const h = stream.Height ?? 1080
  const br = stream.BitRate ?? 8000000
  // Use the main10 profile codec string for HDR content so the
  // MediaCapabilities query matches what the actual stream requires.
  // The previous main (8-bit) string caused HDR sources to probe
  // against the wrong profile.
  const range = (stream.VideoRangeType || stream.VideoRange || '').toUpperCase()
  const isHdr = range === 'HDR' || range === 'HDR10' || range === 'HLG' || range.startsWith('DOVI')
  const mimeCodec =
    codec === 'h264' ? 'avc1.640033'
    : codec === 'hevc' && isHdr ? 'hev1.2.4.L153.B0'
    : codec === 'hevc' ? 'hev1.1.6.L150.90'
    : codec === 'av1' ? 'av01.0.05M.08'
    : codec === 'vp9' ? 'vp09.00.50.08'
    : null
  if (!mimeCodec) return { supported: null, hwAccelerated: null }
  try {
    const info = await navigator.mediaCapabilities!.decodingInfo({
      type: 'media-source',
      video: {
        contentType: `video/mp4; codecs="${mimeCodec}"`,
        width: w,
        height: h,
        bitrate: br,
        framerate: 24,
      },
    })
    return { supported: info.supported, hwAccelerated: info.powerEfficient }
  } catch {
    return { supported: null, hwAccelerated: null }
  }
}

/**
 * Infer why transcoding is happening by comparing the original source
 * streams against what the browser natively supports and what the user
 * has selected (audio track, subtitle track, max bitrate).
 */
function inferTranscodeReasons(
  item: BaseItemDto | null | undefined,
  playMethod: Props['playMethod'],
): string[] {
  if (playMethod !== 'Transcode') return []
  const reasons: string[] = []
  const source = pickPrimarySource(item || {})
  const v = getVideoStream(item || {})
  const a = getAudioStreams(item || {})[0]
  if (v) {
    const codec = (v.Codec || '').toLowerCase()
    if (codec === 'hevc' || codec === 'h265') reasons.push('HEVC not natively supported')
    else if (codec === 'av1') reasons.push('AV1 not natively supported')
    else if (codec === 'mpeg2video') reasons.push('MPEG-2 not natively supported')
    else if (codec === 'vc1') reasons.push('VC-1 not natively supported')
  }

  if (a) {
    const codec = (a.Codec || '').toLowerCase()
    if (codec === 'truehd') reasons.push('Dolby TrueHD requires transcode')
    else if (codec === 'dts' && (a.Profile || '').toLowerCase().includes('hd')) reasons.push('DTS-HD requires transcode')
    else if (codec === 'dts') reasons.push('DTS requires transcode')
    else if (codec === 'eac3' && (a.Profile || '').toLowerCase().includes('atmos')) {
      // Atmos in EAC3 may pass through on some browsers; if transcoding, note it
    }
  }

  // Check container
  const container = (source?.Container || '').toLowerCase()
  if (container === 'mkv' || container === 'avi' || container === 'wmv') {
    reasons.push(`Container .${container} not natively supported`)
  }

  // Subtitle burn-in: image-based subtitles usually force transcode
  const subStreams = getSubtitleStreams(item || {})
  const imageSub = subStreams.find((s: any) => {
    const c = (s.Codec || '').toLowerCase()
    return c === 'pgssub' || c === 'dvdsub' || c === 'vobsub' || c === 'sup'
  })
  if (imageSub) reasons.push('Image subtitles require burn-in')

  // If we couldn't infer anything specific, give a generic reason
  if (reasons.length === 0) reasons.push('Codec or container incompatible with browser')

  return reasons
}

export default function StreamInfo({ item, visible, playMethod }: Props) {
  const [live, setLive] = useState<LiveStats>({
    bufferAheadSec: null,
    droppedFrames: null,
    totalFrames: null,
    currentFps: null,
  })
  const [hw, setHw] = useState<HwDecodeState>({ supported: null, hwAccelerated: null })

  useEffect(() => {
    if (!visible) return
    const tick = () => setLive(readLiveStats())
    tick()
    const id = setInterval(tick, 1000)
    return () => clearInterval(id)
  }, [visible])

  useEffect(() => {
    if (!visible) return
    const v = getVideoStream(item || {})
    checkHwDecode(v).then(setHw)
  }, [visible, item])

  if (!visible || !item) return null
  const source = pickPrimarySource(item)
  const v = getVideoStream(item)
  const a = getAudioStreams(item)[0]

  const rows: { label: string; value: string; accent?: boolean; warn?: boolean }[] = []

  // Play method with color coding + hardware decode badge
  if (playMethod) {
    const hwLabel = hw.hwAccelerated === true ? ' · HW' : hw.hwAccelerated === false ? ' · SW' : ''
    rows.push({
      label: 'Method',
      value: playMethod + hwLabel,
      accent: playMethod === 'DirectPlay',
      warn: playMethod === 'Transcode',
    })
  }

  // Transcoding reasons
  const transcodeReasons = inferTranscodeReasons(item, playMethod)
  if (transcodeReasons.length > 0) {
    rows.push({ label: 'Why transcoding', value: transcodeReasons.join(', '), warn: true })
  }

  const res = resolutionLabel(item)
  const range = videoRangeLabel(item)

  // HDR display status - tells the user whether their display is actually
  // in HDR mode. When the source is HDR but the display is SDR, the browser
  // tone-maps the content. If the profile was built with HDR display off,
  // the server did FFmpeg tone-mapping instead.
  const hdrDisplay = isHdrDisplayActive()
  if (range && range !== 'SDR') {
    rows.push({
      label: 'HDR display',
      value: hdrDisplay ? 'Active' : 'Off (tone-mapped)',
      accent: hdrDisplay,
      warn: !hdrDisplay,
    })
  }

  if (res) rows.push({ label: 'Resolution', value: range ? `${res} · ${range}` : res })
  if (v) {
    rows.push({ label: 'Video', value: videoCodecLabel(v) })
    const fps = framerateLabel(v)
    if (fps) rows.push({ label: 'Framerate', value: fps })
    if (v.BitRate) rows.push({ label: 'Video bitrate', value: formatBitrate(v.BitRate) })
  }
  if (a) {
    rows.push({ label: 'Audio', value: audioFormatLabel(a) || '' })
    if (a.BitRate) rows.push({ label: 'Audio bitrate', value: formatBitrate(a.BitRate) })
  }
  if (source?.Container) rows.push({ label: 'Container', value: source.Container.toUpperCase() })
  if (source?.Bitrate) rows.push({ label: 'Total bitrate', value: formatBitrate(source.Bitrate) })

  // Live block - only meaningful when there's a video element
  const liveRows: { label: string; value: string }[] = []
  if (live.bufferAheadSec != null) {
    liveRows.push({ label: 'Buffer ahead', value: `${live.bufferAheadSec.toFixed(1)}s` })
  }
  if (live.droppedFrames != null && live.totalFrames != null) {
    const pct = live.totalFrames > 0 ? (live.droppedFrames / live.totalFrames) * 100 : 0
    liveRows.push({
      label: 'Dropped frames',
      value: `${live.droppedFrames} / ${live.totalFrames} (${pct.toFixed(2)}%)`,
    })
  }

  if (!rows.length && !liveRows.length) return null

  return (
    <motion.div
      initial={{ opacity: 0, scale: 0.96 }}
      animate={{ opacity: 1, scale: 1 }}
      exit={{ opacity: 0, scale: 0.96 }}
      transition={{ duration: 0.18 }}
      className="absolute top-20 right-6 z-20 w-80 rounded-lg bg-black/75 backdrop-blur-md border border-white/10 p-4 pointer-events-none"
    >
      <p className="text-[10px] uppercase tracking-[0.14em] font-semibold text-white/55 mb-3">
        Stream info
      </p>
      <div className="space-y-1.5">
        {rows.map(r => (
          <div key={r.label} className="flex items-baseline justify-between gap-3 text-[11.5px]">
            <span className="text-white/55 shrink-0">{r.label}</span>
            <span
              className={`font-mono tabular-nums text-right ${
                r.accent ? 'text-success' : r.warn ? 'text-warning' : 'text-white'
              }`}
            >
              {r.value}
            </span>
          </div>
        ))}
      </div>
      {liveRows.length > 0 && (
        <>
          <p className="text-[10px] uppercase tracking-[0.14em] font-semibold text-white/55 mt-4 mb-2">
            Live
          </p>
          <div className="space-y-1.5">
            {liveRows.map(r => (
              <div key={r.label} className="flex items-baseline justify-between gap-3 text-[11.5px]">
                <span className="text-white/55">{r.label}</span>
                <span className="text-white font-mono tabular-nums">{r.value}</span>
              </div>
            ))}
          </div>
        </>
      )}
      <p className="text-[9.5px] text-white/45 mt-3 pt-2 border-t border-white/8">Press i to dismiss</p>
    </motion.div>
  )
}
