'use client';

// Per-table day/week calendar with drag-move, drag-resize, and click-to-create.
// Conflict (409) reverts the optimistic change and toasts the staff-detail message.

import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { toast } from 'sonner';
import { ChevronLeft, ChevronRight, Users, Clock } from 'lucide-react';
import { listBookings, updateBooking, createBooking, type Booking } from '@client/api/bookings';
import { listTables, type RestaurantTable } from '@client/api/tables';
import { listBranches, type Branch } from '@client/api/branches';
import { useLanguage } from '@client/contexts/LanguageContext';

const SLOT_MINUTES = 15;
const PX_PER_SLOT = 22;
const ROW_HEIGHT = 44;
const HEADER_LABEL_WIDTH = 160;
const FALLBACK_OPEN_HOUR = 9;
const FALLBACK_CLOSE_HOUR = 24;
const DAY_KEYS = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'] as const;

type ViewMode = 'day' | 'week';

interface BranchHoursEntry {
  open?: string;
  close?: string;
  closed?: boolean;
}

// Widest [openHour, closeHour] across all branches' configured `hours` JSON.
function deriveBranchHours(branches: Branch[]): { openHour: number; closeHour: number } {
  let minOpen = Infinity;
  let maxClose = -Infinity;
  const parseHM = (v?: string): number | null => {
    if (!v) return null;
    const m = String(v).trim().match(/^(\d{1,2}):?(\d{2})?$/);
    if (!m) return null;
    const hh = Number(m[1]);
    const mm = m[2] ? Number(m[2]) : 0;
    if (Number.isNaN(hh) || hh < 0 || hh > 24) return null;
    return hh + mm / 60;
  };
  for (const b of branches) {
    const raw = (b as unknown as { hours?: Record<string, BranchHoursEntry> }).hours;
    if (!raw) continue;
    for (const day of DAY_KEYS) {
      const e = raw[day];
      if (!e || e.closed) continue;
      const o = parseHM(e.open);
      const c = parseHM(e.close);
      if (o != null && c != null && c > o) {
        if (o < minOpen) minOpen = o;
        if (c > maxClose) maxClose = c;
      }
    }
  }
  if (!Number.isFinite(minOpen) || !Number.isFinite(maxClose)) {
    return { openHour: FALLBACK_OPEN_HOUR, closeHour: FALLBACK_CLOSE_HOUR };
  }
  return {
    openHour: Math.max(0, Math.min(23, Math.floor(minOpen))),
    closeHour: Math.max(1, Math.min(24, Math.ceil(maxClose))),
  };
}

const statusColors: Record<string, { bg: string; border: string; text: string }> = {
  confirmed:     { bg: 'hsl(var(--success-bg))', border: 'hsl(var(--success-border))', text: 'hsl(var(--success))' },
  reminder_sent: { bg: 'hsl(var(--info-bg))',    border: 'hsl(var(--info-border))',    text: 'hsl(var(--info))' },
  seated:        { bg: 'hsl(var(--primary-light))', border: 'hsl(22, 89%, 85%)',       text: 'hsl(var(--primary))' },
  escalated:     { bg: 'hsl(var(--danger-bg))',  border: 'hsl(var(--danger-border))',  text: 'hsl(var(--danger))' },
  noshow:        { bg: 'hsl(var(--danger-bg))',  border: 'hsl(var(--danger-border))',  text: 'hsl(var(--danger))' },
  cancelled:     { bg: 'hsl(var(--muted))',      border: 'hsl(var(--border))',         text: 'hsl(var(--muted-foreground))' },
  pending:       { bg: 'hsl(var(--warning-bg))', border: 'hsl(var(--warning-border))', text: 'hsl(var(--warning))' },
  completed:     { bg: 'hsl(var(--success-bg))', border: 'hsl(var(--success-border))', text: 'hsl(var(--success))' },
};

function todayIso(): string {
  return new Date().toISOString().slice(0, 10);
}

