import { useRef, useState } from 'react'
import { X } from 'lucide-react'
import { useMediaDeviceSelect, usePersistentUserChoices } from '@livekit/components-react'
import { useToast } from '@/components/ui/ToastProvider'
import { useIsCompactViewport } from '@/hooks/useIsCompactViewport'
import { useModalDismiss } from '@/hooks/useModalDismiss'
import { StageViewOptions, type StageViewProps } from '@/components/room/StageViewOptions'
import { isAudioOutputSelectable, saveAudioOutputDeviceId } from '@/lib/audioOutputDevice'

interface DeviceSettingsDialogProps extends StageViewProps {
  onClose: () => void
}

/** Свайп панели вниз дальше этого порога (px) закрывает шторку, меньше — она возвращается на место. */
const SHEET_DISMISS_THRESHOLD_PX = 80

/** Человекочитаемая подпись пункта списка устройств — `label` пуст, пока нет разрешения на медиа. */
function deviceLabel(device: MediaDeviceInfo, index: number, fallback: string): string {
  return device.label || `${fallback} ${index + 1}`
}

/**
 * Панель «Настройки устройств» — три селекта на хуках `@livekit/components-react`:
 * список устройств и переключение целиком в `useMediaDeviceSelect` (сама
 * подписана на `RoomEvent.MediaDevicesChanged`), персист выбора микрофона/камеры —
 * в `usePersistentUserChoices` (localStorage, читается заново при следующем
 * входе в комнату — см. `RoomPage.tsx`, `options` пропс `LiveKitRoom`).
 * Аудиовыход персистится отдельно (`lib/audioOutputDevice.ts`) — `LocalUserChoices`
 * LiveKit про него не знает, а `RoomOptions.audioOutput` читает сохранённый
 * `deviceId` при следующем подключении.
 *
 * На мобильной ширине (`useIsCompactViewport`) рендерится шторкой снизу вместо
 * модалки — по клику вне, Escape (`useModalDismiss`) и свайпу вниз по ЛЮБОМУ
 * месту панели (не только за ручку-волосок `.room-sheet-handle` — та её не
 * пережила бы: 40×4px, попасть пальцем почти нереально). Свайп срабатывает,
 * только если содержимое панели проскроллено в самый верх (`scrollTop === 0`
 * на touchstart, см. `dragEligibleRef`) — иначе палец должен листать список
 * устройств, а не закрывать окно; решение фиксируется на весь жест, а не
 * пересчитывается на каждый touchmove, как в любом стандартном bottom sheet.
 * Десктоп не меняется.
 *
 * Там же, и только там, первой секцией идёт «Вид» (режим показа участников и
 * скрытие остальных): на мобильном тулбар ужат до пяти кнопок, отдельной
 * кнопки «Вид» там нет — см. `RoomToolbar`. Поэтому и заголовок панели на
 * мобильном шире по смыслу («Настройки», а не «Настройки устройств»).
 *
 * ДОЛЖЕН рендериться внутри `<LiveKitRoom>`: `useMediaDeviceSelect` без явно
 * переданного `room` берёт активную комнату из `RoomContext` — вне контекста
 * он создал бы отдельный, ни с чем не связанный `Room()` и переключал бы
 * устройство «в никуда».
 */
