import { Fragment, useEffect, useMemo, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import { useLocation, useRoute } from "wouter";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import {
  ArrowLeft,
  ArrowRight,
  LayoutDashboard,
  DoorOpen,
  CalendarClock,
  Handshake,
  Gift,
  Warehouse,
  HandHeart,
  ChevronDown,
  BarChart3,
  ScrollText,
  Plus,
  Pencil,
  Trash2,
  Check,
  X,
  Ban,
  CalendarDays,
  List as ListIcon,
  Search as SearchIcon,
  Link2,
  Copy,
  ExternalLink,
  Upload,
  Loader2,
  Download,
  Image as ImageIcon,
  Settings as SettingsIcon,
  Clock3,
} from "lucide-react";
import {
  ResponsiveContainer,
  BarChart,
  Bar,
  XAxis,
  YAxis,
  Tooltip as RechartsTooltip,
  CartesianGrid,
  AreaChart,
  Area,
  PieChart,
  Pie,
  Cell,
  Legend,
} from "recharts";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Textarea } from "@/components/ui/textarea";
import { Label } from "@/components/ui/label";
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select";
import {
  Dialog,
  DialogContent,
  DialogFooter,
  DialogHeader,
  DialogTitle,
} from "@/components/ui/dialog";
import {
  AlertDialog,
  AlertDialogAction,
  AlertDialogCancel,
  AlertDialogContent,
  AlertDialogDescription,
  AlertDialogFooter,
  AlertDialogHeader,
  AlertDialogTitle,
} from "@/components/ui/alert-dialog";
import {
  Table,
  TableBody,
  TableCell,
  TableHead,
  TableHeader,
  TableRow,
} from "@/components/ui/table";
import { AppDock } from "@/components/app-dock";
import { Calendar } from "@/components/ui/calendar";
import { useNow } from "@/components/clock";
import { useToast } from "@/hooks/use-toast";
import { apiJson, ApiError } from "@/lib/api-json";
import { useUpload, type UploadResponse } from "@workspace/object-storage-web";
import { cn } from "@/lib/utils";

const API = `${import.meta.env.BASE_URL}api`;

// ---------------------------------------------------------------------------
// Types (mirror of the /api/protocol/* response shapes)
// ---------------------------------------------------------------------------
type Capabilities = {
  canRead: boolean;
  canRequest: boolean;
  canMutate: boolean;
  canApprove: boolean;
  canManageRooms: boolean;
  canViewAudit: boolean;
};

type Room = {
  id: number;
  nameAr: string;
  nameEn: string;
  capacity: number | null;
  location: string | null;
  isActive: boolean;
  sortOrder: number;
};

type Slot = {
  id: number;
  startTime: string; // "HH:mm" Riyadh wall clock
  durationMinutes: number;
  daysOfWeek: number[]; // 0=Sunday .. 6=Saturday (Riyadh calendar)
  isActive: boolean;
  sortOrder: number;
};

// Minimal shape returned by /protocol/booking-slots/active (readable by any
// protocol user; the full Slot list is admin-only).
type ActiveSlotInfo = {
  id: number;
  startTime: string;
  durationMinutes: number;
  daysOfWeek: number[];
};

// Weekday (0=Sunday .. 6=Saturday) of a plain "YYYY-MM-DD" calendar day.
function ymdWeekday(ymd: string): number {
  return new Date(`${ymd}T00:00:00Z`).getUTCDay();
}

const ALL_DAYS = [0, 1, 2, 3, 4, 5, 6];
// Saudi work week: Sunday..Thursday.
const WORKDAYS = [0, 1, 2, 3, 4];

function fmtSlotDays(
  days: number[],
  t: (k: string) => string,
  isAr: boolean,
): string {
  if (ALL_DAYS.every((d) => days.includes(d))) {
    return t("protocol.slots.allDays");
  }
  return [...days]
    .sort((a, b) => a - b)
    .map((d) => t(`protocol.slots.dayNames.${d}`))
    .join(isAr ? "، " : ", ");
}

type BookingStatus = "pending" | "approved" | "rejected" | "cancelled";
type MeetingType = "internal" | "external";
type KeyAttendee = {
  name: string;
  position: string;
  side: "internal" | "external";
};
type Booking = {
  id: number;
  roomId: number;
  title: string;
  requesterName: string | null;
  requesterPhone: string | null;
  requesterOrg: string | null;
  attendeeCount: number | null;
  department: string | null;
  meetingType: MeetingType;
  purpose: string | null;
  keyAttendees: KeyAttendee[] | null;
  bookingReference: string | null;
  source: "internal" | "public";
  startsAt: string;
  endsAt: string;
  status: BookingStatus;
  notes: string | null;
  rejectionReason: string | null;
};

type ExternalMeeting = {
  id: number;
  title: string;
  partyName: string | null;
  location: string | null;
  startsAt: string;
  endsAt: string | null;
  status: "pending" | "approved" | "rejected" | "completed" | "cancelled";
  notes: string | null;
  rejectionReason: string | null;
};

type GiftKind = "gift" | "shield";
type Gift = {
  id: number;
  nameAr: string;
  nameEn: string;
  kind: GiftKind;
  descriptionAr: string | null;
  descriptionEn: string | null;
  quantityInStock: number;
  isActive: boolean;
};

type IssueStatus = "pending" | "approved" | "rejected" | "issued";
type GiftIssue = {
  id: number;
  giftId: number;
  recipientName: string;
  occasion: string | null;
  quantity: number;
  status: IssueStatus;
  notes: string | null;
  rejectionReason: string | null;
};

type Dashboard = {
  activeRooms: number;
  roomsAvailableNow: number;
  pendingBookings: number;
  todayBookings: number;
  upcomingExternalMeetings: number;
  giftCatalogCount: number;
  totalGiftStock: number;
  pendingGiftIssues: number;
  giftsIssuedThisMonth: number;
  giftsIssuedThisMonthQty: number;
};

type ReportRow = {
  bookingsByRoom: Array<{
    roomId: number;
    roomNameAr: string;
    roomNameEn: string;
    total: number;
    approved: number;
    pending: number;
    rejected: number;
    cancelled: number;
  }>;
  issuesByGift: Array<{
    giftId: number;
    giftNameAr: string;
    giftNameEn: string;
    kind: GiftKind;
    totalRequests: number;
    issued: number;
    issuedQty: number;
    pending: number;
    rejected: number;
  }>;
  externalMeetings: {
    total: number;
    pending: number;
    approved: number;
    rejected: number;
    completed: number;
    cancelled: number;
  };
};

type TabKey =
  | "dashboard"
  | "roomsDashboard"
  | "rooms"
  | "bookings"
  | "bookingSettings"
  | "external"
  | "gifts"
  | "issues"
  | "photos"
  | "reports"
  | "audit";

// URL slugs for each tab so pages are deep-linkable under /protocol/*.
const TAB_SLUGS: Record<TabKey, string> = {
  dashboard: "dashboard",
  roomsDashboard: "rooms-dashboard",
  rooms: "rooms",
  bookings: "bookings",
  bookingSettings: "booking-settings",
  external: "external-meetings",
  gifts: "gifts",
  issues: "gift-issues",
  photos: "photos",
  reports: "reports",
  audit: "audit",
};
const SLUG_TABS: Record<string, TabKey> = Object.fromEntries(
  Object.entries(TAB_SLUGS).map(([k, v]) => [v, k as TabKey]),
) as Record<string, TabKey>;