function shiftIso(iso: string, days: number): string {
  const d = new Date(`${iso}T00:00:00`);
  d.setDate(d.getDate() + days);
  return d.toISOString().slice(0, 10);
}

function localFormatTime(iso: string): string {
  const d = new Date(iso);
  return d.toLocaleTimeString([], { hour: 'numeric', minute: '2-digit' });
}

function minutesFromOpen(iso: string, dayIso: string, openHour: number, minutesPerDay: number): number {
  const d = new Date(iso);
  const dayStart = new Date(`${dayIso}T00:00:00`);
  const offset = (d.getTime() - dayStart.getTime()) / 60000;
  return Math.max(0, Math.min(minutesPerDay, offset - openHour * 60));
}

function isoFromMinutes(dayIso: string, minsFromOpen: number, openHour: number): string {
  const d = new Date(`${dayIso}T00:00:00`);
  d.setMinutes(d.getMinutes() + openHour * 60 + minsFromOpen);
  return d.toISOString();
}

function startOfWeekIso(iso: string): string {
  const d = new Date(`${iso}T00:00:00`);
  const dow = d.getDay();
  d.setDate(d.getDate() - dow);
  return d.toISOString().slice(0, 10);
}

interface DragState {
  bookingId: string;
  mode: 'move' | 'resize';
  pointerId: number;
  startClientX: number;
  origStartMin: number;
  origDurationMin: number;
  origTableId: string | null;
  origTableRow: number;
  currentStartMin: number;
  currentDurationMin: number;
  currentTableRow: number;
}

interface DayGridProps {
  dayIso: string;
  bookings: Booking[];
  tables: RestaurantTable[];
  openHour: number;
  closeHour: number;
  onMutate: (id: string, patch: Record<string, unknown>, optimistic: (b: Booking, patch: Record<string, unknown>, newTable: RestaurantTable | null) => Booking) => Promise<void>;
  onCreate: (table: RestaurantTable, dayIso: string, startMin: number, openHour: number) => void;
  showHeader?: boolean;
  compactLabel?: string;
}

