import { Anchor, CalendarRange, Copy, GripVertical, Plus, Repeat } from 'lucide-react'
import { useState } from 'react'
import { useTranslation } from 'react-i18next'
import type { GridLayerDto, ScheduleTemplateDto, SlotDto } from '@/shared/api/types'
import { Button } from '@/shared/ui/button'
import { cn } from '@/shared/lib/cn'
import { coversDate, isEmpty } from '../lib/applicability'

const HOUR_HEIGHT = 44

/** Шаг сетки при перетаскивании и растягивании — минуты. */
const SNAP_MINUTES = 15

const snap = (minutes: number) => Math.round(minutes / SNAP_MINUTES) * SNAP_MINUTES
const WEEKDAYS = [1, 2, 3, 4, 5, 6, 0]

/** Цвет блока — по дейпарту: сетка должна читаться одним взглядом, без легенды. */
const DAYPART_CLASS: Record<string, string> = {
  Morning: 'bg-amber-500/20 border-amber-500/40',
  Day: 'bg-sky-500/20 border-sky-500/40',
  Prime: 'bg-violet-500/25 border-violet-500/50',
  Night: 'bg-slate-500/20 border-slate-500/40',
}

function minutesOf(time: string): number {
  const [h, m] = time.split(':')
  return Number(h) * 60 + Number(m)
}

/**
 * Смещение слота от начала вещательных суток. Ночной блок (00:00–06:00 при старте суток в 06:00)
 * принадлежит предыдущему дню, поэтому его смещение больше суточного, а не отрицательное.
 */
function offsetInDay(slotStart: string, dayStart: string): number {
  const diff = minutesOf(slotStart) - minutesOf(dayStart)
  return diff >= 0 ? diff : diff + 24 * 60
}

/** Слоты, попадающие в колонку дня: слот без дня недели идёт каждый день. */
function slotsOfDay(layers: GridLayerDto[], weekday: number) {
  return layers
    .filter((layer) => layer.isEnabled)
    .flatMap((layer) =>
      layer.slots
        .filter((slot) => slot.weekday === null || slot.weekday === weekday)
        .map((slot) => ({ slot, layer })),
    )
}

