"use client";

import { useState, useEffect, useRef, useCallback, useMemo } from "react";
import { SectionEditor } from "../_components/SectionEditor";
import { InputField, SelectField, TimeRangeField, ToggleField } from "../_components/FormField";
import { Plus, X, Trash2 } from "lucide-react";
import { adminFetch } from "@/lib/csrf";
import type { ScheduleLocation, ScheduleDay, ScheduleClass } from "@/types/content";

interface ScheduleData {
  title: string;
  locations: ScheduleLocation[];
}

const DAYS = [
  { day: "Понедельник", dayShort: "ПН" },
  { day: "Вторник", dayShort: "ВТ" },
  { day: "Среда", dayShort: "СР" },
  { day: "Четверг", dayShort: "ЧТ" },
  { day: "Пятница", dayShort: "ПТ" },
  { day: "Суббота", dayShort: "СБ" },
  { day: "Воскресенье", dayShort: "ВС" },
];

const DAY_ORDER: Record<string, number> = Object.fromEntries(
  DAYS.map((d, i) => [d.day, i])
);

const LEVELS = [
  { value: "", label: "Без уровня" },
  { value: "Начинающий/Без опыта", label: "Начинающий/Без опыта" },
  { value: "Продвинутый", label: "Продвинутый" },
];

const GROUP_PALETTE = [
  "bg-rose-500/80 border-rose-400",
  "bg-orange-500/80 border-orange-400",
  "bg-amber-500/80 border-amber-400",
  "bg-yellow-400/80 border-yellow-300",
  "bg-lime-500/80 border-lime-400",
  "bg-emerald-500/80 border-emerald-400",
  "bg-teal-500/80 border-teal-400",
  "bg-cyan-500/80 border-cyan-400",
  "bg-sky-500/80 border-sky-400",
  "bg-blue-500/80 border-blue-400",
  "bg-indigo-500/80 border-indigo-400",
  "bg-violet-500/80 border-violet-400",
  "bg-purple-500/80 border-purple-400",
  "bg-fuchsia-500/80 border-fuchsia-400",
  "bg-pink-500/80 border-pink-400",
  "bg-red-500/80 border-red-400",
];

/** Build a unique group key (trainer + type) */
function groupKey(cls: ScheduleClass): string {
  return `${cls.trainer}|${cls.type}`;
}

/** Assign a color to each unique group across all days */
function buildGroupColors(days: ScheduleDay[]): Record<string, string> {
  const seen = new Set<string>();
  const keys: string[] = [];
  for (const day of days) {
    for (const cls of day.classes) {
      const k = groupKey(cls);
      if (!seen.has(k)) {
        seen.add(k);
        keys.push(k);
      }
    }
  }
  const result: Record<string, string> = {};
  keys.forEach((k, i) => {
    result[k] = GROUP_PALETTE[i % GROUP_PALETTE.length];
  });
  return result;
}

// Calendar config
const HOUR_START = 9;
const HOUR_END = 23;
const HOUR_HEIGHT = 60; // px per hour
const TOTAL_HOURS = HOUR_END - HOUR_START;
const SNAP_MINUTES = 15;

function parseTime(timeStr: string): { h: number; m: number } | null {
  const [h, m] = (timeStr || "").split(":").map(Number);
  if (isNaN(h) || isNaN(m)) return null;
  return { h, m };
}

function timeToMinutes(timeStr: string): number {
  const t = parseTime(timeStr);
  if (!t) return 0;
  return t.h * 60 + t.m;
}

function minutesToY(minutes: number): number {
  return ((minutes - HOUR_START * 60) / 60) * HOUR_HEIGHT;
}

function yToMinutes(y: number): number {
  return Math.round((y / HOUR_HEIGHT) * 60 + HOUR_START * 60);
}

function formatMinutes(minutes: number): string {
  const h = Math.floor(minutes / 60);
  const m = minutes % 60;
  return `${String(h).padStart(2, "0")}:${String(m).padStart(2, "0")}`;
}

function sortDaysByWeekday(days: ScheduleDay[]): ScheduleDay[] {
  return [...days].sort((a, b) => (DAY_ORDER[a.day] ?? 99) - (DAY_ORDER[b.day] ?? 99));
}

/** Check if two time ranges overlap */
function hasOverlap(a: ScheduleClass, b: ScheduleClass): boolean {
  const [aStart, aEnd] = a.time.split("–").map((s) => timeToMinutes(s.trim()));
  const [bStart, bEnd] = b.time.split("–").map((s) => timeToMinutes(s.trim()));
  if (!aStart || !aEnd || !bStart || !bEnd) return false;
  return aStart < bEnd && bStart < aEnd;
}

/** Get all overlapping indices for a given class in the list */
function getOverlaps(classes: ScheduleClass[], index: number): boolean {
  const cls = classes[index];
  for (let i = 0; i < classes.length; i++) {
    if (i === index) continue;
    if (hasOverlap(cls, classes[i])) return true;
  }
  return false;
}

// ---------- Drag state ----------
interface DragState {
  sourceDayIndex: number;
  classIndex: number;
  /** offset from top of block where user grabbed */
  grabOffsetY: number;
  /** duration in minutes (preserved during drag) */
  durationMin: number;
  /** current preview: snapped start minute */
  previewStartMin: number;
  /** current preview: target day index */
  previewDayIndex: number;
  /** did the pointer actually move? (to distinguish click from drag) */
  moved: boolean;
}