export function DeviceSettingsDialog({
  onClose,
  layoutMode,
  onLayoutModeChange,
  hideOthers,
  onHideOthersChange,
}: DeviceSettingsDialogProps) {
  const toast = useToast()
  const { saveAudioInputDeviceId, saveVideoInputDeviceId } = usePersistentUserChoices()
  const mic = useMediaDeviceSelect({ kind: 'audioinput' })
  const camera = useMediaDeviceSelect({ kind: 'videoinput' })
  const speaker = useMediaDeviceSelect({ kind: 'audiooutput' })
  const speakerSelectable = isAudioOutputSelectable()
  const isCompact = useIsCompactViewport()

  useModalDismiss(onClose)

  const dragStartYRef = useRef<number | null>(null)
  // Жест начат, когда контент панели был проскроллен в самый верх — свайп по
  // панели, у которой ещё есть что скроллить, должен листать содержимое, а не
  // закрывать шторку. Решается ОДИН раз в touchstart и держится весь жест
  // (даже если внутри него направление сменится) — так же, как в любом
  // стандартном bottom sheet.
  const dragEligibleRef = useRef(false)
  const [dragOffset, setDragOffset] = useState(0)
  const [isDragging, setIsDragging] = useState(false)

  async function handleMicChange(deviceId: string) {
    try {
      await mic.setActiveMediaDevice(deviceId)
      saveAudioInputDeviceId(deviceId)
    } catch {
      // activeDeviceId хука — источник истины, состояние селекта само не меняется.
      toast.show('Не удалось переключить микрофон — устройство занято или отключено', 'error')
    }
  }

  async function handleCameraChange(deviceId: string) {
    try {
      await camera.setActiveMediaDevice(deviceId)
      saveVideoInputDeviceId(deviceId)
    } catch {
      toast.show('Не удалось переключить камеру — устройство занято или отключено', 'error')
    }
  }

  async function handleSpeakerChange(deviceId: string) {
    try {
      await speaker.setActiveMediaDevice(deviceId)
      saveAudioOutputDeviceId(deviceId)
    } catch {
      toast.show('Не удалось переключить вывод звука — устройство занято или отключено', 'error')
    }
  }

  function handlePanelTouchStart(e: React.TouchEvent<HTMLDivElement>) {
    dragEligibleRef.current = e.currentTarget.scrollTop === 0
    if (!dragEligibleRef.current) return
    dragStartYRef.current = e.touches[0].clientY
    setIsDragging(true)
  }

  function handlePanelTouchMove(e: React.TouchEvent<HTMLDivElement>) {
    if (!dragEligibleRef.current || dragStartYRef.current === null) return
    const delta = e.touches[0].clientY - dragStartYRef.current
    if (delta > 0) setDragOffset(delta)
  }

  function handlePanelTouchEnd() {
    if (!dragEligibleRef.current) return
    if (dragOffset > SHEET_DISMISS_THRESHOLD_PX) {
      onClose()
      return
    }
    dragStartYRef.current = null
    setIsDragging(false)
    setDragOffset(0)
  }

  return (
    <div
      className={isCompact ? 'room-sheet-overlay' : 'room-modal-overlay'}
      role="dialog"
      aria-modal="true"
      aria-labelledby="device-settings-title"
      onClick={onClose}
    >
      <div
        className={isCompact ? 'room-sheet-panel' : 'room-modal-panel'}
        onClick={(e) => e.stopPropagation()}
        onTouchStart={isCompact ? handlePanelTouchStart : undefined}
        onTouchMove={isCompact ? handlePanelTouchMove : undefined}
        onTouchEnd={isCompact ? handlePanelTouchEnd : undefined}
        style={
          isCompact && dragOffset
            ? { transform: `translateY(${dragOffset}px)`, transition: isDragging ? 'none' : undefined }
            : undefined
        }
      >
        {isCompact && <div className="room-sheet-handle" />}

        <div className="room-modal-head">
          <h2 id="device-settings-title">{isCompact ? 'Настройки' : 'Настройки устройств'}</h2>
          <button type="button" className="room-modal-close" aria-label="Закрыть" onClick={onClose}>
            <X className="lucide" style={{ width: 16, height: 16 }} aria-hidden="true" />
          </button>
        </div>

        {/* Обёртка НЕ `.room-field`: там `label { display: block }` со
            специфичностью выше, чем у `.stage-view-mode` — пункты режимов
            рассыпались бы в столбик (кружок отдельно, текст под ним). */}
        {isCompact && (
          <div className="stage-view-section">
            <StageViewOptions
              layoutMode={layoutMode}
              onLayoutModeChange={onLayoutModeChange}
              hideOthers={hideOthers}
              onHideOthersChange={onHideOthersChange}
            />
          </div>
        )}

        <div className="room-field">
          <label htmlFor="device-settings-mic">Микрофон</label>
          <select
            id="device-settings-mic"
            value={mic.activeDeviceId}
            onChange={(e) => void handleMicChange(e.target.value)}
          >
            {/* Заглушка на случай, пока activeDeviceId не совпадает ни с одним
                устройством из списка (нет разрешения на медиа/список ещё не
                перечислен) — без неё controlled-select рассинхронизируется с
                DOM (ни одна из настоящих option не соответствует value). */}
            <option value="" disabled>
              Определяется…
            </option>
            {mic.devices.map((device, index) => (
              <option key={device.deviceId} value={device.deviceId}>
                {deviceLabel(device, index, 'Микрофон')}
              </option>
            ))}
          </select>
        </div>

        <div className="room-field">
          <label htmlFor="device-settings-camera">Камера</label>
          <select
            id="device-settings-camera"
            value={camera.activeDeviceId}
            onChange={(e) => void handleCameraChange(e.target.value)}
          >
            <option value="" disabled>
              Определяется…
            </option>
            {camera.devices.map((device, index) => (
              <option key={device.deviceId} value={device.deviceId}>
                {deviceLabel(device, index, 'Камера')}
              </option>
            ))}
          </select>
        </div>

        {speakerSelectable ? (
          <div className="room-field">
            <label htmlFor="device-settings-speaker">Динамики</label>
            <select
              id="device-settings-speaker"
              value={speaker.activeDeviceId}
              onChange={(e) => void handleSpeakerChange(e.target.value)}
            >
              <option value="" disabled>
                Определяется…
              </option>
              {speaker.devices.map((device, index) => (
                <option key={device.deviceId} value={device.deviceId}>
                  {deviceLabel(device, index, 'Динамик')}
                </option>
              ))}
            </select>
          </div>
        ) : (
          <div className="room-field">
            <span className="room-field-label">Динамики</span>
            <p className="room-field-hint">
              Вывод звука (динамик, наушники, Bluetooth) переключается средствами системы — этот
              браузер не позволяет управлять им со страницы.
            </p>
          </div>
        )}
      </div>
    </div>
  )
}
