import { useCallback, useEffect, useRef, useState, type RefObject } from 'react'
import { useToast } from '@/components/ui/ToastProvider'

/** Какой вид мини-плеера сейчас активен — влияет на то, что рисовать в основном окне (см. `RoomPage.tsx`). */
type PiPMode = 'document' | 'video' | null

interface UseRoomPiPResult {
  /** Доступен ли мини-плеер хоть в каком-то виде (Document PiP либо video-фолбэк) — `false` в Firefox, кнопку нужно скрыть. */
  supported: boolean
  /** Активен ли сейчас мини-плеер (в любом режиме). */
  active: boolean
  mode: PiPMode
  /** Открытое PiP-окно — рендер сетки участников в него через `createPortal` (вызывающий код, `RoomPage.tsx`); `null` вне режима `'document'`. */
  pipWindow: Window | null
  /** Включить/выключить мини-плеер. ВАЖНО: вызывать СИНХРОННО из `onClick` — оба API требуют user gesture (transient activation), никакого `await` до первого вызова. */
  toggle: () => void
}

/**
 * Копирует таблицы стилей текущего документа в PiP-окно (рецепт MDN для
 * Document Picture-in-Picture — окно открывается с пустым `<head>`, своих
 * стилей не наследует). Внешние (cross-origin) таблицы, у которых `cssRules`
 * недоступны из-за CORS, переносятся ссылкой (`<link>`), а не инлайном.
 */
function copyStyleSheets(target: Document) {
  for (const sheet of Array.from(document.styleSheets)) {
    try {
      const cssText = Array.from(sheet.cssRules)
        .map((rule) => rule.cssText)
        .join('\n')
      const style = target.createElement('style')
      style.textContent = cssText
      target.head.appendChild(style)
    } catch {
      if (sheet.href) {
        const link = target.createElement('link')
        link.rel = 'stylesheet'
        link.href = sheet.href
        target.head.appendChild(link)
      }
    }
  }
}

/**
 * Мини-плеер конференции («картинка в картинке»,
 * Приоритет:
 * 1. **Document Picture-in-Picture** (Chrome/Edge 116+) — активное окно (одна
 *    крупная плитка, без карусели/грида — см. `variant="pip"` у `RoomStage`)
 *    рисует вызывающий код через `createPortal(<RoomStage variant="pip"/>,
 *    pipWindow.document.body)`, React-контекст `LiveKitRoom` при этом
 *    остаётся в основном дереве (хуки треков продолжают работать). Основное
 *    окно вместо сцены показывает заглушку — см. `mode === 'document'` в
 *    `RoomPage.tsx`.
 * 2. **Фолбэк video-PiP** (Safari, `document.pictureInPictureEnabled`) —
 *    классический `videoEl.requestPictureInPicture()` на видео активного
 *    спикера; сцена в основном окне остаётся как есть.
 * 3. Ничего не поддерживается (Firefox) — `supported: false`, кнопку в
 *    тулбаре нужно СКРЫТЬ, а не задизейблить (ничего не обещаем).
 *
 * Переход в фуллскрин из PiP и обратно ничем дополнительно не
 * синхронизируется: браузер сам выходит из фуллскрина при входе в PiP,
 * `useFullscreen.active` обновится своим `fullscreenchange`.
 */
