import { useMemo, useRef, type ReactNode, type RefObject } from 'react'
import { ChevronLeft, ChevronRight } from 'lucide-react'
import {
  TrackLoop,
  useGridLayout,
  usePagination,
  useSwipe,
  type GridLayoutDefinition,
  type TrackReferenceOrPlaceholder,
} from '@livekit/components-react'

/**
 * Раскладки равномерной сетки — расширенный набор поверх библиотечного
 * `GRID_LAYOUTS` (@livekit/components-core, `helper/grid-layouts.ts`).
 *
 * Зачем свой набор. Библиотечный требует `minWidth: 560` уже для сетки 2×2,
 * поэтому на телефоне (~390px) максимальной раскладкой оказывается `1×2` — две
 * плитки на страницу, и десяток участников превращается в пять страниц
 * листания. Здесь добавлены два портретных варианта под узкий экран (2×2 и
 * 2×3): плитка шириной ~190px с именем и аватаром читается нормально, а
 * страниц становится втрое меньше.
 *
 * Как это читает `selectGridLayout` (см. её исходник): раскладки сортируются
 * по `maxTiles` (при равенстве — по `minWidth`), берётся первая, вмещающая всех
 * участников, при этом раскладка пропускается, если ДАЛЬШЕ есть другая с тем же
 * `maxTiles` и подходящей ориентацией контейнера. Отсюда пары «портрет/
 * ландшафт» на одно и то же число плиток: 1×2 / 2×1 и 2×3 / 3×2 — на широком
 * экране выигрывает горизонтальный вариант, на узком вертикальный. Если
 * контейнер не дотягивает до `minWidth`/`minHeight`, функция рекурсивно
 * спускается к меньшей раскладке, а лишние участники уходят на следующую
 * страницу (`usePagination`).
 */
const STAGE_GRID_LAYOUTS: GridLayoutDefinition[] = [
  { columns: 1, rows: 1 },
  { columns: 1, rows: 2, orientation: 'portrait' },
  { columns: 2, rows: 1, orientation: 'landscape' },
  // 4 плитки помещаются и на телефоне: 390px / 2 ≈ 190px на плитку.
  { columns: 2, rows: 2, minWidth: 360 },
  { columns: 2, rows: 3, minWidth: 360, minHeight: 480, orientation: 'portrait' },
  { columns: 3, rows: 2, minWidth: 700, orientation: 'landscape' },
  { columns: 3, rows: 3, minWidth: 700 },
  { columns: 4, rows: 4, minWidth: 960 },
  { columns: 5, rows: 5, minWidth: 1100 },
]

interface StageGridProps {
  tracks: TrackReferenceOrPlaceholder[]
  /** Шаблон плитки — рендерится для каждого трека страницы (как у `GridLayout`, через `TrackLoop`). */
  children: ReactNode
  /**
   * Потолок админки на число одновременно видимых плиток (`instance_settings.media_limits.stage_max_tiles`,
   * см. `RoomPage`). Не задан — все раскладки `STAGE_GRID_LAYOUTS` доступны как
   * раньше (текущий максимум сетки — 25, 5×5). Реализовано отсечением раскладок
   * КРУПНЕЕ потолка из набора, который видит `useGridLayout`: она сама выбирает
   * бОльшую свободную раскладку, укладывающую всех участников без пагинации,
   * поэтому урезанный набор просто не даёт ей раздуть сетку сверх лимита —
   * лишние участники уходят на следующую страницу пагинации (`usePagination`),
   * то есть перестают быть подписанными треками, а не просто визуально мельче.
   */
  maxTiles?: number
}

/**
 * Равномерная сетка плиток с пагинацией — собственная сборка вместо
 * библиотечного `GridLayout`.
 *
 * Причина ровно одна: `GridLayout` не пробрасывает `gridLayouts` в
 * `useGridLayout`, то есть набор раскладок у неё жёстко зашит (см.
 * `STAGE_GRID_LAYOUTS` выше). Всё остальное — те же публичные хуки, что
 * использует оригинал (`useGridLayout` + `usePagination` + `useSwipe` +
 * `TrackLoop`) и тот же класс `.lk-grid-layout`, поэтому и стили библиотеки, и
 * `useVisualStableUpdate` внутри пагинации работают как раньше. Отличается
 * только элемент управления страницами: библиотечные `PaginationIndicator`/
 * `PaginationControl` из пакета не экспортируются, вместо них — свой
 * `.stage-grid-pages` (кнопки со стрелками + счётчик, доступен и мышью, и с
 * клавиатуры; на тач-экране страницы листаются ещё и свайпом).
 */
export function StageGrid({ tracks, children, maxTiles }: StageGridProps) {
  const gridEl = useRef<HTMLDivElement | null>(null)
  // Хуки библиотеки объявлены с `RefObject<HTMLDivElement>` (типы React 18, где
  // `current` был readonly и тип вёл себя ковариантно). В типах React 19
  // `current` мутабельный, поэтому `RefObject<HTMLDivElement | null>` в такой
  // параметр уже не присваивается — приведение безопасно: оба хука только
  // читают `.current` (ResizeObserver и слушатели touch-событий).
  const gridRef = gridEl as RefObject<HTMLDivElement>
  const gridLayouts = useMemo(
    () => (maxTiles == null ? STAGE_GRID_LAYOUTS : STAGE_GRID_LAYOUTS.filter((l) => l.columns * l.rows <= maxTiles)),
    [maxTiles],
  )
  const { layout } = useGridLayout(gridRef, tracks.length, { gridLayouts })
  const pagination = usePagination(layout.maxTiles, tracks)

  useSwipe(gridRef, {
    onLeftSwipe: pagination.nextPage,
    onRightSwipe: pagination.prevPage,
  })

  const hasPages = pagination.totalPageCount > 1

  return (
    <div ref={gridEl} className="lk-grid-layout stage-grid" data-lk-pagination={hasPages}>
      <TrackLoop tracks={pagination.tracks}>{children}</TrackLoop>
      {hasPages && (
        <div className="stage-grid-pages">
          <button
            type="button"
            aria-label="Предыдущая страница участников"
            disabled={pagination.currentPage <= 1}
            onClick={pagination.prevPage}
          >
            <ChevronLeft className="lucide" aria-hidden="true" />
          </button>
          <span aria-live="polite">
            {pagination.currentPage} / {pagination.totalPageCount}
          </span>
          <button
            type="button"
            aria-label="Следующая страница участников"
            disabled={pagination.currentPage >= pagination.totalPageCount}
            onClick={pagination.nextPage}
          >
            <ChevronRight className="lucide" aria-hidden="true" />
          </button>
        </div>
      )}
    </div>
  )
}