// ---------- Class Block on Calendar ----------
function ClassBlock({
  cls,
  index,
  isOverlapping,
  isDragging,
  groupColors,
  onClick,
  onDragStart,
}: {
  cls: ScheduleClass;
  index: number;
  isOverlapping: boolean;
  isDragging: boolean;
  groupColors: Record<string, string>;
  onClick: () => void;
  onDragStart: (e: React.MouseEvent) => void;
}) {
  const parts = cls.time.split("–");
  const startMin = timeToMinutes(parts[0]?.trim() || "");
  const endMin = timeToMinutes(parts[1]?.trim() || "");

  if (!startMin || !endMin || endMin <= startMin) return null;

  const top = minutesToY(startMin);
  const height = Math.max(((endMin - startMin) / 60) * HOUR_HEIGHT, 20);
  const colors = groupColors[groupKey(cls)] ?? "bg-neutral-600/80 border-neutral-500";

  return (
    <div
      data-class-block
      onMouseDown={onDragStart}
      onClick={(e) => {
        e.stopPropagation();
        onClick();
      }}
      style={{
        top: `${top}px`,
        height: `${height}px`,
        ...(isOverlapping
          ? { backgroundImage: "repeating-linear-gradient(135deg, transparent, transparent 4px, rgba(239,68,68,0.35) 4px, rgba(239,68,68,0.35) 8px)" }
          : {}),
      }}
      className={`absolute left-1 right-1 rounded-md border-l-3 px-2 py-0.5 text-left text-xs text-white cursor-grab active:cursor-grabbing overflow-hidden select-none ${colors} ${
        isOverlapping ? "ring-2 ring-red-500 ring-offset-1 ring-offset-neutral-900" : ""
      } ${isDragging ? "opacity-30" : "hover:opacity-90"}`}
      title={`${cls.time}\n${cls.type}\n${cls.trainer}${cls.level ? ` (${cls.level})` : ""}`}
    >
      <div className="font-semibold truncate leading-tight">
        {parts[0]?.trim()}–{parts[1]?.trim()}
      </div>
      {height > 30 && (
        <div className="truncate text-white/80 leading-tight">{cls.type}</div>
      )}
      {height > 48 && (
        <div className="truncate text-white/70 leading-tight">{cls.trainer}</div>
      )}
      {isOverlapping && (
        <div className="text-red-200 font-bold leading-tight">⚠ Пересечение</div>
      )}
    </div>
  );
}

// ---------- Edit Modal ----------
/** Same group = matching groupId, or fallback to trainer + type for legacy data */
function isSameGroup(a: ScheduleClass, b: ScheduleClass): boolean {
  if (a.groupId && b.groupId) return a.groupId === b.groupId;
  return a.trainer === b.trainer && a.type === b.type;
}

function generateGroupId(): string {
  return `g_${Date.now()}_${Math.random().toString(36).slice(2, 7)}`;
}