// ---------------------------------------------------------------------------
// Small helpers
// ---------------------------------------------------------------------------
function toLocalInput(iso: string): string {
  // Convert an ISO string into a value for <input type="datetime-local">.
  const d = new Date(iso);
  const pad = (n: number) => String(n).padStart(2, "0");
  return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}T${pad(
    d.getHours(),
  )}:${pad(d.getMinutes())}`;
}
function fromLocalInput(v: string): string {
  return new Date(v).toISOString();
}

const STATUS_PILL: Record<string, string> = {
  pending: "bg-amber-100 text-amber-800",
  approved: "bg-emerald-100 text-emerald-700",
  issued: "bg-emerald-100 text-emerald-700",
  rejected: "bg-rose-100 text-rose-700",
  cancelled: "bg-slate-200 text-slate-600",
  scheduled: "bg-sky-100 text-sky-700",
  completed: "bg-emerald-100 text-emerald-700",
};

// ---- Day time-grid (Google-Calendar style) constants & helpers ----
const GRID_HOUR_START = 7; // 7 AM
const GRID_HOUR_END = 22; // 10 PM
const GRID_PX_PER_HOUR = 56;

// Block fill + inline-start accent border, colored by booking status.
const BOOKING_BLOCK_STYLE: Record<string, string> = {
  pending: "bg-amber-100 border-amber-400 text-amber-900",
  approved: "bg-emerald-100 border-emerald-500 text-emerald-900",
  issued: "bg-emerald-100 border-emerald-500 text-emerald-900",
  rejected: "bg-rose-100 border-rose-400 text-rose-900",
  cancelled: "bg-slate-100 border-slate-300 text-slate-500",
  scheduled: "bg-sky-100 border-sky-400 text-sky-900",
  completed: "bg-emerald-100 border-emerald-500 text-emerald-900",
};

// --- Booking slots (أوقات الحجز) helpers. Slot times are Riyadh wall-clock
// "HH:mm"; Riyadh has no DST so a fixed +03:00 offset is always correct.
const riyadhTimeFmt = new Intl.DateTimeFormat("en-GB", {
  timeZone: "Asia/Riyadh",
  hour: "2-digit",
  minute: "2-digit",
  hourCycle: "h23",
});
function riyadhHHmm(iso: string): string {
  return riyadhTimeFmt.format(new Date(iso));
}
function riyadhYmd(iso: string): string {
  return new Intl.DateTimeFormat("en-CA", { timeZone: "Asia/Riyadh" }).format(
    new Date(iso),
  );
}
function slotToIso(date: string, startTime: string, plusMinutes = 0): string {
  const start = new Date(`${date}T${startTime}:00+03:00`);
  return new Date(start.getTime() + plusMinutes * 60_000).toISOString();
}
function fmtSlotRange(
  startTime: string,
  durationMinutes: number,
  isAr: boolean,
): string {
  const [h, m] = startTime.split(":").map(Number);
  const endTotal = h * 60 + m + durationMinutes;
  const eh = Math.floor(endTotal / 60) % 24;
  const em = endTotal % 60;
  const pad = (n: number) => String(n).padStart(2, "0");
  const to12 = (hh: number, mm: number) => {
    const period = hh < 12 ? (isAr ? "ص" : "AM") : isAr ? "م" : "PM";
    let h12 = hh % 12;
    if (h12 === 0) h12 = 12;
    return `${h12}:${pad(mm)} ${period}`;
  };
  return `${to12(h, m)} – ${to12(eh, em)}`;
}

function sameYmd(a: Date, b: Date): boolean {
  return (
    a.getFullYear() === b.getFullYear() &&
    a.getMonth() === b.getMonth() &&
    a.getDate() === b.getDate()
  );
}

function minutesOfDay(iso: string): number {
  const d = new Date(iso);
  return d.getHours() * 60 + d.getMinutes();
}

type PlacedBooking = {
  b: Booking;
  s: number; // start minutes-of-day
  e: number; // end minutes-of-day (>= s + 15)
  col: number;
  cols: number;
};

// Assign side-by-side columns to overlapping bookings (interval
// partitioning), clustering chained overlaps so each cluster shares a
// column count. View-only layout — no drag/resize.
function layoutDayBookings(items: Booking[]): PlacedBooking[] {
  const evs = items
    .map((b) => {
      const s = minutesOfDay(b.startsAt);
      const e = Math.max(minutesOfDay(b.endsAt), s + 15);
      return { b, s, e, col: 0, cols: 1 };
    })
    .sort((a, b) => a.s - b.s || a.e - b.e);

  const out: PlacedBooking[] = [];
  let i = 0;
  while (i < evs.length) {
    let j = i + 1;
    let clusterEnd = evs[i].e;
    const cluster = [evs[i]];
    while (j < evs.length && evs[j].s < clusterEnd) {
      cluster.push(evs[j]);
      clusterEnd = Math.max(clusterEnd, evs[j].e);
      j++;
    }
    const colEnds: number[] = [];
    for (const ev of cluster) {
      let placed = false;
      for (let c = 0; c < colEnds.length; c++) {
        if (colEnds[c] <= ev.s) {
          ev.col = c;
          colEnds[c] = ev.e;
          placed = true;
          break;
        }
      }
      if (!placed) {
        ev.col = colEnds.length;
        colEnds.push(ev.e);
      }
    }
    const cols = colEnds.length;
    for (const ev of cluster) {
      ev.cols = cols;
      out.push(ev);
    }
    i = j;
  }
  return out;
}

function BookingDayGrid({
  bookings,
  selectedDay,
  now,
  isAr,
  roomName,
  emptyText,
  scrollSignal = 0,
  onSelect,
  onSlotClick,
}: {
  bookings: Booking[];
  selectedDay: Date;
  now: Date;
  isAr: boolean;
  roomName: (id: number) => string;
  emptyText: string;
  scrollSignal?: number;
  onSelect?: (b: Booking) => void;
  onSlotClick?: (start: Date) => void;
}) {
  const hours: number[] = [];
  for (let h = GRID_HOUR_START; h <= GRID_HOUR_END; h++) hours.push(h);

  const dayItems = bookings.filter((b) =>
    sameYmd(new Date(b.startsAt), selectedDay),
  );
  const placed = layoutDayBookings(dayItems);

  const isToday = sameYmd(now, selectedDay);
  const nowMin = now.getHours() * 60 + now.getMinutes();
  const showNow =
    isToday && nowMin >= GRID_HOUR_START * 60 && nowMin <= GRID_HOUR_END * 60;
  const nowTop = ((nowMin - GRID_HOUR_START * 60) / 60) * GRID_PX_PER_HOUR;
  const gridHeight = (GRID_HOUR_END - GRID_HOUR_START) * GRID_PX_PER_HOUR;

  const hourLabel = (h: number) => {
    const d = new Date();
    d.setHours(h, 0, 0, 0);
    return new Intl.DateTimeFormat(isAr ? "ar-u-nu-latn" : "en-US", {
      hour: "numeric",
      hour12: true,
    }).format(d);
  };
  const timeLabel = (iso: string) =>
    new Intl.DateTimeFormat(isAr ? "ar-u-nu-latn" : "en-US", {
      hour: "numeric",
      minute: "2-digit",
      hour12: true,
    }).format(new Date(iso));

  // The grid scrolls inside its own fixed-height container so we can jump
  // straight to the red "now" line without touching the page scroll. When
  // the selected day is today, position the line ~1/3 from the top of the
  // viewport; for other days start at the top.
  const scrollRef = useRef<HTMLDivElement | null>(null);
  const dayKey = `${selectedDay.getFullYear()}-${selectedDay.getMonth()}-${selectedDay.getDate()}`;
  useEffect(() => {
    const el = scrollRef.current;
    if (!el) return;
    if (showNow) {
      el.scrollTop = Math.max(0, nowTop - el.clientHeight / 3);
    } else {
      el.scrollTop = 0;
    }
    // Re-run only when the day changes (or the now-line appears/disappears),
    // not on every minute tick — so we never fight the user's scrolling.
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [dayKey, showNow, scrollSignal]);

  return (
    <div>
      {dayItems.length === 0 && (
        <div className="mb-2 text-center text-xs text-slate-400">
          {emptyText}
        </div>
      )}
      <div
        ref={scrollRef}
        className="max-h-[60vh] overflow-y-auto overscroll-contain"
      >
        <div className="flex" style={{ height: gridHeight + 8 }}>
        <div className="relative w-14 shrink-0" style={{ height: gridHeight }}>
          {hours.map((h) => (
            <div
              key={h}
              className="absolute inset-x-0 -translate-y-1/2 pe-1 text-end text-[10px] text-slate-400"
              style={{ top: (h - GRID_HOUR_START) * GRID_PX_PER_HOUR }}
            >
              {hourLabel(h)}
            </div>
          ))}
        </div>
        <div
          className={cn(
            "relative flex-1 border-s border-slate-200",
            onSlotClick && "cursor-pointer",
          )}
          style={{ height: gridHeight }}
          onClick={(e) => {
            if (!onSlotClick) return;
            // Only clicks on the empty grid itself — booking blocks stop
            // propagation in their own handler.
            const rect = e.currentTarget.getBoundingClientRect();
            const offsetY = e.clientY - rect.top;
            const rawMin =
              GRID_HOUR_START * 60 + (offsetY / GRID_PX_PER_HOUR) * 60;
            // Snap down to the nearest 30 minutes, keep a full hour inside
            // the visible grid range.
            const snapped = Math.floor(rawMin / 30) * 30;
            const startMin = Math.min(
              Math.max(snapped, GRID_HOUR_START * 60),
              GRID_HOUR_END * 60 - 60,
            );
            const start = new Date(selectedDay);
            start.setHours(Math.floor(startMin / 60), startMin % 60, 0, 0);
            onSlotClick(start);
          }}
        >
          {hours.map((h) => (
            <div
              key={h}
              className="absolute inset-x-0 border-t border-slate-100"
              style={{ top: (h - GRID_HOUR_START) * GRID_PX_PER_HOUR }}
            />
          ))}
          {placed.map((ev) => {
            const topM = Math.max(ev.s, GRID_HOUR_START * 60);
            const botM = Math.min(ev.e, GRID_HOUR_END * 60);
            // Skip bookings with no visible intersection with the grid range.
            if (botM <= topM) return null;
            const top = ((topM - GRID_HOUR_START * 60) / 60) * GRID_PX_PER_HOUR;
            const height = Math.max(
              ((botM - topM) / 60) * GRID_PX_PER_HOUR,
              22,
            );
            const widthPct = 100 / ev.cols;
            return (
              <button
                key={ev.b.id}
                type="button"
                onClick={(e) => {
                  e.stopPropagation();
                  onSelect?.(ev.b);
                }}
                className={cn(
                  "absolute overflow-hidden rounded-md border-s-4 px-1.5 py-0.5 text-start text-[11px] leading-tight shadow-sm transition-shadow",
                  "cursor-pointer hover:shadow-md focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-sky-500",
                  BOOKING_BLOCK_STYLE[ev.b.status] ??
                    "bg-slate-100 border-slate-300 text-slate-700",
                )}
                style={{
                  top,
                  height,
                  insetInlineStart: `calc(${ev.col * widthPct}% + 2px)`,
                  width: `calc(${widthPct}% - 4px)`,
                }}
                title={`${ev.b.title} — ${roomName(ev.b.roomId)}`}
                aria-label={`${ev.b.title} — ${roomName(ev.b.roomId)} — ${timeLabel(ev.b.startsAt)} – ${timeLabel(ev.b.endsAt)}`}
                data-testid={`calendar-booking-${ev.b.id}`}
              >
                <div className="truncate font-medium">{ev.b.title}</div>
                <div className="truncate opacity-80">
                  {roomName(ev.b.roomId)}
                </div>
                {height >= 44 && (
                  <div className="truncate opacity-70" dir="ltr">
                    {timeLabel(ev.b.startsAt)} – {timeLabel(ev.b.endsAt)}
                  </div>
                )}
              </button>
            );
          })}
          {showNow && (
            <div
              className="pointer-events-none absolute inset-x-0 z-10"
              style={{ top: nowTop }}
            >
              <div className="relative border-t-2 border-red-500">
                <div
                  className="absolute -top-[5px] h-2.5 w-2.5 rounded-full bg-red-500"
                  style={{ insetInlineStart: -5 }}
                />
              </div>
            </div>
          )}
        </div>
        </div>
      </div>
    </div>
  );
}

export default function ProtocolPage() {
  const { t, i18n } = useTranslation();
  const lang = i18n.language;
  const isAr = lang === "ar";
  const [, setLocation] = useLocation();
  const { toast } = useToast();
  const qc = useQueryClient();
  const [matchTab, tabParams] = useRoute("/protocol/:tab");
  const tab: TabKey =
    (matchTab && SLUG_TABS[tabParams?.tab ?? ""]) || "dashboard";
  const setTab = (k: TabKey) => setLocation(`/protocol/${TAB_SLUGS[k]}`);

  const nameOf = (ar: string, en: string) => (isAr ? ar : en || ar);

  const caps = useQuery<Capabilities>({
    queryKey: ["protocol", "me"],
    queryFn: () => apiJson<Capabilities>(`${API}/protocol/me`),
  });

  const rooms = useQuery<Room[]>({
    queryKey: ["protocol", "rooms"],
    queryFn: () => apiJson<Room[]>(`${API}/protocol/rooms`),
  });
  // Active booking slots (أوقات الحجز) — minimal read available to every
  // protocol user; the booking dialog restricts times to these whenever at
  // least one is configured. The full admin list lives in SlotsAdminSection
  // (requires rooms-manage permission).
  const slots = useQuery<ActiveSlotInfo[]>({
    queryKey: ["protocol", "slots"],
    queryFn: () =>
      apiJson<ActiveSlotInfo[]>(`${API}/protocol/booking-slots/active`),
  });
  const [bookingSearch, setBookingSearch] = useState("");
  const [debouncedBookingSearch, setDebouncedBookingSearch] = useState("");
  useEffect(() => {
    const id = setTimeout(
      () => setDebouncedBookingSearch(bookingSearch.trim()),
      300,
    );
    return () => clearTimeout(id);
  }, [bookingSearch]);
  const bookings = useQuery<Booking[]>({
    queryKey: ["protocol", "bookings", debouncedBookingSearch],
    queryFn: () =>
      apiJson<Booking[]>(
        `${API}/protocol/bookings${
          debouncedBookingSearch
            ? `?q=${encodeURIComponent(debouncedBookingSearch)}`
            : ""
        }`,
      ),
  });
  const external = useQuery<ExternalMeeting[]>({
    queryKey: ["protocol", "external"],
    queryFn: () =>
      apiJson<ExternalMeeting[]>(`${API}/protocol/external-meetings`),
  });
  const gifts = useQuery<Gift[]>({
    queryKey: ["protocol", "gifts"],
    queryFn: () => apiJson<Gift[]>(`${API}/protocol/gifts`),
  });
  const issues = useQuery<GiftIssue[]>({
    queryKey: ["protocol", "issues"],
    queryFn: () => apiJson<GiftIssue[]>(`${API}/protocol/gift-issues`),
  });
  const dashboard = useQuery<Dashboard>({
    queryKey: ["protocol", "dashboard"],
    queryFn: () => apiJson<Dashboard>(`${API}/protocol/dashboard`),
  });
  const reports = useQuery<ReportRow>({
    queryKey: ["protocol", "reports"],
    queryFn: () => apiJson<ReportRow>(`${API}/protocol/reports`),
    enabled: tab === "reports",
  });
  const audit = useQuery<
    Array<{
      id: number;
      action: string;
      entityType: string;
      entityId: number | null;
      performedAt: string;
      actorNameAr: string | null;
      actorNameEn: string | null;
      actorUsername: string | null;
    }>
  >({
    queryKey: ["protocol", "audit"],
    queryFn: () => apiJson(`${API}/protocol/audit`),
    enabled: tab === "audit",
  });

  const c = caps.data;
  const roomName = (id: number) => {
    const r = rooms.data?.find((x) => x.id === id);
    return r ? nameOf(r.nameAr, r.nameEn) : `#${id}`;
  };
  const giftName = (id: number) => {
    const g = gifts.data?.find((x) => x.id === id);
    return g ? nameOf(g.nameAr, g.nameEn) : `#${id}`;
  };

  const invalidate = (...keys: string[]) => {
    for (const k of keys) qc.invalidateQueries({ queryKey: ["protocol", k] });
    qc.invalidateQueries({ queryKey: ["protocol", "dashboard"] });
  };

  const fmtDateTime = (iso: string | null) => {
    if (!iso) return "—";
    return new Date(iso).toLocaleString(isAr ? "ar" : "en", {
      dateStyle: "medium",
      timeStyle: "short",
    });
  };

  const onApiError = (e: unknown) => {
    const msg =
      e instanceof ApiError ? e.message : t("protocol.errors.generic");
    toast({ title: msg, variant: "destructive" });
  };

  // ---- Booking dialog state ----
  const [bookingDialog, setBookingDialog] = useState<{
    open: boolean;
    edit?: Booking;
    day?: Date;
    start?: Date;
  }>({ open: false });
  const [rejectTarget, setRejectTarget] = useState<{
    kind: "booking" | "issue" | "external";
    id: number;
  } | null>(null);
  const [bookingsView, setBookingsView] = useState<"list" | "calendar">("list");
  const [expandedBookings, setExpandedBookings] = useState<Set<number>>(
    () => new Set(),
  );
  const toggleBookingExpanded = (id: number) =>
    setExpandedBookings((prev) => {
      const next = new Set(prev);
      if (next.has(id)) next.delete(id);
      else next.add(id);
      return next;
    });
  // Bulk selection of bookings (list view, canMutate only).
  const [selectedBookings, setSelectedBookings] = useState<Set<number>>(
    () => new Set(),
  );
  const [bulkDeleteOpen, setBulkDeleteOpen] = useState(false);
  const toggleBookingSelected = (id: number) =>
    setSelectedBookings((prev) => {
      const next = new Set(prev);
      if (next.has(id)) next.delete(id);
      else next.add(id);
      return next;
    });
  // Clear selection whenever the visible list changes (search, view, tab).
  useEffect(() => {
    setSelectedBookings(new Set());
  }, [debouncedBookingSearch, bookingsView, tab]);
  const [selectedDay, setSelectedDay] = useState<Date>(() => new Date());
  // Bumped by the "اليوم" button to re-scroll the day grid to the now-line
  // even when the selected day is already today.
  const [todayScrollSignal, setTodayScrollSignal] = useState(0);
  // Booking selected from the calendar day grid → read-only details dialog.
  const [detailsBooking, setDetailsBooking] = useState<Booking | null>(null);
  const [calendarMonth, setCalendarMonth] = useState<Date>(() => new Date());
  const now = useNow(60000);
  const bookedDates = useMemo(() => {
    const map = new Map<string, Date>();
    for (const b of bookings.data ?? []) {
      const d = new Date(b.startsAt);
      const key = `${d.getFullYear()}-${d.getMonth()}-${d.getDate()}`;
      if (!map.has(key))
        map.set(key, new Date(d.getFullYear(), d.getMonth(), d.getDate()));
    }
    return Array.from(map.values());
  }, [bookings.data]);
  const publicBookingUrl = `${window.location.origin}${import.meta.env.BASE_URL.replace(/\/$/, "")}/protocol/request`;
  const [linkCopied, setLinkCopied] = useState(false);
  const linkCopiedTimer = useRef<number | null>(null);
  const copyBookingLink = async () => {
    try {
      if (navigator.clipboard?.writeText) {
        await navigator.clipboard.writeText(publicBookingUrl);
      } else {
        const ta = document.createElement("textarea");
        ta.value = publicBookingUrl;
        ta.style.position = "fixed";
        ta.style.opacity = "0";
        document.body.appendChild(ta);
        ta.focus();
        ta.select();
        let ok = false;
        try {
          ok = document.execCommand("copy");
        } finally {
          document.body.removeChild(ta);
        }
        if (!ok) throw new Error("execCommand copy failed");
      }
      setLinkCopied(true);
      toast({ title: t("protocol.bookings.linkCopied") });
      if (linkCopiedTimer.current) window.clearTimeout(linkCopiedTimer.current);
      linkCopiedTimer.current = window.setTimeout(
        () => setLinkCopied(false),
        2000,
      );
    } catch {
      toast({
        title: t("protocol.bookings.linkCopyFailed"),
        variant: "destructive",
      });
    }
  };
  const [deleteTarget, setDeleteTarget] = useState<
    | { kind: "room" | "booking" | "external" | "gift" | "issue"; id: number }
    | null
  >(null);
  const [roomDialog, setRoomDialog] = useState<{
    open: boolean;
    edit?: Room;
  }>({ open: false });
  const [externalDialog, setExternalDialog] = useState<{
    open: boolean;
    edit?: ExternalMeeting;
  }>({ open: false });
  const [giftDialog, setGiftDialog] = useState<{
    open: boolean;
    edit?: Gift;
  }>({ open: false });
  const [issueDialog, setIssueDialog] = useState<{ open: boolean }>({
    open: false,
  });
  const [giftsGroupOpen, setGiftsGroupOpen] = useState(false);
  const [roomsGroupOpen, setRoomsGroupOpen] = useState(false);

  const bookingAction = useMutation({
    mutationFn: (args: { id: number; action: "approve" | "cancel" }) =>
      apiJson(`${API}/protocol/bookings/${args.id}/${args.action}`, {
        method: "POST",
        body: JSON.stringify({}),
      }),
    onSuccess: () => invalidate("bookings"),
    onError: onApiError,
  });
  const rejectMut = useMutation({
    mutationFn: (args: { kind: "booking" | "issue" | "external"; id: number }) => {
      const path =
        args.kind === "booking"
          ? "bookings"
          : args.kind === "issue"
            ? "gift-issues"
            : "external-meetings";
      return apiJson(`${API}/protocol/${path}/${args.id}/reject`, {
        method: "POST",
        body: JSON.stringify({}),
      });
    },
    onSuccess: () => {
      setRejectTarget(null);
      invalidate("bookings", "issues", "external");
    },
    onError: onApiError,
  });
  const externalApprove = useMutation({
    mutationFn: (id: number) =>
      apiJson(`${API}/protocol/external-meetings/${id}/approve`, {
        method: "POST",
        body: JSON.stringify({}),
      }),
    onSuccess: () => invalidate("external"),
    onError: onApiError,
  });
  const issueApprove = useMutation({
    mutationFn: (id: number) =>
      apiJson(`${API}/protocol/gift-issues/${id}/approve`, {
        method: "POST",
        body: JSON.stringify({}),
      }),
    onSuccess: () => invalidate("issues", "gifts"),
    onError: onApiError,
  });
  const deleteMut = useMutation({
    mutationFn: (target: { kind: string; id: number }) => {
      const path =
        target.kind === "room"
          ? "rooms"
          : target.kind === "booking"
            ? "bookings"
            : target.kind === "external"
              ? "external-meetings"
              : target.kind === "gift"
                ? "gifts"
                : "gift-issues";
      return apiJson(`${API}/protocol/${path}/${target.id}`, {
        method: "DELETE",
      });
    },
    onSuccess: () => {
      setDeleteTarget(null);
      invalidate("rooms", "bookings", "external", "gifts", "issues");
    },
    onError: (e) => {
      setDeleteTarget(null);
      onApiError(e);
    },
  });

  const bulkDeleteMut = useMutation({
    mutationFn: (ids: number[]) =>
      apiJson<{ requested: number; deleted: number; deletedIds: number[] }>(
        `${API}/protocol/bookings/bulk-delete`,
        { method: "POST", body: JSON.stringify({ ids }) },
      ),
    onSuccess: (data, ids) => {
      setBulkDeleteOpen(false);
      setSelectedBookings(new Set());
      if (data.deleted < ids.length) {
        toast({
          title: t("protocol.bookings.bulk.deletePartial", {
            deleted: data.deleted,
            total: ids.length,
          }),
        });
      } else {
        toast({
          title: t("protocol.bookings.bulk.deleted", { count: data.deleted }),
        });
      }
      invalidate("bookings");
    },
    onError: (e) => {
      setBulkDeleteOpen(false);
      onApiError(e);
    },
  });

  const back = () => setLocation("/");

  const TABS: Array<{ key: TabKey; label: string; icon: typeof Gift }> = [
    { key: "dashboard", label: t("protocol.tabs.dashboard"), icon: LayoutDashboard },
    { key: "roomsDashboard", label: t("protocol.tabs.roomsDashboard"), icon: LayoutDashboard },
    { key: "rooms", label: t("protocol.tabs.rooms"), icon: DoorOpen },
    { key: "bookings", label: t("protocol.tabs.bookings"), icon: CalendarClock },
    ...(c?.canManageRooms
      ? [
          {
            key: "bookingSettings" as TabKey,
            label: t("protocol.tabs.bookingSettings"),
            icon: SettingsIcon,
          },
        ]
      : []),
    { key: "external", label: t("protocol.tabs.external"), icon: Handshake },
    { key: "gifts", label: t("protocol.tabs.gifts"), icon: Warehouse },
    { key: "issues", label: t("protocol.tabs.issues"), icon: Gift },
    { key: "photos", label: t("protocol.tabs.photos"), icon: ImageIcon },
    { key: "reports", label: t("protocol.tabs.reports"), icon: BarChart3 },
    ...(c?.canViewAudit
      ? [{ key: "audit" as TabKey, label: t("protocol.tabs.audit"), icon: ScrollText }]
      : []),
  ];

  const BackIcon = isAr ? ArrowRight : ArrowLeft;

  const bookingNumber = (b: Booking) => {
    const digits = (b.bookingReference ?? "")
      .replace(/[^\d-]/g, "")
      .replace(/^-+|-+$/g, "");
    return digits || String(b.id).padStart(4, "0");
  };

  const fmtDateOnly = (iso: string) =>
    new Date(iso).toLocaleDateString(isAr ? "ar" : "en", {
      dateStyle: "medium",
    });
  const fmtTimeOnly = (iso: string) =>
    new Date(iso).toLocaleTimeString(isAr ? "ar" : "en", {
      timeStyle: "short",
    });

  const renderBookingRow = (b: Booking, index: number) => {
    const expanded = expandedBookings.has(b.id);
    const bookingIsToday = sameYmd(new Date(b.startsAt), now);
    const detailColSpan = c?.canMutate ? 9 : 8;
    return (
      <>
        <TableRow
          key={b.id}
          onClick={() => toggleBookingExpanded(b.id)}
          aria-expanded={expanded}
          className={cn(
            "cursor-pointer",
            bookingIsToday
              ? "bg-emerald-50/60 hover:bg-emerald-50"
              : "odd:bg-white even:bg-slate-50/60",
          )}
          data-testid={`booking-row-${b.id}`}
        >
          {c?.canMutate && (
            <TableCell
              className="w-8 text-center align-middle"
              onClick={(e) => e.stopPropagation()}
            >
              <input
                type="checkbox"
                className="h-4 w-4 cursor-pointer accent-sky-600 align-middle"
                checked={selectedBookings.has(b.id)}
                onChange={() => toggleBookingSelected(b.id)}
                aria-label={t("protocol.bookings.bulk.selectRow", {
                  number: bookingNumber(b),
                })}
                data-testid={`booking-select-${b.id}`}
              />
            </TableCell>
          )}
          <TableCell className="w-10 text-center align-middle">
            <span className="mx-auto flex h-6 w-6 items-center justify-center rounded-full bg-slate-100 text-xs font-semibold text-slate-600">
              {index}
            </span>
          </TableCell>
          <TableCell className="whitespace-nowrap text-center align-middle font-semibold text-sky-700">
            <div className="flex flex-col items-center gap-0.5">
              <span dir="ltr">{bookingNumber(b)}</span>
              {bookingIsToday && (
                <span className="text-[10px] px-1.5 py-0.5 rounded-full bg-emerald-100 text-emerald-700 font-normal">
                  {t("protocol.bookings.today")}
                </span>
              )}
            </div>
          </TableCell>
          <TableCell className="min-w-0 text-center align-middle">
            <div className="flex flex-col items-center gap-0.5">
              <div className="font-medium text-slate-800 truncate max-w-[180px]">
                {b.requesterName || "—"}
              </div>
              {b.source === "public" && (
                <span className="text-[10px] px-1.5 py-0.5 rounded-full bg-sky-100 text-sky-700">
                  {t("protocol.bookings.publicRequest")}
                </span>
              )}
            </div>
          </TableCell>
          <TableCell className="whitespace-nowrap text-center align-middle text-slate-600">
            <div className="flex flex-col items-center gap-0.5 leading-tight">
              <div>{fmtDateOnly(b.startsAt)}</div>
              <div className="text-xs text-slate-400" dir="ltr">
                {fmtTimeOnly(b.startsAt)} – {fmtTimeOnly(b.endsAt)}
              </div>
            </div>
          </TableCell>
          <TableCell className="whitespace-nowrap text-center align-middle">
            <span
              className={cn(
                "text-xs px-2 py-0.5 rounded-full",
                b.meetingType === "external"
                  ? "bg-violet-100 text-violet-700"
                  : "bg-slate-100 text-slate-600",
              )}
            >
              {t(`protocol.bookings.meetingTypesShort.${b.meetingType}`)}
            </span>
          </TableCell>
          <TableCell className="text-center align-middle text-slate-600">
            <div className="flex flex-col items-center gap-0.5 leading-tight">
              <div className="truncate max-w-[160px]">{roomName(b.roomId)}</div>
              {b.attendeeCount != null && (
                <div className="text-xs text-slate-400">
                  {t("protocol.bookings.table.attendeesShort")}:{" "}
                  {b.attendeeCount}
                </div>
              )}
            </div>
          </TableCell>
          <TableCell className="whitespace-nowrap text-center align-middle">
            <span
              className={cn(
                "text-xs px-2 py-0.5 rounded-full",
                STATUS_PILL[b.status],
              )}
            >
              {t(`protocol.status.${b.status}`)}
            </span>
          </TableCell>
          <TableCell
            className="whitespace-nowrap text-center align-middle"
            onClick={(e) => e.stopPropagation()}
          >
            <div className="flex items-center justify-center gap-0.5">
              {c?.canApprove && b.status === "pending" && (
                <>
                  <Button
                    size="sm"
                    variant="ghost"
                    className="h-7 w-7 p-0 text-emerald-600"
                    title={t("protocol.actions.approve")}
                    aria-label={t("protocol.actions.approve")}
                    onClick={() =>
                      bookingAction.mutate({ id: b.id, action: "approve" })
                    }
                  >
                    <Check size={14} />
                  </Button>
                  <Button
                    size="sm"
                    variant="ghost"
                    className="h-7 w-7 p-0 text-rose-500"
                    title={t("protocol.actions.reject")}
                    aria-label={t("protocol.actions.reject")}
                    onClick={() =>
                      setRejectTarget({ kind: "booking", id: b.id })
                    }
                  >
                    <X size={14} />
                  </Button>
                </>
              )}
              {c?.canMutate &&
                (b.status === "pending" || b.status === "approved") && (
                  <Button
                    size="sm"
                    variant="ghost"
                    className="h-7 w-7 p-0 text-slate-500"
                    title={t("protocol.actions.cancel")}
                    aria-label={t("protocol.actions.cancel")}
                    onClick={() =>
                      bookingAction.mutate({ id: b.id, action: "cancel" })
                    }
                  >
                    <Ban size={14} />
                  </Button>
                )}
              {c?.canMutate && (
                <>
                  <Button
                    size="sm"
                    variant="ghost"
                    className="h-7 w-7 p-0"
                    title={t("protocol.bookings.edit")}
                    aria-label={t("protocol.bookings.edit")}
                    onClick={() => setBookingDialog({ open: true, edit: b })}
                  >
                    <Pencil size={14} />
                  </Button>
                  <Button
                    size="sm"
                    variant="ghost"
                    className="h-7 w-7 p-0"
                    title={t("common.delete")}
                    aria-label={t("common.delete")}
                    onClick={() =>
                      setDeleteTarget({ kind: "booking", id: b.id })
                    }
                  >
                    <Trash2 size={14} className="text-rose-500" />
                  </Button>
                </>
              )}
              <ChevronDown
                size={14}
                className={cn(
                  "ms-1 text-slate-400 transition-transform cursor-pointer",
                  expanded && "rotate-180",
                )}
                onClick={() => toggleBookingExpanded(b.id)}
              />
            </div>
          </TableCell>
        </TableRow>
        {expanded && (
          <TableRow key={`${b.id}-details`} className="bg-slate-50/80">
            <TableCell colSpan={detailColSpan} className="py-3">
              <div className="space-y-1">
                <div className="text-sm text-slate-700 font-semibold">
                  {b.title}
                </div>
                <div className="grid gap-x-6 gap-y-1 sm:grid-cols-2 lg:grid-cols-3 text-xs">
                  {(
                    [
                      [t("protocol.bookings.requester"), b.requesterName],
                      [t("protocol.bookings.department"), b.department],
                      [
                        t("protocol.bookings.meetingType"),
                        t(`protocol.bookings.meetingTypes.${b.meetingType}`),
                      ],
                      ...(b.meetingType === "external"
                        ? [
                            [
                              t("protocol.bookings.entity"),
                              b.requesterOrg,
                            ] as const,
                          ]
                        : []),
                      [
                        t("protocol.bookings.phone"),
                        b.requesterPhone ? (
                          <span dir="ltr">{b.requesterPhone}</span>
                        ) : null,
                      ],
                      [
                        t("protocol.bookings.attendeeCount"),
                        b.attendeeCount != null ? b.attendeeCount : null,
                      ],
                      [t("protocol.bookings.purpose"), b.purpose],
                      [
                        t("protocol.bookings.reference"),
                        b.bookingReference ? (
                          <span dir="ltr">{b.bookingReference}</span>
                        ) : null,
                      ],
                    ] as Array<[string, React.ReactNode]>
                  ).map(([label, value], i) => (
                    <div key={i} className="flex gap-1">
                      <span className="text-slate-400 shrink-0">
                        {label}:
                      </span>
                      <span className="text-slate-600 min-w-0 break-words">
                        {value != null && value !== "" ? value : "—"}
                      </span>
                    </div>
                  ))}
                </div>
                <div className="text-xs">
                  <span className="text-slate-400 font-medium">
                    {t("protocol.bookings.keyAttendees")}:
                  </span>
                  {b.keyAttendees && b.keyAttendees.length > 0 ? (
                    <div className="mt-1.5 max-w-2xl">
                      <AttendeesTable attendees={b.keyAttendees} t={t} />
                    </div>
                  ) : (
                    <span className="ms-1 text-slate-600">—</span>
                  )}
                </div>
                {b.status === "rejected" && b.rejectionReason && (
                  <div className="text-xs text-rose-500">
                    {b.rejectionReason}
                  </div>
                )}
              </div>
            </TableCell>
          </TableRow>
        )}
      </>
    );
  };

  const renderBookingsTable = (list: Booking[]) => {
    const selectedInList = list.filter((b) => selectedBookings.has(b.id));
    const allSelected =
      list.length > 0 && selectedInList.length === list.length;
    const someSelected = selectedInList.length > 0 && !allSelected;
    return (
      <div className="space-y-2">
        {c?.canMutate && selectedInList.length > 0 && (
          <div className="flex flex-wrap items-center gap-2 rounded-xl border border-sky-200 bg-sky-50 px-3 py-2">
            <span className="text-sm font-medium text-sky-800">
              {t("protocol.bookings.bulk.selectedCount", {
                count: selectedInList.length,
              })}
            </span>
            <div className="ms-auto flex items-center gap-2">
              <Button
                size="sm"
                variant="outline"
                onClick={() => setSelectedBookings(new Set())}
              >
                {t("protocol.bookings.bulk.clearSelection")}
              </Button>
              <Button
                size="sm"
                variant="destructive"
                onClick={() => setBulkDeleteOpen(true)}
                disabled={bulkDeleteMut.isPending}
                data-testid="booking-bulk-delete"
              >
                <Trash2 size={14} className="me-1" />
                {t("protocol.bookings.bulk.delete")}
              </Button>
            </div>
          </div>
        )}
        <div className="rounded-xl border border-slate-200 bg-white overflow-hidden">
          <Table>
            <TableHeader>
              <TableRow className="bg-slate-100/80 hover:bg-slate-100/80">
                {c?.canMutate && (
                  <TableHead className="w-8 text-center">
                    <input
                      type="checkbox"
                      className="h-4 w-4 cursor-pointer accent-sky-600 align-middle"
                      checked={allSelected}
                      ref={(el) => {
                        if (el) el.indeterminate = someSelected;
                      }}
                      onChange={() =>
                        setSelectedBookings(
                          allSelected
                            ? new Set()
                            : new Set(list.map((b) => b.id)),
                        )
                      }
                      aria-label={t("protocol.bookings.bulk.selectAll")}
                      title={t("protocol.bookings.bulk.selectAll")}
                      data-testid="booking-select-all"
                    />
                  </TableHead>
                )}
                <TableHead className="w-10 text-center">#</TableHead>
            <TableHead className="text-center whitespace-nowrap">
              {t("protocol.bookings.table.number")}
            </TableHead>
            <TableHead className="text-center whitespace-nowrap">
              {t("protocol.bookings.table.requester")}
            </TableHead>
            <TableHead className="text-center whitespace-nowrap">
              {t("protocol.bookings.table.date")}
            </TableHead>
            <TableHead className="text-center whitespace-nowrap">
              {t("protocol.bookings.table.type")}
            </TableHead>
            <TableHead className="text-center whitespace-nowrap">
              {t("protocol.bookings.table.roomAttendees")}
            </TableHead>
            <TableHead className="text-center whitespace-nowrap">
              {t("protocol.bookings.table.status")}
            </TableHead>
            <TableHead className="text-center whitespace-nowrap">
              {t("protocol.bookings.table.actions")}
            </TableHead>
              </TableRow>
            </TableHeader>
            <TableBody>
              {list.map((b, i) => (
                <Fragment key={b.id}>{renderBookingRow(b, i + 1)}</Fragment>
              ))}
            </TableBody>
          </Table>
        </div>
      </div>
    );
  };

  return (
    <div className="min-h-screen bg-slate-50" dir={isAr ? "rtl" : "ltr"}>
      <header className="sticky top-0 z-10 bg-white border-b border-slate-200">
        <div className="max-w-6xl mx-auto px-4 py-3 flex items-center gap-3">
          <Button variant="ghost" size="icon" onClick={back} aria-label={t("common.back")}>
            <BackIcon size={20} />
          </Button>
          <div className="flex items-center gap-2 min-w-0">
            <div className="h-9 w-9 rounded-xl bg-sky-500 text-white flex items-center justify-center shrink-0">
              <Handshake size={20} />
            </div>
            <h1 className="text-lg font-bold text-slate-800 truncate">
              {t("protocol.title")}
            </h1>
          </div>
        </div>
      </header>

      <div className="max-w-6xl mx-auto px-4 py-5 flex flex-col md:flex-row gap-4 md:gap-6">
        <aside className="md:w-56 shrink-0">
          <nav className="flex md:flex-col gap-1 overflow-x-auto md:overflow-visible pb-2 md:pb-0 md:sticky md:top-20">
            {TABS.map((item) => {
              const { key, label, icon: Icon } = item;
              if (key === "issues") return null;
              if (
                key === "rooms" ||
                key === "bookings" ||
                key === "bookingSettings"
              ) {
                return null;
              }
              if (key === "roomsDashboard") {
                // "إدارة القاعات" collapsible group (same pattern as gifts).
                const children = TABS.filter(
                  (x) =>
                    x.key === "roomsDashboard" ||
                    x.key === "rooms" ||
                    x.key === "bookings" ||
                    x.key === "bookingSettings",
                );
                const groupActive = children.some((x) => x.key === tab);
                const open = roomsGroupOpen || groupActive;
                return (
                  <div key="roomsGroup" className="flex md:flex-col gap-1">
                    <button
                      type="button"
                      onClick={() => setRoomsGroupOpen((v) => !v)}
                      aria-expanded={open}
                      className={cn(
                        "flex items-center gap-2 px-3 py-2 rounded-lg text-sm whitespace-nowrap transition-colors md:w-full md:justify-start",
                        groupActive
                          ? "text-sky-600 font-medium"
                          : "text-slate-600 hover:bg-slate-100",
                      )}
                    >
                      <DoorOpen size={16} className="shrink-0" />
                      {t("protocol.tabs.roomsGroup")}
                      <ChevronDown
                        size={14}
                        className={cn(
                          "ms-auto shrink-0 transition-transform",
                          open && "rotate-180",
                        )}
                      />
                    </button>
                    {open &&
                      children.map(({ key: ck, label: cl, icon: CIcon }) => (
                        <button
                          key={ck}
                          onClick={() => setTab(ck)}
                          className={cn(
                            "flex items-center gap-2 px-3 py-2 rounded-lg text-sm whitespace-nowrap transition-colors md:w-full md:justify-start md:ps-6",
                            tab === ck
                              ? "bg-sky-500 text-white"
                              : "text-slate-600 hover:bg-slate-100",
                          )}
                        >
                          <CIcon size={16} className="shrink-0" />
                          {cl}
                        </button>
                      ))}
                  </div>
                );
              }
              if (key === "gifts") {
                const children = TABS.filter(
                  (x) => x.key === "gifts" || x.key === "issues",
                );
                const groupActive = children.some((x) => x.key === tab);
                const open = giftsGroupOpen || groupActive;
                return (
                  <div key="giftsGroup" className="flex md:flex-col gap-1">
                    <button
                      type="button"
                      onClick={() => setGiftsGroupOpen((v) => !v)}
                      aria-expanded={open}
                      className={cn(
                        "flex items-center gap-2 px-3 py-2 rounded-lg text-sm whitespace-nowrap transition-colors md:w-full md:justify-start",
                        groupActive
                          ? "text-sky-600 font-medium"
                          : "text-slate-600 hover:bg-slate-100",
                      )}
                    >
                      <HandHeart size={16} className="shrink-0" />
                      {t("protocol.tabs.giftsGroup")}
                      <ChevronDown
                        size={14}
                        className={cn(
                          "ms-auto shrink-0 transition-transform",
                          open && "rotate-180",
                        )}
                      />
                    </button>
                    {open &&
                      children.map(({ key: ck, label: cl, icon: CIcon }) => (
                        <button
                          key={ck}
                          onClick={() => setTab(ck)}
                          className={cn(
                            "flex items-center gap-2 px-3 py-2 rounded-lg text-sm whitespace-nowrap transition-colors md:w-full md:justify-start md:ps-6",
                            tab === ck
                              ? "bg-sky-500 text-white"
                              : "text-slate-600 hover:bg-slate-100",
                          )}
                        >
                          <CIcon size={16} className="shrink-0" />
                          {cl}
                        </button>
                      ))}
                  </div>
                );
              }
              return (
                <button
                  key={key}
                  onClick={() => setTab(key)}
                  className={cn(
                    "flex items-center gap-2 px-3 py-2 rounded-lg text-sm whitespace-nowrap transition-colors md:w-full md:justify-start",
                    tab === key
                      ? "bg-sky-500 text-white"
                      : "text-slate-600 hover:bg-slate-100",
                  )}
                >
                  <Icon size={16} className="shrink-0" />
                  {label}
                </button>
              );
            })}
          </nav>
        </aside>

        <main className="flex-1 min-w-0">
        {tab === "dashboard" && (
          <DashboardView data={dashboard.data} t={t} goTab={setTab} />
        )}

        {tab === "bookings" && (
          <section className="space-y-4">
            <div className="flex items-center justify-between">
              <h2 className="font-semibold text-slate-700">
                {t("protocol.tabs.bookings")}
              </h2>
              <div className="flex items-center gap-2">
                <div className="flex rounded-lg border border-slate-200 overflow-hidden">
                  <button
                    type="button"
                    onClick={() => setBookingsView("list")}
                    className={cn(
                      "px-2.5 py-1.5 text-xs flex items-center gap-1",
                      bookingsView === "list"
                        ? "bg-sky-500 text-white"
                        : "text-slate-600 hover:bg-slate-100",
                    )}
                  >
                    <ListIcon size={14} />
                    {t("protocol.bookings.viewList")}
                  </button>
                  <button
                    type="button"
                    onClick={() => setBookingsView("calendar")}
                    className={cn(
                      "px-2.5 py-1.5 text-xs flex items-center gap-1",
                      bookingsView === "calendar"
                        ? "bg-sky-500 text-white"
                        : "text-slate-600 hover:bg-slate-100",
                    )}
                  >
                    <CalendarDays size={14} />
                    {t("protocol.bookings.viewCalendar")}
                  </button>
                </div>
                {c?.canRequest && (
                  <Button
                    size="sm"
                    onClick={() => setBookingDialog({ open: true })}
                  >
                    <Plus size={16} className="me-1" />
                    {t("protocol.bookings.new")}
                  </Button>
                )}
              </div>
            </div>
            <div className="rounded-lg border border-sky-200 bg-sky-50/60 p-3">
              <div className="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
                <div className="flex min-w-0 items-center gap-2">
                  <Link2 size={16} className="shrink-0 text-sky-600" />
                  <p className="text-xs font-medium text-slate-700">
                    {t("protocol.bookings.shareLinkTitle")}
                  </p>
                </div>
                <div className="flex shrink-0 items-center gap-2 self-start sm:self-auto">
                  <Button
                    size="sm"
                    variant="outline"
                    onClick={() =>
                      window.open(publicBookingUrl, "_blank", "noopener,noreferrer")
                    }
                  >
                    <ExternalLink size={16} className="me-1" />
                    {t("protocol.bookings.openLink")}
                  </Button>
                  <Button size="sm" variant="outline" onClick={copyBookingLink}>
                    {linkCopied ? (
                      <Check size={16} className="me-1 text-emerald-600" />
                    ) : (
                      <Copy size={16} className="me-1" />
                    )}
                    {linkCopied
                      ? t("protocol.bookings.linkCopied")
                      : t("protocol.bookings.copyLink")}
                  </Button>
                </div>
              </div>
            </div>
            {bookingsView === "list" && (
              <div className="relative">
                <SearchIcon
                  size={16}
                  className="pointer-events-none absolute top-1/2 -translate-y-1/2 start-3 text-slate-400"
                />
                <Input
                  value={bookingSearch}
                  onChange={(e) => setBookingSearch(e.target.value)}
                  placeholder={t("protocol.bookings.searchPlaceholder")}
                  className="ps-9"
                />
              </div>
            )}
            {bookings.data?.length === 0 && !debouncedBookingSearch && (
              <EmptyState text={t("protocol.bookings.empty")} />
            )}
            {bookingsView === "list" ? (
              (() => {
                const list = bookings.data ?? [];
                if (debouncedBookingSearch && list.length === 0) {
                  return (
                    <EmptyState
                      text={t("protocol.bookings.searchEmpty")}
                    />
                  );
                }
                if (list.length === 0) return null;
                return renderBookingsTable(list);
              })()
            ) : (
              <div className="flex flex-col gap-4 lg:flex-row">
                <div className="shrink-0 self-center lg:self-start">
                  <div className="rounded-xl border border-slate-200 bg-white p-2">
                    <Calendar
                      mode="single"
                      required
                      selected={selectedDay}
                      onSelect={(d) => d && setSelectedDay(d)}
                      month={calendarMonth}
                      onMonthChange={setCalendarMonth}
                      modifiers={{ booked: bookedDates }}
                      modifiersClassNames={{
                        booked:
                          "relative after:absolute after:bottom-1 after:start-1/2 after:-translate-x-1/2 after:h-1 after:w-1 after:rounded-full after:bg-sky-500",
                      }}
                    />
                  </div>
                </div>
                <div className="min-w-0 flex-1 rounded-xl border border-slate-200 bg-white p-3">
                  <div className="mb-3 flex flex-wrap items-center justify-between gap-2">
                    <div className="text-sm font-semibold text-slate-600">
                      {selectedDay.toLocaleDateString(isAr ? "ar" : "en", {
                        weekday: "long",
                        year: "numeric",
                        month: "long",
                        day: "numeric",
                      })}
                    </div>
                    <div className="flex items-center gap-2">
                      <Button
                        size="sm"
                        variant="outline"
                        onClick={() => {
                          const today = new Date();
                          setSelectedDay(today);
                          setCalendarMonth(today);
                          setTodayScrollSignal((v) => v + 1);
                        }}
                      >
                        <CalendarDays size={14} className="me-1" />
                        {t("protocol.bookings.today")}
                      </Button>
                      {c?.canRequest && (
                        <Button
                          size="sm"
                          onClick={() =>
                            setBookingDialog({ open: true, day: selectedDay })
                          }
                        >
                          <Plus size={14} className="me-1" />
                          {t("protocol.bookings.book")}
                        </Button>
                      )}
                    </div>
                  </div>
                  <BookingDayGrid
                    bookings={bookings.data ?? []}
                    selectedDay={selectedDay}
                    now={now}
                    isAr={isAr}
                    roomName={roomName}
                    emptyText={t("protocol.bookings.emptyDay")}
                    scrollSignal={todayScrollSignal}
                    onSelect={(b) => setDetailsBooking(b)}
                    onSlotClick={
                      c?.canRequest
                        ? (start) => setBookingDialog({ open: true, start })
                        : undefined
                    }
                  />
                </div>
              </div>
            )}
          </section>
        )}

        {tab === "rooms" && (
          <section className="space-y-4">
            <div className="flex items-center justify-between">
              <h2 className="font-semibold text-slate-700">
                {t("protocol.tabs.rooms")}
              </h2>
              {c?.canManageRooms && (
                <Button size="sm" onClick={() => setRoomDialog({ open: true })}>
                  <Plus size={16} className="me-1" />
                  {t("protocol.rooms.new")}
                </Button>
              )}
            </div>
            <div className="grid gap-2 sm:grid-cols-2">
              {(rooms.data ?? []).map((r) => (
                <div
                  key={r.id}
                  className="bg-white rounded-xl border border-slate-200 p-3"
                >
                  <div className="flex items-start justify-between gap-2">
                    <div className="min-w-0">
                      <div className="font-medium text-slate-800">
                        {nameOf(r.nameAr, r.nameEn)}
                      </div>
                      <div className="text-xs text-slate-500">
                        {r.capacity
                          ? `${t("protocol.rooms.capacity")}: ${r.capacity}`
                          : ""}
                      </div>
                    </div>
                    <span
                      className={cn(
                        "text-xs px-2 py-0.5 rounded-full shrink-0",
                        r.isActive
                          ? "bg-emerald-100 text-emerald-700"
                          : "bg-slate-200 text-slate-500",
                      )}
                    >
                      {r.isActive
                        ? t("protocol.rooms.active")
                        : t("protocol.rooms.inactive")}
                    </span>
                  </div>
                  {c?.canManageRooms && (
                    <div className="flex gap-1.5 mt-2">
                      <Button
                        size="sm"
                        variant="ghost"
                        onClick={() => setRoomDialog({ open: true, edit: r })}
                      >
                        <Pencil size={14} />
                      </Button>
                      <Button
                        size="sm"
                        variant="ghost"
                        onClick={() => setDeleteTarget({ kind: "room", id: r.id })}
                      >
                        <Trash2 size={14} className="text-rose-500" />
                      </Button>
                    </div>
                  )}
                </div>
              ))}
              {rooms.data?.length === 0 && (
                <EmptyState text={t("protocol.rooms.empty")} />
              )}
            </div>
          </section>
        )}

        {tab === "bookingSettings" && c?.canManageRooms && (
          <section className="space-y-4">
            <h2 className="font-semibold text-slate-700">
              {t("protocol.tabs.bookingSettings")}
            </h2>
            <SlotsAdminSection
              isAr={isAr}
              onChanged={() => invalidate("slots")}
              onError={onApiError}
            />
          </section>
        )}

        {tab === "roomsDashboard" && (
          <RoomsDashboardView isAr={isAr} now={now} goTab={setTab} />
        )}

        {tab === "bookingSettings" && !c?.canManageRooms && (
          <EmptyState text={t("protocol.roomsDash.noAccess")} />
        )}

        {tab === "external" && (
          <section className="space-y-4">
            <div className="flex items-center justify-between">
              <h2 className="font-semibold text-slate-700">
                {t("protocol.tabs.external")}
              </h2>
              {c?.canMutate && (
                <Button size="sm" onClick={() => setExternalDialog({ open: true })}>
                  <Plus size={16} className="me-1" />
                  {t("protocol.external.new")}
                </Button>
              )}
            </div>
            <div className="grid gap-2">
              {(external.data ?? []).map((m) => (
                <div
                  key={m.id}
                  className="bg-white rounded-xl border border-slate-200 p-3"
                >
                  <div className="flex items-start justify-between gap-2">
                    <div className="min-w-0">
                      <div className="font-medium text-slate-800">{m.title}</div>
                      <div className="text-sm text-slate-500">
                        {m.partyName ? `${m.partyName} · ` : ""}
                        {fmtDateTime(m.startsAt)}
                      </div>
                      {m.location && (
                        <div className="text-xs text-slate-400">{m.location}</div>
                      )}
                    </div>
                    <span
                      className={cn(
                        "text-xs px-2 py-0.5 rounded-full shrink-0",
                        STATUS_PILL[m.status],
                      )}
                    >
                      {t(`protocol.status.${m.status}`)}
                    </span>
                  </div>
                  {m.status === "rejected" && m.rejectionReason && (
                    <div className="text-xs text-rose-500 mt-1">
                      {t("protocol.actions.reject")}: {m.rejectionReason}
                    </div>
                  )}
                  <div className="flex flex-wrap gap-1.5 mt-2">
                    {c?.canApprove && m.status === "pending" && (
                      <>
                        <Button
                          size="sm"
                          variant="outline"
                          onClick={() => externalApprove.mutate(m.id)}
                        >
                          <Check size={14} className="me-1" />
                          {t("protocol.actions.approve")}
                        </Button>
                        <Button
                          size="sm"
                          variant="outline"
                          onClick={() =>
                            setRejectTarget({ kind: "external", id: m.id })
                          }
                        >
                          <X size={14} className="me-1" />
                          {t("protocol.actions.reject")}
                        </Button>
                      </>
                    )}
                    {c?.canMutate && (
                      <>
                        <Button
                          size="sm"
                          variant="ghost"
                          onClick={() =>
                            setExternalDialog({ open: true, edit: m })
                          }
                        >
                          <Pencil size={14} />
                        </Button>
                        <Button
                          size="sm"
                          variant="ghost"
                          onClick={() =>
                            setDeleteTarget({ kind: "external", id: m.id })
                          }
                        >
                          <Trash2 size={14} className="text-rose-500" />
                        </Button>
                      </>
                    )}
                  </div>
                </div>
              ))}
              {external.data?.length === 0 && (
                <EmptyState text={t("protocol.external.empty")} />
              )}
            </div>
          </section>
        )}

        {tab === "gifts" && (
          <section className="space-y-4">
            <div className="flex items-center justify-between">
              <h2 className="font-semibold text-slate-700">
                {t("protocol.tabs.gifts")}
              </h2>
              <div className="flex gap-2">
                {c?.canMutate && (
                  <Button size="sm" onClick={() => setGiftDialog({ open: true })}>
                    <Plus size={16} className="me-1" />
                    {t("protocol.gifts.new")}
                  </Button>
                )}
              </div>
            </div>

            <div className="grid gap-2 sm:grid-cols-2">
              {(gifts.data ?? []).map((g) => (
                <div
                  key={g.id}
                  className="bg-white rounded-xl border border-slate-200 p-3"
                >
                  <div className="flex items-start justify-between gap-2">
                    <div className="min-w-0">
                      <div className="font-medium text-slate-800">
                        {nameOf(g.nameAr, g.nameEn)}
                      </div>
                      <div className="text-xs text-slate-500">
                        {t(`protocol.giftKind.${g.kind}`)} ·{" "}
                        {t("protocol.gifts.stock")}: {g.quantityInStock}
                      </div>
                    </div>
                    <div className="flex gap-1 shrink-0">
                      {c?.canRequest && (
                        <Button
                          size="sm"
                          variant="outline"
                          onClick={() => setIssueDialog({ open: true })}
                        >
                          {t("protocol.issues.new")}
                        </Button>
                      )}
                      {c?.canMutate && (
                        <>
                          <Button
                            size="sm"
                            variant="ghost"
                            onClick={() =>
                              setGiftDialog({ open: true, edit: g })
                            }
                          >
                            <Pencil size={14} />
                          </Button>
                          <Button
                            size="sm"
                            variant="ghost"
                            onClick={() =>
                              setDeleteTarget({ kind: "gift", id: g.id })
                            }
                          >
                            <Trash2 size={14} className="text-rose-500" />
                          </Button>
                        </>
                      )}
                    </div>
                  </div>
                </div>
              ))}
              {gifts.data?.length === 0 && (
                <EmptyState text={t("protocol.gifts.empty")} />
              )}
            </div>
          </section>
        )}

        {tab === "issues" && (
          <section className="space-y-4">
            <div className="flex items-center justify-between">
              <h2 className="font-semibold text-slate-700">
                {t("protocol.tabs.issues")}
              </h2>
              {c?.canRequest && (
                <Button size="sm" onClick={() => setIssueDialog({ open: true })}>
                  <Plus size={16} className="me-1" />
                  {t("protocol.issues.new")}
                </Button>
              )}
            </div>
            <div className="grid gap-2">
              {(issues.data ?? []).map((it) => (
                <div
                  key={it.id}
                  className="bg-white rounded-xl border border-slate-200 p-3"
                >
                  <div className="flex items-start justify-between gap-2">
                    <div className="min-w-0">
                      <div className="font-medium text-slate-800">
                        {giftName(it.giftId)} × {it.quantity}
                      </div>
                      <div className="text-sm text-slate-500">
                        {t("protocol.issues.recipient")}: {it.recipientName}
                        {it.occasion ? ` · ${it.occasion}` : ""}
                      </div>
                    </div>
                    <span
                      className={cn(
                        "text-xs px-2 py-0.5 rounded-full shrink-0",
                        STATUS_PILL[it.status],
                      )}
                    >
                      {t(`protocol.status.${it.status}`)}
                    </span>
                  </div>
                  {c?.canApprove && it.status === "pending" && (
                    <div className="flex gap-1.5 mt-2">
                      <Button
                        size="sm"
                        variant="outline"
                        onClick={() => issueApprove.mutate(it.id)}
                      >
                        <Check size={14} className="me-1" />
                        {t("protocol.actions.approveIssue")}
                      </Button>
                      <Button
                        size="sm"
                        variant="outline"
                        onClick={() =>
                          setRejectTarget({ kind: "issue", id: it.id })
                        }
                      >
                        <X size={14} className="me-1" />
                        {t("protocol.actions.reject")}
                      </Button>
                    </div>
                  )}
                </div>
              ))}
              {issues.data?.length === 0 && (
                <EmptyState text={t("protocol.issues.empty")} />
              )}
            </div>
          </section>
        )}

        {tab === "photos" && (
          <PhotosView
            canMutate={!!c?.canMutate}
            isAr={isAr}
            externalMeetings={external.data ?? []}
            onApiError={onApiError}
          />
        )}

        {tab === "reports" && (
          <ReportsView data={reports.data} nameOf={nameOf} t={t} />
        )}

        {tab === "audit" && (
          <section className="space-y-2">
            <h2 className="font-semibold text-slate-700">
              {t("protocol.tabs.audit")}
            </h2>
            <div className="bg-white rounded-xl border border-slate-200 divide-y divide-slate-100">
              {(audit.data ?? []).map((a) => (
                <div key={a.id} className="p-3 text-sm flex justify-between gap-2">
                  <span className="text-slate-700">
                    {t(`protocol.audit.${a.action}`, a.action)}
                    <span className="text-slate-400">
                      {" "}
                      · {a.entityType}
                      {a.entityId ? `#${a.entityId}` : ""}
                    </span>
                  </span>
                  <span className="text-slate-400 shrink-0">
                    {isAr ? a.actorNameAr : a.actorNameEn || a.actorUsername} ·{" "}
                    {fmtDateTime(a.performedAt)}
                  </span>
                </div>
              ))}
              {audit.data?.length === 0 && (
                <EmptyState text={t("protocol.audit.empty")} />
              )}
            </div>
          </section>
        )}
        </main>
      </div>

      {/* Dialogs */}
      {bookingDialog.open && (
        <BookingDialog
          rooms={rooms.data ?? []}
          slots={slots.data ?? []}
          edit={bookingDialog.edit}
          initialDay={bookingDialog.day}
          initialStart={bookingDialog.start}
          nameOf={nameOf}
          onClose={() => setBookingDialog({ open: false })}
          onSaved={() => {
            setBookingDialog({ open: false });
            invalidate("bookings");
          }}
          onError={onApiError}
        />
      )}
      {roomDialog.open && (
        <RoomDialog
          edit={roomDialog.edit}
          onClose={() => setRoomDialog({ open: false })}
          onSaved={() => {
            setRoomDialog({ open: false });
            invalidate("rooms");
          }}
          onError={onApiError}
        />
      )}
      {externalDialog.open && (
        <ExternalDialog
          edit={externalDialog.edit}
          onClose={() => setExternalDialog({ open: false })}
          onSaved={() => {
            setExternalDialog({ open: false });
            invalidate("external");
          }}
          onError={onApiError}
        />
      )}
      {giftDialog.open && (
        <GiftDialog
          edit={giftDialog.edit}
          onClose={() => setGiftDialog({ open: false })}
          onSaved={() => {
            setGiftDialog({ open: false });
            invalidate("gifts");
          }}
          onError={onApiError}
        />
      )}
      {issueDialog.open && (
        <IssueDialog
          gifts={(gifts.data ?? []).filter((g) => g.isActive)}
          nameOf={nameOf}
          onClose={() => setIssueDialog({ open: false })}
          onSaved={() => {
            setIssueDialog({ open: false });
            invalidate("issues");
          }}
          onError={onApiError}
        />
      )}

      <AlertDialog
        open={rejectTarget !== null}
        onOpenChange={(o) => !o && setRejectTarget(null)}
      >
        <AlertDialogContent dir={isAr ? "rtl" : "ltr"}>
          <AlertDialogHeader>
            <AlertDialogTitle>{t("protocol.actions.reject")}</AlertDialogTitle>
            <AlertDialogDescription>
              {t("protocol.confirmReject")}
            </AlertDialogDescription>
          </AlertDialogHeader>
          <AlertDialogFooter>
            <AlertDialogCancel>{t("common.cancel")}</AlertDialogCancel>
            <AlertDialogAction
              onClick={() => rejectTarget && rejectMut.mutate(rejectTarget)}
            >
              {t("protocol.actions.reject")}
            </AlertDialogAction>
          </AlertDialogFooter>
        </AlertDialogContent>
      </AlertDialog>

      <AlertDialog
        open={deleteTarget !== null}
        onOpenChange={(o) => !o && setDeleteTarget(null)}
      >
        <AlertDialogContent dir={isAr ? "rtl" : "ltr"}>
          <AlertDialogHeader>
            <AlertDialogTitle>{t("common.confirmDelete")}</AlertDialogTitle>
            <AlertDialogDescription>
              {t("protocol.confirmDelete")}
            </AlertDialogDescription>
          </AlertDialogHeader>
          <AlertDialogFooter>
            <AlertDialogCancel>{t("common.cancel")}</AlertDialogCancel>
            <AlertDialogAction
              className="bg-rose-600 hover:bg-rose-700"
              onClick={() => deleteTarget && deleteMut.mutate(deleteTarget)}
            >
              {t("common.delete")}
            </AlertDialogAction>
          </AlertDialogFooter>
        </AlertDialogContent>
      </AlertDialog>

      <AlertDialog
        open={bulkDeleteOpen}
        onOpenChange={(o) => !o && setBulkDeleteOpen(false)}
      >
        <AlertDialogContent dir={isAr ? "rtl" : "ltr"}>
          <AlertDialogHeader>
            <AlertDialogTitle>
              {t("protocol.bookings.bulk.deleteConfirmTitle", {
                count: selectedBookings.size,
              })}
            </AlertDialogTitle>
            <AlertDialogDescription>
              {t("protocol.bookings.bulk.deleteConfirmBody", {
                count: selectedBookings.size,
              })}
            </AlertDialogDescription>
          </AlertDialogHeader>
          <AlertDialogFooter>
            <AlertDialogCancel>{t("common.cancel")}</AlertDialogCancel>
            <AlertDialogAction
              className="bg-rose-600 hover:bg-rose-700"
              onClick={() =>
                selectedBookings.size > 0 &&
                bulkDeleteMut.mutate(Array.from(selectedBookings))
              }
            >
              {t("common.delete")}
            </AlertDialogAction>
          </AlertDialogFooter>
        </AlertDialogContent>
      </AlertDialog>

      <Dialog
        open={!!detailsBooking}
        onOpenChange={(o) => !o && setDetailsBooking(null)}
      >
        <DialogContent
          dir={isAr ? "rtl" : "ltr"}
          className="max-h-[85vh] overflow-y-auto sm:max-w-lg"
        >
          {detailsBooking && (
            <>
              <DialogHeader>
                <DialogTitle className="flex flex-wrap items-center gap-2 text-start">
                  <span>{detailsBooking.title}</span>
                  <span
                    className={cn(
                      "rounded-full px-2 py-0.5 text-xs font-normal",
                      STATUS_PILL[detailsBooking.status],
                    )}
                  >
                    {t(`protocol.status.${detailsBooking.status}`)}
                  </span>
                  {detailsBooking.source === "public" && (
                    <span className="rounded-full bg-sky-100 px-2 py-0.5 text-xs font-normal text-sky-700">
                      {t("protocol.bookings.publicRequest")}
                    </span>
                  )}
                </DialogTitle>
              </DialogHeader>

              <div className="space-y-4 text-sm">
                <div className="rounded-lg border border-slate-200 bg-slate-50/60 p-3">
                  <div className="grid gap-x-6 gap-y-2 sm:grid-cols-2">
                    <DetailRow
                      label={t("protocol.bookings.details.date")}
                      value={fmtDateOnly(detailsBooking.startsAt)}
                    />
                    <DetailRow
                      label={t("protocol.bookings.details.time")}
                      value={
                        <span dir="ltr">
                          {fmtTimeOnly(detailsBooking.startsAt)} –{" "}
                          {fmtTimeOnly(detailsBooking.endsAt)}
                        </span>
                      }
                    />
                    <DetailRow
                      label={t("protocol.bookings.details.duration")}
                      value={formatDuration(
                        detailsBooking.startsAt,
                        detailsBooking.endsAt,
                        t,
                      )}
                    />
                    <DetailRow
                      label={t("protocol.bookings.roomLabel")}
                      value={roomName(detailsBooking.roomId)}
                    />
                    <DetailRow
                      label={t("protocol.bookings.reference")}
                      value={
                        detailsBooking.bookingReference ? (
                          <span dir="ltr">
                            {detailsBooking.bookingReference}
                          </span>
                        ) : null
                      }
                    />
                    <DetailRow
                      label={t("protocol.bookings.meetingType")}
                      value={t(
                        `protocol.bookings.meetingTypes.${detailsBooking.meetingType}`,
                      )}
                    />
                    <DetailRow
                      label={t("protocol.bookings.details.source")}
                      value={t(
                        `protocol.bookings.details.sources.${detailsBooking.source}`,
                      )}
                    />
                  </div>
                </div>

                <div>
                  <div className="mb-1.5 text-xs font-semibold uppercase tracking-wide text-slate-400">
                    {t("protocol.bookings.details.requesterSection")}
                  </div>
                  <div className="grid gap-x-6 gap-y-2 sm:grid-cols-2">
                    <DetailRow
                      label={t("protocol.bookings.requester")}
                      value={detailsBooking.requesterName}
                    />
                    <DetailRow
                      label={t("protocol.bookings.phone")}
                      value={
                        detailsBooking.requesterPhone ? (
                          <span dir="ltr">
                            {detailsBooking.requesterPhone}
                          </span>
                        ) : null
                      }
                    />
                    {detailsBooking.meetingType === "external" && (
                      <DetailRow
                        label={t("protocol.bookings.entity")}
                        value={detailsBooking.requesterOrg}
                      />
                    )}
                    <DetailRow
                      label={t("protocol.bookings.department")}
                      value={detailsBooking.department}
                    />
                    <DetailRow
                      label={t("protocol.bookings.attendeeCount")}
                      value={
                        detailsBooking.attendeeCount != null
                          ? detailsBooking.attendeeCount
                          : null
                      }
                    />
                  </div>
                </div>

                {(detailsBooking.purpose ||
                  (detailsBooking.keyAttendees?.length ?? 0) > 0) && (
                  <div>
                    <div className="mb-1.5 text-xs font-semibold uppercase tracking-wide text-slate-400">
                      {t("protocol.bookings.details.meetingSection")}
                    </div>
                    <div className="space-y-2">
                      <DetailRow
                        label={t("protocol.bookings.purpose")}
                        value={detailsBooking.purpose}
                      />
                      {(detailsBooking.keyAttendees?.length ?? 0) > 0 && (
                        <div>
                          <div className="text-xs text-slate-400">
                            {t("protocol.bookings.keyAttendees")}
                          </div>
                          <div className="mt-1.5">
                            <AttendeesTable
                              attendees={detailsBooking.keyAttendees!}
                              t={t}
                            />
                          </div>
                        </div>
                      )}
                    </div>
                  </div>
                )}

                {detailsBooking.notes && (
                  <div>
                    <div className="mb-1 text-xs text-slate-400">
                      {t("protocol.bookings.details.notes")}
                    </div>
                    <div className="whitespace-pre-wrap break-words text-slate-700">
                      {detailsBooking.notes}
                    </div>
                  </div>
                )}

                {detailsBooking.status === "rejected" &&
                  detailsBooking.rejectionReason && (
                    <div className="rounded-lg border border-rose-200 bg-rose-50 p-3">
                      <div className="mb-1 text-xs font-semibold text-rose-600">
                        {t("protocol.bookings.details.rejectionReason")}
                      </div>
                      <div className="whitespace-pre-wrap break-words text-rose-700">
                        {detailsBooking.rejectionReason}
                      </div>
                    </div>
                  )}
              </div>

              <DialogFooter>
                <Button
                  variant="outline"
                  onClick={() => setDetailsBooking(null)}
                >
                  {t("common.close")}
                </Button>
              </DialogFooter>
            </>
          )}
        </DialogContent>
      </Dialog>

      <AppDock currentSlug="protocol" />
    </div>
  );
}