export function useRoomPiP(containerRef: RefObject<HTMLElement | null>): UseRoomPiPResult {
  const toast = useToast()
  const [pipWindow, setPipWindow] = useState<Window | null>(null)
  const [videoPipActive, setVideoPipActive] = useState(false)

  const hasDocumentPiP = typeof window !== 'undefined' && 'documentPictureInPicture' in window
  const hasVideoPiP = typeof document !== 'undefined' && document.pictureInPictureEnabled

  // Video-PiP фолбэк: состояние читаем из событий входа/выхода — они летят
  // на document независимо от того, какой именно <video> сейчас в PiP.
  useEffect(() => {
    function handleEnter() {
      setVideoPipActive(true)
    }
    function handleLeave() {
      setVideoPipActive(false)
    }
    document.addEventListener('enterpictureinpicture', handleEnter, true)
    document.addEventListener('leavepictureinpicture', handleLeave, true)
    return () => {
      document.removeEventListener('enterpictureinpicture', handleEnter, true)
      document.removeEventListener('leavepictureinpicture', handleLeave, true)
    }
  }, [])

  // Document PiP: пользователь может закрыть окно крестиком ОС, минуя нашу
  // кнопку — единственный надёжный сигнал об этом, `pagehide` самого окна.
  useEffect(() => {
    if (!pipWindow) return
    function handlePageHide() {
      setPipWindow(null)
    }
    pipWindow.addEventListener('pagehide', handlePageHide)
    return () => pipWindow.removeEventListener('pagehide', handlePageHide)
  }, [pipWindow])

  // Зеркало `pipWindow` в ref — для cleanup-эффекта ниже: при размонтировании
  // самого хука (уход с RoomPage при активном PiP — навигация после
  // onDisconnected, смена :slug и т.п.) эффект с пустыми зависимостями видит
  // только значение на момент МОНТИРОВАНИЯ, если брать его из состояния
  // напрямую; ref всегда содержит актуальное значение на момент unmount.
  const pipWindowRef = useRef<Window | null>(null)
  useEffect(() => {
    pipWindowRef.current = pipWindow
  }, [pipWindow])

  // Закрыть осиротевшее PiP-окно при размонтировании хука — иначе при выходе
  // из конференции (без явного «Вернуть в это окно») окно браузера остаётся
  // висеть само по себе (WICG-рекомендация для Document PiP). Объявлен ПОСЛЕ
  // эффекта с `pagehide`, поэтому при unmount его подписка уже снята до
  // вызова `close()` — повторный `setPipWindow(null)` из `handlePageHide` не
  // произойдёт (да и был бы безвреден, если бы порядок был другим: `close()`
  // на уже закрытом окне — no-op, а обновление состояния уже
  // размонтированного компонента React просто игнорирует).
  useEffect(() => {
    return () => {
      pipWindowRef.current?.close()
    }
  }, [])

  const toggle = useCallback(() => {
    if (hasDocumentPiP) {
      if (pipWindow) {
        pipWindow.close()
        setPipWindow(null)
        return
      }
      // requestWindow вызывается СИНХРОННО прямо в обработчике клика — это и
      // есть user gesture (transient activation); никакой асинхронной
      // подготовки/await до этого вызова быть не должно.
      window.documentPictureInPicture
        ?.requestWindow({ width: 480, height: 320 })
        .then((win) => {
          copyStyleSheets(win.document)
          // Комната — всегда своя тёмная тема;
          // `[data-theme="room"]` на <html> PiP-окна включает переменные
          // --color-room-* из скопированного tokens.css.
          win.document.documentElement.setAttribute('data-theme', 'room')
          setPipWindow(win)
        })
        .catch(() => {
          toast.show('Не удалось открыть мини-окно конференции', 'error')
        })
      return
    }

    if (hasVideoPiP) {
      if (document.pictureInPictureElement) {
        void document.exitPictureInPicture()
        return
      }
      const container = containerRef.current
      // `.lk-focus-layout video` без уточнения брал бы ПЕРВОЕ video в DOM —
      // это миниатюра карусели (она в разметке идёт раньше фокус-плитки), а
      // не крупный контент. Фокус-плитка — прямой ребёнок `.lk-focus-layout`
      // (`> .lk-participant-tile`), карусельные — вложены внутрь `.lk-carousel`
      // ещё на уровень глубже, поэтому прямой child-селектор берёт именно её —
      // включая демонстрацию экрана, когда она в фокусе.
      const video =
        container?.querySelector<HTMLVideoElement>('.lk-focus-layout > .lk-participant-tile video') ??
        container?.querySelector<HTMLVideoElement>('video')
      if (!video) {
        toast.show('Нет активного видео для мини-окна', 'error')
        return
      }
      video.requestPictureInPicture().catch(() => {
        toast.show('Не удалось открыть мини-окно видео', 'error')
      })
    }
  }, [hasDocumentPiP, hasVideoPiP, pipWindow, containerRef, toast])

  return {
    supported: hasDocumentPiP || hasVideoPiP,
    active: pipWindow !== null || videoPipActive,
    mode: pipWindow ? 'document' : videoPipActive ? 'video' : null,
    pipWindow,
    toggle,
  }
}