function ClassModal({
  cls,
  trainers,
  classTypes,
  onSave,
  onDelete,
  onClose,
  allDays,
  currentDay,
}: {
  cls: ScheduleClass;
  trainers: string[];
  classTypes: string[];
  onSave: (cls: ScheduleClass, dayTimes: Record<string, string>) => void;
  onDelete?: () => void;
  onClose: () => void;
  allDays: ScheduleDay[];
  currentDay: string;
}) {
  const [draft, setDraft] = useState<ScheduleClass>(cls);
  const trainerOptions = trainers.map((t) => ({ value: t, label: t }));
  const typeOptions = classTypes.map((t) => ({ value: t, label: t }));
  const isNew = !onDelete;

  // For edit mode: build per-day times from existing group entries
  const initialDayTimes = useMemo(() => {
    if (isNew) return { [currentDay]: cls.time };
    const times: Record<string, string> = {};
    for (const day of allDays) {
      const match = day.classes.find((c) => isSameGroup(c, cls));
      if (match) times[day.day] = match.time;
    }
    return times;
  }, [allDays, cls, isNew, currentDay]);

  const [dayTimes, setDayTimes] = useState<Record<string, string>>(initialDayTimes);

  // "Same time for all days" — default true if all existing times match
  const allTimesMatch = useMemo(() => {
    const vals = Object.values(initialDayTimes);
    return vals.length <= 1 || vals.every((t) => t === vals[0]);
  }, [initialDayTimes]);
  const [sameTime, setSameTime] = useState(allTimesMatch);

  const selectedDays = new Set(Object.keys(dayTimes));

  function toggleDay(day: string) {
    setDayTimes((prev) => {
      // Must keep at least one day
      if (day in prev && Object.keys(prev).length <= 1) return prev;
      if (day in prev) {
        const next = { ...prev };
        delete next[day];
        return next;
      }
      // New day gets time from current day or first existing
      const refTime = sameTime
        ? (Object.values(prev)[0] || cls.time)
        : (prev[currentDay] || cls.time);
      return { ...prev, [day]: refTime };
    });
  }

  function updateDayTime(day: string, time: string) {
    if (sameTime) {
      // Update all days at once
      setDayTimes((prev) => {
        const next: Record<string, string> = {};
        for (const d of Object.keys(prev)) next[d] = time;
        return next;
      });
    } else {
      setDayTimes((prev) => ({ ...prev, [day]: time }));
    }
  }

  function updateSharedTime(time: string) {
    setDraft({ ...draft, time });
    setDayTimes((prev) => {
      const next: Record<string, string> = {};
      for (const d of Object.keys(prev)) next[d] = time;
      return next;
    });
  }

  const [touched, setTouched] = useState(false);

  // Validation
  const errors = useMemo(() => {
    const errs: string[] = [];
    if (!draft.trainer) errs.push("Выберите тренера");
    if (!draft.type) errs.push("Выберите тип занятия");
    const times = Object.values(dayTimes);
    for (const t of times) {
      if (!t || !t.includes("–")) {
        errs.push("Укажите время");
        break;
      }
      const [s, e] = t.split("–").map((p) => timeToMinutes(p.trim()));
      if (!s || !e) {
        errs.push("Неверный формат времени");
        break;
      }
      if (e <= s) {
        errs.push("Время окончания должно быть позже начала");
        break;
      }
    }
    return errs;
  }, [draft.trainer, draft.type, dayTimes]);

  const isValid = errors.length === 0;

  // Check for time overlaps on each selected day
  const overlaps = useMemo(() => {
    const result: { day: string; dayShort: string; conflicting: string[] }[] = [];
    for (const [dayName, time] of Object.entries(dayTimes)) {
      const dayData = allDays.find((d) => d.day === dayName);
      if (!dayData || !time) continue;
      const dayShort = DAYS.find((d) => d.day === dayName)?.dayShort || dayName;
      // Build a temporary class to check overlap
      const tempCls: ScheduleClass = { ...draft, time };
      const conflicts: string[] = [];
      for (const existing of dayData.classes) {
        // Skip the class being edited (same group)
        if (!isNew && isSameGroup(existing, cls)) continue;
        if (hasOverlap(tempCls, existing)) {
          conflicts.push(`${existing.time} ${existing.type} (${existing.trainer})`);
        }
      }
      if (conflicts.length > 0) {
        result.push({ day: dayName, dayShort, conflicting: conflicts });
      }
    }
    return result;
  }, [dayTimes, allDays, draft, cls, isNew]);

  return (
    <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/60" onClick={onClose}>
      <div
        className="w-full max-w-md rounded-xl border border-white/10 bg-neutral-900 p-6 shadow-2xl"
        onClick={(e) => e.stopPropagation()}
      >
        <div className="flex items-center justify-between mb-4">
          <h3 className="text-lg font-bold text-white">
            {isNew ? "Новое занятие" : "Редактировать занятие"}
          </h3>
          <button type="button" onClick={onClose} className="text-neutral-400 hover:text-white">
            <X size={20} />
          </button>
        </div>

        <div className="space-y-4">
          {/* Day selector */}
          {allDays.length > 1 && (
            <div>
              <label className="block text-sm text-neutral-400 mb-2">Дни</label>

              {/* Day toggle buttons */}
              <div className="flex flex-wrap gap-1.5">
                {allDays.map((d) => {
                  const active = selectedDays.has(d.day);
                  return (
                    <button
                      key={d.day}
                      type="button"
                      onClick={() => toggleDay(d.day)}
                      className={`rounded-lg px-3 py-1.5 text-xs font-medium transition-all ${
                        active
                          ? "bg-gold/20 text-gold border border-gold/40"
                          : "border border-white/10 text-neutral-500 hover:text-white hover:border-white/20"
                      }`}
                    >
                      {d.dayShort}
                    </button>
                  );
                })}
              </div>

            </div>
          )}

          {/* Same time checkbox + time fields */}
          {selectedDays.size > 1 && (
            <button
              type="button"
              onClick={() => {
                const checked = !sameTime;
                setSameTime(checked);
                if (checked) {
                  const refTime = dayTimes[currentDay] || Object.values(dayTimes)[0] || cls.time;
                  setDayTimes((prev) => {
                    const next: Record<string, string> = {};
                    for (const d of Object.keys(prev)) next[d] = refTime;
                    return next;
                  });
                }
              }}
              className="flex items-center gap-2 text-sm text-neutral-300 select-none"
            >
              <span className={`inline-flex items-center justify-center w-4 h-4 rounded border transition-colors ${
                sameTime ? "bg-gold border-gold" : "border-white/20 bg-neutral-800"
              }`}>
                {sameTime && <span className="text-black text-xs font-bold leading-none">✓</span>}
              </span>
              Одинаковое время
            </button>
          )}

          {sameTime || selectedDays.size <= 1 ? (
            <TimeRangeField
              label="Время"
              value={Object.values(dayTimes)[0] || draft.time}
              onChange={updateSharedTime}
            />
          ) : (
            <div className="space-y-1.5">
              <label className="block text-sm text-neutral-400">Время по дням</label>
              {allDays.filter((d) => selectedDays.has(d.day)).map((d) => (
                <div key={d.day} className="flex items-center gap-2">
                  <span className="shrink-0 text-xs font-medium text-neutral-400 min-w-[28px]">
                    {d.dayShort}
                  </span>
                  <div className="flex-1">
                    <TimeRangeField
                      label=""
                      value={dayTimes[d.day] || ""}
                      onChange={(v) => updateDayTime(d.day, v)}
                    />
                  </div>
                </div>
              ))}
            </div>
          )}

          <SelectField
            label="Тренер"
            value={draft.trainer}
            onChange={(v) => setDraft({ ...draft, trainer: v })}
            options={trainerOptions}
            placeholder="Выберите тренера"
          />
          <SelectField
            label="Тип"
            value={draft.type}
            onChange={(v) => setDraft({ ...draft, type: v })}
            options={typeOptions}
            placeholder="Выберите тип"
          />
          <SelectField
            label="Уровень"
            value={draft.level || ""}
            onChange={(v) => setDraft({ ...draft, level: v || undefined })}
            options={LEVELS}
          />
          <div className="flex gap-6">
            <ToggleField
              label="Есть места"
              checked={draft.hasSlots ?? false}
              onChange={(v) => setDraft({ ...draft, hasSlots: v })}
            />
            <ToggleField
              label="Набор открыт"
              checked={draft.recruiting ?? false}
              onChange={(v) => setDraft({ ...draft, recruiting: v })}
            />
          </div>
        </div>

        {/* Overlap warning */}
        {overlaps.length > 0 && (
          <div className="mt-4 rounded-lg border border-amber-500/30 bg-amber-500/10 px-3 py-2.5 text-sm">
            <div className="font-medium text-amber-400 mb-1">⚠ Пересечение времени</div>
            {overlaps.map((o) => (
              <div key={o.day} className="text-amber-300/80 text-xs">
                <span className="font-medium">{o.dayShort}:</span>{" "}
                {o.conflicting.join(", ")}
              </div>
            ))}
          </div>
        )}

        {/* Validation errors */}
        {touched && !isValid && (
          <div className="mt-4 rounded-lg border border-red-500/30 bg-red-500/10 px-3 py-2 text-xs text-red-400">
            {errors.map((e, i) => <div key={i}>{e}</div>)}
          </div>
        )}

        <div className="mt-6 flex items-center gap-2">
          <button
            type="button"
            onClick={() => {
              setTouched(true);
              if (!isValid) return;
              onSave(draft, dayTimes);
              onClose();
            }}
            className={`flex-1 rounded-lg px-4 py-2.5 text-sm font-medium transition-opacity ${
              touched && !isValid
                ? "bg-neutral-700 text-neutral-400 cursor-not-allowed"
                : "bg-gold text-black hover:opacity-90"
            }`}
          >
            Сохранить
          </button>
          {onDelete && (
            <button
              type="button"
              onClick={() => {
                onDelete();
                onClose();
              }}
              className="rounded-lg border border-red-500/30 px-4 py-2.5 text-sm text-red-400 hover:bg-red-500/10 transition-colors flex items-center gap-1.5"
            >
              <Trash2 size={14} />
              Удалить
            </button>
          )}
        </div>
      </div>
    </div>
  );
}