// ---------------------------------------------------------------------------
// Sub-views & dialogs
// ---------------------------------------------------------------------------
function AttendeesTable({
  attendees,
  t,
}: {
  attendees: KeyAttendee[];
  t: (key: string) => string;
}) {
  return (
    <div className="overflow-x-auto rounded-xl border border-slate-200 shadow-sm">
      <Table>
        <TableHeader>
          <TableRow className="border-b border-slate-200 bg-slate-100/80 hover:bg-slate-100/80">
            {(["name", "position", "side"] as const).map((col) => (
              <TableHead
                key={col}
                className="h-10 whitespace-nowrap px-4 text-center text-xs font-bold uppercase tracking-wide text-slate-600"
              >
                {t(`protocol.bookings.details.attendeeCols.${col}`)}
              </TableHead>
            ))}
          </TableRow>
        </TableHeader>
        <TableBody>
          {attendees.map((a, i) => (
            <TableRow
              key={i}
              className="border-b border-slate-100 last:border-b-0 even:bg-slate-50/60"
            >
              <TableCell className="px-4 py-2.5 text-center font-medium text-slate-700">
                {a.name || "—"}
              </TableCell>
              <TableCell className="px-4 py-2.5 text-center text-slate-600">
                {a.position || "—"}
              </TableCell>
              <TableCell className="px-4 py-2.5 text-center">
                <span
                  className={cn(
                    "inline-block whitespace-nowrap rounded-full px-2.5 py-0.5 text-[11px] font-medium",
                    a.side === "external"
                      ? "bg-violet-100 text-violet-700"
                      : "bg-sky-100 text-sky-700",
                  )}
                >
                  {t(`protocol.bookings.attendeeSides.${a.side}`)}
                </span>
              </TableCell>
            </TableRow>
          ))}
        </TableBody>
      </Table>
    </div>
  );
}