function DayGrid({ dayIso, bookings, tables, openHour, closeHour, onMutate, onCreate, showHeader = true, compactLabel }: DayGridProps) {
  const minutesPerDay = (closeHour - openHour) * 60;
  const totalSlots = minutesPerDay / SLOT_MINUTES;
  const [drag, setDrag] = useState<DragState | null>(null);
  const dragRef = useRef<DragState | null>(null);
  const gridRef = useRef<HTMLDivElement>(null);

  const tableIndex = useMemo(() => {
    const m = new Map<string, number>();
    tables.forEach((tbl, i) => m.set(tbl.id, i));
    return m;
  }, [tables]);

  const positioned = useMemo(() => {
    return bookings
      .filter(b => b.startAt && b.endAt && b.tableId && tableIndex.has(b.tableId))
      .filter(b => b.startAt!.slice(0, 10) === dayIso || b.endAt!.slice(0, 10) === dayIso || (b.startAt! < `${dayIso}T23:59:59Z` && b.endAt! > `${dayIso}T00:00:00Z`))
      .map(b => {
        const startMin = minutesFromOpen(b.startAt!, dayIso, openHour, minutesPerDay);
        const endMin   = minutesFromOpen(b.endAt!, dayIso, openHour, minutesPerDay);
        return { booking: b, startMin, durationMin: Math.max(SLOT_MINUTES, endMin - startMin) };
      });
  }, [bookings, tableIndex, dayIso, openHour, minutesPerDay]);

  const onPointerDown = (
    e: React.PointerEvent<HTMLDivElement>,
    booking: Booking,
    startMin: number,
    durationMin: number,
    mode: 'move' | 'resize'
  ) => {
    if (!booking.tableId) return;
    e.preventDefault();
    e.stopPropagation();
    (e.target as HTMLElement).setPointerCapture(e.pointerId);
    const rowIdx = tableIndex.get(booking.tableId) ?? 0;
    const ds: DragState = {
      bookingId: booking.id, mode, pointerId: e.pointerId,
      startClientX: e.clientX,
      origStartMin: startMin, origDurationMin: durationMin,
      origTableId: booking.tableId, origTableRow: rowIdx,
      currentStartMin: startMin, currentDurationMin: durationMin, currentTableRow: rowIdx,
    };
    dragRef.current = ds;
    setDrag(ds);
  };

  const onPointerMove = (e: React.PointerEvent<HTMLDivElement>) => {
    const ds = dragRef.current;
    if (!ds || ds.pointerId !== e.pointerId) return;
    const dxMin = Math.round((e.clientX - ds.startClientX) / PX_PER_SLOT) * SLOT_MINUTES;
    let newStart = ds.origStartMin, newDur = ds.origDurationMin, newRow = ds.origTableRow;
    if (ds.mode === 'move') {
      newStart = Math.max(0, Math.min(minutesPerDay - newDur, ds.origStartMin + dxMin));
      if (gridRef.current) {
        const rect = gridRef.current.getBoundingClientRect();
        const idx = Math.floor((e.clientY - rect.top) / ROW_HEIGHT);
        if (idx >= 0 && idx < tables.length) newRow = idx;
      }
    } else {
      newDur = Math.max(SLOT_MINUTES, Math.min(minutesPerDay - newStart, ds.origDurationMin + dxMin));
    }
    const next = { ...ds, currentStartMin: newStart, currentDurationMin: newDur, currentTableRow: newRow };
    dragRef.current = next;
    setDrag(next);
  };

  const onPointerUp = async (e: React.PointerEvent<HTMLDivElement>) => {
    const ds = dragRef.current;
    if (!ds || ds.pointerId !== e.pointerId) return;
    try { (e.target as HTMLElement).releasePointerCapture(e.pointerId); } catch { /* ignore */ }
    dragRef.current = null;
    setDrag(null);

    const moved = ds.currentStartMin !== ds.origStartMin || ds.currentDurationMin !== ds.origDurationMin || ds.currentTableRow !== ds.origTableRow;
    if (!moved) return;

    const newTable = tables[ds.currentTableRow] ?? null;
    const patch: Record<string, unknown> = {};
    if (ds.currentStartMin !== ds.origStartMin) patch.start_at = isoFromMinutes(dayIso, ds.currentStartMin, openHour);
    if (ds.currentDurationMin !== ds.origDurationMin) patch.duration_min = ds.currentDurationMin;
    if (newTable && newTable.id !== ds.origTableId) patch.table_id = newTable.id;

    await onMutate(ds.bookingId, patch, (b, p, nt) => {
      const newStartIso = p.start_at ? String(p.start_at) : b.startAt;
      const dur = (p.duration_min as number | undefined) ?? b.durationMin ?? 90;
      const newEndIso = newStartIso ? new Date(new Date(newStartIso).getTime() + dur * 60000).toISOString() : b.endAt;
      return {
        ...b,
        startAt: newStartIso,
        endAt: newEndIso,
        durationMin: dur,
        tableId: (p.table_id as string | undefined) ?? b.tableId,
        tableNumber: nt && p.table_id ? nt.table_number : b.tableNumber,
      };
    });
  };

  const onGridClick = (e: React.MouseEvent<HTMLDivElement>) => {
    if (!gridRef.current || drag) return;
    if ((e.target as HTMLElement).closest('[data-booking-block]')) return;
    const rect = gridRef.current.getBoundingClientRect();
    const slotIdx = Math.floor((e.clientX - rect.left) / PX_PER_SLOT);
    const rowIdx = Math.floor((e.clientY - rect.top) / ROW_HEIGHT);
    if (slotIdx < 0 || slotIdx >= totalSlots || rowIdx < 0 || rowIdx >= tables.length) return;
    onCreate(tables[rowIdx], dayIso, slotIdx * SLOT_MINUTES, openHour);
  };

  const hourLabels = useMemo(() => {
    const labels: { hour: number; px: number }[] = [];
    for (let h = openHour; h < closeHour; h++) {
      labels.push({ hour: h, px: ((h - openHour) * 60) / SLOT_MINUTES * PX_PER_SLOT });
    }
    return labels;
  }, [openHour, closeHour]);

  const nowLineLeft = useMemo(() => {
    if (dayIso !== todayIso()) return null;
    const now = new Date();
    const mins = (now.getHours() - openHour) * 60 + now.getMinutes();
    if (mins < 0 || mins >= minutesPerDay) return null;
    return (mins / SLOT_MINUTES) * PX_PER_SLOT;
  }, [dayIso, openHour, minutesPerDay]);

  return (
    <div className="overflow-x-auto scrollbar-thin">
      {compactLabel && (
        <div className="text-xs font-semibold mb-1.5 px-1" style={{ color: 'hsl(var(--foreground))' }}>
          {compactLabel}
        </div>
      )}
      <div style={{ minWidth: HEADER_LABEL_WIDTH + totalSlots * PX_PER_SLOT, position: 'relative' }}>
        {showHeader && (
          <div className="flex" style={{ marginLeft: HEADER_LABEL_WIDTH, position: 'relative', height: 22 }}>
            {hourLabels.map(({ hour, px }) => (
              <div key={hour} className="absolute text-xs font-mono" style={{ left: px, color: 'hsl(var(--muted-foreground))' }}>
                {hour % 12 === 0 ? 12 : hour % 12}{hour < 12 ? 'a' : 'p'}
              </div>
            ))}
          </div>
        )}
        <div className="flex">
          <div style={{ width: HEADER_LABEL_WIDTH, flexShrink: 0 }}>
            {tables.map(tbl => (
              <div key={tbl.id} className="flex items-center gap-2 px-2 border-b text-xs"
                style={{ height: ROW_HEIGHT, borderColor: 'hsl(var(--border))', color: 'hsl(var(--foreground))' }}>
                <span className="font-semibold">#{tbl.table_number}</span>
                <span style={{ color: 'hsl(var(--muted-foreground))' }} className="flex items-center gap-1">
                  <Users size={10} />{tbl.capacity}
                </span>
                {tbl.zone && (
                  <span className="text-xs px-1.5 py-0.5 rounded" style={{ backgroundColor: 'hsl(var(--muted))', color: 'hsl(var(--muted-foreground))' }}>
                    {tbl.zone}
                  </span>
                )}
              </div>
            ))}
          </div>
          <div ref={gridRef} className="relative flex-1 cursor-pointer"
            style={{ height: tables.length * ROW_HEIGHT, width: totalSlots * PX_PER_SLOT }}
            onPointerMove={onPointerMove} onPointerUp={onPointerUp} onClick={onGridClick}>
            {tables.map((_, i) => (
              <div key={i} className="absolute left-0 right-0 border-b"
                style={{ top: (i + 1) * ROW_HEIGHT - 1, borderColor: 'hsl(var(--border))' }} />
            ))}
            {hourLabels.map(({ hour, px }) => (
              <div key={hour} className="absolute top-0 bottom-0"
                style={{ left: px, width: 1, backgroundColor: 'hsl(var(--border))' }} />
            ))}
            {nowLineLeft != null && (
              <div className="absolute top-0 bottom-0 pointer-events-none"
                style={{ left: nowLineLeft, width: 2, backgroundColor: 'hsl(var(--primary))', opacity: 0.6 }} />
            )}
            {positioned.map(({ booking, startMin, durationMin }) => {
              const isDragging = drag?.bookingId === booking.id;
              const renderStartMin = isDragging ? drag!.currentStartMin : startMin;
              const renderDur      = isDragging ? drag!.currentDurationMin : durationMin;
              const renderRow      = isDragging ? drag!.currentTableRow : (tableIndex.get(booking.tableId!) ?? 0);
              const colors = statusColors[booking.status] || statusColors.confirmed;
              const bufferMin = booking.bufferMin ?? 15;
              const bufferPx = (bufferMin / SLOT_MINUTES) * PX_PER_SLOT;
              const left = (renderStartMin / SLOT_MINUTES) * PX_PER_SLOT;
              const widthPx = (renderDur / SLOT_MINUTES) * PX_PER_SLOT - 2;
              return (
                <div key={booking.id}>
                  {bufferMin > 0 && (
                    <div className="absolute pointer-events-none"
                      style={{
                        left: left + widthPx + 1, top: renderRow * ROW_HEIGHT + 3,
                        width: Math.max(0, bufferPx - 1), height: ROW_HEIGHT - 6,
                        background: `repeating-linear-gradient(135deg, ${colors.border} 0 4px, transparent 4px 8px)`,
                        opacity: 0.5, borderRadius: 4, zIndex: isDragging ? 28 : 8,
                      }}
                      title={`Turnover buffer: ${bufferMin} min`} />
                  )}
                  <div data-booking-block
                    className="absolute rounded-md text-xs select-none flex items-center px-2 gap-1 overflow-hidden"
                    style={{
                      left, top: renderRow * ROW_HEIGHT + 3, width: widthPx, height: ROW_HEIGHT - 6,
                      backgroundColor: colors.bg, border: `1px solid ${colors.border}`, color: colors.text,
                      cursor: isDragging ? 'grabbing' : 'grab',
                      opacity: isDragging ? 0.85 : 1, zIndex: isDragging ? 30 : 10,
                    }}
                    onPointerDown={e => onPointerDown(e, booking, startMin, durationMin, 'move')}
                    title={`${booking.guestName} · ${booking.partySize} guests · ${booking.startAt ? localFormatTime(booking.startAt) : ''} (+${bufferMin}m buffer)`}>
                    <span className="font-semibold truncate">{booking.guestName}</span>
                    <Users size={10} className="shrink-0" />
                    <span className="font-mono tabular-nums">{booking.partySize}</span>
                    <div className="absolute top-0 right-0 bottom-0"
                      style={{ width: 6, cursor: 'ew-resize', backgroundColor: colors.text, opacity: 0.25 }}
                      onPointerDown={e => onPointerDown(e, booking, startMin, durationMin, 'resize')} />
                  </div>
                </div>
              );
            })}
          </div>
        </div>
      </div>
    </div>
  );
}

