import { useMutation, useQueryClient } from '@tanstack/react-query'
import { Check, ChevronDown, Plus } from 'lucide-react'
import { useMemo, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { qk } from '@/shared/api/query-keys'
import type { ShowKind, ShowSummaryDto } from '@/shared/api/types'
import { cn } from '@/shared/lib/cn'
import { useApiError } from '@/shared/lib/use-api-error'
import { Input } from '@/shared/ui/input'
import { Popover, PopoverContent, PopoverTrigger } from '@/shared/ui/popover'
import { toast } from '@/shared/ui/toast-store'
import { createShow } from './api'

/** Сколько шоу показывать за раз: длинный список всё равно не читают — его сужают поиском. */
const MAX_VISIBLE = 50

/**
 * Выбор шоу с поиском и созданием на месте. Шоу в библиотеке бывают сотни, поэтому обычный
 * `Select` не годится: здесь строка поиска, а недостающее шоу заводится прямо из выпадашки —
 * не покидая разбора файлов. Кнопка создания появляется, только когда набранное имя ни с чем не
 * совпало, так что в обычном случае (шоу есть) она на глаза не попадается.
 */
export function ShowPicker({
  value,
  onChange,
  shows,
  noneLabel,
  placeholder,
  className,
}: Readonly<{
  value: string
  onChange: (showId: string) => void
  shows: ShowSummaryDto[] | undefined
  /** Подпись пункта «без шоу» (в загрузке — «в библиотеку»). Не задан — шоу обязательно. */
  noneLabel?: string
  /** Что показывать на кнопке, когда ничего не выбрано. */
  placeholder?: string
  className?: string
}>) {
  const { t } = useTranslation()
  const queryClient = useQueryClient()
  const onError = useApiError()
  const [open, setOpen] = useState(false)
  const [query, setQuery] = useState('')
  const [active, setActive] = useState(0)
  const listRef = useRef<HTMLDivElement>(null)
  const searchRef = useRef<HTMLInputElement>(null)

  const trimmed = query.trim()
  const matches = useMemo(() => {
    const q = trimmed.toLowerCase()
    const all = shows ?? []
    if (!q) return all.slice(0, MAX_VISIBLE)
    return all
      .filter(
        (s) => s.name.toLowerCase().includes(q) || (s.originalName ?? '').toLowerCase().includes(q),
      )
      .slice(0, MAX_VISIBLE)
  }, [shows, trimmed])

  // Точное совпадение — создавать нечего: шоу уже есть, его надо просто выбрать.
  const exists = (shows ?? []).some((s) => s.name.toLowerCase() === trimmed.toLowerCase())
  const canCreate = trimmed.length > 0 && !exists

  const selected = shows?.find((s) => s.id === value)
  const label = selected?.name ?? placeholder ?? noneLabel ?? t('admin.shows.pickerPlaceholder')

  const close = () => {
    setOpen(false)
    setQuery('')
    setActive(0)
  }

  const pick = (showId: string) => {
    onChange(showId)
    close()
  }

  const create = useMutation({
    mutationFn: (kind: ShowKind) => createShow({ name: trimmed, kind }),
    onSuccess: async (created) => {
      // Список шоу перечитываем до выбора: иначе кнопка покажет плейсхолдер вместо нового имени.
      await queryClient.invalidateQueries({ queryKey: qk.shows.all })
      toast.success(t('admin.shows.pickerCreated'))
      pick(created.id)
    },
    onError,
  })

  /** Стрелки/Enter по списку — иначе после поиска пришлось бы тянуться к мыши. */
  const onKeyDown = (event: React.KeyboardEvent) => {
    const options = noneLabel ? matches.length + 1 : matches.length
    if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {
      event.preventDefault()
      if (options === 0) return
      const next = event.key === 'ArrowDown' ? active + 1 : active - 1
      setActive((next + options) % options)
      requestAnimationFrame(() =>
        listRef.current
          ?.querySelector('[data-active="true"]')
          ?.scrollIntoView({ block: 'nearest' }),
      )
      return
    }
    if (event.key !== 'Enter') return
    event.preventDefault()
    if (options === 0) {
      if (canCreate && !create.isPending) create.mutate('Series')
      return
    }
    if (noneLabel && active === 0) {
      pick('')
      return
    }
    const show = matches[noneLabel ? active - 1 : active]
    if (show) pick(show.id)
  }

  return (
    // modal — потому что пикер живёт внутри диалогов: поповер регистрирует свой слой поверх
    // диалогового, забирает фокус-ловушку и получает указатель. Работает это лишь пока Radix
    // загружен одним экземпляром — единственность версий закреплена в pnpm-workspace.yaml.
    <Popover modal open={open} onOpenChange={(next) => (next ? setOpen(true) : close())}>
      <PopoverTrigger
        className={cn(
          'flex h-10 w-full items-center justify-between gap-2 rounded-sm border border-border bg-transparent px-3 py-2 text-sm outline-none focus-visible:ring-2 focus-visible:ring-primary/50',
          className,
        )}
      >
        <span className={cn('min-w-0 truncate', !selected && 'text-muted-foreground')}>
          {label}
        </span>
        <ChevronDown className="h-4 w-4 shrink-0 opacity-60" />
      </PopoverTrigger>
      <PopoverContent
        className="w-[var(--radix-popover-trigger-width)] min-w-64 p-1"
        onKeyDown={onKeyDown}
        onOpenAutoFocus={(event) => {
          // Фокус наводим сами: внутри диалога за него борются две фокус-ловушки, и по умолчанию
          // он оседает на самом поповере — строка поиска остаётся мёртвой.
          event.preventDefault()
          requestAnimationFrame(() => searchRef.current?.focus())
        }}
      >
        <Input
          ref={searchRef}
          className="h-8"
          placeholder={t('admin.shows.pickerSearch')}
          value={query}
          onChange={(e) => {
            setQuery(e.target.value)
            setActive(0)
          }}
        />

        <div ref={listRef} className="mt-1 max-h-64 overflow-y-auto">
          {noneLabel && (
            <Option
              active={active === 0}
              selected={!value}
              label={noneLabel}
              onSelect={() => pick('')}
            />
          )}
          {matches.map((show, index) => (
            <Option
              key={show.id}
              active={active === (noneLabel ? index + 1 : index)}
              selected={show.id === value}
              label={show.name}
              hint={show.originalName ?? undefined}
              onSelect={() => pick(show.id)}
            />
          ))}
          {matches.length === 0 && !canCreate && (
            <p className="px-2 py-1.5 text-sm text-muted-foreground">
              {t('admin.shows.pickerEmpty')}
            </p>
          )}
        </div>

        {canCreate && (
          // Сериал — основной случай (в шоу заливают сезоны), полнометражка рядом отдельной кнопкой:
          // так создание остаётся в один клик и при этом не навязывает тип.
          <div className="mt-1 flex items-center gap-1 border-t border-border pt-1">
            <button
              type="button"
              disabled={create.isPending}
              className="flex min-w-0 flex-1 items-center gap-2 rounded-sm px-2 py-1.5 text-left text-sm text-primary hover:bg-muted disabled:opacity-50"
              onClick={() => create.mutate('Series')}
            >
              <Plus className="h-3.5 w-3.5 shrink-0" />
              <span className="truncate">{t('admin.shows.pickerCreate', { name: trimmed })}</span>
            </button>
            <button
              type="button"
              disabled={create.isPending}
              className="shrink-0 rounded-sm border border-border px-1.5 py-0.5 text-xs text-muted-foreground hover:border-primary hover:text-primary disabled:opacity-50"
              onClick={() => create.mutate('Single')}
              title={t('admin.shows.pickerCreateSingleHint')}
            >
              {t('admin.shows.kinds.Single')}
            </button>
          </div>
        )}
      </PopoverContent>
    </Popover>
  )
}

function Option({
  active,
  selected,
  label,
  hint,
  onSelect,
}: Readonly<{
  active: boolean
  selected: boolean
  label: string
  hint?: string
  onSelect: () => void
}>) {
  return (
    <button
      type="button"
      data-active={active}
      className={cn(
        'flex w-full items-center gap-2 rounded-sm px-2 py-1.5 text-left text-sm hover:bg-muted',
        active && 'bg-muted',
      )}
      onClick={onSelect}
    >
      <Check className={cn('h-3.5 w-3.5 shrink-0', !selected && 'opacity-0')} />
      <span className="min-w-0 truncate">{label}</span>
      {hint && hint !== label && (
        <span className="min-w-0 shrink truncate text-xs text-muted-foreground">{hint}</span>
      )}
    </button>
  )
}