function DetailRow({
  label,
  value,
}: {
  label: string;
  value: React.ReactNode;
}) {
  if (value == null || value === "") return null;
  return (
    <div className="min-w-0">
      <div className="text-xs text-slate-400">{label}</div>
      <div className="min-w-0 break-words text-slate-700">{value}</div>
    </div>
  );
}

function formatDuration(
  startIso: string,
  endIso: string,
  t: (key: string, opts?: Record<string, unknown>) => string,
): string {
  const mins = Math.max(
    0,
    Math.round(
      (new Date(endIso).getTime() - new Date(startIso).getTime()) / 60000,
    ),
  );
  const h = Math.floor(mins / 60);
  const m = mins % 60;
  const parts: string[] = [];
  if (h > 0) parts.push(t("protocol.bookings.details.durationHours", { count: h }));
  if (m > 0 || h === 0)
    parts.push(t("protocol.bookings.details.durationMinutes", { count: m }));
  return parts.join(" ");
}

// Admin management of booking slots (أوقات الحجز) + the minimum lead-time
// setting for public requests. Shown in the rooms tab for canManageRooms.
function SlotsAdminSection({
  isAr,
  onChanged,
  onError,
}: {
  isAr: boolean;
  onChanged: () => void;
  onError: (e: unknown) => void;
}) {
  const { t } = useTranslation();
  const { toast } = useToast();
  const qc = useQueryClient();
  const [newTime, setNewTime] = useState("");
  const [newDuration, setNewDuration] = useState("30");
  // Interval-generation form (Calendly-style weekly availability).
  const [intFrom, setIntFrom] = useState("");
  const [intTo, setIntTo] = useState("");
  const [intDuration, setIntDuration] = useState("30");
  const [intDays, setIntDays] = useState<number[]>(WORKDAYS);
  const toggleDay = (d: number) =>
    setIntDays((prev) =>
      prev.includes(d)
        ? prev.filter((x) => x !== d)
        : [...prev, d].sort((a, b) => a - b),
    );

  // Full slot list (incl. inactive) — admin-only endpoint; this component is
  // only rendered for canManageRooms.
  const slotsQuery = useQuery<Slot[]>({
    queryKey: ["protocol", "slots-admin"],
    queryFn: () => apiJson<Slot[]>(`${API}/protocol/booking-slots`),
  });
  const slots = slotsQuery.data ?? [];
  const changed = () => {
    qc.invalidateQueries({ queryKey: ["protocol", "slots-admin"] });
    onChanged();
  };

  const settings = useQuery<{ minLeadMinutes: number }>({
    queryKey: ["protocol", "settings"],
    queryFn: () =>
      apiJson<{ minLeadMinutes: number }>(`${API}/protocol/settings`),
  });
  const [leadInput, setLeadInput] = useState<string | null>(null);
  const leadValue =
    leadInput ??
    (settings.data ? String(settings.data.minLeadMinutes) : "");

  const addMut = useMutation({
    mutationFn: () =>
      apiJson(`${API}/protocol/booking-slots`, {
        method: "POST",
        body: JSON.stringify({
          startTime: newTime,
          durationMinutes: Number(newDuration),
        }),
      }),
    onSuccess: () => {
      setNewTime("");
      changed();
    },
    onError,
  });
  const generateMut = useMutation({
    mutationFn: () =>
      apiJson<{ created: number; merged: number; skipped: number }>(
        `${API}/protocol/booking-slots/generate`,
        {
          method: "POST",
          body: JSON.stringify({
            startTime: intFrom,
            endTime: intTo,
            durationMinutes: Number(intDuration),
            daysOfWeek: intDays,
          }),
        },
      ),
    onSuccess: (r) => {
      toast({
        title:
          r.created === 0 && r.merged === 0
            ? t("protocol.slots.generatedNone")
            : t("protocol.slots.generated", {
                created: r.created,
                merged: r.merged,
                skipped: r.skipped,
              }),
      });
      changed();
    },
    onError,
  });
  const toggleMut = useMutation({
    mutationFn: (s: Slot) =>
      apiJson(`${API}/protocol/booking-slots/${s.id}`, {
        method: "PATCH",
        body: JSON.stringify({ isActive: !s.isActive }),
      }),
    onSuccess: changed,
    onError,
  });
  const deleteMut = useMutation({
    mutationFn: (id: number) =>
      apiJson(`${API}/protocol/booking-slots/${id}`, { method: "DELETE" }),
    onSuccess: changed,
    onError,
  });
  const leadMut = useMutation({
    mutationFn: () =>
      apiJson(`${API}/protocol/settings`, {
        method: "PATCH",
        body: JSON.stringify({ minLeadMinutes: Number(leadValue) }),
      }),
    onSuccess: () => {
      setLeadInput(null);
      settings.refetch();
      toast({ title: t("protocol.slots.leadSaved") });
    },
    onError,
  });

  const canAdd = /^\d{2}:\d{2}$/.test(newTime) && Number(newDuration) >= 5;
  const canGenerate =
    /^\d{2}:\d{2}$/.test(intFrom) &&
    /^\d{2}:\d{2}$/.test(intTo) &&
    intFrom < intTo &&
    Number(intDuration) >= 5 &&
    intDays.length > 0;
  const leadDirty =
    leadInput !== null &&
    settings.data !== undefined &&
    leadInput !== String(settings.data.minLeadMinutes) &&
    /^\d+$/.test(leadInput);

  return (
    <div className="space-y-4">
      <div className="bg-white rounded-xl border border-slate-200 p-4 space-y-3">
        <div>
          <h3 className="font-semibold text-slate-700">
            {t("protocol.slots.title")}
          </h3>
          <p className="text-xs text-slate-500 mt-0.5">
            {t("protocol.slots.hint")}
          </p>
        </div>
        <div className="flex flex-wrap items-end gap-2">
          <div>
            <Label className="text-xs">{t("protocol.slots.startTime")}</Label>
            <Input
              type="time"
              dir="ltr"
              className="w-32"
              value={newTime}
              onChange={(e) => setNewTime(e.target.value)}
            />
          </div>
          <div>
            <Label className="text-xs">{t("protocol.slots.duration")}</Label>
            <Input
              type="number"
              inputMode="numeric"
              min={5}
              max={480}
              step={5}
              className="w-24"
              value={newDuration}
              onChange={(e) => setNewDuration(e.target.value)}
            />
          </div>
          <Button
            size="sm"
            disabled={!canAdd || addMut.isPending}
            onClick={() => addMut.mutate()}
          >
            <Plus size={16} className="me-1" />
            {t("protocol.slots.add")}
          </Button>
        </div>

        <div className="rounded-lg border border-slate-200 bg-slate-50 p-3 space-y-2">
          <div>
            <h4 className="text-sm font-semibold text-slate-700">
              {t("protocol.slots.intervalTitle")}
            </h4>
            <p className="text-xs text-slate-500 mt-0.5">
              {t("protocol.slots.intervalHint")}
            </p>
          </div>
          <div className="flex flex-wrap items-end gap-2">
            <div>
              <Label className="text-xs">
                {t("protocol.slots.intervalFrom")}
              </Label>
              <Input
                type="time"
                dir="ltr"
                className="w-32"
                value={intFrom}
                onChange={(e) => setIntFrom(e.target.value)}
              />
            </div>
            <div>
              <Label className="text-xs">{t("protocol.slots.intervalTo")}</Label>
              <Input
                type="time"
                dir="ltr"
                className="w-32"
                value={intTo}
                onChange={(e) => setIntTo(e.target.value)}
              />
            </div>
            <div>
              <Label className="text-xs">{t("protocol.slots.duration")}</Label>
              <Input
                type="number"
                inputMode="numeric"
                min={5}
                max={480}
                step={5}
                className="w-24"
                value={intDuration}
                onChange={(e) => setIntDuration(e.target.value)}
              />
            </div>
          </div>
          <div className="space-y-1.5">
            <Label className="text-xs">{t("protocol.slots.days")}</Label>
            <div className="flex flex-wrap gap-1.5">
              {ALL_DAYS.map((d) => (
                <button
                  key={d}
                  type="button"
                  onClick={() => toggleDay(d)}
                  className={cn(
                    "rounded-full border px-2.5 py-1 text-xs font-medium transition-colors",
                    intDays.includes(d)
                      ? "border-sky-500 bg-sky-50 text-sky-700"
                      : "border-slate-200 bg-white text-slate-500 hover:border-slate-300",
                  )}
                >
                  {t(`protocol.slots.dayNames.${d}`)}
                </button>
              ))}
            </div>
            <div className="flex gap-2">
              <button
                type="button"
                className="text-xs text-sky-600 hover:underline"
                onClick={() => setIntDays(WORKDAYS)}
              >
                {t("protocol.slots.weekdaysPreset")}
              </button>
              <button
                type="button"
                className="text-xs text-sky-600 hover:underline"
                onClick={() => setIntDays(ALL_DAYS)}
              >
                {t("protocol.slots.allDaysPreset")}
              </button>
            </div>
            {intDays.length === 0 && (
              <p className="text-xs text-amber-600">
                {t("protocol.slots.pickDay")}
              </p>
            )}
          </div>
          <Button
            size="sm"
            disabled={!canGenerate || generateMut.isPending}
            onClick={() => generateMut.mutate()}
          >
            <Plus size={16} className="me-1" />
            {t("protocol.slots.generate")}
          </Button>
        </div>
        {slots.length === 0 ? (
          <p className="text-sm text-slate-400">{t("protocol.slots.empty")}</p>
        ) : (
          <div className="grid gap-1.5 sm:grid-cols-2">
            {slots.map((s) => (
              <div
                key={s.id}
                className="flex items-center justify-between gap-2 rounded-lg border border-slate-200 px-3 py-2"
              >
                <div className="flex items-center gap-2 min-w-0">
                  <div className="min-w-0">
                    <span
                      className="block text-sm font-medium text-slate-700"
                      dir={isAr ? "rtl" : "ltr"}
                    >
                      {fmtSlotRange(s.startTime, s.durationMinutes, isAr)}
                    </span>
                    <span className="block text-[11px] text-slate-500 truncate">
                      {fmtSlotDays(s.daysOfWeek, t, isAr)}
                    </span>
                  </div>
                  <span
                    className={cn(
                      "text-[11px] px-2 py-0.5 rounded-full shrink-0",
                      s.isActive
                        ? "bg-emerald-100 text-emerald-700"
                        : "bg-slate-200 text-slate-500",
                    )}
                  >
                    {s.isActive
                      ? t("protocol.rooms.active")
                      : t("protocol.rooms.inactive")}
                  </span>
                </div>
                <div className="flex gap-1 shrink-0">
                  <Button
                    size="sm"
                    variant="ghost"
                    title={
                      s.isActive
                        ? t("protocol.slots.deactivate")
                        : t("protocol.slots.activate")
                    }
                    onClick={() => toggleMut.mutate(s)}
                  >
                    {s.isActive ? <Ban size={14} /> : <Check size={14} />}
                  </Button>
                  <Button
                    size="sm"
                    variant="ghost"
                    onClick={() => deleteMut.mutate(s.id)}
                  >
                    <Trash2 size={14} className="text-rose-500" />
                  </Button>
                </div>
              </div>
            ))}
          </div>
        )}
      </div>

      <div className="bg-white rounded-xl border border-slate-200 p-4 space-y-2">
        <h3 className="font-semibold text-slate-700">
          {t("protocol.slots.leadTitle")}
        </h3>
        <p className="text-xs text-slate-500">{t("protocol.slots.leadHint")}</p>
        <div className="flex items-end gap-2">
          <div>
            <Label className="text-xs">{t("protocol.slots.leadLabel")}</Label>
            <Input
              type="number"
              inputMode="numeric"
              min={0}
              max={10080}
              className="w-28"
              value={leadValue}
              onChange={(e) => setLeadInput(e.target.value)}
              disabled={settings.isLoading}
            />
          </div>
          <Button
            size="sm"
            disabled={!leadDirty || leadMut.isPending}
            onClick={() => leadMut.mutate()}
          >
            {t("common.save")}
          </Button>
        </div>
      </div>
    </div>
  );
}