export function ScheduleGrid({
  template,
  selectedSlotId,
  viewDate,
  onSelectSlot,
  onAddSlot,
  onMoveSlot,
  onResizeSlot,
  onCopyDay,
}: Readonly<{
  template: ScheduleTemplateDto
  selectedSlotId: string | null
  /** Дата, на которую смотрим сетку («показать 25 декабря»); null — все слои разом. */
  viewDate: string | null
  onSelectSlot: (slot: SlotDto) => void
  onAddSlot: (weekday: number, startMinutes: number) => void
  /** Перенос слота: новое время старта и (для слота с днём недели) новый день. */
  onMoveSlot: (slot: SlotDto, weekday: number, startMinutes: number) => void
  onResizeSlot: (slot: SlotDto, durationMinutes: number) => void
  onCopyDay: (fromWeekday: number) => void
}>) {
  const { t } = useTranslation()
  const dayStart = template.dayStartTime.slice(0, 5)
  const dayStartMinutes = minutesOf(dayStart)

  // Подписи часов идут от начала вещательных суток, а не от полуночи.
  const hours = Array.from({ length: 24 }, (_, i) => (dayStartMinutes / 60 + i) % 24)

  // На выбранную дату показываем только те слои, которые в этот день действуют, — иначе сетка
  // «на 25 декабря» показывала бы и обычный день, и новогодний одновременно.
  const day = viewDate ? parseIsoDate(viewDate) : null
  const applicable = day
    ? template.layers.filter((layer) => coversDate(layer.applicability, day))
    : template.layers

  // Слои отсортированы по убыванию приоритета: слот, лежащий под более приоритетным, штрихуем.
  const ordered = [...applicable].sort((a, b) => b.priority - a.priority)
  const highlightWeekday = day?.getDay() ?? null

  const [dragged, setDragged] = useState<SlotDto | null>(null)
  const [resizing, setResizing] = useState<{ slot: SlotDto; minutes: number } | null>(null)

  /** Позиция курсора в колонке дня — минуты суток, округлённые до шага сетки. */
  const minutesAt = (clientY: number, column: HTMLElement) => {
    const rect = column.getBoundingClientRect()
    const offset = Math.max(0, Math.min(rect.height, clientY - rect.top))
    const fromDayStart = snap((offset / HOUR_HEIGHT) * 60)
    return (dayStartMinutes + fromDayStart) % (24 * 60)
  }

  const drop = (event: React.DragEvent<HTMLDivElement>, weekday: number) => {
    event.preventDefault()
    if (!dragged) return
    // Слот «каждый день» при переносе таким и остаётся: молча превратить его в слот одного дня
    // значило бы убрать его сразу из шести колонок.
    onMoveSlot(dragged, dragged.weekday ?? weekday, minutesAt(event.clientY, event.currentTarget))
    setDragged(null)
  }

  /** Растягивание за нижний край: пока тянем — видно новую высоту, отпустили — сохраняем. */
  const startResize = (event: React.MouseEvent, slot: SlotDto, column: HTMLElement) => {
    event.preventDefault()
    event.stopPropagation()
    const from = offsetInDay(slot.targetStart, dayStart)

    const move = (moveEvent: MouseEvent) => {
      const rect = column.getBoundingClientRect()
      const offset = Math.max(0, Math.min(rect.height, moveEvent.clientY - rect.top))
      const end = snap((offset / HOUR_HEIGHT) * 60)
      setResizing({ slot, minutes: Math.max(SNAP_MINUTES, end - from) })
    }
    const up = () => {
      window.removeEventListener('mousemove', move)
      window.removeEventListener('mouseup', up)
      setResizing((current) => {
        if (current && current.minutes !== slot.targetDurationMinutes)
          onResizeSlot(slot, current.minutes)
        return null
      })
    }
    window.addEventListener('mousemove', move)
    window.addEventListener('mouseup', up)
  }

  const isCovered = (slot: SlotDto, layer: GridLayerDto, weekday: number) => {
    const from = offsetInDay(slot.targetStart, dayStart)
    const to = from + slot.targetDurationMinutes
    return ordered
      .filter((other) => other.isEnabled && other.priority > layer.priority)
      .some((other) =>
        other.slots
          .filter((s) => s.weekday === null || s.weekday === weekday)
          .some((s) => {
            const otherFrom = offsetInDay(s.targetStart, dayStart)
            return from < otherFrom + s.targetDurationMinutes && otherFrom < to
          }),
      )
  }

  return (
    <div className="crt-panel overflow-x-auto rounded-md">
      <div className="min-w-[720px]">
        <div className="grid grid-cols-[56px_repeat(7,1fr)] border-b border-border text-xs text-muted-foreground">
          <div className="px-2 py-1">{dayStart}</div>
          {WEEKDAYS.map((weekday) => (
            <div
              key={weekday}
              className={cn(
                'flex items-center justify-center gap-1 px-2 py-1 font-medium',
                highlightWeekday === weekday && 'text-primary',
              )}
            >
              {t(`admin.channels.weekdays.${weekday}`)}
              <button
                type="button"
                title={t('admin.channels.copyDay')}
                className="opacity-40 hover:opacity-100"
                onClick={() => onCopyDay(weekday)}
              >
                <Copy className="h-3 w-3" />
              </button>
            </div>
          ))}
        </div>

        <div className="grid grid-cols-[56px_repeat(7,1fr)]">
          <div>
            {hours.map((hour) => (
              <div
                key={hour}
                className="border-b border-border/40 px-2 text-[11px] text-muted-foreground"
                style={{ height: HOUR_HEIGHT }}
              >
                {hour.toString().padStart(2, '0')}:00
              </div>
            ))}
          </div>

          {WEEKDAYS.map((weekday) => (
            <div
              key={weekday}
              className={cn(
                'relative border-l border-border',
                highlightWeekday === weekday && 'bg-primary/5',
                dragged && 'bg-primary/10',
              )}
              style={{ height: HOUR_HEIGHT * 24 }}
              onDragOver={(e) => dragged && e.preventDefault()}
              onDrop={(e) => drop(e, weekday)}
            >
              {hours.map((hour, index) => (
                <button
                  key={hour}
                  type="button"
                  title={t('admin.channels.addSlotHere')}
                  className="group absolute inset-x-0 border-b border-border/40 hover:bg-muted/30"
                  style={{ top: HOUR_HEIGHT * index, height: HOUR_HEIGHT }}
                  onClick={() => onAddSlot(weekday, (dayStartMinutes + index * 60) % (24 * 60))}
                >
                  <Plus className="mx-auto h-3 w-3 opacity-0 group-hover:opacity-40" />
                </button>
              ))}

              {slotsOfDay(ordered, weekday).map(({ slot, layer }) => {
                const from = offsetInDay(slot.targetStart, dayStart)
                const covered = isCovered(slot, layer, weekday)
                const minutes =
                  resizing?.slot.id === slot.id ? resizing.minutes : slot.targetDurationMinutes
                return (
                  // Кнопка, а не div: слот выбирается кликом, и с клавиатуры это должно работать
                  // тоже. Перетаскивание на кнопке сохраняется — draggable к роли не привязан.
                  <button
                    key={`${slot.id}-${weekday}`}
                    type="button"
                    draggable
                    onDragStart={() => setDragged(slot)}
                    onDragEnd={() => setDragged(null)}
                    onClick={() => onSelectSlot(slot)}
                    className={cn(
                      'absolute inset-x-1 cursor-grab overflow-hidden rounded border px-1.5 py-0.5 text-left text-[11px] leading-tight',
                      DAYPART_CLASS[slot.daypart] ?? DAYPART_CLASS.Day,
                      selectedSlotId === slot.id && 'ring-2 ring-primary',
                      dragged?.id === slot.id && 'opacity-50',
                      // Перекрытый слот виден, но приглушён: он не сыграет, пока лежит под старшим слоем.
                      covered &&
                        'opacity-40 [background-image:repeating-linear-gradient(45deg,transparent,transparent_4px,rgba(0,0,0,.15)_4px,rgba(0,0,0,.15)_8px)]',
                    )}
                    style={{
                      top: (from / 60) * HOUR_HEIGHT,
                      height: Math.max(16, (minutes / 60) * HOUR_HEIGHT - 2),
                    }}
                  >
                    <span className="flex items-center gap-1 font-medium">
                      {slot.isAnchor && <Anchor className="h-3 w-3 shrink-0" />}
                      {slot.targetStart.slice(0, 5)}
                      {slot.weekday === null && <Repeat className="h-3 w-3 shrink-0 opacity-60" />}
                    </span>
                    <span className="block truncate">{slot.title}</span>
                    {/* Ручка растягивания — исключительно мышиная: role="presentation" на элементе
                        с обработчиком противоречив (роль говорит «меня нет», а элемент реагирует),
                        поэтому прячем её от вспомогательных технологий. Длительность слота
                        правится с клавиатуры в инспекторе — доступный путь есть. */}
                    <span
                      aria-hidden="true"
                      title={t('admin.channels.resizeSlot')}
                      className="absolute inset-x-0 bottom-0 h-1.5 cursor-ns-resize hover:bg-primary/40"
                      onMouseDown={(e) =>
                        startResize(e, slot, e.currentTarget.parentElement!.parentElement!)
                      }
                    />
                  </button>
                )
              })}
            </div>
          ))}
        </div>
      </div>
    </div>
  )
}