// ---------- Calendar Grid for one location ----------
function CalendarGrid({
  location,
  trainers,
  addresses,
  classTypes,
  onChange,
}: {
  location: ScheduleLocation;
  trainers: string[];
  addresses: string[];
  classTypes: string[];
  onChange: (loc: ScheduleLocation) => void;
}) {
  const [editingClass, setEditingClass] = useState<{
    dayIndex: number;
    classIndex: number;
  } | null>(null);
  const [newClass, setNewClass] = useState<{
    dayIndex: number;
    cls: ScheduleClass;
  } | null>(null);

  // Auto-assign groupId to legacy classes that don't have one
  useEffect(() => {
    const needsMigration = location.days.some((d) =>
      d.classes.some((c) => !c.groupId)
    );
    if (!needsMigration) return;

    // Collect all legacy classes per trainer+type key
    const buckets = new Map<string, { dayIdx: number; clsIdx: number; time: string }[]>();
    location.days.forEach((d, di) => {
      d.classes.forEach((c, ci) => {
        if (c.groupId) return;
        const key = `${c.trainer}|${c.type}`;
        const bucket = buckets.get(key);
        if (bucket) bucket.push({ dayIdx: di, clsIdx: ci, time: c.time });
        else buckets.set(key, [{ dayIdx: di, clsIdx: ci, time: c.time }]);
      });
    });

    // For each bucket, figure out how many distinct groups there are.
    // If any day has N entries with same trainer+type, there are at least N groups.
    // Assign groups by matching closest times across days.
    const assignedIds = new Map<string, string>(); // "dayIdx:clsIdx" -> groupId

    for (const entries of buckets.values()) {
      // Count max entries per day
      const perDay = new Map<number, typeof entries>();
      for (const e of entries) {
        const arr = perDay.get(e.dayIdx);
        if (arr) arr.push(e);
        else perDay.set(e.dayIdx, [e]);
      }
      const maxPerDay = Math.max(...[...perDay.values()].map((a) => a.length));

      if (maxPerDay <= 1) {
        // Simple: one group
        const gid = generateGroupId();
        for (const e of entries) assignedIds.set(`${e.dayIdx}:${e.clsIdx}`, gid);
      } else {
        // Find the day with most entries — those define the seed groups
        const busiestDay = [...perDay.entries()].sort((a, b) => b[1].length - a[1].length)[0];
        const seeds = busiestDay[1].map((e) => ({
          gid: generateGroupId(),
          time: timeToMinutes(e.time.split("–")[0]?.trim() || ""),
          entry: e,
        }));
        // Assign seeds
        for (const s of seeds) assignedIds.set(`${s.entry.dayIdx}:${s.entry.clsIdx}`, s.gid);

        // Assign remaining entries to closest seed by start time
        for (const e of entries) {
          const k = `${e.dayIdx}:${e.clsIdx}`;
          if (assignedIds.has(k)) continue;
          const eMin = timeToMinutes(e.time.split("–")[0]?.trim() || "");
          let bestSeed = seeds[0];
          let bestDiff = Infinity;
          for (const s of seeds) {
            const diff = Math.abs(eMin - s.time);
            if (diff < bestDiff) { bestDiff = diff; bestSeed = s; }
          }
          assignedIds.set(k, bestSeed.gid);
        }
      }
    }

    // Apply groupIds
    const migratedDays = location.days.map((d, di) => ({
      ...d,
      classes: d.classes.map((c, ci) => {
        if (c.groupId) return c;
        return { ...c, groupId: assignedIds.get(`${di}:${ci}`) ?? generateGroupId() };
      }),
    }));
    onChange({ ...location, days: migratedDays });
  // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  // Compute group-based colors for calendar blocks
  const sortedDaysForColors = sortDaysByWeekday(location.days);
  const groupColors = useMemo(
    () => buildGroupColors(sortedDaysForColors),
    // eslint-disable-next-line react-hooks/exhaustive-deps
    [JSON.stringify(sortedDaysForColors.map((d) => d.classes.map((c) => groupKey(c))))]
  );

  // Hover highlight state
  const [hover, setHover] = useState<{ dayIndex: number; startMin: number } | null>(null);

  // Drag state
  const [drag, setDrag] = useState<DragState | null>(null);
  const dragRef = useRef<DragState | null>(null);
  const justDraggedRef = useRef(false);
  const columnRefs = useRef<(HTMLDivElement | null)[]>([]);
  const gridRef = useRef<HTMLDivElement | null>(null);

  const sortedDays = sortDaysByWeekday(location.days);
  const usedDays = new Set(location.days.map((d) => d.day));
  const availableDays = DAYS.filter((d) => !usedDays.has(d.day));

  const hours: number[] = [];
  for (let h = HOUR_START; h <= HOUR_END; h++) {
    hours.push(h);
  }

  // --- Drag handlers ---
  function startDrag(dayIndex: number, classIndex: number, e: React.MouseEvent) {
    e.preventDefault();
    e.stopPropagation();

    const col = columnRefs.current[dayIndex];
    if (!col) return;

    const cls = sortedDays[dayIndex].classes[classIndex];
    const parts = cls.time.split("–");
    const startMin = timeToMinutes(parts[0]?.trim() || "");
    const endMin = timeToMinutes(parts[1]?.trim() || "");
    if (!startMin || !endMin) return;

    const colRect = col.getBoundingClientRect();
    const blockTop = minutesToY(startMin);
    const grabOffsetY = e.clientY - colRect.top - blockTop;

    const state: DragState = {
      sourceDayIndex: dayIndex,
      classIndex,
      grabOffsetY,
      durationMin: endMin - startMin,
      previewStartMin: startMin,
      previewDayIndex: dayIndex,
      moved: false,
    };
    dragRef.current = state;
    setDrag(state);
  }

  const handleMouseMove = useCallback((e: MouseEvent) => {
    const d = dragRef.current;
    if (!d) return;

    // Determine which day column the mouse is over
    let targetDayIndex = d.previewDayIndex;
    for (let i = 0; i < columnRefs.current.length; i++) {
      const col = columnRefs.current[i];
      if (!col) continue;
      const rect = col.getBoundingClientRect();
      if (e.clientX >= rect.left && e.clientX < rect.right) {
        targetDayIndex = i;
        break;
      }
    }

    // Calculate Y position in the target column
    const col = columnRefs.current[targetDayIndex];
    if (!col) return;
    const colRect = col.getBoundingClientRect();
    const y = e.clientY - colRect.top - d.grabOffsetY;
    const rawMinutes = yToMinutes(y);
    const snapped = Math.round(rawMinutes / SNAP_MINUTES) * SNAP_MINUTES;
    // Clamp to grid bounds
    const clamped = Math.max(
      HOUR_START * 60,
      Math.min(snapped, HOUR_END * 60 - d.durationMin)
    );

    const hasMoved =
      clamped !== d.previewStartMin || targetDayIndex !== d.previewDayIndex || d.moved;

    const updated: DragState = {
      ...d,
      previewStartMin: clamped,
      previewDayIndex: targetDayIndex,
      moved: hasMoved,
    };
    dragRef.current = updated;
    setDrag(updated);
  }, []);

  const handleMouseUp = useCallback(() => {
    const d = dragRef.current;
    dragRef.current = null;

    if (!d) {
      setDrag(null);
      return;
    }

    if (d.moved) {
      // Suppress the click event that fires right after mouseup
      justDraggedRef.current = true;
      requestAnimationFrame(() => {
        justDraggedRef.current = false;
      });

      // Commit the move
      const newStart = formatMinutes(d.previewStartMin);
      const newEnd = formatMinutes(d.previewStartMin + d.durationMin);
      const sourceDay = sortedDays[d.sourceDayIndex];
      const cls = sourceDay.classes[d.classIndex];
      const updatedCls: ScheduleClass = { ...cls, time: `${newStart}–${newEnd}` };

      if (d.previewDayIndex === d.sourceDayIndex) {
        // Same day — just update time
        const classes = [...sourceDay.classes];
        classes[d.classIndex] = updatedCls;
        commitDayUpdate(d.sourceDayIndex, { ...sourceDay, classes });
      } else {
        // Move to different day
        const targetDay = sortedDays[d.previewDayIndex];

        // Remove from source
        const sourceClasses = sourceDay.classes.filter((_, i) => i !== d.classIndex);
        // Add to target
        const targetClasses = [...targetDay.classes, updatedCls];

        const days = [...location.days];
        const sourceActual = days.findIndex((dd) => dd.day === sourceDay.day);
        const targetActual = days.findIndex((dd) => dd.day === targetDay.day);
        if (sourceActual !== -1) days[sourceActual] = { ...sourceDay, classes: sourceClasses };
        if (targetActual !== -1) days[targetActual] = { ...targetDay, classes: targetClasses };
        onChange({ ...location, days });
      }
    }

    setDrag(null);
  }, [sortedDays, location, onChange]);

  // Attach global mouse listeners while dragging
  useEffect(() => {
    if (!drag) return;
    window.addEventListener("mousemove", handleMouseMove);
    window.addEventListener("mouseup", handleMouseUp);
    return () => {
      window.removeEventListener("mousemove", handleMouseMove);
      window.removeEventListener("mouseup", handleMouseUp);
    };
  }, [drag, handleMouseMove, handleMouseUp]);

  function handleCellClick(dayIndex: number, e: React.MouseEvent<HTMLDivElement>) {
    if (drag || justDraggedRef.current) return;

    // Use hover position if available, otherwise calculate from click
    let snapped: number;
    if (hover && hover.dayIndex === dayIndex) {
      snapped = hover.startMin;
    } else {
      const rect = e.currentTarget.getBoundingClientRect();
      const y = e.clientY - rect.top;
      const rawMin = yToMinutes(y);
      snapped = Math.round((rawMin - 30) / SNAP_MINUTES) * SNAP_MINUTES;
      snapped = Math.max(HOUR_START * 60, Math.min(snapped, HOUR_END * 60 - 60));
    }
    const startTime = formatMinutes(snapped);
    const endTime = formatMinutes(snapped + 60);

    setHover(null);
    setNewClass({
      dayIndex,
      cls: {
        time: `${startTime}–${endTime}`,
        trainer: "",
        type: "",
        groupId: generateGroupId(),
      },
    });
  }

  function commitDayUpdate(dayIndex: number, updatedDay: ScheduleDay) {
    const actualDay = sortedDays[dayIndex];
    const actualIndex = location.days.findIndex((d) => d.day === actualDay.day);
    if (actualIndex === -1) return;

    const days = [...location.days];
    days[actualIndex] = updatedDay;
    onChange({ ...location, days });
  }

  function updateDay(dayIndex: number, updatedDay: ScheduleDay) {
    commitDayUpdate(dayIndex, updatedDay);
  }

  function deleteDay(dayIndex: number) {
    const actualDay = sortedDays[dayIndex];
    const days = location.days.filter((d) => d.day !== actualDay.day);
    onChange({ ...location, days });
  }

  function addDay(dayName: string, dayShort: string) {
    onChange({
      ...location,
      days: sortDaysByWeekday([
        ...location.days,
        { day: dayName, dayShort, classes: [] },
      ]),
    });
  }

  // Get the class being edited
  const editingData = editingClass
    ? {
        day: sortedDays[editingClass.dayIndex],
        cls: sortedDays[editingClass.dayIndex]?.classes[editingClass.classIndex],
      }
    : null;

  // Build drag ghost preview
  const dragPreview = drag?.moved ? (() => {
    const sourceDay = sortedDays[drag.sourceDayIndex];
    const cls = sourceDay.classes[drag.classIndex];
    const colors = groupColors[groupKey(cls)] ?? "bg-neutral-600/80 border-neutral-500";
    const top = minutesToY(drag.previewStartMin);
    const height = (drag.durationMin / 60) * HOUR_HEIGHT;
    const newStart = formatMinutes(drag.previewStartMin);
    const newEnd = formatMinutes(drag.previewStartMin + drag.durationMin);
    return { colors, top, height, dayIndex: drag.previewDayIndex, newStart, newEnd, type: cls.type };
  })() : null;

  return (
    <div className="space-y-4">
      {/* Location name/address */}
      <div className="grid gap-3 sm:grid-cols-2">
        <InputField
          label="Название локации"
          value={location.name}
          onChange={(v) => onChange({ ...location, name: v })}
        />
        <SelectField
          label="Адрес"
          value={location.address}
          onChange={(v) => onChange({ ...location, address: v })}
          options={addresses.map((a) => ({ value: a, label: a }))}
          placeholder="Выберите адрес"
        />
      </div>

      {/* Calendar */}
      {sortedDays.length > 0 && (
        <div className="overflow-x-auto rounded-lg border border-white/10" ref={gridRef}>
          <div className="min-w-[600px]">
            {/* Day headers */}
            <div className="flex border-b border-white/10 bg-neutral-800/50">
              <div className="w-14 shrink-0" />
              {sortedDays.map((day, di) => (
                <div
                  key={day.day}
                  className="flex-1 border-l border-white/10 px-2 py-2 text-center"
                >
                  <div className="flex items-center justify-center gap-1">
                    <span className="text-sm font-medium text-white">{day.dayShort}</span>
                    <span className="text-xs text-neutral-500">({day.classes.length})</span>
                  </div>
                </div>
              ))}
            </div>

            {/* Time grid */}
            <div className="flex">
              {/* Time labels */}
              <div className="w-14 shrink-0 relative" style={{ height: `${TOTAL_HOURS * HOUR_HEIGHT}px` }}>
                {hours.slice(0, -1).map((h) => (
                  <div
                    key={h}
                    className="absolute left-0 right-0 text-right pr-2 text-xs text-neutral-500"
                    style={{ top: `${(h - HOUR_START) * HOUR_HEIGHT - 6}px` }}
                  >
                    {String(h).padStart(2, "0")}:00
                  </div>
                ))}
              </div>

              {/* Day columns */}
              {sortedDays.map((day, di) => {
                const showHover = hover && hover.dayIndex === di && !drag && !newClass && !editingClass;
                const hoverTop = showHover ? minutesToY(hover.startMin) : 0;
                const hoverHeight = HOUR_HEIGHT; // 1 hour
                const hoverEndMin = showHover ? hover.startMin + 60 : 0;

                return (
                  <div
                    key={day.day}
                    ref={(el) => { columnRefs.current[di] = el; }}
                    className={`flex-1 border-l border-white/10 relative ${drag ? "cursor-grabbing" : "cursor-pointer"}`}
                    style={{ height: `${TOTAL_HOURS * HOUR_HEIGHT}px` }}
                    onMouseMove={(e) => {
                      if (drag) return;
                      // Ignore if hovering over a class block
                      if ((e.target as HTMLElement).closest("[data-class-block]")) {
                        setHover(null);
                        return;
                      }
                      const rect = e.currentTarget.getBoundingClientRect();
                      const y = e.clientY - rect.top;
                      const rawMin = yToMinutes(y);
                      // Snap to 15-min and offset so the block is centered on cursor
                      const snapped = Math.round((rawMin - 30) / SNAP_MINUTES) * SNAP_MINUTES;
                      const clamped = Math.max(HOUR_START * 60, Math.min(snapped, HOUR_END * 60 - 60));
                      setHover({ dayIndex: di, startMin: clamped });
                    }}
                    onMouseLeave={() => setHover(null)}
                    onClick={(e) => {
                      if ((e.target as HTMLElement).closest("[data-class-block]")) return;
                      handleCellClick(di, e);
                    }}
                  >
                    {/* Hour lines */}
                    {hours.slice(0, -1).map((h) => (
                      <div
                        key={h}
                        className="absolute left-0 right-0 border-t border-white/5"
                        style={{ top: `${(h - HOUR_START) * HOUR_HEIGHT}px` }}
                      />
                    ))}
                    {/* Half-hour lines */}
                    {hours.slice(0, -1).map((h) => (
                      <div
                        key={`${h}-30`}
                        className="absolute left-0 right-0 border-t border-white/[0.02]"
                        style={{ top: `${(h - HOUR_START) * HOUR_HEIGHT + HOUR_HEIGHT / 2}px` }}
                      />
                    ))}

                    {/* Hover highlight — 1h preview */}
                    {showHover && (
                      <div
                        style={{ top: `${hoverTop}px`, height: `${hoverHeight}px` }}
                        className="absolute left-1 right-1 rounded-md border border-dashed border-gold/40 bg-gold/10 px-2 py-1 text-xs text-gold/70 pointer-events-none"
                      >
                        <div className="font-medium">
                          {formatMinutes(hover.startMin)}–{formatMinutes(hoverEndMin)}
                        </div>
                        <div className="text-gold/50 text-[10px]">Нажмите чтобы добавить</div>
                      </div>
                    )}

                    {/* Class blocks */}
                    {day.classes.map((cls, ci) => (
                      <ClassBlock
                        key={ci}
                        cls={cls}
                        index={ci}
                        isOverlapping={getOverlaps(day.classes, ci)}
                        isDragging={
                          drag !== null &&
                          drag.sourceDayIndex === di &&
                          drag.classIndex === ci &&
                          drag.moved
                        }
                        groupColors={groupColors}
                        onClick={() => {
                          if (justDraggedRef.current) return;
                          setEditingClass({ dayIndex: di, classIndex: ci });
                        }}
                        onDragStart={(e) => startDrag(di, ci, e)}
                      />
                    ))}

                    {/* Drag preview ghost */}
                    {dragPreview && dragPreview.dayIndex === di && (
                      <div
                        style={{ top: `${dragPreview.top}px`, height: `${dragPreview.height}px` }}
                        className={`absolute left-1 right-1 rounded-md border-l-3 border-dashed px-2 py-0.5 text-xs text-white/80 pointer-events-none ${dragPreview.colors} opacity-60`}
                      >
                        <div className="font-semibold truncate leading-tight">
                          {dragPreview.newStart}–{dragPreview.newEnd}
                        </div>
                        {dragPreview.height > 30 && (
                          <div className="truncate text-white/60 leading-tight">{dragPreview.type}</div>
                        )}
                      </div>
                    )}
                  </div>
                );
              })}
            </div>
          </div>
        </div>
      )}

      {/* Edit modal */}
      {editingData?.cls && editingClass && (
        <ClassModal
          cls={editingData.cls}
          trainers={trainers}
          classTypes={classTypes}
          allDays={sortedDays}
          currentDay={sortedDays[editingClass.dayIndex]?.day}
          onSave={(updated, dayTimes) => {
            const original = editingData.cls;

            const updatedDays = location.days.map((d) => {
              // Remove old matching group entries
              let classes = d.classes.filter((c) => !isSameGroup(c, original));
              // Add updated class with per-day time
              if (d.day in dayTimes) {
                classes = [...classes, { ...updated, time: dayTimes[d.day] }];
              }
              return { ...d, classes };
            });
            onChange({ ...location, days: updatedDays });
          }}
          onDelete={() => {
            // Delete from ALL days that have this group
            const original = editingData.cls;
            const updatedDays = location.days.map((d) => ({
              ...d,
              classes: d.classes.filter((c) => !isSameGroup(c, original)),
            }));
            onChange({ ...location, days: updatedDays });
          }}
          onClose={() => setEditingClass(null)}
        />
      )}

      {/* New class modal */}
      {newClass && (
        <ClassModal
          cls={newClass.cls}
          trainers={trainers}
          classTypes={classTypes}
          allDays={sortedDays}
          currentDay={sortedDays[newClass.dayIndex]?.day}
          onSave={(created, dayTimes) => {
            const updatedDays = location.days.map((d) => {
              if (d.day in dayTimes) {
                return { ...d, classes: [...d.classes, { ...created, time: dayTimes[d.day] }] };
              }
              return d;
            });
            onChange({ ...location, days: updatedDays });
          }}
          onClose={() => setNewClass(null)}
        />
      )}
    </div>
  );
}

// ---------- Main Page ----------
export default function ScheduleEditorPage() {
  const [activeLocation, setActiveLocation] = useState(0);
  const [trainers, setTrainers] = useState<string[]>([]);
  const [addresses, setAddresses] = useState<string[]>([]);
  const [classTypes, setClassTypes] = useState<string[]>([]);

  useEffect(() => {
    adminFetch("/api/admin/team")
      .then((r) => r.json())
      .then((members: { name: string }[]) => {
        setTrainers(members.map((m) => m.name));
      })
      .catch(() => {});

    adminFetch("/api/admin/sections/contact")
      .then((r) => r.json())
      .then((contact: { addresses?: string[] }) => {
        setAddresses(contact.addresses ?? []);
      })
      .catch(() => {});

    adminFetch("/api/admin/sections/classes")
      .then((r) => r.json())
      .then((classes: { items?: { name: string }[] }) => {
        setClassTypes((classes.items ?? []).map((c) => c.name));
      })
      .catch(() => {});
  }, []);

  return (
    <SectionEditor<ScheduleData> sectionKey="schedule" title="Расписание">
      {(data, update) => {
        const location = data.locations[activeLocation];

        function updateLocation(updated: ScheduleLocation) {
          const locations = [...data.locations];
          locations[activeLocation] = updated;
          update({ ...data, locations });
        }

        function deleteLocation(index: number) {
          if (data.locations.length <= 1) return;
          const locations = data.locations.filter((_, i) => i !== index);
          update({ ...data, locations });
          if (activeLocation >= locations.length) {
            setActiveLocation(locations.length - 1);
          }
        }

        return (
          <>
            <InputField
              label="Заголовок секции"
              value={data.title}
              onChange={(v) => update({ ...data, title: v })}
            />

            {/* Location tabs */}
            <div className="flex flex-wrap gap-2">
              {data.locations.map((loc, i) => (
                <div
                  key={i}
                  className={`flex items-center gap-1 rounded-lg px-4 py-2 text-sm font-medium transition-colors ${
                    i === activeLocation
                      ? "bg-gold/10 text-gold border border-gold/30"
                      : "border border-white/10 text-neutral-400 hover:text-white"
                  }`}
                >
                  <button
                    type="button"
                    onClick={() => setActiveLocation(i)}
                    className="text-left"
                  >
                    {loc.name}
                  </button>
                  {data.locations.length > 1 && (
                    <button
                      type="button"
                      onClick={() => deleteLocation(i)}
                      className="ml-1 rounded p-0.5 text-neutral-500 hover:text-red-400 transition-colors"
                      title="Удалить локацию"
                    >
                      <X size={12} />
                    </button>
                  )}
                </div>
              ))}
              <button
                type="button"
                onClick={() => {
                  const newLocations = [
                    ...data.locations,
                    { name: "Новая локация", address: "", days: DAYS.map((d) => ({ day: d.day, dayShort: d.dayShort, classes: [] })) },
                  ];
                  update({ ...data, locations: newLocations });
                  setActiveLocation(newLocations.length - 1);
                }}
                className="rounded-lg border border-dashed border-white/20 px-4 py-2 text-sm text-neutral-500 hover:text-white transition-colors"
              >
                <Plus size={14} className="inline" /> Локация
              </button>
            </div>

            {location && (
              <CalendarGrid
                location={location}
                trainers={trainers}
                addresses={addresses}
                classTypes={classTypes}
                onChange={updateLocation}
              />
            )}
          </>
        );
      }}
    </SectionEditor>
  );
}