function EmptyState({ text }: { text: string }) {
  return (
    <div className="text-center text-slate-400 py-10 text-sm">{text}</div>
  );
}

function DashboardView({
  data,
  t,
  goTab,
}: {
  data: Dashboard | undefined;
  t: (k: string) => string;
  goTab: (k: TabKey) => void;
}) {
  const cards: Array<{ label: string; value?: number; tab: TabKey }> = [
    {
      label: t("protocol.dashboard.roomsAvailableNow"),
      value: data?.roomsAvailableNow,
      tab: "rooms",
    },
    {
      label: t("protocol.dashboard.activeRooms"),
      value: data?.activeRooms,
      tab: "rooms",
    },
    {
      label: t("protocol.dashboard.todayBookings"),
      value: data?.todayBookings,
      tab: "bookings",
    },
    {
      label: t("protocol.dashboard.pendingBookings"),
      value: data?.pendingBookings,
      tab: "bookings",
    },
    {
      label: t("protocol.dashboard.upcomingExternal"),
      value: data?.upcomingExternalMeetings,
      tab: "external",
    },
    {
      label: t("protocol.dashboard.giftStock"),
      value: data?.totalGiftStock,
      tab: "gifts",
    },
    {
      label: t("protocol.dashboard.pendingIssues"),
      value: data?.pendingGiftIssues,
      tab: "issues",
    },
    {
      label: t("protocol.dashboard.giftsIssuedThisMonth"),
      value: data?.giftsIssuedThisMonth,
      tab: "issues",
    },
  ];
  return (
    <div className="grid gap-3 grid-cols-2 sm:grid-cols-3">
      {cards.map((c) => (
        <button
          key={c.label}
          type="button"
          onClick={() => goTab(c.tab)}
          className="bg-white rounded-2xl border border-slate-200 p-4 text-start hover:border-sky-300 hover:shadow-sm transition"
        >
          <div className="text-3xl font-bold text-sky-600">{c.value ?? "—"}</div>
          <div className="text-sm text-slate-500 mt-1">{c.label}</div>
        </button>
      ))}
    </div>
  );
}