/**
 * Панель слоёв: видимость, приоритет (перетаскиванием), применимость и выбор редактируемого.
 * Выше в списке — приоритетнее; фоновый слой всегда внизу и не двигается.
 */
export function LayerList({
  template,
  activeLayerId,
  viewDate,
  onSelect,
  onDelete,
  onToggle,
  onReorder,
  onEditApplicability,
}: Readonly<{
  template: ScheduleTemplateDto
  activeLayerId: string | null
  viewDate: string | null
  onSelect: (layer: GridLayerDto) => void
  onDelete: (layer: GridLayerDto) => void
  onToggle: (layer: GridLayerDto) => void
  onReorder: (layerIdsTopFirst: string[]) => void
  onEditApplicability: (layer: GridLayerDto) => void
}>) {
  const { t } = useTranslation()
  const [dragged, setDragged] = useState<string | null>(null)

  const ordered = [...template.layers].sort((a, b) => b.priority - a.priority)
  const day = viewDate ? parseIsoDate(viewDate) : null

  const dropOn = (targetId: string) => {
    if (!dragged || dragged === targetId) return
    const movable = ordered.filter((l) => !l.isBackground).map((l) => l.id)
    const order = movable.filter((id) => id !== dragged)
    const at = order.indexOf(targetId)
    // Бросок на фоновый слой означает «в самый низ»: он в порядке не участвует.
    order.splice(at === -1 ? order.length : at, 0, dragged)
    setDragged(null)
    onReorder(order)
  }

  return (
    <ul className="divide-y divide-border text-sm">
      {ordered.map((layer) => {
        const inactiveToday = day !== null && !coversDate(layer.applicability, day)
        return (
          <li
            key={layer.id}
            draggable={!layer.isBackground}
            onDragStart={() => setDragged(layer.id)}
            onDragOver={(e) => e.preventDefault()}
            onDrop={() => dropOn(layer.id)}
            className={cn('flex items-center gap-1.5 py-1.5', inactiveToday && 'opacity-40')}
          >
            {layer.isBackground ? (
              <span className="w-4 shrink-0" />
            ) : (
              <GripVertical className="h-4 w-4 shrink-0 cursor-grab text-muted-foreground" />
            )}
            <input
              type="checkbox"
              className="shrink-0"
              title={t('admin.channels.layerVisible')}
              checked={layer.isEnabled}
              onChange={() => onToggle(layer)}
            />
            <button
              type="button"
              className={cn(
                'min-w-0 flex-1 truncate text-left',
                activeLayerId === layer.id && 'text-primary',
              )}
              onClick={() => onSelect(layer)}
            >
              {layer.name}
            </button>
            <span className="shrink-0 text-xs text-muted-foreground">
              {layer.isBackground ? t('admin.channels.background') : layer.slots.length}
            </span>
            {!layer.isBackground && (
              <>
                <Button
                  size="sm"
                  variant="ghost"
                  title={t('admin.channels.layerApplicability')}
                  onClick={() => onEditApplicability(layer)}
                >
                  <CalendarRange
                    className={cn(
                      'h-4 w-4',
                      isEmpty(layer.applicability) ? 'text-muted-foreground' : 'text-primary',
                    )}
                  />
                </Button>
                <Button size="sm" variant="ghost" onClick={() => onDelete(layer)}>
                  ×
                </Button>
              </>
            )}
          </li>
        )
      })}
    </ul>
  )
}

/** «2026-12-25» → локальная дата. `new Date(iso)` разобрал бы её как UTC и сместил день. */
function parseIsoDate(iso: string): Date {
  const [year, month, day] = iso.split('-').map(Number)
  return new Date(year, month - 1, day)
}