export default function TimelineView() {
  const { t } = useLanguage();
  const [viewMode, setViewMode] = useState<ViewMode>('day');
  const [date, setDate] = useState<string>(todayIso());
  const [tables, setTables] = useState<RestaurantTable[]>([]);
  const [bookings, setBookings] = useState<Booking[]>([]);
  const [branches, setBranches] = useState<Branch[]>([]);
  const [loading, setLoading] = useState(true);
  const [creating, setCreating] = useState(false);

  const [newBooking, setNewBooking] = useState<{
    table: RestaurantTable;
    startMin: number;
    startIso: string;
    guestName: string;
    partySize: number;
    durationMin: number;
  } | null>(null);

  const { openHour, closeHour } = useMemo(() => deriveBranchHours(branches), [branches]);

  const reload = useCallback(async () => {
    setLoading(true);
    try {
      const fromDate = viewMode === 'week' ? startOfWeekIso(date) : date;
      const dates: string[] = viewMode === 'week'
        ? Array.from({ length: 7 }, (_, i) => shiftIso(fromDate, i))
        : [date];
      const [tablesRes, branchesRes, ...bookingsResults] = await Promise.all([
        listTables(),
        listBranches({ limit: '50' }),
        ...dates.map(d => listBookings({ date: d, limit: '500' })),
      ]);
      setTables(
        (tablesRes.tables ?? [])
          .filter(t => t.is_active)
          .sort((a, b) => {
            const z = (a.zone ?? '').localeCompare(b.zone ?? '');
            if (z !== 0) return z;
            const an = Number(a.table_number); const bn = Number(b.table_number);
            if (!isNaN(an) && !isNaN(bn)) return an - bn;
            return String(a.table_number).localeCompare(String(b.table_number));
          })
      );
      setBranches(branchesRes.branches ?? []);
      setBookings(bookingsResults.flatMap(r => r.bookings ?? []));
    } catch (err) {
      console.error('[TimelineView] load failed', err);
      toast.error(t('tableBooking.loadFailed', 'Failed to load timeline'));
    } finally {
      setLoading(false);
    }
  }, [date, viewMode, t]);

  useEffect(() => { void reload(); }, [reload]);

  const tableIndex = useMemo(() => {
    const m = new Map<string, number>();
    tables.forEach((tbl, i) => m.set(tbl.id, i));
    return m;
  }, [tables]);

  const unassigned = useMemo(
    () => bookings.filter(b => !b.tableId || !tableIndex.has(b.tableId ?? '')),
    [bookings, tableIndex]
  );

  const handleMutate = useCallback(async (
    id: string,
    patch: Record<string, unknown>,
    optimistic: (b: Booking, patch: Record<string, unknown>, newTable: RestaurantTable | null) => Booking,
  ) => {
    const original = bookings.find(b => b.id === id) ?? null;
    const newTable = patch.table_id ? (tables.find(t => t.id === patch.table_id) ?? null) : null;
    setBookings(prev => prev.map(b => (b.id === id ? optimistic(b, patch, newTable) : b)));
    try {
      await updateBooking(id, patch);
      void reload();
    } catch (err) {
      if (original) {
        setBookings(prev => prev.map(b => (b.id === id ? original : b)));
      } else {
        void reload();
      }
      const msg = err instanceof Error ? err.message : 'Update failed';
      toast.error(msg);
    }
  }, [bookings, tables, reload]);

  const handleCreate = useCallback((tbl: RestaurantTable, dayIso: string, startMin: number, openH: number) => {
    setNewBooking({
      table: tbl,
      startMin,
      startIso: isoFromMinutes(dayIso, startMin, openH),
      guestName: '',
      partySize: Math.min(tbl.capacity, 2),
      durationMin: 90,
    });
  }, []);

  const submitNewBooking = async () => {
    if (!newBooking) return;
    if (!newBooking.guestName.trim()) {
      toast.error(t('tableBooking.guestRequired', 'Guest name is required'));
      return;
    }
    setCreating(true);
    try {
      const startDate = newBooking.startIso.slice(0, 10);
      await createBooking({
        guest_name: newBooking.guestName.trim(),
        party_size: newBooking.partySize,
        booking_date: startDate,
        booking_time: new Date(newBooking.startIso).toISOString().slice(11, 16),
        start_at: newBooking.startIso,
        duration_min: newBooking.durationMin,
        table_id: newBooking.table.id,
        branch_id: newBooking.table.branch_id,
        channel: 'chat',
        status: 'confirmed',
      });
      toast.success(t('tableBooking.walkInCreated', 'Booking created'));
      setNewBooking(null);
      void reload();
    } catch (err) {
      const msg = err instanceof Error ? err.message : 'Create failed';
      toast.error(msg);
    } finally {
      setCreating(false);
    }
  };

  const weekDates = useMemo(() => {
    if (viewMode !== 'week') return [date];
    const start = startOfWeekIso(date);
    return Array.from({ length: 7 }, (_, i) => shiftIso(start, i));
  }, [date, viewMode]);

  return (
    <div className="card p-5 mb-5">
      {/* Top bar */}
      <div className="flex items-center justify-between mb-4 gap-4 flex-wrap">
        <div>
          <p className="section-label">{t('tableBooking.timeline.title', 'Floor timeline')}</p>
          <p className="text-sm font-semibold mt-0.5" style={{ color: 'hsl(var(--foreground))' }}>
            {new Date(`${date}T00:00:00`).toLocaleDateString(undefined, { weekday: 'long', month: 'long', day: 'numeric' })}
          </p>
        </div>
        <div className="flex items-center gap-2">
          <button
            className="btn-ghost p-1.5 rounded"
            onClick={() => setDate(d => shiftIso(d, -1))}
            aria-label={t('tableBooking.timeline.prevDay', 'Previous day')}
          >
            <ChevronLeft size={16} />
          </button>
          <input
            type="date"
            className="input-base"
            value={date}
            onChange={e => setDate(e.target.value)}
            style={{ minWidth: 160 }}
          />
          <button
            className="btn-ghost p-1.5 rounded"
            onClick={() => setDate(d => shiftIso(d, 1))}
            aria-label={t('tableBooking.timeline.nextDay', 'Next day')}
          >
            <ChevronRight size={16} />
          </button>
          <button
            className="btn-secondary text-xs px-2.5 py-1.5 rounded"
            onClick={() => setDate(todayIso())}
          >
            {t('common.today', 'Today')}
          </button>
          <div className="flex items-center rounded border" style={{ borderColor: 'hsl(var(--border))' }}>
            <button
              className="text-xs px-2.5 py-1.5"
              onClick={() => setViewMode('day')}
              style={{
                backgroundColor: viewMode === 'day' ? 'hsl(var(--primary))' : 'transparent',
                color: viewMode === 'day' ? 'hsl(var(--primary-foreground))' : 'hsl(var(--foreground-secondary))',
              }}
            >
              {t('tableBooking.timeline.day', 'Day')}
            </button>
            <button
              className="text-xs px-2.5 py-1.5"
              onClick={() => setViewMode('week')}
              style={{
                backgroundColor: viewMode === 'week' ? 'hsl(var(--primary))' : 'transparent',
                color: viewMode === 'week' ? 'hsl(var(--primary-foreground))' : 'hsl(var(--foreground-secondary))',
              }}
            >
              {t('tableBooking.timeline.week', 'Week')}
            </button>
          </div>
        </div>
      </div>

      {/* Unassigned banner */}
      {unassigned.length > 0 && (
        <div
          className="mb-3 px-3 py-2 rounded-lg text-xs flex items-center gap-2"
          style={{
            backgroundColor: 'hsl(var(--warning-bg))',
            border: '1px solid hsl(var(--warning-border))',
            color: 'hsl(var(--warning))',
          }}
        >
          <Clock size={12} />
          {t('tableBooking.timeline.unassigned', '{count} booking(s) without a table — drag onto the grid to assign.', { count: unassigned.length })}
        </div>
      )}

      {loading && (
        <div className="py-10 text-center text-sm" style={{ color: 'hsl(var(--muted-foreground))' }}>
          {t('common.loading', 'Loading...')}
        </div>
      )}

      {!loading && tables.length === 0 && (
        <div className="py-10 text-center text-sm" style={{ color: 'hsl(var(--muted-foreground))' }}>
          {t('tableBooking.timeline.noTables', 'No active tables. Add tables in the Tables section first.')}
        </div>
      )}

      {!loading && tables.length > 0 && (
        viewMode === 'day' ? (
          <DayGrid
            dayIso={date}
            bookings={bookings}
            tables={tables}
            openHour={openHour}
            closeHour={closeHour}
            onMutate={handleMutate}
            onCreate={handleCreate}
          />
        ) : (
          <div className="space-y-4">
            {weekDates.map((d, i) => (
              <DayGrid
                key={d}
                dayIso={d}
                bookings={bookings}
                tables={tables}
                openHour={openHour}
                closeHour={closeHour}
                onMutate={handleMutate}
                onCreate={handleCreate}
                showHeader={i === 0}
                compactLabel={new Date(`${d}T00:00:00`).toLocaleDateString(undefined, { weekday: 'short', month: 'short', day: 'numeric' })}
              />
            ))}
          </div>
        )
      )}

      {/* ─── New booking modal (opened by clicking an empty grid cell) ─── */}
      {newBooking && (
        <div
          className="fixed inset-0 z-50 flex items-center justify-center px-4"
          style={{ backgroundColor: 'rgba(0,0,0,0.45)' }}
          onClick={(e) => { if (e.target === e.currentTarget) setNewBooking(null); }}
        >
          <div
            className="rounded-lg shadow-2xl w-full max-w-md p-5"
            style={{ backgroundColor: 'hsl(var(--card))', border: '1px solid hsl(var(--border))' }}
          >
            <div className="flex items-center justify-between mb-4">
              <div>
                <p className="section-label">{t('tableBooking.newBooking', 'New booking')}</p>
                <p className="text-sm font-semibold mt-0.5" style={{ color: 'hsl(var(--foreground))' }}>
                  {t('tableBooking.tableLabel', 'Table')} #{newBooking.table.table_number}
                  {newBooking.table.zone ? ` · ${newBooking.table.zone}` : ''}
                  {' · '}
                  {localFormatTime(newBooking.startIso)}
                </p>
              </div>
              <button
                className="btn-ghost p-1.5 rounded"
                onClick={() => setNewBooking(null)}
                aria-label={t('common.close', 'Close')}
              >
                <span aria-hidden>×</span>
              </button>
            </div>

            <div className="space-y-3">
              <div>
                <label className="block text-xs font-semibold mb-1.5" style={{ color: 'hsl(var(--foreground-secondary))' }}>
                  {t('tableBooking.guestName', 'Guest name')}
                </label>
                <input
                  className="input-base w-full"
                  autoFocus
                  value={newBooking.guestName}
                  onChange={e => setNewBooking({ ...newBooking, guestName: e.target.value })}
                  placeholder={t('tableBooking.guestNamePlaceholder', 'e.g. Walk-in / Smith')}
                  onKeyDown={e => { if (e.key === 'Enter') void submitNewBooking(); }}
                />
              </div>
              <div className="grid grid-cols-2 gap-3">
                <div>
                  <label className="block text-xs font-semibold mb-1.5" style={{ color: 'hsl(var(--foreground-secondary))' }}>
                    {t('tableBooking.partySize', 'Party size')}
                  </label>
                  <input
                    className="input-base w-full"
                    type="number"
                    min={1}
                    max={Math.max(newBooking.table.capacity, 1)}
                    value={newBooking.partySize}
                    onChange={e => setNewBooking({ ...newBooking, partySize: Math.max(1, Number(e.target.value) || 1) })}
                  />
                </div>
                <div>
                  <label className="block text-xs font-semibold mb-1.5" style={{ color: 'hsl(var(--foreground-secondary))' }}>
                    {t('tableBooking.duration', 'Duration (min)')}
                  </label>
                  <input
                    className="input-base w-full"
                    type="number"
                    min={15}
                    max={480}
                    step={15}
                    value={newBooking.durationMin}
                    onChange={e => setNewBooking({ ...newBooking, durationMin: Math.max(15, Number(e.target.value) || 90) })}
                  />
                </div>
              </div>
            </div>

            <div className="flex justify-end gap-2 mt-5">
              <button className="btn-secondary px-3 py-1.5 rounded text-xs" onClick={() => setNewBooking(null)} disabled={creating}>
                {t('common.cancel', 'Cancel')}
              </button>
              <button
                className="btn-primary px-3 py-1.5 rounded text-xs"
                onClick={() => void submitNewBooking()}
                disabled={creating || !newBooking.guestName.trim()}
              >
                {creating ? t('common.saving', 'Saving...') : t('tableBooking.createBooking', 'Create booking')}
              </button>
            </div>
          </div>
        </div>
      )}
    </div>
  );
}