// ---------------------------------------------------------------------------
// Rooms dashboard (لوحة بيانات القاعات) — recharts KPI + charts view.
// ---------------------------------------------------------------------------
type RoomsStats = {
  rooms: { active: number; inactive: number };
  todayBookings: number;
  weekBookings: number;
  pendingBookings: number;
  nextBooking: {
    id: number;
    title: string;
    roomNameAr: string;
    roomNameEn: string;
    startsAt: string;
    endsAt: string;
    status: BookingStatus;
  } | null;
  daily: Array<{ date: string; count: number }>;
  byRoom: Array<{
    roomId: number;
    nameAr: string;
    nameEn: string;
    count: number;
  }>;
  byStatus: Array<{ status: BookingStatus; count: number }>;
};

const STATUS_CHART_COLORS: Record<string, string> = {
  pending: "#f59e0b",
  approved: "#10b981",
  rejected: "#f43f5e",
  cancelled: "#94a3b8",
};

function RoomsDashboardView({
  isAr,
  now,
  goTab,
}: {
  isAr: boolean;
  now: Date;
  goTab: (k: TabKey) => void;
}) {
  const { t } = useTranslation();
  const stats = useQuery<RoomsStats>({
    queryKey: ["protocol", "rooms-stats"],
    queryFn: () => apiJson<RoomsStats>(`${API}/protocol/rooms-stats`),
  });
  const d = stats.data;
  const [range, setRange] = useState<14 | 30>(14);

  const nameOf = (ar: string, en: string) => (isAr ? ar : en || ar);
  const locale = isAr ? "ar-u-nu-latn" : "en";

  // Countdown to the next upcoming booking: "بعد X يوم" or "بعد X ساعة ودقيقة".
  const countdown = useMemo(() => {
    if (!d?.nextBooking) return null;
    const ms = new Date(d.nextBooking.startsAt).getTime() - now.getTime();
    if (ms <= 0) return t("protocol.roomsDash.startingNow");
    const totalMinutes = Math.floor(ms / 60_000);
    const days = Math.floor(totalMinutes / (24 * 60));
    if (days >= 1) return t("protocol.roomsDash.inDays", { count: days });
    const hours = Math.floor(totalMinutes / 60);
    const minutes = totalMinutes % 60;
    if (hours >= 1) {
      return t("protocol.roomsDash.inHoursMinutes", { hours, minutes });
    }
    return t("protocol.roomsDash.inMinutes", { count: minutes });
  }, [d?.nextBooking, now, t]);

  const dailyData = useMemo(() => {
    const rows = d?.daily ?? [];
    const window = rows.slice(-range);
    return window.map((r) => ({
      ...r,
      label: new Intl.DateTimeFormat(locale, {
        day: "numeric",
        month: "short",
      }).format(new Date(`${r.date}T00:00:00+03:00`)),
    }));
  }, [d?.daily, range, locale]);

  const roomData = useMemo(
    () =>
      (d?.byRoom ?? []).map((r) => ({
        name: nameOf(r.nameAr, r.nameEn),
        count: r.count,
      })),
    // eslint-disable-next-line react-hooks/exhaustive-deps
    [d?.byRoom, isAr],
  );

  const statusData = useMemo(
    () =>
      (d?.byStatus ?? [])
        .filter((s) => s.count > 0)
        .map((s) => ({
          name: t(`protocol.status.${s.status}`),
          status: s.status,
          value: s.count,
        })),
    [d?.byStatus, t],
  );

  const fmtNextTime = (iso: string) =>
    new Date(iso).toLocaleString(isAr ? "ar" : "en", {
      dateStyle: "medium",
      timeStyle: "short",
    });

  const kpis: Array<{ label: string; value?: number; sub?: string; tab: TabKey }> = [
    {
      label: t("protocol.roomsDash.roomsCount"),
      value: d ? d.rooms.active + d.rooms.inactive : undefined,
      sub: d
        ? t("protocol.roomsDash.roomsBreakdown", {
            active: d.rooms.active,
            inactive: d.rooms.inactive,
          })
        : undefined,
      tab: "rooms",
    },
    {
      label: t("protocol.roomsDash.todayBookings"),
      value: d?.todayBookings,
      tab: "bookings",
    },
    {
      label: t("protocol.roomsDash.weekBookings"),
      value: d?.weekBookings,
      tab: "bookings",
    },
    {
      label: t("protocol.roomsDash.pendingBookings"),
      value: d?.pendingBookings,
      tab: "bookings",
    },
  ];

  return (
    <section className="space-y-4">
      <h2 className="font-semibold text-slate-700">
        {t("protocol.roomsDash.title")}
      </h2>

      <div className="grid gap-3 grid-cols-2 lg:grid-cols-4">
        {kpis.map((k) => (
          <button
            key={k.label}
            type="button"
            onClick={() => goTab(k.tab)}
            className="bg-white rounded-2xl border border-slate-200 p-4 text-start hover:border-sky-300 hover:shadow-sm transition"
          >
            <div className="text-3xl font-bold text-sky-600">
              {k.value ?? "—"}
            </div>
            <div className="text-sm text-slate-500 mt-1">{k.label}</div>
            {k.sub && (
              <div className="text-xs text-slate-400 mt-0.5">{k.sub}</div>
            )}
          </button>
        ))}
      </div>

      <div className="bg-gradient-to-l from-sky-500 to-sky-600 rounded-2xl p-4 text-white shadow-sm">
        <div className="flex items-center gap-2 text-sky-100 text-sm">
          <Clock3 size={16} />
          {t("protocol.roomsDash.nextBooking")}
        </div>
        {d?.nextBooking ? (
          <div className="mt-2 flex flex-wrap items-center justify-between gap-3">
            <div className="min-w-0">
              <div className="font-bold text-lg truncate">
                {d.nextBooking.title}
              </div>
              <div className="text-sm text-sky-100">
                {nameOf(d.nextBooking.roomNameAr, d.nextBooking.roomNameEn)}
                {" · "}
                {fmtNextTime(d.nextBooking.startsAt)}
              </div>
            </div>
            <div className="rounded-xl bg-white/15 px-4 py-2 text-xl font-bold whitespace-nowrap">
              {countdown}
            </div>
          </div>
        ) : (
          <div className="mt-2 text-sky-100 text-sm">
            {stats.isLoading
              ? t("common.loading")
              : t("protocol.roomsDash.noUpcoming")}
          </div>
        )}
      </div>

      <div className="bg-white rounded-2xl border border-slate-200 p-4">
        <div className="flex items-center justify-between mb-3">
          <h3 className="font-semibold text-slate-700 text-sm">
            {t("protocol.roomsDash.bookingsOverTime")}
          </h3>
          <div className="flex rounded-lg border border-slate-200 overflow-hidden">
            {([14, 30] as const).map((r) => (
              <button
                key={r}
                type="button"
                onClick={() => setRange(r)}
                className={cn(
                  "px-2.5 py-1 text-xs",
                  range === r
                    ? "bg-sky-500 text-white"
                    : "text-slate-600 hover:bg-slate-100",
                )}
              >
                {t("protocol.roomsDash.lastNDays", { count: r })}
              </button>
            ))}
          </div>
        </div>
        <div className="h-56" dir="ltr">
          <ResponsiveContainer width="100%" height="100%">
            <AreaChart data={dailyData} margin={{ top: 4, left: -20, right: 4 }}>
              <defs>
                <linearGradient id="roomsDashArea" x1="0" y1="0" x2="0" y2="1">
                  <stop offset="0%" stopColor="#0ea5e9" stopOpacity={0.35} />
                  <stop offset="100%" stopColor="#0ea5e9" stopOpacity={0.02} />
                </linearGradient>
              </defs>
              <CartesianGrid strokeDasharray="3 3" stroke="#e2e8f0" vertical={false} />
              <XAxis
                dataKey="label"
                tick={{ fontSize: 11, fill: "#64748b" }}
                tickLine={false}
                axisLine={{ stroke: "#e2e8f0" }}
                interval="preserveStartEnd"
              />
              <YAxis
                allowDecimals={false}
                tick={{ fontSize: 11, fill: "#64748b" }}
                tickLine={false}
                axisLine={false}
              />
              <RechartsTooltip
                formatter={(v: number) => [
                  v,
                  t("protocol.roomsDash.bookings"),
                ]}
                labelStyle={{ color: "#334155" }}
              />
              <Area
                type="monotone"
                dataKey="count"
                stroke="#0ea5e9"
                strokeWidth={2}
                fill="url(#roomsDashArea)"
              />
            </AreaChart>
          </ResponsiveContainer>
        </div>
      </div>

      <div className="grid gap-4 lg:grid-cols-2">
        <div className="bg-white rounded-2xl border border-slate-200 p-4">
          <h3 className="font-semibold text-slate-700 text-sm mb-3">
            {t("protocol.roomsDash.byRoom")}
          </h3>
          {roomData.length === 0 ? (
            <EmptyState text={t("protocol.roomsDash.noData")} />
          ) : (
            <div className="h-56" dir="ltr">
              <ResponsiveContainer width="100%" height="100%">
                <BarChart
                  data={roomData}
                  layout="vertical"
                  margin={{ top: 4, left: 8, right: 16 }}
                >
                  <CartesianGrid
                    strokeDasharray="3 3"
                    stroke="#e2e8f0"
                    horizontal={false}
                  />
                  <XAxis
                    type="number"
                    allowDecimals={false}
                    tick={{ fontSize: 11, fill: "#64748b" }}
                    tickLine={false}
                    axisLine={{ stroke: "#e2e8f0" }}
                  />
                  <YAxis
                    type="category"
                    dataKey="name"
                    width={110}
                    tick={{ fontSize: 11, fill: "#334155" }}
                    tickLine={false}
                    axisLine={false}
                  />
                  <RechartsTooltip
                    formatter={(v: number) => [
                      v,
                      t("protocol.roomsDash.bookings"),
                    ]}
                  />
                  <Bar
                    dataKey="count"
                    fill="#0ea5e9"
                    radius={[0, 6, 6, 0]}
                    barSize={18}
                  />
                </BarChart>
              </ResponsiveContainer>
            </div>
          )}
        </div>

        <div className="bg-white rounded-2xl border border-slate-200 p-4">
          <h3 className="font-semibold text-slate-700 text-sm mb-3">
            {t("protocol.roomsDash.byStatus")}
          </h3>
          {statusData.length === 0 ? (
            <EmptyState text={t("protocol.roomsDash.noData")} />
          ) : (
            <div className="h-56" dir="ltr">
              <ResponsiveContainer width="100%" height="100%">
                <PieChart>
                  <Pie
                    data={statusData}
                    dataKey="value"
                    nameKey="name"
                    innerRadius={55}
                    outerRadius={80}
                    paddingAngle={2}
                    strokeWidth={0}
                  >
                    {statusData.map((s) => (
                      <Cell
                        key={s.status}
                        fill={STATUS_CHART_COLORS[s.status] ?? "#0ea5e9"}
                      />
                    ))}
                  </Pie>
                  <Legend
                    formatter={(value: string) => (
                      <span style={{ color: "#334155", fontSize: 12 }}>
                        {value}
                      </span>
                    )}
                  />
                  <RechartsTooltip
                    formatter={(v: number) => [
                      v,
                      t("protocol.roomsDash.bookings"),
                    ]}
                  />
                </PieChart>
              </ResponsiveContainer>
            </div>
          )}
        </div>
      </div>

      <p className="text-xs text-slate-400">
        {t("protocol.roomsDash.windowHint")}
      </p>
    </section>
  );
}

function ReportsView({
  data,
  nameOf,
  t,
}: {
  data: ReportRow | undefined;
  nameOf: (ar: string, en: string) => string;
  t: (k: string) => string;
}) {
  return (
    <div className="space-y-6">
      <div>
        <h3 className="font-semibold text-slate-700 mb-2">
          {t("protocol.reports.byRoom")}
        </h3>
        <div className="bg-white rounded-xl border border-slate-200 overflow-hidden">
          <table className="w-full text-sm">
            <thead className="bg-slate-50 text-slate-500">
              <tr>
                <th className="text-start p-2">{t("protocol.reports.room")}</th>
                <th className="p-2">{t("protocol.reports.total")}</th>
                <th className="p-2">{t("protocol.status.approved")}</th>
                <th className="p-2">{t("protocol.status.pending")}</th>
              </tr>
            </thead>
            <tbody>
              {(data?.bookingsByRoom ?? []).map((r) => (
                <tr key={r.roomId} className="border-t border-slate-100">
                  <td className="p-2 text-slate-700">
                    {nameOf(r.roomNameAr, r.roomNameEn)}
                  </td>
                  <td className="p-2 text-center">{r.total}</td>
                  <td className="p-2 text-center">{r.approved}</td>
                  <td className="p-2 text-center">{r.pending}</td>
                </tr>
              ))}
              {data?.bookingsByRoom.length === 0 && (
                <tr>
                  <td colSpan={4} className="p-4 text-center text-slate-400">
                    {t("protocol.reports.empty")}
                  </td>
                </tr>
              )}
            </tbody>
          </table>
        </div>
      </div>

      <div>
        <h3 className="font-semibold text-slate-700 mb-2">
          {t("protocol.reports.byGift")}
        </h3>
        <div className="bg-white rounded-xl border border-slate-200 overflow-hidden">
          <table className="w-full text-sm">
            <thead className="bg-slate-50 text-slate-500">
              <tr>
                <th className="text-start p-2">{t("protocol.reports.gift")}</th>
                <th className="p-2">{t("protocol.reports.requests")}</th>
                <th className="p-2">{t("protocol.reports.issued")}</th>
                <th className="p-2">{t("protocol.reports.issuedQty")}</th>
              </tr>
            </thead>
            <tbody>
              {(data?.issuesByGift ?? []).map((r) => (
                <tr key={r.giftId} className="border-t border-slate-100">
                  <td className="p-2 text-slate-700">
                    {nameOf(r.giftNameAr, r.giftNameEn)}
                  </td>
                  <td className="p-2 text-center">{r.totalRequests}</td>
                  <td className="p-2 text-center">{r.issued}</td>
                  <td className="p-2 text-center">{r.issuedQty}</td>
                </tr>
              ))}
              {data?.issuesByGift.length === 0 && (
                <tr>
                  <td colSpan={4} className="p-4 text-center text-slate-400">
                    {t("protocol.reports.empty")}
                  </td>
                </tr>
              )}
            </tbody>
          </table>
        </div>
      </div>

      <div>
        <h3 className="font-semibold text-slate-700 mb-2">
          {t("protocol.reports.byExternal")}
        </h3>
        <div className="grid gap-2 grid-cols-3 sm:grid-cols-6">
          {(
            [
              ["total", "protocol.reports.total"],
              ["pending", "protocol.status.pending"],
              ["approved", "protocol.status.approved"],
              ["rejected", "protocol.status.rejected"],
              ["completed", "protocol.status.completed"],
              ["cancelled", "protocol.status.cancelled"],
            ] as const
          ).map(([key, label]) => (
            <div
              key={key}
              className="bg-white rounded-xl border border-slate-200 p-3 text-center"
            >
              <div className="text-2xl font-bold text-sky-600">
                {data?.externalMeetings?.[key] ?? "—"}
              </div>
              <div className="text-xs text-slate-500 mt-1">{t(label)}</div>
            </div>
          ))}
        </div>
      </div>
    </div>
  );
}

function DialogShell({
  title,
  onClose,
  children,
  onSubmit,
  submitLabel,
  saving,
}: {
  title: string;
  onClose: () => void;
  children: React.ReactNode;
  onSubmit: () => void;
  submitLabel: string;
  saving: boolean;
}) {
  const { i18n, t } = useTranslation();
  return (
    <Dialog open onOpenChange={(o) => !o && onClose()}>
      <DialogContent
        dir={i18n.language === "ar" ? "rtl" : "ltr"}
        className="flex max-h-[85vh] flex-col overflow-hidden"
      >
        <DialogHeader className="shrink-0">
          <DialogTitle>{title}</DialogTitle>
        </DialogHeader>
        <form
          className="flex min-h-0 flex-1 flex-col"
          onSubmit={(e) => {
            e.preventDefault();
            onSubmit();
          }}
        >
          {/* Long forms (e.g. the booking form) scroll here while the
              title and the save/cancel buttons stay visible. */}
          <div className="min-h-0 flex-1 space-y-3 overflow-y-auto overscroll-contain px-1 py-1 -mx-1">
            {children}
          </div>
          <DialogFooter className="shrink-0 pt-3">
            <Button type="button" variant="ghost" onClick={onClose}>
              {t("common.cancel")}
            </Button>
            <Button type="submit" disabled={saving}>
              {submitLabel}
            </Button>
          </DialogFooter>
        </form>
      </DialogContent>
    </Dialog>
  );
}

