import React, { useEffect, useMemo, useRef, useState } from 'react'
import { useActiveAudioPlayer } from 'stream-chat-react'

import { pauseNativeMediaPlayers } from '../../../utils/nativeMediaPlayers'
import type { MediaPreloadMode } from '../types'

import CarouselNav from './CarouselNav'
import { filenameFromUrl } from './fileMeta'
import { useCarousel } from './useCarousel'
import ViewerShell from './ViewerShell'

export interface VideoViewerItem {
  src: string
  poster?: string
  mimeType?: string
  filename?: string
  /**
   * Per-item `<video preload>` override. Defaults to `'metadata'`
   * for the active item so duration / first-frame are available
   * immediately. Non-active siblings aren't mounted here (we render
   * only `items[index]` at a time via a `key` swap), so they
   * naturally stay unloaded.
   */
  preload?: MediaPreloadMode
}

export interface VideoViewerProps {
  open: boolean
  items: VideoViewerItem[]
  /** Index to display when the viewer first opens. Defaults to `0`. */
  initialIndex?: number
  onClose: () => void
}

/**
 * How long the viewer's `<video>` survives a close, in ms. Matches
 * `--transition-duration` on `.mes-media-viewer` in `styles.css` — the
 * dialog and its backdrop fade over the same window, and the frame has
 * to stay put until they are gone.
 */
const VIEWER_EXIT_TRANSITION_MS = 150

/**
 * Full-viewport video viewer used by every `MessageAttachment.Video.*`
 * variant. Renders a single `<video controls>` element with the
 * browser's native chrome (play / pause / seek / fullscreen / volume /
 * download).
 *
 * When `items.length > 1` the viewer becomes a carousel — see
 * `ImageViewer` for the same behavior; arrow-key navigation yields to
 * the focused `<video>` so the native seek shortcuts still work.
 */
const VideoViewer: React.FC<VideoViewerProps> = ({
  open,
  items,
  initialIndex = 0,
  onClose,
}) => {
  const { index, prev, next } = useCarousel({
    length: items.length,
    initialIndex,
    open,
  })

  const videoRef = useRef<HTMLVideoElement | null>(null)

  // Opening the lightbox stops any audio still playing in the thread —
  // two media surfaces should never sound at once. That takes both
  // halves: stream-chat-react's `AudioPlayerPool` for the players it
  // mounts (voice recordings), and `pauseNativeMediaPlayers` for the
  // plain `<audio>` elements the toolkit renders itself, which never
  // register with the pool. Outside a `<Channel>` (Storybook, profiles'
  // mock chat) the context default makes the pool call a no-op, which
  // is why the optional call is load-bearing.
  const activeAudioPlayer = useActiveAudioPlayer()
  useEffect(() => {
    if (!open) return
    activeAudioPlayer?.pause()
    pauseNativeMediaPlayers(videoRef.current)
  }, [open, activeAudioPlayer])

  // The `<video>` outlives `open` by the length of the dialog's exit
  // transition. `ViewerShell` keeps its `<dialog>` mounted so the
  // platform close animation can play (`--transition-duration` in
  // `styles.css`), and unmounting the element the moment `open` flips
  // made the frame vanish while an empty overlay faded out. It is
  // paused immediately instead, so nothing plays under the fade, and
  // the element is gone once the dialog is — which is what keeps a
  // closed viewer from preloading every clip in the thread.
  const [mounted, setMounted] = useState(open)
  // Adjusted during render rather than from an effect, so the element is
  // in the very first committed tree of an open viewer — an effect would
  // paint one empty frame first, and `react-hooks/set-state-in-effect`
  // rejects it anyway.
  if (open && !mounted) setMounted(true)
  useEffect(() => {
    if (open || !mounted) return undefined
    videoRef.current?.pause()
    const timer = window.setTimeout(
      () => setMounted(false),
      VIEWER_EXIT_TRANSITION_MS
    )
    return () => window.clearTimeout(timer)
  }, [open, mounted])

  const item = items[index]
  const filename = useMemo(
    () => item?.filename ?? (item ? filenameFromUrl(item.src) : 'video'),
    [item]
  )

  if (!item) return null

  return (
    <ViewerShell
      open={open}
      onClose={onClose}
      ariaLabel={filename}
      counter={items.length > 1 ? `${index + 1} / ${items.length}` : undefined}
      data-testid="video-viewer"
    >
      {/* No `<track>` is rendered — we don't author caption sidecars
          for chat attachments, and an empty `<track kind="captions" />`
          emits a runtime warning. Re-add a real track (with `src`,
          `srcLang`, and `default`) once caption support ships. */}
      {/* Mounted from the first open until the close transition ends —
          see the effect above. A `<video preload="metadata">` sitting in
          a never-opened (or already-closed) dialog fetches the clip's
          header for every video bubble in the thread, which is exactly
          what the poster-only bubble surface exists to avoid. */}
      {mounted ? (
        <video
          ref={videoRef}
          // Forcing a key swap on item change ensures the new source
          // mounts a fresh element instead of reusing the previous
          // playback state — keeps the video paused-at-start when
          // navigating between stacked items.
          key={`${index}:${item.src}`}
          src={item.src}
          poster={item.poster}
          controls
          // `autoPlay` without `muted` is blocked by Chrome / Safari /
          // Firefox autoplay policies — the click that opens the viewer
          // doesn't count as a user-gesture for the freshly-mounted
          // `<video>` element. Defaulting to muted means playback
          // actually starts (matches how IG / Slack handle their video
          // lightboxes); the user can unmute via the native controls if
          // they want sound.
          autoPlay
          muted
          playsInline
          preload={item.preload ?? 'metadata'}
          className="block h-auto max-h-[calc(100dvh-128px)] w-auto max-w-[min(96vw,1400px)] bg-black"
        >
          {item.mimeType ? (
            <source src={item.src} type={item.mimeType} />
          ) : null}
        </video>
      ) : null}

      {items.length > 1 ? (
        <CarouselNav
          onPrev={prev}
          onNext={next}
          prevLabel="Previous video"
          nextLabel="Next video"
        />
      ) : null}
    </ViewerShell>
  )
}

export default VideoViewer
