import { 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,
  BarChart3,
  ScrollText,
  Plus,
  Pencil,
  Trash2,
  Check,
  X,
  PackageCheck,
  CalendarDays,
  List as ListIcon,
  Link2,
  Copy,
  Camera,
  Upload,
  Loader2,
  Download,
  Image as ImageIcon,
} from "lucide-react";
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 { AppDock } from "@/components/app-dock";
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 BookingStatus = "pending" | "approved" | "rejected" | "cancelled";
type Booking = {
  id: number;
  roomId: number;
  title: string;
  requesterName: string | null;
  requesterPhone: string | null;
  requesterOrg: string | null;
  attendeeCount: number | 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"
  | "rooms"
  | "bookings"
  | "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",
  rooms: "rooms",
  bookings: "bookings",
  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();
}

// Group bookings by calendar day (ascending) for the calendar view.
function groupBookingsByDay(
  items: Booking[],
  isAr: boolean,
): Array<{ key: string; label: string; items: Booking[] }> {
  const map = new Map<string, Booking[]>();
  for (const b of items) {
    const d = new Date(b.startsAt);
    const key = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(
      2,
      "0",
    )}-${String(d.getDate()).padStart(2, "0")}`;
    if (!map.has(key)) map.set(key, []);
    map.get(key)!.push(b);
  }
  return Array.from(map.entries())
    .sort(([a], [b]) => a.localeCompare(b))
    .map(([key, group]) => ({
      key,
      label: new Date(`${key}T00:00:00`).toLocaleDateString(
        isAr ? "ar" : "en",
        { weekday: "long", year: "numeric", month: "long", day: "numeric" },
      ),
      items: group.sort(
        (a, b) =>
          new Date(a.startsAt).getTime() - new Date(b.startsAt).getTime(),
      ),
    }));
}

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",
};

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`),
  });
  const bookings = useQuery<Booking[]>({
    queryKey: ["protocol", "bookings"],
    queryFn: () => apiJson<Booking[]>(`${API}/protocol/bookings`),
  });
  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;
  }>({ open: false });
  const [rejectTarget, setRejectTarget] = useState<{
    kind: "booking" | "issue" | "external";
    id: number;
  } | null>(null);
  const [bookingsView, setBookingsView] = useState<"list" | "calendar">("list");
  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 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 back = () => setLocation("/");

  const TABS: Array<{ key: TabKey; label: string; icon: typeof Gift }> = [
    { key: "dashboard", label: t("protocol.tabs.dashboard"), icon: LayoutDashboard },
    { key: "rooms", label: t("protocol.tabs.rooms"), icon: DoorOpen },
    { key: "bookings", label: t("protocol.tabs.bookings"), icon: CalendarClock },
    { key: "external", label: t("protocol.tabs.external"), icon: Handshake },
    { key: "gifts", label: t("protocol.tabs.gifts"), icon: Gift },
    { key: "issues", label: t("protocol.tabs.issues"), icon: PackageCheck },
    { key: "photos", label: t("protocol.tabs.photos"), icon: Camera },
    { 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 renderBookingCard = (b: Booking) => (
    <div
      key={b.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">{b.title}</div>
          <div className="text-sm text-slate-500">
            {roomName(b.roomId)} · {fmtDateTime(b.startsAt)} –{" "}
            {fmtDateTime(b.endsAt)}
          </div>
          {b.requesterName && (
            <div className="text-xs text-slate-400">
              {t("protocol.bookings.requester")}: {b.requesterName}
            </div>
          )}
          {b.requesterOrg && (
            <div className="text-xs text-slate-400">
              {t("protocol.bookings.org")}: {b.requesterOrg}
            </div>
          )}
          {b.requesterPhone && (
            <div className="text-xs text-slate-400">
              {t("protocol.bookings.phone")}:{" "}
              <span dir="ltr">{b.requesterPhone}</span>
            </div>
          )}
          {b.attendeeCount != null && (
            <div className="text-xs text-slate-400">
              {t("protocol.bookings.attendeeCount")}: {b.attendeeCount}
            </div>
          )}
        </div>
        <div className="flex flex-col items-end gap-1 shrink-0">
          {b.source === "public" && (
            <span className="text-xs px-2 py-0.5 rounded-full bg-sky-100 text-sky-700">
              {t("protocol.bookings.publicRequest")}
            </span>
          )}
          <span
            className={cn(
              "text-xs px-2 py-0.5 rounded-full",
              STATUS_PILL[b.status],
            )}
          >
            {t(`protocol.status.${b.status}`)}
          </span>
        </div>
      </div>
      <div className="flex flex-wrap gap-1.5 mt-2">
        {c?.canApprove && b.status === "pending" && (
          <>
            <Button
              size="sm"
              variant="outline"
              onClick={() =>
                bookingAction.mutate({ id: b.id, action: "approve" })
              }
            >
              <Check size={14} className="me-1" />
              {t("protocol.actions.approve")}
            </Button>
            <Button
              size="sm"
              variant="outline"
              onClick={() => setRejectTarget({ kind: "booking", id: b.id })}
            >
              <X size={14} className="me-1" />
              {t("protocol.actions.reject")}
            </Button>
          </>
        )}
        {c?.canMutate &&
          (b.status === "pending" || b.status === "approved") && (
            <Button
              size="sm"
              variant="ghost"
              onClick={() =>
                bookingAction.mutate({ id: b.id, action: "cancel" })
              }
            >
              {t("protocol.actions.cancel")}
            </Button>
          )}
        {c?.canMutate && (
          <>
            <Button
              size="sm"
              variant="ghost"
              onClick={() => setBookingDialog({ open: true, edit: b })}
            >
              <Pencil size={14} />
            </Button>
            <Button
              size="sm"
              variant="ghost"
              onClick={() => setDeleteTarget({ kind: "booking", id: b.id })}
            >
              <Trash2 size={14} className="text-rose-500" />
            </Button>
          </>
        )}
      </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(({ key, label, icon: Icon }) => (
              <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" />
                  <div className="min-w-0">
                    <p className="text-xs font-medium text-slate-700">
                      {t("protocol.bookings.shareLinkTitle")}
                    </p>
                    <p
                      dir="ltr"
                      className="truncate text-xs text-slate-500 text-start"
                      title={publicBookingUrl}
                    >
                      {publicBookingUrl}
                    </p>
                  </div>
                </div>
                <Button
                  size="sm"
                  variant="outline"
                  onClick={copyBookingLink}
                  className="shrink-0 self-start sm:self-auto"
                >
                  {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>
            {bookings.data?.length === 0 && (
              <EmptyState text={t("protocol.bookings.empty")} />
            )}
            {bookingsView === "list" ? (
              <div className="grid gap-2">
                {(bookings.data ?? []).map((b) => renderBookingCard(b))}
              </div>
            ) : (
              <div className="space-y-4">
                {groupBookingsByDay(bookings.data ?? [], isAr).map((group) => (
                  <div key={group.key}>
                    <div className="text-sm font-semibold text-slate-600 mb-2 sticky top-16 bg-slate-50 py-1">
                      {group.label}
                    </div>
                    <div className="grid gap-2">
                      {group.items.map((b) => renderBookingCard(b))}
                    </div>
                  </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 === "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 ?? []}
          edit={bookingDialog.edit}
          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>

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

// ---------------------------------------------------------------------------
// Sub-views & dialogs
// ---------------------------------------------------------------------------
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>
  );
}

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"}>
        <DialogHeader>
          <DialogTitle>{title}</DialogTitle>
        </DialogHeader>
        <form
          className="space-y-3"
          onSubmit={(e) => {
            e.preventDefault();
            onSubmit();
          }}
        >
          {children}
          <DialogFooter>
            <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,
  edit,
  nameOf,
  onClose,
  onSaved,
  onError,
}: {
  rooms: Room[];
  edit?: Booking;
  nameOf: (ar: string, en: string) => string;
  onClose: () => void;
  onSaved: () => void;
  onError: (e: unknown) => void;
}) {
  const { t } = useTranslation();
  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(
    edit ? toLocalInput(edit.startsAt) : "",
  );
  const [endsAt, setEndsAt] = useState(edit ? toLocalInput(edit.endsAt) : "");
  const [notes, setNotes] = useState(edit?.notes ?? "");

  const mut = useMutation({
    mutationFn: () => {
      const body = {
        roomId: Number(roomId),
        title,
        requesterName: requesterName || null,
        startsAt: fromLocalInput(startsAt),
        endsAt: 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={() => mut.mutate()}
      submitLabel={t("common.save")}
      saving={mut.isPending}
    >
      <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.titleLabel")}</Label>
        <Input value={title} onChange={(e) => setTitle(e.target.value)} required />
      </div>
      <div>
        <Label>{t("protocol.bookings.requester")}</Label>
        <Input
          value={requesterName}
          onChange={(e) => setRequesterName(e.target.value)}
        />
      </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>
        <Label>{t("protocol.notes")}</Label>
        <Textarea value={notes} onChange={(e) => setNotes(e.target.value)} />
      </div>
    </DialogShell>
  );
}

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>
  );
}
