import { useMutation, useQuery } from '@tanstack/react-query'
import { Eraser, FileJson, Plus, Wand2 } from 'lucide-react'
import { useState } from 'react'
import { useTranslation } from 'react-i18next'
import { HttpError } from '@/shared/api/client'
import { qk } from '@/shared/api/query-keys'
import type { GridLayerDto, ScheduleTemplateDto, SlotDto } from '@/shared/api/types'
import { Button } from '@/shared/ui/button'
import { Card, CardContent } from '@/shared/ui/card'
import { Input } from '@/shared/ui/input'
import { toast } from '@/shared/ui/toast-store'
import {
  copyTemplateTo,
  createChannelTemplate,
  createLayer,
  createSlot,
  deleteLayer,
  listChannels,
  resetChannelTemplate,
  toSlotBody,
  updateLayer,
  updateSlot,
} from '../api'
import { toTime } from '../lib/format'
import { GenerateGridDialog } from './GenerateGridDialog'
import { GridTransferDialog } from './GridTransferDialog'
import { LayerApplicabilityDialog } from './LayerApplicabilityDialog'
import { LayerList, ScheduleGrid } from './ScheduleGrid'
import { SlotInspector, type SlotDraft } from './SlotInspector'
import { TemplateIssues } from './TemplateIssues'
import { TemplatePreview } from './TemplatePreview'

/**
 * Вкладка «Сетка»: слои, слоты и всё, что их правит. Вынесена из экрана канала целиком со своим
 * состоянием — остальным вкладкам ни черновик слота, ни выбранный день копирования не нужны, а
 * держать их в родителе значило перерисовывать весь экран на каждое движение мыши по сетке.
 */