function BookingDialog({
  rooms,
  slots,
  edit,
  initialDay,
  initialStart,
  nameOf,
  onClose,
  onSaved,
  onError,
}: {
  rooms: Room[];
  // Active booking slots. When non-empty the dialog restricts times to the
  // slot grid (same rule the server enforces); when empty it keeps the
  // legacy free start/end inputs.
  slots: ActiveSlotInfo[];
  edit?: Booking;
  initialDay?: Date;
  initialStart?: Date;
  nameOf: (ar: string, en: string) => string;
  onClose: () => void;
  onSaved: () => void;
  onError: (e: unknown) => void;
}) {
  const { t, i18n } = useTranslation();
  const isAr = i18n.language === "ar";
  const dayAt = (hour: number) => {
    if (!initialDay) return "";
    const d = new Date(initialDay);
    d.setHours(hour, 0, 0, 0);
    return toLocalInput(d.toISOString());
  };
  const [title, setTitle] = useState(edit?.title ?? "");
  const [roomId, setRoomId] = useState<string>(
    edit ? String(edit.roomId) : rooms[0] ? String(rooms[0].id) : "",
  );
  const [requesterName, setRequesterName] = useState(edit?.requesterName ?? "");
  const [startsAt, setStartsAt] = useState(() => {
    if (edit) return toLocalInput(edit.startsAt);
    if (initialStart) return toLocalInput(initialStart.toISOString());
    return dayAt(9);
  });
  const [endsAt, setEndsAt] = useState(() => {
    if (edit) return toLocalInput(edit.endsAt);
    if (initialStart)
      return toLocalInput(
        new Date(initialStart.getTime() + 60 * 60 * 1000).toISOString(),
      );
    return dayAt(10);
  });
  const slotMode = slots.length > 0;
  // Slot-mode state: a Riyadh calendar day + one of the active slots.
  const [slotDate, setSlotDate] = useState(() => {
    if (edit) return riyadhYmd(edit.startsAt);
    if (initialStart) return riyadhYmd(initialStart.toISOString());
    if (initialDay) return riyadhYmd(initialDay.toISOString());
    return "";
  });
  const [slotId, setSlotId] = useState<string>(() => {
    if (!slotMode) return "";
    const wanted = edit
      ? riyadhHHmm(edit.startsAt)
      : initialStart
        ? riyadhHHmm(initialStart.toISOString())
        : "";
    const match = slots.find((s) => s.startTime === wanted);
    return match ? String(match.id) : "";
  });
  const selectedSlot = slots.find((s) => String(s.id) === slotId);
  // Only slots offered on the chosen date's weekday (Riyadh calendar) can
  // be picked — same rule the server enforces.
  const daySlots = slotDate
    ? slots.filter((s) => s.daysOfWeek.includes(ymdWeekday(slotDate)))
    : slots;
  useEffect(() => {
    if (slotId && !daySlots.some((s) => String(s.id) === slotId)) {
      setSlotId("");
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [slotDate]);
  const [notes, setNotes] = useState(edit?.notes ?? "");
  const [requesterPhone, setRequesterPhone] = useState(
    edit?.requesterPhone ?? "",
  );
  const [department, setDepartment] = useState(edit?.department ?? "");
  const [meetingType, setMeetingType] = useState<MeetingType>(
    edit?.meetingType ?? "internal",
  );
  const [requesterOrg, setRequesterOrg] = useState(edit?.requesterOrg ?? "");
  const [attendeeCount, setAttendeeCount] = useState(
    edit?.attendeeCount != null ? String(edit.attendeeCount) : "",
  );
  const [purpose, setPurpose] = useState(edit?.purpose ?? "");
  const emptyRow = { name: "", position: "" };
  const initialRows = (side: "internal" | "external") => {
    const rows = (edit?.keyAttendees ?? [])
      .filter((a) => a.side === side)
      .map((a) => ({ name: a.name, position: a.position }));
    return rows.length > 0 ? rows : [{ ...emptyRow }];
  };
  const [internalAttendees, setInternalAttendees] = useState(() =>
    initialRows("internal"),
  );
  const [externalAttendees, setExternalAttendees] = useState(() =>
    initialRows("external"),
  );
  const isExternal = meetingType === "external";

  const mut = useMutation({
    mutationFn: () => {
      const keyAttendees = [
        ...internalAttendees
          .filter((r) => r.name.trim() !== "")
          .map((r) => ({
            name: r.name.trim(),
            position: r.position.trim(),
            side: "internal" as const,
          })),
        ...externalAttendees
          .filter((r) => r.name.trim() !== "")
          .map((r) => ({
            name: r.name.trim(),
            position: r.position.trim(),
            side: "external" as const,
          })),
      ];
      const body = {
        roomId: Number(roomId),
        title,
        requesterName: requesterName || null,
        requesterPhone: requesterPhone.trim() || null,
        department: department.trim() || null,
        meetingType,
        requesterOrg: isExternal ? requesterOrg.trim() || null : null,
        attendeeCount:
          attendeeCount.trim() === "" ? null : Number(attendeeCount),
        purpose: purpose.trim() || null,
        keyAttendees,
        startsAt:
          slotMode && selectedSlot
            ? slotToIso(slotDate, selectedSlot.startTime)
            : fromLocalInput(startsAt),
        endsAt:
          slotMode && selectedSlot
            ? slotToIso(
                slotDate,
                selectedSlot.startTime,
                selectedSlot.durationMinutes,
              )
            : fromLocalInput(endsAt),
        notes: notes || null,
      };
      return edit
        ? apiJson(`${API}/protocol/bookings/${edit.id}`, {
            method: "PATCH",
            body: JSON.stringify(body),
          })
        : apiJson(`${API}/protocol/bookings`, {
            method: "POST",
            body: JSON.stringify(body),
          });
    },
    onSuccess: onSaved,
    onError,
  });

  return (
    <DialogShell
      title={edit ? t("protocol.bookings.edit") : t("protocol.bookings.new")}
      onClose={onClose}
      onSubmit={() => {
        // Slot mode: both a date and a slot are required; block the submit
        // client-side instead of round-tripping to a server invalid_slot.
        if (slotMode && (!slotDate || !selectedSlot)) return;
        mut.mutate();
      }}
      submitLabel={t("common.save")}
      saving={mut.isPending}
    >
      <div>
        <Label>{t("protocol.bookings.requester")}</Label>
        <Input
          value={requesterName}
          onChange={(e) => setRequesterName(e.target.value)}
        />
      </div>
      <div>
        <Label>{t("protocol.bookings.phone")}</Label>
        <Input
          type="tel"
          inputMode="tel"
          dir="ltr"
          value={requesterPhone}
          onChange={(e) => setRequesterPhone(e.target.value)}
          placeholder="05xxxxxxxx"
        />
      </div>
      <div>
        <Label>{t("protocol.bookings.roomLabel")}</Label>
        <Select value={roomId} onValueChange={setRoomId}>
          <SelectTrigger>
            <SelectValue />
          </SelectTrigger>
          <SelectContent>
            {rooms.map((r) => (
              <SelectItem key={r.id} value={String(r.id)}>
                {nameOf(r.nameAr, r.nameEn)}
              </SelectItem>
            ))}
          </SelectContent>
        </Select>
      </div>
      <div>
        <Label>{t("protocol.bookings.department")}</Label>
        <Input
          value={department}
          onChange={(e) => setDepartment(e.target.value)}
        />
      </div>
      <div>
        <Label>{t("protocol.bookings.titleLabel")}</Label>
        <Input value={title} onChange={(e) => setTitle(e.target.value)} required />
      </div>
      <div>
        <Label>{t("protocol.bookings.meetingType")}</Label>
        <div className="mt-1 grid grid-cols-2 gap-2">
          {(["internal", "external"] as const).map((opt) => (
            <button
              key={opt}
              type="button"
              onClick={() => setMeetingType(opt)}
              className={cn(
                "rounded-lg border px-3 py-2 text-sm font-medium transition-colors",
                meetingType === opt
                  ? "border-sky-500 bg-sky-50 text-sky-700"
                  : "border-slate-200 bg-white text-slate-600 hover:border-slate-300",
              )}
            >
              {t(`protocol.bookings.meetingTypes.${opt}`)}
            </button>
          ))}
        </div>
      </div>
      {isExternal && (
        <div>
          <Label>{t("protocol.bookings.entity")}</Label>
          <Input
            value={requesterOrg}
            onChange={(e) => setRequesterOrg(e.target.value)}
          />
        </div>
      )}
      <div>
        <Label>{t("protocol.bookings.attendeeCount")}</Label>
        <Input
          type="number"
          inputMode="numeric"
          min={1}
          value={attendeeCount}
          onChange={(e) => setAttendeeCount(e.target.value)}
        />
      </div>
      <div>
        <Label>{t("protocol.bookings.purpose")}</Label>
        <Textarea
          value={purpose}
          onChange={(e) => setPurpose(e.target.value)}
          rows={2}
        />
      </div>
      {slotMode ? (
        <div className="grid grid-cols-2 gap-2">
          <div>
            <Label>{t("protocol.slots.dateLabel")}</Label>
            <Input
              type="date"
              value={slotDate}
              onChange={(e) => setSlotDate(e.target.value)}
              required
            />
          </div>
          <div>
            <Label>{t("protocol.slots.slotLabel")}</Label>
            <Select value={slotId} onValueChange={setSlotId}>
              <SelectTrigger>
                <SelectValue
                  placeholder={t("protocol.slots.pickSlot")}
                />
              </SelectTrigger>
              <SelectContent>
                {daySlots.map((s) => (
                  <SelectItem key={s.id} value={String(s.id)}>
                    <span dir={isAr ? "rtl" : "ltr"}>
                      {fmtSlotRange(s.startTime, s.durationMinutes, isAr)}
                    </span>
                  </SelectItem>
                ))}
              </SelectContent>
            </Select>
            {slotDate && daySlots.length === 0 && (
              <p className="text-xs text-amber-600 mt-1">
                {t("protocol.slots.noSlotsForDay")}
              </p>
            )}
          </div>
        </div>
      ) : (
        <div className="grid grid-cols-2 gap-2">
          <div>
            <Label>{t("protocol.bookings.startsAt")}</Label>
            <Input
              type="datetime-local"
              value={startsAt}
              onChange={(e) => setStartsAt(e.target.value)}
              required
            />
          </div>
          <div>
            <Label>{t("protocol.bookings.endsAt")}</Label>
            <Input
              type="datetime-local"
              value={endsAt}
              onChange={(e) => setEndsAt(e.target.value)}
              required
            />
          </div>
        </div>
      )}
      <div className="space-y-2">
        <Label>{t("protocol.bookings.keyAttendees")}</Label>
        <div className="grid grid-cols-1 gap-3 sm:grid-cols-2">
          <BookingAttendeeColumn
            title={t("protocol.bookings.attendeeSides.internal")}
            rows={internalAttendees}
            setRows={setInternalAttendees}
          />
          <BookingAttendeeColumn
            title={t("protocol.bookings.attendeeSides.external")}
            rows={externalAttendees}
            setRows={setExternalAttendees}
          />
        </div>
      </div>
      <div>
        <Label>{t("protocol.notes")}</Label>
        <Textarea value={notes} onChange={(e) => setNotes(e.target.value)} />
      </div>
    </DialogShell>
  );
}

// One "أبرز الحضور" column (من الهيئة / خارج الهيئة) in the internal
// booking dialog: rows of name + position with add/remove.
function BookingAttendeeColumn({
  title,
  rows,
  setRows,
}: {
  title: string;
  rows: Array<{ name: string; position: string }>;
  setRows: React.Dispatch<
    React.SetStateAction<Array<{ name: string; position: string }>>
  >;
}) {
  const { t } = useTranslation();
  return (
    <div className="space-y-2 rounded-lg border border-slate-200 p-3">
      <p className="text-sm font-semibold text-slate-700">{title}</p>
      {rows.map((row, idx) => (
        <div key={idx} className="space-y-1.5">
          <div className="flex items-center gap-1.5">
            <Input
              value={row.name}
              onChange={(e) =>
                setRows((rs) =>
                  rs.map((r, i) =>
                    i === idx ? { ...r, name: e.target.value } : r,
                  ),
                )
              }
              placeholder={t("protocol.bookings.form.name")}
            />
            {rows.length > 1 && (
              <button
                type="button"
                onClick={() =>
                  setRows((rs) => rs.filter((_, i) => i !== idx))
                }
                className="shrink-0 p-1 text-slate-400 hover:text-rose-500"
                aria-label={t("protocol.bookings.form.removeRow")}
              >
                <X className="h-4 w-4" />
              </button>
            )}
          </div>
          <Input
            value={row.position}
            onChange={(e) =>
              setRows((rs) =>
                rs.map((r, i) =>
                  i === idx ? { ...r, position: e.target.value } : r,
                ),
              )
            }
            placeholder={t("protocol.bookings.form.position")}
          />
        </div>
      ))}
      <button
        type="button"
        onClick={() => setRows((rs) => [...rs, { name: "", position: "" }])}
        className="flex items-center gap-1 text-sm font-medium text-sky-600 hover:text-sky-700"
      >
        <Plus className="h-4 w-4" />
        {t("protocol.bookings.form.addRow")}
      </button>
    </div>
  );
}

function RoomDialog({
  edit,
  onClose,
  onSaved,
  onError,
}: {
  edit?: Room;
  onClose: () => void;
  onSaved: () => void;
  onError: (e: unknown) => void;
}) {
  const { t } = useTranslation();
  const [nameAr, setNameAr] = useState(edit?.nameAr ?? "");
  const [nameEn, setNameEn] = useState(edit?.nameEn ?? "");
  const [capacity, setCapacity] = useState(
    edit?.capacity != null ? String(edit.capacity) : "",
  );
  const [location, setLocation] = useState(edit?.location ?? "");
  const [isActive, setIsActive] = useState(edit?.isActive ?? true);

  const mut = useMutation({
    mutationFn: () => {
      const body = {
        nameAr,
        nameEn,
        capacity: capacity ? Number(capacity) : null,
        location: location || null,
        isActive,
      };
      return edit
        ? apiJson(`${API}/protocol/rooms/${edit.id}`, {
            method: "PATCH",
            body: JSON.stringify(body),
          })
        : apiJson(`${API}/protocol/rooms`, {
            method: "POST",
            body: JSON.stringify(body),
          });
    },
    onSuccess: onSaved,
    onError,
  });

  return (
    <DialogShell
      title={edit ? t("protocol.rooms.edit") : t("protocol.rooms.new")}
      onClose={onClose}
      onSubmit={() => mut.mutate()}
      submitLabel={t("common.save")}
      saving={mut.isPending}
    >
      <div>
        <Label>{t("protocol.rooms.nameAr")}</Label>
        <Input value={nameAr} onChange={(e) => setNameAr(e.target.value)} required />
      </div>
      <div>
        <Label>{t("protocol.rooms.nameEn")}</Label>
        <Input value={nameEn} onChange={(e) => setNameEn(e.target.value)} />
      </div>
      <div className="grid grid-cols-2 gap-2">
        <div>
          <Label>{t("protocol.rooms.capacity")}</Label>
          <Input
            type="number"
            value={capacity}
            onChange={(e) => setCapacity(e.target.value)}
          />
        </div>
        <div>
          <Label>{t("protocol.rooms.location")}</Label>
          <Input value={location} onChange={(e) => setLocation(e.target.value)} />
        </div>
      </div>
      <label className="flex items-center gap-2 text-sm text-slate-700">
        <input
          type="checkbox"
          checked={isActive}
          onChange={(e) => setIsActive(e.target.checked)}
          className="h-4 w-4 rounded border-slate-300"
        />
        {t("protocol.rooms.isActive")}
      </label>
    </DialogShell>
  );
}

function ExternalDialog({
  edit,
  onClose,
  onSaved,
  onError,
}: {
  edit?: ExternalMeeting;
  onClose: () => void;
  onSaved: () => void;
  onError: (e: unknown) => void;
}) {
  const { t } = useTranslation();
  const [title, setTitle] = useState(edit?.title ?? "");
  const [partyName, setPartyName] = useState(edit?.partyName ?? "");
  const [location, setLocation] = useState(edit?.location ?? "");
  const [startsAt, setStartsAt] = useState(
    edit ? toLocalInput(edit.startsAt) : "",
  );
  const [status, setStatus] = useState<"completed" | "cancelled" | "">(
    edit?.status === "completed" || edit?.status === "cancelled"
      ? edit.status
      : "",
  );
  const [notes, setNotes] = useState(edit?.notes ?? "");

  const mut = useMutation({
    mutationFn: () => {
      const body: Record<string, unknown> = {
        title,
        partyName: partyName || null,
        location: location || null,
        startsAt: fromLocalInput(startsAt),
        notes: notes || null,
      };
      if (edit && status) body.status = status;
      return edit
        ? apiJson(`${API}/protocol/external-meetings/${edit.id}`, {
            method: "PATCH",
            body: JSON.stringify(body),
          })
        : apiJson(`${API}/protocol/external-meetings`, {
            method: "POST",
            body: JSON.stringify(body),
          });
    },
    onSuccess: onSaved,
    onError,
  });

  return (
    <DialogShell
      title={edit ? t("protocol.external.edit") : t("protocol.external.new")}
      onClose={onClose}
      onSubmit={() => mut.mutate()}
      submitLabel={t("common.save")}
      saving={mut.isPending}
    >
      <div>
        <Label>{t("protocol.external.titleLabel")}</Label>
        <Input value={title} onChange={(e) => setTitle(e.target.value)} required />
      </div>
      <div>
        <Label>{t("protocol.external.party")}</Label>
        <Input value={partyName} onChange={(e) => setPartyName(e.target.value)} />
      </div>
      <div>
        <Label>{t("protocol.external.location")}</Label>
        <Input value={location} onChange={(e) => setLocation(e.target.value)} />
      </div>
      <div className="grid grid-cols-2 gap-2">
        <div>
          <Label>{t("protocol.external.startsAt")}</Label>
          <Input
            type="datetime-local"
            value={startsAt}
            onChange={(e) => setStartsAt(e.target.value)}
            required
          />
        </div>
        {edit && (
          <div>
            <Label>{t("protocol.statusLabel")}</Label>
            <Select
              value={status}
              onValueChange={(v) =>
                setStatus(v as "completed" | "cancelled")
              }
            >
              <SelectTrigger>
                <SelectValue placeholder={t("protocol.status.pending")} />
              </SelectTrigger>
              <SelectContent>
                <SelectItem value="completed">
                  {t("protocol.status.completed")}
                </SelectItem>
                <SelectItem value="cancelled">
                  {t("protocol.status.cancelled")}
                </SelectItem>
              </SelectContent>
            </Select>
          </div>
        )}
      </div>
      <div>
        <Label>{t("protocol.notes")}</Label>
        <Textarea value={notes} onChange={(e) => setNotes(e.target.value)} />
      </div>
    </DialogShell>
  );
}

function GiftDialog({
  edit,
  onClose,
  onSaved,
  onError,
}: {
  edit?: Gift;
  onClose: () => void;
  onSaved: () => void;
  onError: (e: unknown) => void;
}) {
  const { t } = useTranslation();
  const [nameAr, setNameAr] = useState(edit?.nameAr ?? "");
  const [nameEn, setNameEn] = useState(edit?.nameEn ?? "");
  const [kind, setKind] = useState<GiftKind>(edit?.kind ?? "gift");
  const [quantityInStock, setQuantityInStock] = useState(
    edit ? String(edit.quantityInStock) : "0",
  );

  const mut = useMutation({
    mutationFn: () => {
      const body = {
        nameAr,
        nameEn,
        kind,
        quantityInStock: Number(quantityInStock) || 0,
      };
      return edit
        ? apiJson(`${API}/protocol/gifts/${edit.id}`, {
            method: "PATCH",
            body: JSON.stringify(body),
          })
        : apiJson(`${API}/protocol/gifts`, {
            method: "POST",
            body: JSON.stringify(body),
          });
    },
    onSuccess: onSaved,
    onError,
  });

  return (
    <DialogShell
      title={edit ? t("protocol.gifts.edit") : t("protocol.gifts.new")}
      onClose={onClose}
      onSubmit={() => mut.mutate()}
      submitLabel={t("common.save")}
      saving={mut.isPending}
    >
      <div>
        <Label>{t("protocol.gifts.nameAr")}</Label>
        <Input value={nameAr} onChange={(e) => setNameAr(e.target.value)} required />
      </div>
      <div>
        <Label>{t("protocol.gifts.nameEn")}</Label>
        <Input value={nameEn} onChange={(e) => setNameEn(e.target.value)} />
      </div>
      <div className="grid grid-cols-2 gap-2">
        <div>
          <Label>{t("protocol.gifts.kind")}</Label>
          <Select value={kind} onValueChange={(v) => setKind(v as GiftKind)}>
            <SelectTrigger>
              <SelectValue />
            </SelectTrigger>
            <SelectContent>
              <SelectItem value="gift">{t("protocol.giftKind.gift")}</SelectItem>
              <SelectItem value="shield">
                {t("protocol.giftKind.shield")}
              </SelectItem>
            </SelectContent>
          </Select>
        </div>
        <div>
          <Label>{t("protocol.gifts.stock")}</Label>
          <Input
            type="number"
            value={quantityInStock}
            onChange={(e) => setQuantityInStock(e.target.value)}
          />
        </div>
      </div>
    </DialogShell>
  );
}

function IssueDialog({
  gifts,
  nameOf,
  onClose,
  onSaved,
  onError,
}: {
  gifts: Gift[];
  nameOf: (ar: string, en: string) => string;
  onClose: () => void;
  onSaved: () => void;
  onError: (e: unknown) => void;
}) {
  const { t } = useTranslation();
  const [giftId, setGiftId] = useState<string>(
    gifts[0] ? String(gifts[0].id) : "",
  );
  const [recipientName, setRecipientName] = useState("");
  const [occasion, setOccasion] = useState("");
  const [quantity, setQuantity] = useState("1");

  const mut = useMutation({
    mutationFn: () =>
      apiJson(`${API}/protocol/gift-issues`, {
        method: "POST",
        body: JSON.stringify({
          giftId: Number(giftId),
          recipientName,
          occasion: occasion || null,
          quantity: Number(quantity) || 1,
        }),
      }),
    onSuccess: onSaved,
    onError,
  });

  return (
    <DialogShell
      title={t("protocol.issues.new")}
      onClose={onClose}
      onSubmit={() => mut.mutate()}
      submitLabel={t("common.save")}
      saving={mut.isPending}
    >
      <div>
        <Label>{t("protocol.issues.gift")}</Label>
        <Select value={giftId} onValueChange={setGiftId}>
          <SelectTrigger>
            <SelectValue />
          </SelectTrigger>
          <SelectContent>
            {gifts.map((g) => (
              <SelectItem key={g.id} value={String(g.id)}>
                {nameOf(g.nameAr, g.nameEn)} ({g.quantityInStock})
              </SelectItem>
            ))}
          </SelectContent>
        </Select>
      </div>
      <div>
        <Label>{t("protocol.issues.recipient")}</Label>
        <Input
          value={recipientName}
          onChange={(e) => setRecipientName(e.target.value)}
          required
        />
      </div>
      <div className="grid grid-cols-2 gap-2">
        <div>
          <Label>{t("protocol.issues.occasion")}</Label>
          <Input value={occasion} onChange={(e) => setOccasion(e.target.value)} />
        </div>
        <div>
          <Label>{t("protocol.issues.quantity")}</Label>
          <Input
            type="number"
            min={1}
            value={quantity}
            onChange={(e) => setQuantity(e.target.value)}
          />
        </div>
      </div>
    </DialogShell>
  );
}

// ---------------------------------------------------------------------------
// Visit photos (تصوير) — album grid + album detail with upload gallery.
// ---------------------------------------------------------------------------
type Album = {
  id: number;
  name: string;
  externalMeetingId: number | null;
  meetingTitle: string | null;
  createdBy: number | null;
  createdAt: string;
  updatedAt: string;
  photoCount: number;
  coverPath: string | null;
};
type Photo = {
  id: number;
  albumId: number;
  objectPath: string;
  sortOrder: number;
  uploadedBy: number | null;
  createdAt: string;
};

// Build the authorized image URL from a stored `/objects/<id>` path.
const photoSrc = (objectPath: string) => `${API}/storage${objectPath}`;
// Force-download URL for a single photo (attachment via ?download=1). The
// optional name base gets an extension appended server-side.
const photoDownloadSrc = (objectPath: string, name?: string) => {
  const base = `${API}/storage${objectPath}?download=1`;
  return name ? `${base}&name=${encodeURIComponent(name)}` : base;
};
// ZIP download URL for a whole album.
const albumDownloadSrc = (albumId: number) =>
  `${API}/protocol/photo-albums/${albumId}/download`;

function PhotosView({
  canMutate,
  isAr,
  externalMeetings,
  onApiError,
}: {
  canMutate: boolean;
  isAr: boolean;
  externalMeetings: ExternalMeeting[];
  onApiError: (e: unknown) => void;
}) {
  const { t } = useTranslation();
  const qc = useQueryClient();
  const [openAlbumId, setOpenAlbumId] = useState<number | null>(null);
  const [albumDialog, setAlbumDialog] = useState<{
    open: boolean;
    edit?: Album;
  }>({ open: false });
  const [deleteAlbum, setDeleteAlbum] = useState<Album | null>(null);
  const [deletePhoto, setDeletePhoto] = useState<Photo | null>(null);
  const BackIcon = isAr ? ArrowRight : ArrowLeft;

  const albums = useQuery({
    queryKey: ["protocol", "photo-albums"],
    queryFn: () => apiJson<Album[]>(`${API}/protocol/photo-albums`),
  });
  const photos = useQuery({
    queryKey: ["protocol", "photos", openAlbumId],
    queryFn: () =>
      apiJson<Photo[]>(`${API}/protocol/photo-albums/${openAlbumId}/photos`),
    enabled: openAlbumId !== null,
  });

  const openAlbum =
    openAlbumId !== null
      ? (albums.data ?? []).find((a) => a.id === openAlbumId)
      : undefined;

  const { uploadFile, isUploading, progress } = useUpload({
    basePath: `${API}/storage`,
    onError: onApiError,
  });

  const finalize = useMutation({
    mutationFn: (objectPath: string) =>
      apiJson(`${API}/protocol/photo-albums/${openAlbumId}/photos`, {
        method: "POST",
        body: JSON.stringify({ objectPath }),
      }),
    onError: onApiError,
  });

  const handleFiles = async (files: FileList | null) => {
    if (!files || openAlbumId === null) return;
    for (const file of Array.from(files)) {
      const resp: UploadResponse | null = await uploadFile(file);
      if (resp) await finalize.mutateAsync(resp.objectPath);
    }
    await qc.invalidateQueries({ queryKey: ["protocol", "photos", openAlbumId] });
    await qc.invalidateQueries({ queryKey: ["protocol", "photo-albums"] });
  };

  const delAlbumMut = useMutation({
    mutationFn: (id: number) =>
      apiJson(`${API}/protocol/photo-albums/${id}`, { method: "DELETE" }),
    onSuccess: () => {
      setDeleteAlbum(null);
      qc.invalidateQueries({ queryKey: ["protocol", "photo-albums"] });
    },
    onError: onApiError,
  });
  const delPhotoMut = useMutation({
    mutationFn: (id: number) =>
      apiJson(`${API}/protocol/photos/${id}`, { method: "DELETE" }),
    onSuccess: () => {
      setDeletePhoto(null);
      qc.invalidateQueries({ queryKey: ["protocol", "photos", openAlbumId] });
      qc.invalidateQueries({ queryKey: ["protocol", "photo-albums"] });
    },
    onError: onApiError,
  });

  // --- Album detail view -----------------------------------------------------
  if (openAlbum) {
    return (
      <section className="space-y-4">
        <div className="flex items-center justify-between gap-2">
          <div className="flex items-center gap-2 min-w-0">
            <Button
              size="sm"
              variant="ghost"
              onClick={() => setOpenAlbumId(null)}
            >
              <BackIcon size={16} />
            </Button>
            <div className="min-w-0">
              <h2 className="font-semibold text-slate-700 truncate">
                {openAlbum.name}
              </h2>
              {openAlbum.meetingTitle && (
                <div className="text-xs text-slate-400 truncate">
                  <Link2 size={11} className="inline me-1" />
                  {openAlbum.meetingTitle}
                </div>
              )}
            </div>
          </div>
          <div className="flex items-center gap-2 shrink-0">
            {(photos.data?.length ?? 0) > 0 ? (
              <a
                href={albumDownloadSrc(openAlbum.id)}
                className="inline-flex items-center gap-2 px-3 py-2 rounded-lg border border-slate-200 bg-white text-slate-700 text-sm hover:bg-slate-50 transition"
              >
                <Download size={16} />
                {t("protocol.photos.downloadAll")}
              </a>
            ) : (
              <span
                aria-disabled="true"
                className="inline-flex items-center gap-2 px-3 py-2 rounded-lg border border-slate-200 bg-white text-slate-400 text-sm opacity-60 cursor-not-allowed pointer-events-none"
              >
                <Download size={16} />
                {t("protocol.photos.downloadAll")}
              </span>
            )}
            {canMutate && (
              <label className="cursor-pointer">
                <input
                  type="file"
                  accept="image/*"
                  multiple
                  disabled={isUploading}
                  className="hidden"
                  onChange={(e) => {
                    void handleFiles(e.target.files);
                    e.currentTarget.value = "";
                  }}
                />
                <span className="inline-flex items-center gap-2 px-3 py-2 rounded-lg bg-primary text-primary-foreground text-sm hover:opacity-90 transition">
                  {isUploading ? (
                    <Loader2 size={16} className="animate-spin" />
                  ) : (
                    <Upload size={16} />
                  )}
                  {isUploading
                    ? `${t("protocol.photos.uploading")} ${progress}%`
                    : t("protocol.photos.addPhotos")}
                </span>
              </label>
            )}
          </div>
        </div>

        {photos.data && photos.data.length === 0 && (
          <EmptyState text={t("protocol.photos.emptyPhotos")} />
        )}
        <div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 gap-2">
          {(photos.data ?? []).map((p) => (
            <div
              key={p.id}
              className="group relative aspect-square rounded-xl overflow-hidden border border-slate-200 bg-slate-100"
            >
              <a href={photoSrc(p.objectPath)} target="_blank" rel="noreferrer">
                <img
                  src={photoSrc(p.objectPath)}
                  alt=""
                  loading="lazy"
                  className="w-full h-full object-cover"
                />
              </a>
              <a
                href={photoDownloadSrc(
                  p.objectPath,
                  `${openAlbum.name}-${p.sortOrder + 1}`,
                )}
                download
                title={t("protocol.photos.download")}
                aria-label={t("protocol.photos.download")}
                className="absolute bottom-1 end-1 p-1.5 rounded-lg bg-black/50 text-white opacity-0 group-hover:opacity-100 transition"
              >
                <Download size={14} />
              </a>
              {canMutate && (
                <button
                  type="button"
                  onClick={() => setDeletePhoto(p)}
                  className="absolute top-1 end-1 p-1.5 rounded-lg bg-black/50 text-white opacity-0 group-hover:opacity-100 transition"
                >
                  <Trash2 size={14} />
                </button>
              )}
            </div>
          ))}
        </div>

        {albumDialog.open && (
          <AlbumDialog
            edit={albumDialog.edit}
            externalMeetings={externalMeetings}
            onClose={() => setAlbumDialog({ open: false })}
            onSaved={() => {
              setAlbumDialog({ open: false });
              qc.invalidateQueries({ queryKey: ["protocol", "photo-albums"] });
            }}
            onError={onApiError}
          />
        )}
        <AlertDialog
          open={deletePhoto !== null}
          onOpenChange={(o) => !o && setDeletePhoto(null)}
        >
          <AlertDialogContent dir={isAr ? "rtl" : "ltr"}>
            <AlertDialogHeader>
              <AlertDialogTitle>{t("common.confirmDelete")}</AlertDialogTitle>
              <AlertDialogDescription>
                {t("protocol.photos.confirmDeletePhoto")}
              </AlertDialogDescription>
            </AlertDialogHeader>
            <AlertDialogFooter>
              <AlertDialogCancel>{t("common.cancel")}</AlertDialogCancel>
              <AlertDialogAction
                className="bg-rose-600 hover:bg-rose-700"
                onClick={() => deletePhoto && delPhotoMut.mutate(deletePhoto.id)}
              >
                {t("common.delete")}
              </AlertDialogAction>
            </AlertDialogFooter>
          </AlertDialogContent>
        </AlertDialog>
      </section>
    );
  }

  // --- Album grid view -------------------------------------------------------
  return (
    <section className="space-y-4">
      <div className="flex items-center justify-between">
        <h2 className="font-semibold text-slate-700">
          {t("protocol.tabs.photos")}
        </h2>
        {canMutate && (
          <Button size="sm" onClick={() => setAlbumDialog({ open: true })}>
            <Plus size={16} className="me-1" />
            {t("protocol.photos.newAlbum")}
          </Button>
        )}
      </div>

      {albums.data && albums.data.length === 0 && (
        <EmptyState text={t("protocol.photos.emptyAlbums")} />
      )}
      <div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 gap-3">
        {(albums.data ?? []).map((a) => (
          <div
            key={a.id}
            className="bg-white rounded-xl border border-slate-200 overflow-hidden"
          >
            <button
              type="button"
              onClick={() => setOpenAlbumId(a.id)}
              className="block w-full aspect-square bg-slate-100 relative"
            >
              {a.coverPath ? (
                <img
                  src={photoSrc(a.coverPath)}
                  alt=""
                  loading="lazy"
                  className="w-full h-full object-cover"
                />
              ) : (
                <span className="absolute inset-0 flex items-center justify-center text-slate-300">
                  <ImageIcon size={32} />
                </span>
              )}
              <span className="absolute bottom-1 end-1 text-[11px] px-1.5 py-0.5 rounded-full bg-black/55 text-white">
                {a.photoCount}
              </span>
            </button>
            <div className="p-2">
              <button
                type="button"
                onClick={() => setOpenAlbumId(a.id)}
                className="block w-full text-start font-medium text-sm text-slate-800 truncate"
              >
                {a.name}
              </button>
              {a.meetingTitle && (
                <div className="text-[11px] text-slate-400 truncate">
                  <Link2 size={10} className="inline me-1" />
                  {a.meetingTitle}
                </div>
              )}
              {canMutate && (
                <div className="flex gap-1 mt-1">
                  <Button
                    size="sm"
                    variant="ghost"
                    className="h-7 px-2"
                    onClick={() => setAlbumDialog({ open: true, edit: a })}
                  >
                    <Pencil size={13} />
                  </Button>
                  <Button
                    size="sm"
                    variant="ghost"
                    className="h-7 px-2"
                    onClick={() => setDeleteAlbum(a)}
                  >
                    <Trash2 size={13} className="text-rose-500" />
                  </Button>
                </div>
              )}
            </div>
          </div>
        ))}
      </div>

      {albumDialog.open && (
        <AlbumDialog
          edit={albumDialog.edit}
          externalMeetings={externalMeetings}
          onClose={() => setAlbumDialog({ open: false })}
          onSaved={() => {
            setAlbumDialog({ open: false });
            qc.invalidateQueries({ queryKey: ["protocol", "photo-albums"] });
          }}
          onError={onApiError}
        />
      )}
      <AlertDialog
        open={deleteAlbum !== null}
        onOpenChange={(o) => !o && setDeleteAlbum(null)}
      >
        <AlertDialogContent dir={isAr ? "rtl" : "ltr"}>
          <AlertDialogHeader>
            <AlertDialogTitle>{t("common.confirmDelete")}</AlertDialogTitle>
            <AlertDialogDescription>
              {t("protocol.photos.confirmDeleteAlbum")}
            </AlertDialogDescription>
          </AlertDialogHeader>
          <AlertDialogFooter>
            <AlertDialogCancel>{t("common.cancel")}</AlertDialogCancel>
            <AlertDialogAction
              className="bg-rose-600 hover:bg-rose-700"
              onClick={() => deleteAlbum && delAlbumMut.mutate(deleteAlbum.id)}
            >
              {t("common.delete")}
            </AlertDialogAction>
          </AlertDialogFooter>
        </AlertDialogContent>
      </AlertDialog>
    </section>
  );
}

function AlbumDialog({
  edit,
  externalMeetings,
  onClose,
  onSaved,
  onError,
}: {
  edit?: Album;
  externalMeetings: ExternalMeeting[];
  onClose: () => void;
  onSaved: () => void;
  onError: (e: unknown) => void;
}) {
  const { t } = useTranslation();
  const [name, setName] = useState(edit?.name ?? "");
  const [meetingId, setMeetingId] = useState<string>(
    edit?.externalMeetingId != null ? String(edit.externalMeetingId) : "none",
  );

  const save = useMutation({
    mutationFn: () => {
      const payload = {
        name: name.trim(),
        externalMeetingId: meetingId === "none" ? null : Number(meetingId),
      };
      return edit
        ? apiJson(`${API}/protocol/photo-albums/${edit.id}`, {
            method: "PATCH",
            body: JSON.stringify(payload),
          })
        : apiJson(`${API}/protocol/photo-albums`, {
            method: "POST",
            body: JSON.stringify(payload),
          });
    },
    onSuccess: onSaved,
    onError,
  });

  return (
    <DialogShell
      title={edit ? t("protocol.photos.editAlbum") : t("protocol.photos.newAlbum")}
      onClose={onClose}
      onSubmit={() => save.mutate()}
      submitLabel={t("common.save")}
      saving={save.isPending}
    >
      <div>
        <Label>{t("protocol.photos.albumName")}</Label>
        <Input
          value={name}
          onChange={(e) => setName(e.target.value)}
          required
        />
      </div>
      <div>
        <Label>{t("protocol.photos.linkMeeting")}</Label>
        <Select value={meetingId} onValueChange={setMeetingId}>
          <SelectTrigger>
            <SelectValue />
          </SelectTrigger>
          <SelectContent>
            <SelectItem value="none">{t("protocol.photos.noMeeting")}</SelectItem>
            {externalMeetings.map((m) => (
              <SelectItem key={m.id} value={String(m.id)}>
                {m.title}
              </SelectItem>
            ))}
          </SelectContent>
        </Select>
      </div>
    </DialogShell>
  );
}