export function GridTab({
  channelId,
  channelName,
  template,
  templateError,
  onChanged,
  onError,
}: Readonly<{
  channelId: string
  channelName: string
  template: ScheduleTemplateDto | undefined
  templateError: unknown
  onChanged: () => void
  onError: (error: unknown) => void
}>) {
  const { t } = useTranslation()
  const [draft, setDraft] = useState<SlotDraft | null>(null)
  const [activeLayerId, setActiveLayerId] = useState<string | null>(null)
  const [viewDate, setViewDate] = useState<string>('')
  const [applicabilityLayer, setApplicabilityLayer] = useState<GridLayerDto | null>(null)
  const [generating, setGenerating] = useState(false)
  const [transferring, setTransferring] = useState(false)
  // День, который копируем, и отмеченные дни-приёмники.
  const [copySource, setCopySource] = useState<number | null>(null)
  const [copyTargets, setCopyTargets] = useState<number[]>([])
  const [copyFromChannel, setCopyFromChannel] = useState('')

  const toggleCopyTarget = (day: number, checked: boolean) =>
    setCopyTargets((current) => (checked ? [...current, day] : current.filter((d) => d !== day)))

  const { data: channels } = useQuery({ queryKey: qk.channels.all, queryFn: listChannels })

  const addLayerMutation = useMutation({
    mutationFn: () => {
      const nextPriority = Math.max(0, ...(template?.layers.map((l) => l.priority) ?? [0])) + 10
      return createLayer(template!.id, {
        name: t('admin.channels.newLayerName'),
        priority: nextPriority,
      })
    },
    onSuccess: onChanged,
    onError,
  })

  const deleteLayerMutation = useMutation({
    mutationFn: (layer: GridLayerDto) => deleteLayer(layer.id),
    onSuccess: onChanged,
    onError,
  })

  const toggleLayerMutation = useMutation({
    mutationFn: (layer: GridLayerDto) =>
      updateLayer(layer.id, {
        name: layer.name,
        priority: layer.priority,
        applicability: layer.applicability,
        isEnabled: !layer.isEnabled,
      }),
    onSuccess: onChanged,
    onError,
  })

  /**
   * Порядок слоёв задаётся перетаскиванием, а хранится приоритетом. Раздаём приоритеты с шагом 10
   * снизу вверх: шаг оставляет место, чтобы следующая вставка не переписывала весь список.
   */
  const reorderLayersMutation = useMutation({
    mutationFn: async (layerIdsTopFirst: string[]) => {
      const byId = new Map(template!.layers.map((l) => [l.id, l]))
      const total = layerIdsTopFirst.length
      await Promise.all(
        layerIdsTopFirst.map((id, index) => {
          const layer = byId.get(id)
          const priority = (total - index) * 10
          if (!layer || layer.priority === priority) return Promise.resolve()
          return updateLayer(id, {
            name: layer.name,
            priority,
            applicability: layer.applicability,
            isEnabled: layer.isEnabled,
          })
        }),
      )
    },
    onSuccess: onChanged,
    onError,
  })

  // Канал без сетки — наследство старой ротации: заводим шаблон на месте, а не пересоздаём канал.
  const createTemplateMutation = useMutation({
    mutationFn: () => createChannelTemplate(channelId),
    onSuccess: onChanged,
    onError,
  })

  const copyTemplateMutation = useMutation({
    mutationFn: (sourceChannelId: string) => copyTemplateTo(sourceChannelId, channelId),
    onSuccess: (result) => {
      setCopyFromChannel('')
      toast.success(
        t('admin.channels.templateCopied', { layers: result.layers, slots: result.slots }),
      )
      if (result.droppedBumperRefs > 0)
        toast.error(t('admin.channels.copyDroppedBumpers', { count: result.droppedBumperRefs }))
    },
    onError,
  })

  /**
   * Сброс сетки и собранного по ней эфира. Прошлое остаётся, поэтому «полностью» здесь — про
   * будущее: то, что уже показано зрителю, не переписывается.
   */
  const resetMutation = useMutation({
    mutationFn: () => resetChannelTemplate(channelId),
    onSuccess: (result) => {
      toast.success(
        t('admin.channels.resetDone', {
          slots: result.slots,
          layers: result.layers,
          entries: result.entries,
        }),
      )
      // Пустая сетка отдаёт эфир аварийной группе целиком: без предупреждения админ решит, что
      // сброс не сработал, увидев в предпросмотре те же шоу через минуту после него.
      if (template?.fallbackGroupId) toast.message(t('admin.channels.resetHint'))
      onChanged()
    },
    onError,
  })

  const moveSlotMutation = useMutation({
    mutationFn: ({
      slot,
      weekday,
      startMinutes,
    }: Readonly<{
      slot: SlotDto
      weekday: number
      startMinutes: number
    }>) => updateSlot(slot.id, { ...toSlotBody(slot), weekday, targetStart: toTime(startMinutes) }),
    onSuccess: onChanged,
    onError,
  })

  const resizeSlotMutation = useMutation({
    mutationFn: ({ slot, minutes }: Readonly<{ slot: SlotDto; minutes: number }>) =>
      updateSlot(slot.id, { ...toSlotBody(slot), targetDurationMinutes: minutes }),
    onSuccess: onChanged,
    onError,
  })

  /** Копирование дня: слоты «каждый день» не копируются — они и так есть во всех колонках. */
  const copyDayMutation = useMutation({
    mutationFn: async ({ from, to }: Readonly<{ from: number; to: number[] }>) => {
      const sources = (template?.layers ?? []).flatMap((layer) =>
        layer.slots.filter((slot) => slot.weekday === from).map((slot) => ({ layer, slot })),
      )
      for (const weekday of to)
        for (const { layer, slot } of sources)
          await createSlot(layer.id, { ...toSlotBody(slot), weekday })
    },
    onSuccess: () => {
      setCopySource(null)
      onChanged()
    },
    onError,
  })

  // Шаблон мог не загрузиться — раньше вкладка сетки просто оказывалась пустой.
  if (!template)
    return (
      <div className="flex flex-col items-start gap-3">
        <p className="text-sm text-muted-foreground">
          {templateError instanceof HttpError
            ? templateError.detail
            : t('admin.channels.noTemplate')}
        </p>
        <Button
          size="sm"
          disabled={createTemplateMutation.isPending}
          onClick={() => createTemplateMutation.mutate()}
        >
          <Plus className="h-4 w-4" /> {t('admin.channels.createTemplate')}
        </Button>
        <p className="text-xs text-muted-foreground">{t('admin.channels.createTemplateHint')}</p>
      </div>
    )

  const layerForNewSlot =
    activeLayerId ?? template.layers.find((l) => !l.isBackground)?.id ?? template.layers[0]?.id

  const openNewSlot = (weekday: number, startMinutes: number) => {
    if (!layerForNewSlot) return
    const hh = Math.floor(startMinutes / 60)
      .toString()
      .padStart(2, '0')
    const mm = (startMinutes % 60).toString().padStart(2, '0')
    setDraft({
      layerId: layerForNewSlot,
      slot: null,
      defaults: { weekday, targetStart: `${hh}:${mm}:00`, title: t('admin.channels.newSlot') },
    })
  }

  const openSlot = (slot: SlotDto) => setDraft({ layerId: slot.layerId, slot })

  return (
    <>
      <Card>
        <CardContent>
          <div className="grid gap-4 lg:grid-cols-[220px_1fr]">
            <div className="flex flex-col gap-2">
              <div className="flex items-center justify-between gap-2">
                <h3 className="text-xs font-semibold uppercase tracking-wide text-muted-foreground">
                  {t('admin.channels.layers')}
                </h3>
                <Button
                  size="sm"
                  variant="ghost"
                  onClick={() => addLayerMutation.mutate()}
                  disabled={addLayerMutation.isPending}
                >
                  <Plus className="h-4 w-4" />
                </Button>
              </div>
              <LayerList
                template={template}
                activeLayerId={layerForNewSlot ?? null}
                viewDate={viewDate || null}
                onSelect={(layer) => setActiveLayerId(layer.id)}
                onDelete={(layer) => deleteLayerMutation.mutate(layer)}
                onToggle={(layer) => toggleLayerMutation.mutate(layer)}
                onReorder={(order) => reorderLayersMutation.mutate(order)}
                onEditApplicability={setApplicabilityLayer}
              />
              <p className="text-xs text-muted-foreground">{t('admin.channels.layersHint')}</p>

              {/* Копия сетки с другого канала: группы общие, поэтому переносятся только правила. */}
              <div className="flex flex-col gap-1.5 border-t border-border pt-2">
                <span className="text-xs font-semibold uppercase tracking-wide text-muted-foreground">
                  {t('admin.channels.copyTemplate')}
                </span>
                <select
                  className="h-8 rounded-md border border-border bg-transparent px-2 text-sm"
                  value={copyFromChannel}
                  onChange={(e) => setCopyFromChannel(e.target.value)}
                >
                  <option value="">{t('admin.channels.pickSourceChannel')}</option>
                  {(channels ?? [])
                    .filter((c) => c.id !== channelId)
                    .map((c) => (
                      <option key={c.id} value={c.id}>
                        {c.name}
                      </option>
                    ))}
                </select>
                <Button
                  size="sm"
                  variant="outline"
                  disabled={!copyFromChannel || copyTemplateMutation.isPending}
                  onClick={() => {
                    // Замена своей сетки — необратимая правка, поэтому спрашиваем перед ней.
                    if (!window.confirm(t('admin.channels.copyTemplateConfirm'))) return
                    copyTemplateMutation.mutate(copyFromChannel)
                  }}
                >
                  {t('admin.channels.copyHere')}
                </Button>
                <p className="text-xs text-muted-foreground">
                  {t('admin.channels.copyTemplateHint')}
                </p>
              </div>
            </div>

            <div className="flex flex-col gap-3">
              <TemplatePreview channelId={channelId} />

              {/* Один ряд служебных элементов: проверки, дата просмотра и добавление слота. Всё,
                  что не сама сетка, живёт в одной строке — вертикаль экрана нужна сетке. */}
              <div className="flex flex-wrap items-center gap-2 text-sm">
                <TemplateIssues
                  channelId={channelId}
                  slotsById={
                    new Map(template.layers.flatMap((l) => l.slots).map((slot) => [slot.id, slot]))
                  }
                  onGoToSlot={openSlot}
                />
                <Button
                  size="sm"
                  variant="outline"
                  disabled={!layerForNewSlot}
                  onClick={() => openNewSlot(1, 20 * 60)}
                >
                  <Plus className="h-4 w-4" />
                  {t('admin.channels.newSlot')}
                </Button>
                <Button size="sm" variant="outline" onClick={() => setGenerating(true)}>
                  <Wand2 className="h-4 w-4" />
                  {t('admin.channels.generate.action')}
                </Button>
                <Button size="sm" variant="outline" onClick={() => setTransferring(true)}>
                  <FileJson className="h-4 w-4" />
                  {t('admin.channels.transfer.action')}
                </Button>
                <Button
                  size="sm"
                  variant="outline"
                  className="text-red-500 hover:text-red-400"
                  disabled={resetMutation.isPending}
                  onClick={() => {
                    // Сетку и хвост эфира назад не вернёшь — спрашиваем, как и перед заменой сетки.
                    if (!window.confirm(t('admin.channels.resetConfirm'))) return
                    resetMutation.mutate()
                  }}
                >
                  <Eraser className="h-4 w-4" />
                  {t('admin.channels.reset')}
                </Button>
                <span className="text-muted-foreground">{t('admin.channels.showForDate')}</span>
                <Input
                  type="date"
                  className="h-8 w-40"
                  value={viewDate}
                  onChange={(e) => setViewDate(e.target.value)}
                />
                {viewDate && (
                  <Button size="sm" variant="ghost" onClick={() => setViewDate('')}>
                    {t('admin.channels.allDates')}
                  </Button>
                )}
              </div>

              {/* Копирование дня: сначала выбирается источник, потом дни-приёмники. */}
              {copySource !== null && (
                <div className="flex flex-wrap items-center gap-2 rounded-md border border-border px-3 py-2 text-sm">
                  <span>
                    {t('admin.channels.copyDayFrom', {
                      day: t(`admin.channels.weekdays.${copySource}`),
                    })}
                  </span>
                  {[1, 2, 3, 4, 5, 6, 0]
                    .filter((day) => day !== copySource)
                    .map((day) => (
                      <label key={day} className="flex items-center gap-1">
                        <input
                          type="checkbox"
                          checked={copyTargets.includes(day)}
                          onChange={(e) => toggleCopyTarget(day, e.target.checked)}
                        />
                        {t(`admin.channels.weekdays.${day}`)}
                      </label>
                    ))}
                  <Button
                    size="sm"
                    disabled={copyTargets.length === 0 || copyDayMutation.isPending}
                    onClick={() => copyDayMutation.mutate({ from: copySource, to: copyTargets })}
                  >
                    {t('admin.channels.copy')}
                  </Button>
                  <Button size="sm" variant="ghost" onClick={() => setCopySource(null)}>
                    {t('common.cancel')}
                  </Button>
                </div>
              )}

              <ScheduleGrid
                template={template}
                selectedSlotId={draft?.slot?.id ?? null}
                viewDate={viewDate || null}
                onSelectSlot={openSlot}
                onAddSlot={openNewSlot}
                onMoveSlot={(slot, weekday, startMinutes) =>
                  moveSlotMutation.mutate({ slot, weekday, startMinutes })
                }
                onResizeSlot={(slot, minutes) => resizeSlotMutation.mutate({ slot, minutes })}
                onCopyDay={(weekday) => {
                  setCopySource(weekday)
                  setCopyTargets([])
                }}
              />
              {draft && (
                <SlotInspector draft={draft} onClose={() => setDraft(null)} onChanged={onChanged} />
              )}
            </div>
          </div>
        </CardContent>
      </Card>

      {transferring && (
        <GridTransferDialog
          channelId={channelId}
          channelName={channelName}
          onClose={() => setTransferring(false)}
          onImported={onChanged}
          onError={onError}
        />
      )}

      {generating && (
        <GenerateGridDialog
          channelId={channelId}
          onClose={() => setGenerating(false)}
          onGenerated={onChanged}
          onError={onError}
        />
      )}

      {applicabilityLayer && (
        <LayerApplicabilityDialog
          layer={applicabilityLayer}
          onClose={() => setApplicabilityLayer(null)}
          onChanged={onChanged}
          onError={onError}
        />
      )}
    </>
  )
}
