"use client";

import * as React from "react";
import Link from "next/link";
import { useRouter } from "next/navigation";
import { toast } from "sonner";
import { CalendarDays, CalendarOff, CalendarPlus, ChevronLeft, ChevronRight, Filter, LogIn, LogOut, Users, X, GripVertical, Ban, Clock, Trash2, CheckCheck, ExternalLink, Sparkles } from "lucide-react";
import { cn } from "@/lib/utils";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { FilterSelect } from "@/components/ui/data-table";
import { PageHeader } from "@/components/ui/page-header";
import { Tabs, TabsList, TabsTrigger, Popover, PopoverContent, PopoverTrigger, TooltipRoot, TooltipTrigger, TooltipContent } from "@/components/ui/primitives";
import { StatusBadge, SourceBadge, Badge } from "@/components/ui/badge";
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogBody, DialogFooter } from "@/components/ui/dialog";
import { useConfirm } from "@/components/ui/confirm";
import { addDays, parseDay, dayKey, fmtDate, diffDays, startOfMonth, endOfMonth, fmtRange } from "@/lib/dates";
import { fmtMoney, fmtPhone } from "@/lib/format";
import { RESERVATION_STATUSES, RESERVATION_STATUS_META, RESERVATION_SOURCES, RESERVATION_SOURCE_META, BLOCK_SOURCE_META, type BlockSource } from "@/lib/domain";
import { changeApartment, changeDates } from "@/lib/actions/reservations";
import { releaseHold, removeBlock } from "@/lib/actions/inventory";
import { BlockDatesDialog } from "@/components/inventory/inventory-dialogs";
import { SourceChip } from "@/components/inventory/conflict-alternatives";
import { useReservationDrawer } from "@/components/reservations/reservation-drawer";
import { CalendarAgenda, ApartmentStrips, AvailabilitySearch } from "./calendar-mobile";
import { CalendarPhone } from "./calendar-phone";

export interface CalRes {
  id: string;
  code: string;
  status: string;
  source: string;
  checkIn: string;
  checkOut: string;
  actualCheckOut: string | null;
  earlyCheckout: boolean;
  recoveredNights: number;
  nights: number;
  adults: number;
  children: number;
  totalAmount: number;
  amountPaid: number;
  apartmentId: string;
  createdById: string;
  assignedToId: string | null;
  customer: { fullName: string; phone: string };
}
export interface CalApt {
  id: string;
  code: string;
  name: string;
  status: string;
  cleaningStatus: string;
  maxGuests: number;
  building: string | null;
}
export interface CalBlock {
  id: string;
  apartmentId: string;
  startDate: string;
  endDate: string;
  reason: string | null;
  type: string;
  source: string;
  guestName: string | null;
  externalRef: string | null;
  amount: number | null;
  pendingApproval: boolean;
  releaseOnCleaning: boolean;
  reservationId: string | null;
}
export interface CalPerms {
  create: boolean;
  move: boolean;
  dates: boolean;
  money: boolean;
  checkin: boolean;
  checkout: boolean;
  block: boolean;
}

const STATUS_STYLE: Record<string, string> = {
  CONFIRMED: "bg-positive-500/90 text-white border-positive-600",
  CHECKED_IN: "bg-info-500/90 text-white border-info-600",
  PENDING: "bg-warning-500/90 text-white border-warning-600",
  CHECKED_OUT: "bg-stone-400/80 text-white border-stone-500",
  INQUIRY: "bg-surface-3 text-fg border-border-strong border-dashed",
};

/** Effective visual end of a reservation: an early check-out frees the tail. */
const visualEnd = (r: CalRes) => (r.actualCheckOut && r.actualCheckOut < r.checkOut ? r.actualCheckOut : r.checkOut);

type DragPayload = { id: string; offset: number };
type CellPick = { apartmentId: string; day: string };

export function CalendarView(p: { apartments: CalApt[]; reservations: CalRes[]; blocks: CalBlock[]; workers: { id: string; fullName: string }[]; today: string; start: string; loadedFrom: string; loadedTo: string; view: "timeline" | "month" | "day"; perms: CalPerms; currency: string; weekendDays: number[]; openBlock?: boolean; holdsOnly?: boolean; initialApartment?: string }) {
  const router = useRouter();
  const [view, setView] = React.useState<"timeline" | "month" | "day" | "agenda" | "strips" | "search">(p.view);
  const [start, setStart] = React.useState(parseDay(p.start));
  const [days, setDays] = React.useState(21);
  const [filters, setFilters] = React.useState({ apartment: p.initialApartment ?? "", status: "", source: "", worker: "", holds: !!p.holdsOnly });
  const [showFilters, setShowFilters] = React.useState(false);
  const [pendingMove, setPendingMove] = React.useState<{ res: CalRes; apartmentId: string; checkIn: string; checkOut: string } | null>(null);
  const [blockDlg, setBlockDlg] = React.useState<{ apartmentId?: string; start?: string; end?: string } | null>(p.openBlock ? {} : null);
  const [cellPick, setCellPick] = React.useState<CellPick | null>(null);

  // Responsive density: fewer days on small screens, default to Day view on phones.
  React.useEffect(() => {
    const apply = () => {
      const w = window.innerWidth;
      setDays(w < 640 ? 3 : w < 1024 ? 14 : w < 1440 ? 21 : 28);
      if (w < 640 && p.view === "timeline") setView("day");
    };
    apply();
    window.addEventListener("resize", apply);
    return () => window.removeEventListener("resize", apply);
  }, [p.view]);

  // If the requested window leaves the loaded range, ask the server for more.
  const loadedFrom = parseDay(p.loadedFrom);
  const loadedTo = parseDay(p.loadedTo);
  const ensureLoaded = React.useCallback(
    (s: Date, n: number) => {
      if (s < addDays(loadedFrom, 1) || addDays(s, n + 1) > loadedTo) router.replace(`/calendar?start=${dayKey(s)}&view=${view}`);
    },
    [router, loadedFrom, loadedTo, view]
  );
  const go = (n: number) => {
    const s = addDays(start, n);
    setStart(s);
    ensureLoaded(s, days);
  };
  const jump = (s: Date) => {
    setStart(s);
    ensureLoaded(s, days);
  };

  const holdAptIds = React.useMemo(() => new Set(p.blocks.filter((b) => b.type === "HOLD").map((b) => b.apartmentId)), [p.blocks]);
  const apartments = p.apartments.filter((a) => (!filters.apartment || a.id === filters.apartment) && (!filters.holds || holdAptIds.has(a.id)));
  const reservations = p.reservations.filter((r) => (!filters.status || r.status === filters.status) && (!filters.source || r.source === filters.source) && (!filters.worker || r.createdById === filters.worker || r.assignedToId === filters.worker));
  const activeFilterCount = [filters.apartment, filters.status, filters.source, filters.worker, filters.holds].filter(Boolean).length;
  const hasFilters = activeFilterCount > 0;
  const externalCount = p.blocks.filter((b) => b.type === "EXTERNAL" && parseDay(b.endDate) > parseDay(p.today)).length;
  const holdCount = p.blocks.filter((b) => b.type === "HOLD").length;

  async function confirmMove() {
    if (!pendingMove) return;
    const { res, apartmentId, checkIn, checkOut } = pendingMove;
    const apartmentChanged = apartmentId !== res.apartmentId;
    const datesChanged = checkIn !== res.checkIn || checkOut !== res.checkOut;
    if (datesChanged) {
      const r = await changeDates({ id: res.id, checkIn, checkOut, recalculate: false, reason: "Moved on calendar" });
      if (!r.ok) {
        toast.error(r.error);
        return;
      }
    }
    if (apartmentChanged) {
      const r = await changeApartment({ id: res.id, apartmentId, recalculate: false, reason: "Moved on calendar" });
      if (!r.ok) {
        toast.error(r.error);
        return;
      }
    }
    toast.success(`${res.code} moved`);
    setPendingMove(null);
    router.refresh();
  }

  const title = view === "month" ? start.toLocaleString("en", { month: "long", year: "numeric", timeZone: "UTC" }) : view === "day" ? fmtDate(start, { style: "weekday" }) : view === "search" ? "Availability search" : `${fmtDate(start)} → ${fmtDate(addDays(start, days - 1))}`;
  const pickedApt = cellPick ? p.apartments.find((a) => a.id === cellPick.apartmentId) : null;

  return (
    <div className="space-y-3">
      <PageHeader
        title="Calendar"
        description={
          <span className="flex flex-wrap items-center gap-x-3 gap-y-1">
            <span>Live inventory across {p.apartments.length} apartments</span>
            {externalCount ? (
              <span className="inline-flex items-center gap-1 text-xs">
                <ExternalLink className="size-3" /> {externalCount} external block{externalCount > 1 ? "s" : ""}
              </span>
            ) : null}
            {holdCount ? (
              <button type="button" onClick={() => setFilters({ ...filters, holds: !filters.holds })} className={cn("inline-flex items-center gap-1 rounded-full border px-2 py-0.5 text-2xs font-medium transition", filters.holds ? "border-warning-500 bg-warning-50 text-warning-700 dark:bg-warning-500/10" : "border-border text-fg-muted hover:border-warning-500/60")}>
                <Clock className="size-3" /> {holdCount} hold{holdCount > 1 ? "s" : ""}
              </button>
            ) : null}
          </span>
        }
        className="mb-2"
        actions={
          <>
            {p.perms.block ? (
              <Button variant="secondary" size="sm" onClick={() => setBlockDlg({ start: dayKey(start) })}>
                <CalendarOff /> Block dates
              </Button>
            ) : null}
            {p.perms.create ? (
              <Button asChild size="sm">
                <Link href={`/reservations/new?checkIn=${dayKey(start)}&checkOut=${dayKey(addDays(start, 1))}`}>
                  <CalendarPlus /> New reservation
                </Link>
              </Button>
            ) : null}
          </>
        }
      />
      <div className="hidden flex-wrap items-center gap-2 sm:flex">
        <div className="flex items-center gap-1 rounded-md border border-border bg-surface p-0.5">
          <Button variant="ghost" size="iconSm" onClick={() => go(view === "month" ? -30 : view === "day" ? -1 : -7)} aria-label="Previous">
            <ChevronLeft />
          </Button>
          <Button variant="ghost" size="sm" onClick={() => jump(parseDay(p.today))}>
            Today
          </Button>
          <Button variant="ghost" size="iconSm" onClick={() => go(view === "month" ? 30 : view === "day" ? 1 : 7)} aria-label="Next">
            <ChevronRight />
          </Button>
        </div>
        <Input type="date" value={dayKey(start)} onChange={(e) => e.target.value && jump(parseDay(e.target.value))} className="h-9 w-[150px] text-xs" aria-label="Jump to date" />
        <span className="hidden text-sm font-semibold text-fg sm:inline">{title}</span>
        <div className="ml-auto flex items-center gap-2">
          <Button variant={showFilters || hasFilters ? "subtle" : "ghost"} size="sm" onClick={() => setShowFilters((s) => !s)}>
            <Filter /> Filters{hasFilters ? ` · ${activeFilterCount}` : ""}
          </Button>
          <Tabs
            value={view}
            onValueChange={(v) => {
              setView(v as typeof view);
              if (v === "month") jump(startOfMonth(start));
            }}
          >
            <TabsList variant="pill" className="hidden h-9 sm:flex">
              <TabsTrigger value="day" className="py-1.5 text-xs">
                Day
              </TabsTrigger>
              <TabsTrigger value="timeline" className="py-1.5 text-xs">
                Timeline
              </TabsTrigger>
              <TabsTrigger value="month" className="py-1.5 text-xs">
                Month
              </TabsTrigger>
            </TabsList>
          </Tabs>
        </div>
      </div>
      {showFilters ? (
        <div className="flex flex-wrap items-center gap-2 rounded-lg border border-border bg-surface p-2 animate-fade-in">
          <FilterSelect value={filters.apartment} onChange={(v) => setFilters({ ...filters, apartment: v })} placeholder="All apartments" options={p.apartments.map((a) => ({ value: a.id, label: `${a.code} · ${a.name}` }))} />
          <FilterSelect value={filters.status} onChange={(v) => setFilters({ ...filters, status: v })} placeholder="All statuses" options={RESERVATION_STATUSES.filter((s) => s !== "CANCELLED" && s !== "NO_SHOW").map((s) => ({ value: s, label: RESERVATION_STATUS_META[s].label }))} />
          <FilterSelect value={filters.source} onChange={(v) => setFilters({ ...filters, source: v })} placeholder="All sources" options={RESERVATION_SOURCES.map((s) => ({ value: s, label: RESERVATION_SOURCE_META[s].label }))} />
          <FilterSelect value={filters.worker} onChange={(v) => setFilters({ ...filters, worker: v })} placeholder="All workers" options={p.workers.map((w) => ({ value: w.id, label: w.fullName }))} />
          <label className="flex items-center gap-1.5 text-xs text-fg-muted">
            <input type="checkbox" checked={filters.holds} onChange={(e) => setFilters({ ...filters, holds: e.target.checked })} className="accent-primary" /> Only apartments with holds
          </label>
          {hasFilters ? (
            <Button variant="ghost" size="sm" onClick={() => setFilters({ apartment: "", status: "", source: "", worker: "", holds: false })}>
              <X /> Clear
            </Button>
          ) : null}
        </div>
      ) : null}

      <div className="sm:hidden">
        <CalendarPhone apartments={p.apartments} reservations={p.reservations} blocks={p.blocks} today={p.today} loadedFrom={p.loadedFrom} loadedTo={p.loadedTo} perms={p.perms} currency={p.currency} weekendDays={p.weekendDays} onBlock={(apartmentId, start, end) => setBlockDlg({ apartmentId, start, end })} />
      </div>
      <div className="hidden sm:block">
      {view === "agenda" ? (
        <CalendarAgenda apartments={apartments} reservations={reservations} blocks={p.blocks} start={start} today={p.today} perms={p.perms} />
      ) : view === "strips" ? (
        <ApartmentStrips apartments={apartments} reservations={reservations} blocks={p.blocks} start={start} today={p.today} />
      ) : view === "search" ? (
        <AvailabilitySearch today={p.today} perms={p.perms} currency={p.currency} />
      ) : view === "timeline" ? (
        <Timeline apartments={apartments} reservations={reservations} blocks={p.blocks} start={start} days={days} today={p.today} perms={p.perms} currency={p.currency} weekendDays={p.weekendDays} onDrop={(res, apartmentId, checkIn) => setPendingMove({ res, apartmentId, checkIn, checkOut: dayKey(addDays(parseDay(checkIn), res.nights)) })} onCell={(apartmentId, day) => setCellPick({ apartmentId, day })} onBlockRange={(apartmentId, s, e) => setBlockDlg({ apartmentId, start: s, end: e })} />
      ) : view === "month" ? (
        <MonthGrid apartments={apartments} reservations={reservations} blocks={p.blocks} month={start} today={p.today} onPickDay={(d) => { jump(d); setView("day"); }} />
      ) : (
        <DayAgenda apartments={apartments} reservations={reservations} blocks={p.blocks} day={start} today={p.today} perms={p.perms} currency={p.currency} onBlock={(apartmentId, s) => setBlockDlg({ apartmentId, start: s, end: dayKey(addDays(parseDay(s), 1)) })} />
      )}

      <Legend />
      </div>

      <Dialog open={!!pendingMove} onOpenChange={(o) => !o && setPendingMove(null)}>
        <DialogContent size="sm">
          {pendingMove ? <MoveConfirm move={pendingMove} apartments={p.apartments} onCancel={() => setPendingMove(null)} onConfirm={confirmMove} canMove={p.perms.move} canDates={p.perms.dates} /> : null}
        </DialogContent>
      </Dialog>

      <Dialog open={!!blockDlg} onOpenChange={(o) => !o && setBlockDlg(null)}>
        <DialogContent size="lg">{blockDlg ? <BlockDatesDialog apartments={p.apartments} presetApartmentId={blockDlg.apartmentId} presetStart={blockDlg.start} presetEnd={blockDlg.end} currency={p.currency} onClose={() => { setBlockDlg(null); router.refresh(); }} /> : null}</DialogContent>
      </Dialog>

      <Dialog open={!!cellPick} onOpenChange={(o) => !o && setCellPick(null)}>
        <DialogContent size="sm">
          {cellPick && pickedApt ? (
            <>
              <DialogHeader>
                <DialogTitle>
                  {pickedApt.code} · {fmtDate(parseDay(cellPick.day), { style: "weekday" })}
                </DialogTitle>
                <DialogDescription>{pickedApt.name} is available this night. What would you like to do?</DialogDescription>
              </DialogHeader>
              <DialogBody className="grid gap-2 sm:grid-cols-2">
                {p.perms.create ? (
                  <Link href={`/reservations/new?apartment=${pickedApt.id}&checkIn=${cellPick.day}&checkOut=${dayKey(addDays(parseDay(cellPick.day), 1))}`} className="card-lift flex flex-col items-start gap-1 rounded-lg border border-border p-3 text-left hover:border-primary">
                    <CalendarPlus className="size-5 text-primary" />
                    <span className="text-sm font-semibold">New reservation</span>
                    <span className="text-xs text-fg-muted">Start the booking wizard with this apartment and night pre-selected.</span>
                  </Link>
                ) : null}
                {p.perms.block ? (
                  <button type="button" onClick={() => { setBlockDlg({ apartmentId: pickedApt.id, start: cellPick.day, end: dayKey(addDays(parseDay(cellPick.day), 1)) }); setCellPick(null); }} className="card-lift flex flex-col items-start gap-1 rounded-lg border border-border p-3 text-left hover:border-primary">
                    <CalendarOff className="size-5 text-negative-600" />
                    <span className="text-sm font-semibold">Block dates</span>
                    <span className="text-xs text-fg-muted">Airbnb, Booking, owner use, maintenance or a private hold.</span>
                  </button>
                ) : null}
              </DialogBody>
              <DialogFooter>
                <Button variant="ghost" onClick={() => setCellPick(null)}>
                  Cancel
                </Button>
              </DialogFooter>
            </>
          ) : null}
        </DialogContent>
      </Dialog>
    </div>
  );
}

function Legend() {
  return (
    <div className="flex flex-wrap items-center gap-x-4 gap-y-1.5 text-2xs text-fg-muted">
      {(["CONFIRMED", "CHECKED_IN", "PENDING", "CHECKED_OUT", "INQUIRY"] as const).map((s) => (
        <span key={s} className="flex items-center gap-1.5">
          <span className={cn("h-2.5 w-4 rounded-sm border", STATUS_STYLE[s])} /> {RESERVATION_STATUS_META[s].label}
        </span>
      ))}
      {(["AIRBNB", "BOOKING", "EXPEDIA", "OWNER", "PRIVATE"] as const).map((s) => (
        <span key={s} className="flex items-center gap-1.5">
          <span className="h-2.5 w-4 rounded-sm" style={{ background: BLOCK_SOURCE_META[s].color }} /> {BLOCK_SOURCE_META[s].short}
        </span>
      ))}
      <span className="flex items-center gap-1.5">
        <span className="h-2.5 w-4 rounded-sm bg-[repeating-linear-gradient(45deg,var(--color-negative-500)_0_2px,transparent_2px_5px)]" /> Maintenance / manual
      </span>
      <span className="flex items-center gap-1.5">
        <span className="h-2.5 w-4 rounded-sm border border-dashed border-warning-500 bg-warning-500/20" /> Hold
      </span>
      <span className="flex items-center gap-1.5">
        <span className="h-2.5 w-4 rounded-sm border border-dashed border-positive-500 bg-positive-500/15" /> Released nights
      </span>
      <span className="ml-auto hidden sm:inline">Hover for details · click to open · drag to move · click an empty cell to book or block</span>
    </div>
  );
}

// ── Timeline (apartment rows × dates) ─────────────────────────
function Timeline({ apartments, reservations, blocks, start, days, today, perms, currency, weekendDays, onDrop, onCell, onBlockRange }: { apartments: CalApt[]; reservations: CalRes[]; blocks: CalBlock[]; start: Date; days: number; today: string; perms: CalPerms; currency: string; weekendDays: number[]; onDrop: (res: CalRes, apartmentId: string, checkIn: string) => void; onCell: (apartmentId: string, day: string) => void; onBlockRange: (apartmentId: string, start: string, end: string) => void }) {
  const dates = React.useMemo(() => Array.from({ length: days }, (_, i) => addDays(start, i)), [start, days]);
  const end = addDays(start, days);
  const canDrag = perms.move || perms.dates;
  const [dragOver, setDragOver] = React.useState<{ apt: string; day: string } | null>(null);
  const COL = 44;
  const LABEL = 132;

  const rowFor = (apt: CalApt) => {
    const rs = reservations.filter((r) => r.apartmentId === apt.id && parseDay(r.checkIn) < end && parseDay(r.checkOut) > start);
    const bs = blocks.filter((b) => b.apartmentId === apt.id && parseDay(b.startDate) < end && parseDay(b.endDate) > start);
    return { rs, bs };
  };
  const span = (s0: string, e0: string) => {
    const s = Math.max(0, diffDays(start, parseDay(s0)));
    const e = Math.min(days, diffDays(start, parseDay(e0)));
    return { left: LABEL + s * COL + 2, width: Math.max(0, (e - s) * COL - 4) };
  };

  return (
    <div className="surface overflow-hidden">
      <div className="overflow-x-auto scrollbar-thin">
        <div style={{ minWidth: LABEL + days * COL }}>
          {/* header */}
          <div className="sticky top-0 z-[2] flex border-b border-border bg-surface-2/80 backdrop-blur">
            <div className="sticky left-0 z-[3] flex shrink-0 items-end border-r border-border bg-surface-2 px-3 py-2 text-2xs font-semibold uppercase tracking-wider text-fg-muted" style={{ width: LABEL }}>
              Apartment
            </div>
            {dates.map((d) => {
              const k = dayKey(d);
              const isToday = k === today;
              const wk = weekendDays.includes(d.getUTCDay());
              return (
                <div key={k} className={cn("flex shrink-0 flex-col items-center justify-end py-1.5 text-center", wk && "bg-gold-100/40 dark:bg-gold-500/5", isToday && "bg-primary/10")} style={{ width: COL }}>
                  <span className="text-2xs uppercase text-fg-subtle">{d.toLocaleString("en", { weekday: "short", timeZone: "UTC" }).slice(0, 2)}</span>
                  <span className={cn("text-sm font-semibold tabular", isToday && "flex size-6 items-center justify-center rounded-full bg-primary text-primary-fg text-xs")}>{d.getUTCDate()}</span>
                  {d.getUTCDate() === 1 || k === dayKey(start) ? <span className="text-2xs text-fg-muted">{d.toLocaleString("en", { month: "short", timeZone: "UTC" })}</span> : null}
                </div>
              );
            })}
          </div>
          {/* rows */}
          {apartments.map((apt) => {
            const { rs, bs } = rowFor(apt);
            const hasHold = bs.some((b) => b.type === "HOLD");
            return (
              <div key={apt.id} className={cn("relative flex border-b border-border last:border-0", hasHold && "bg-warning-500/[0.03]")} style={{ height: 52 }}>
                <Link href={`/apartments/${apt.id}?tab=timeline`} className="sticky left-0 z-[1] flex shrink-0 items-center gap-2 border-r border-border bg-surface px-3 hover:bg-surface-2" style={{ width: LABEL }}>
                  <span className="font-mono text-xs font-semibold">{apt.code}</span>
                  <span className="min-w-0 flex-1 truncate text-xs text-fg-muted">{apt.name}</span>
                  {apt.cleaningStatus !== "CLEAN" && apt.cleaningStatus !== "READY" ? <Sparkles className="size-3 shrink-0 text-warning-500" aria-label="Needs cleaning" /> : null}
                </Link>
                {/* cells */}
                {dates.map((d) => {
                  const k = dayKey(d);
                  const wk = weekendDays.includes(d.getUTCDay());
                  const over = dragOver?.apt === apt.id && dragOver.day === k;
                  const clickable = perms.create || perms.block;
                  return (
                    <div
                      key={k}
                      className={cn("shrink-0 border-r border-border/60 transition-colors", wk && "bg-gold-100/30 dark:bg-gold-500/5", k === today && "bg-primary/5", over && "bg-primary/20", clickable && "cursor-pointer hover:bg-primary/10")}
                      style={{ width: COL }}
                      onClick={() => clickable && onCell(apt.id, k)}
                      onDragOver={(e) => {
                        if (!canDrag) return;
                        e.preventDefault();
                        setDragOver({ apt: apt.id, day: k });
                      }}
                      onDragLeave={() => setDragOver(null)}
                      onDrop={(e) => {
                        e.preventDefault();
                        setDragOver(null);
                        try {
                          const payload = JSON.parse(e.dataTransfer.getData("application/x-locajour")) as DragPayload;
                          const res = reservations.find((r) => r.id === payload.id);
                          if (!res) return;
                          const newCheckIn = dayKey(addDays(d, -payload.offset));
                          if (res.apartmentId === apt.id && res.checkIn === newCheckIn) return;
                          onDrop(res, apt.id, newCheckIn);
                        } catch {}
                      }}
                    />
                  );
                })}
                {/* released tails (early check-outs) */}
                {rs
                  .filter((r) => r.status === "CHECKED_OUT" && r.actualCheckOut && r.actualCheckOut < r.checkOut && parseDay(r.checkOut) > start && parseDay(r.actualCheckOut) < end)
                  .map((r) => {
                    const { left, width } = span(r.actualCheckOut!, r.checkOut);
                    if (width <= 0) return null;
                    const n = diffDays(parseDay(r.actualCheckOut!), parseDay(r.checkOut));
                    const future = r.checkOut > today;
                    return (
                      <TooltipRoot key={`rel-${r.id}`} delayDuration={200}>
                        <TooltipTrigger asChild>
                          <button type="button" onClick={() => future && perms.create && onCell(apt.id, r.actualCheckOut! > today ? r.actualCheckOut! : today)} className={cn("absolute top-[11px] flex h-[30px] items-center justify-center overflow-hidden rounded-md border border-dashed border-positive-500 bg-positive-500/10 px-1 text-2xs font-medium text-positive-700 transition-[left,width] duration-300 dark:text-positive-500", future && perms.create && "hover:bg-positive-500/20")} style={{ left, width }}>
                            {width > 60 ? `Released · ${n}n` : n}
                          </button>
                        </TooltipTrigger>
                        <TooltipContent className="max-w-[260px] bg-surface p-3 text-fg shadow-xl ring-1 ring-border dark:bg-surface">
                          <p className="text-xs font-semibold">
                            {n} night{n > 1 ? "s" : ""} released by early check-out
                          </p>
                          <p className="mt-0.5 text-2xs text-fg-muted">
                            {r.customer.fullName} left on {fmtDate(r.actualCheckOut!, { style: "short" })} instead of {fmtDate(r.checkOut, { style: "short" })}.
                          </p>
                          {future ? <p className="mt-1 text-2xs text-positive-600">Available to sell{perms.create ? " · click to book" : ""}</p> : null}
                        </TooltipContent>
                      </TooltipRoot>
                    );
                  })}
                {/* blocks & holds */}
                {bs.map((b) => {
                  const { left, width } = span(b.startDate, b.endDate);
                  if (width <= 0) return null;
                  return <BlockBar key={b.id} b={b} left={left} width={width} perms={perms} currency={currency} onEdit={() => onBlockRange(b.apartmentId, b.startDate, b.endDate)} />;
                })}
                {/* reservations */}
                {rs.map((r) => {
                  const ci = parseDay(r.checkIn);
                  const co = parseDay(visualEnd(r));
                  const s = diffDays(start, ci);
                  const e = diffDays(start, co);
                  if (e <= 0 || s >= days) return null;
                  const clampedS = Math.max(0, s);
                  const clampedE = Math.min(days, e);
                  const left = LABEL + clampedS * COL + (s >= 0 ? COL / 2 : 0);
                  const width = (clampedE - clampedS) * COL - (s >= 0 ? COL / 2 : 0) - (e <= days ? COL / 2 : 0);
                  return <ResBlock key={r.id} r={r} left={left} width={Math.max(width, 18)} draggable={canDrag && !["CHECKED_OUT", "INQUIRY"].includes(r.status)} perms={perms} currency={currency} clippedStart={s < 0} clippedEnd={e > days} today={today} />;
                })}
              </div>
            );
          })}
          {apartments.length === 0 ? <div className="p-10 text-center text-sm text-fg-muted">No apartments match the filter.</div> : null}
        </div>
      </div>
    </div>
  );
}

function blockStyle(b: CalBlock): { className: string; style?: React.CSSProperties; label: string } {
  const meta = BLOCK_SOURCE_META[b.source as BlockSource] ?? BLOCK_SOURCE_META.OTHER;
  if (b.type === "HOLD") return { className: "border border-dashed border-warning-500 bg-warning-500/15 text-warning-700 dark:text-warning-500", label: b.pendingApproval ? "Hold · approval" : b.releaseOnCleaning ? "Hold · cleaning" : "Hold" };
  if (b.type === "EXTERNAL" || meta.external) return { className: "text-white shadow-sm", style: { background: meta.color, borderColor: meta.color }, label: `${meta.short}${b.guestName ? ` · ${b.guestName}` : ""}` };
  if (b.type === "MAINTENANCE" || b.source === "MAINTENANCE") return { className: "bg-[repeating-linear-gradient(45deg,var(--color-negative-500)_0_2px,transparent_2px_6px)] text-negative-700 dark:text-negative-500", label: "Maintenance" };
  if (b.source === "OWNER" || b.source === "PRIVATE" || b.source === "CLEANING") return { className: "text-white shadow-sm", style: { background: meta.color }, label: `${meta.short}${b.guestName ? ` · ${b.guestName}` : ""}` };
  return { className: "bg-[repeating-linear-gradient(45deg,var(--color-stone-400)_0_2px,transparent_2px_6px)] text-fg-muted", label: b.reason ?? "Blocked" };
}

function BlockBar({ b, left, width, perms, currency, onEdit }: { b: CalBlock; left: number; width: number; perms: CalPerms; currency: string; onEdit: () => void }) {
  const router = useRouter();
  const confirm = useConfirm();
  const { open } = useReservationDrawer();
  const [busy, setBusy] = React.useState(false);
  const meta = BLOCK_SOURCE_META[b.source as BlockSource] ?? BLOCK_SOURCE_META.OTHER;
  const s = blockStyle(b);
  const nights = diffDays(parseDay(b.startDate), parseDay(b.endDate));
  async function release() {
    const { ok, reason } = await confirm({ title: b.type === "HOLD" ? "Release these nights?" : "Remove this block?", description: `${nights} night${nights > 1 ? "s" : ""} on ${fmtRange(b.startDate, b.endDate)} become available immediately.`, confirmLabel: b.type === "HOLD" ? "Release" : "Remove" });
    if (!ok) return;
    setBusy(true);
    const r = b.type === "HOLD" ? await releaseHold(b.id, reason) : await removeBlock(b.id, reason);
    setBusy(false);
    if (!r.ok) return toast.error(r.error);
    toast.success(b.type === "HOLD" ? "Nights released" : "Block removed");
    router.refresh();
  }
  return (
    <Popover>
      <PopoverTrigger asChild>
        <button type="button" className={cn("absolute top-[9px] flex h-[34px] items-center gap-1 overflow-hidden rounded-md border border-transparent px-2 text-left text-2xs font-semibold transition-[left,width] duration-300 hover:brightness-110 focus:outline-none focus-visible:ring-2 focus-visible:ring-ring", s.className)} style={{ left, width, ...s.style }} title={`${meta.label} · ${fmtRange(b.startDate, b.endDate)}`}>
          {b.type === "EXTERNAL" ? <ExternalLink className="size-3 shrink-0 opacity-80" /> : b.type === "HOLD" ? <Clock className="size-3 shrink-0" /> : <Ban className="size-3 shrink-0 opacity-70" />}
          <span className="truncate">{s.label}</span>
        </button>
      </PopoverTrigger>
      <PopoverContent align="start" className="w-80 p-0">
        <div className="border-b border-border p-3">
          <div className="flex items-start justify-between gap-2">
            <div>
              <p className="flex items-center gap-2 font-semibold">
                <span className="size-2.5 rounded-full" style={{ background: meta.color }} /> {meta.label}
              </p>
              <p className="text-xs text-fg-muted">
                {fmtRange(b.startDate, b.endDate)} · {nights} night{nights > 1 ? "s" : ""}
              </p>
            </div>
            <SourceChip source={b.source} />
          </div>
          <dl className="mt-2 grid grid-cols-2 gap-x-3 gap-y-1 text-xs">
            {b.guestName ? (
              <>
                <dt className="text-fg-subtle">Guest</dt>
                <dd className="font-medium">{b.guestName}</dd>
              </>
            ) : null}
            {b.externalRef ? (
              <>
                <dt className="text-fg-subtle">Reference</dt>
                <dd className="font-mono">{b.externalRef}</dd>
              </>
            ) : null}
            {b.amount != null && perms.money ? (
              <>
                <dt className="text-fg-subtle">Amount</dt>
                <dd className="font-medium tabular">{fmtMoney(b.amount, currency)}</dd>
              </>
            ) : null}
            {b.type === "HOLD" ? (
              <>
                <dt className="text-fg-subtle">Policy</dt>
                <dd>{b.pendingApproval ? "Awaiting admin approval" : b.releaseOnCleaning ? "Released once cleaned" : "Kept blocked"}</dd>
              </>
            ) : null}
            {b.reason ? (
              <>
                <dt className="text-fg-subtle">Note</dt>
                <dd className="col-span-1 truncate">{b.reason}</dd>
              </>
            ) : null}
          </dl>
        </div>
        <div className="flex flex-wrap gap-1.5 p-2">
          {b.reservationId ? (
            <Button size="sm" variant="secondary" onClick={() => open(b.reservationId!)}>
              Reservation
            </Button>
          ) : null}
          {perms.block && b.type !== "MAINTENANCE" ? (
            <>
              <Button size="sm" variant="secondary" onClick={onEdit}>
                Re-block
              </Button>
              <Button size="sm" variant={b.type === "HOLD" ? "primary" : "destructive"} loading={busy} onClick={release} className="ml-auto">
                {b.type === "HOLD" ? (
                  <>
                    <CheckCheck /> Release nights
                  </>
                ) : (
                  <>
                    <Trash2 /> Remove
                  </>
                )}
              </Button>
            </>
          ) : b.type === "MAINTENANCE" ? (
            <Button size="sm" variant="secondary" asChild className="ml-auto">
              <Link href="/maintenance">Open maintenance</Link>
            </Button>
          ) : null}
        </div>
      </PopoverContent>
    </Popover>
  );
}

function ResBlock({ r, left, width, draggable, perms, currency, clippedStart, clippedEnd, today }: { r: CalRes; left: number; width: number; draggable: boolean; perms: CalPerms; currency: string; clippedStart: boolean; clippedEnd: boolean; today: string }) {
  const { open } = useReservationDrawer();
  const balance = r.totalAmount - r.amountPaid;
  const departsToday = visualEnd(r) === today && r.status === "CHECKED_IN";
  const arrivesToday = r.checkIn === today && (r.status === "CONFIRMED" || r.status === "PENDING");
  return (
    <TooltipRoot delayDuration={250}>
      <TooltipTrigger asChild>
        <button
          type="button"
          draggable={draggable}
          onClick={() => open(r.id)}
          onDragStart={(e) => {
            const rect = (e.currentTarget as HTMLElement).getBoundingClientRect();
            const offset = Math.floor((e.clientX - rect.left) / 44);
            e.dataTransfer.setData("application/x-locajour", JSON.stringify({ id: r.id, offset: Math.max(0, offset) } satisfies DragPayload));
            e.dataTransfer.effectAllowed = "move";
          }}
          className={cn("absolute top-[9px] flex h-[34px] items-center gap-1 overflow-hidden border px-2 text-left text-xs shadow-sm transition-[left,width,filter,box-shadow] duration-300 hover:brightness-105 hover:shadow-md focus:outline-none focus-visible:ring-2 focus-visible:ring-ring", STATUS_STYLE[r.status] ?? STATUS_STYLE.CONFIRMED, clippedStart ? "rounded-l-none" : "rounded-l-full", clippedEnd ? "rounded-r-none" : "rounded-r-full", draggable && "cursor-grab active:cursor-grabbing", (departsToday || arrivesToday) && "ring-2 ring-offset-1 ring-offset-surface ring-primary/60")}
          style={{ left, width }}
          aria-label={`${r.code} · ${r.customer.fullName} · ${fmtRange(r.checkIn, r.checkOut)}`}
        >
          {draggable ? <GripVertical className="size-3 shrink-0 opacity-60" /> : null}
          <span className="truncate font-medium">{r.customer.fullName}</span>
          {width > 120 ? <span className="ml-auto shrink-0 opacity-80">{r.nights}n</span> : null}
          {width > 160 && balance > 0 && perms.money ? <span className="shrink-0 rounded-full bg-white/25 px-1.5 text-2xs">due</span> : null}
          {width > 160 && r.earlyCheckout ? <span className="shrink-0 rounded-full bg-white/25 px-1.5 text-2xs">early</span> : null}
        </button>
      </TooltipTrigger>
      <TooltipContent side="top" className="w-72 bg-surface p-0 text-fg shadow-xl ring-1 ring-border dark:bg-surface animate-scale-in">
        <div className="p-3">
          <div className="flex items-start justify-between gap-2">
            <div className="min-w-0">
              <p className="truncate text-sm font-semibold">{r.customer.fullName}</p>
              <p className="text-2xs text-fg-muted">
                <span className="font-mono">{r.code}</span> · {fmtPhone(r.customer.phone)}
              </p>
            </div>
            <StatusBadge status={r.status} size="sm" />
          </div>
          <div className="mt-2 flex flex-wrap items-center gap-x-3 gap-y-1 text-2xs text-fg-muted">
            <span>
              {fmtDate(r.checkIn, { style: "short" })} → {fmtDate(visualEnd(r), { style: "short" })}
            </span>
            <span>{r.nights}n</span>
            <span className="flex items-center gap-1">
              <Users className="size-3" /> {r.adults + r.children}
            </span>
            <SourceBadge source={r.source} size="sm" />
          </div>
          {perms.money ? (
            <p className="mt-1.5 text-xs">
              <span className="font-medium tabular">{fmtMoney(r.totalAmount, currency)}</span>{" "}
              <span className={balance <= 0 ? "text-positive-600" : "text-warning-600"}>· {balance <= 0 ? "paid" : `${fmtMoney(balance, currency)} due`}</span>
            </p>
          ) : null}
          {r.earlyCheckout ? <p className="mt-1 text-2xs text-accent-600">Left early on {fmtDate(r.actualCheckOut!, { style: "short" })}</p> : null}
          {r.recoveredNights ? <p className="mt-1 text-2xs text-positive-600">{r.recoveredNights} recovered night{r.recoveredNights > 1 ? "s" : ""}</p> : null}
        </div>
        <div className="border-t border-border px-3 py-1.5 text-2xs text-fg-subtle">Click to open the reservation panel</div>
      </TooltipContent>
    </TooltipRoot>
  );
}

function MoveConfirm({ move, apartments, onCancel, onConfirm, canMove, canDates }: { move: { res: CalRes; apartmentId: string; checkIn: string; checkOut: string }; apartments: CalApt[]; onCancel: () => void; onConfirm: () => Promise<void>; canMove: boolean; canDates: boolean }) {
  const [busy, setBusy] = React.useState(false);
  const from = apartments.find((a) => a.id === move.res.apartmentId);
  const to = apartments.find((a) => a.id === move.apartmentId);
  const aptChanged = move.apartmentId !== move.res.apartmentId;
  const datesChanged = move.checkIn !== move.res.checkIn;
  const blocked = (aptChanged && !canMove) || (datesChanged && !canDates);
  return (
    <>
      <DialogHeader>
        <DialogTitle>Move {move.res.code}?</DialogTitle>
        <DialogDescription>{move.res.customer.fullName} · availability is verified before saving. The change is recorded in the reservation history.</DialogDescription>
      </DialogHeader>
      <DialogBody className="space-y-2 text-sm">
        {aptChanged ? (
          <p>
            Apartment: <b>{from?.code}</b> → <b>{to?.code} · {to?.name}</b>
            {to && move.res.adults + move.res.children > to.maxGuests ? <span className="ml-2 text-negative-600">too small for {move.res.adults + move.res.children} guests</span> : null}
          </p>
        ) : null}
        {datesChanged ? (
          <p>
            Dates: {fmtRange(move.res.checkIn, move.res.checkOut)} → <b>{fmtRange(move.checkIn, move.checkOut)}</b>
          </p>
        ) : null}
        {blocked ? (
          <p className="flex items-center gap-2 rounded-md bg-negative-50 p-2 text-xs text-negative-700 dark:bg-negative-500/10">
            <Ban className="size-3.5" /> You don&apos;t have permission for this change.
          </p>
        ) : (
          <p className="text-xs text-fg-muted">The price is kept as is. Use the reservation page to recalculate if needed.</p>
        )}
      </DialogBody>
      <DialogFooter>
        <Button variant="secondary" onClick={onCancel}>
          Cancel
        </Button>
        <Button
          loading={busy}
          disabled={blocked}
          onClick={async () => {
            setBusy(true);
            await onConfirm();
            setBusy(false);
          }}
        >
          Confirm move
        </Button>
      </DialogFooter>
    </>
  );
}

// ── Month grid ────────────────────────────────────────────────
function MonthGrid({ apartments, reservations, blocks, month, today, onPickDay }: { apartments: CalApt[]; reservations: CalRes[]; blocks: CalBlock[]; month: Date; today: string; onPickDay: (d: Date) => void }) {
  const first = startOfMonth(month);
  const last = endOfMonth(month);
  const lead = (first.getUTCDay() + 6) % 7;
  const cells: (Date | null)[] = [...Array(lead).fill(null), ...Array.from({ length: last.getUTCDate() }, (_, i) => addDays(first, i))];
  while (cells.length % 7) cells.push(null);
  const aptIds = new Set(apartments.map((a) => a.id));
  const total = apartments.length || 1;
  return (
    <div className="surface overflow-hidden">
      <div className="grid grid-cols-7 border-b border-border bg-surface-2/60 text-center text-2xs font-semibold uppercase tracking-wider text-fg-muted">
        {["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"].map((d) => (
          <div key={d} className="py-2">
            {d}
          </div>
        ))}
      </div>
      <div className="grid grid-cols-7">
        {cells.map((d, i) => {
          if (!d) return <div key={i} className="min-h-[88px] border-b border-r border-border bg-surface-2/30" />;
          const k = dayKey(d);
          const occupied = reservations.filter((r) => aptIds.has(r.apartmentId) && ["CONFIRMED", "CHECKED_IN", "CHECKED_OUT", "PENDING"].includes(r.status) && parseDay(r.checkIn) <= d && parseDay(visualEnd(r)) > d);
          const ins = reservations.filter((r) => aptIds.has(r.apartmentId) && r.checkIn === k && r.status !== "INQUIRY").length;
          const outs = reservations.filter((r) => aptIds.has(r.apartmentId) && visualEnd(r) === k && r.status !== "INQUIRY").length;
          const dayBlocks = blocks.filter((b) => aptIds.has(b.apartmentId) && parseDay(b.startDate) <= d && parseDay(b.endDate) > d);
          const external = dayBlocks.filter((b) => b.type === "EXTERNAL");
          const holds = dayBlocks.filter((b) => b.type === "HOLD").length;
          const pctOcc = Math.round(((occupied.length + external.length) / total) * 100);
          return (
            <button key={k} type="button" onClick={() => onPickDay(d)} className={cn("group flex min-h-[88px] flex-col border-b border-r border-border p-1.5 text-left transition hover:bg-surface-2", k === today && "bg-primary/5")}>
              <div className="flex items-start justify-between">
                <span className="flex gap-0.5">
                  {external.slice(0, 4).map((b) => (
                    <span key={b.id} className="size-1.5 rounded-full" style={{ background: BLOCK_SOURCE_META[b.source as BlockSource]?.color ?? "#888" }} title={BLOCK_SOURCE_META[b.source as BlockSource]?.label} />
                  ))}
                </span>
                <span className={cn("text-xs font-semibold tabular", k === today && "flex size-5 items-center justify-center rounded-full bg-primary text-primary-fg")}>{d.getUTCDate()}</span>
              </div>
              <div className="mt-auto space-y-1">
                <div className="h-1.5 w-full overflow-hidden rounded-full bg-surface-3">
                  <div className={cn("h-full rounded-full transition-all duration-500", pctOcc >= 90 ? "bg-negative-500" : pctOcc >= 60 ? "bg-warning-500" : "bg-positive-500")} style={{ width: `${pctOcc}%` }} />
                </div>
                <div className="flex items-center justify-between text-2xs text-fg-muted">
                  <span className="tabular">
                    {occupied.length + external.length}/{total}
                  </span>
                  <span className="flex gap-1.5">
                    {ins ? <span className="text-brand-700 dark:text-brand-300">↓{ins}</span> : null}
                    {outs ? <span>↑{outs}</span> : null}
                    {dayBlocks.length - holds - external.length > 0 ? <span className="text-negative-600">⊘{dayBlocks.length - holds - external.length}</span> : null}
                    {holds ? <span className="text-warning-600">◷{holds}</span> : null}
                  </span>
                </div>
              </div>
            </button>
          );
        })}
      </div>
    </div>
  );
}

// ── Day agenda ────────────────────────────────────────────────
function DayAgenda({ apartments, reservations, blocks, day, today, perms, currency, onBlock }: { apartments: CalApt[]; reservations: CalRes[]; blocks: CalBlock[]; day: Date; today: string; perms: CalPerms; currency: string; onBlock: (apartmentId: string, day: string) => void }) {
  const { open } = useReservationDrawer();
  const k = dayKey(day);
  const aptIds = new Set(apartments.map((a) => a.id));
  const arrivals = reservations.filter((r) => aptIds.has(r.apartmentId) && r.checkIn === k && r.status !== "INQUIRY");
  const departures = reservations.filter((r) => aptIds.has(r.apartmentId) && visualEnd(r) === k && r.status !== "INQUIRY");
  const inHouse = reservations.filter((r) => aptIds.has(r.apartmentId) && parseDay(r.checkIn) < day && parseDay(visualEnd(r)) > day && ["CONFIRMED", "CHECKED_IN", "PENDING"].includes(r.status));
  const dayBlocks = blocks.filter((b) => aptIds.has(b.apartmentId) && parseDay(b.startDate) <= day && parseDay(b.endDate) > day);
  const free = apartments.filter((a) => !reservations.some((r) => r.apartmentId === a.id && parseDay(r.checkIn) <= day && parseDay(r.checkOut) > day && ["CONFIRMED", "CHECKED_IN", "PENDING"].includes(r.status)) && !dayBlocks.some((b) => b.apartmentId === a.id));
  const aptOf = (id: string) => apartments.find((a) => a.id === id);
  const Row = ({ r, action }: { r: CalRes; action?: "checkin" | "checkout" }) => (
    <li className="flex items-center gap-3 py-2.5">
      <button type="button" onClick={() => open(r.id)} className="flex size-9 shrink-0 items-center justify-center rounded-md bg-surface-2 font-mono text-xs font-semibold transition hover:bg-primary/10 hover:text-primary">
        {aptOf(r.apartmentId)?.code}
      </button>
      <span className="min-w-0 flex-1">
        <button type="button" onClick={() => open(r.id)} className="block max-w-full truncate text-left text-sm font-medium hover:text-primary">
          {r.customer.fullName}
        </button>
        <span className="block text-xs text-fg-muted">
          {r.code} · {fmtRange(r.checkIn, visualEnd(r))} · {r.adults + r.children} guests{perms.money ? ` · ${fmtMoney(r.totalAmount, currency)}` : ""}
          {r.earlyCheckout ? <Badge tone="accent" size="sm" className="ml-1.5">early</Badge> : null}
        </span>
      </span>
      {action === "checkin" && perms.checkin && (r.status === "CONFIRMED" || r.status === "PENDING") ? (
        <Button asChild size="xs">
          <Link href={`/reservations/${r.id}?action=checkin`}>Check in</Link>
        </Button>
      ) : action === "checkout" && perms.checkout && r.status === "CHECKED_IN" ? (
        <Button asChild size="xs">
          <Link href={`/reservations/${r.id}?action=checkout`}>Check out</Link>
        </Button>
      ) : (
        <StatusBadge status={r.status} size="sm" />
      )}
    </li>
  );
  const Section = ({ title, icon: Icon, items, action, empty }: { title: string; icon: React.ComponentType<{ className?: string }>; items: CalRes[]; action?: "checkin" | "checkout"; empty: string }) => (
    <div className="surface p-4">
      <h3 className="flex items-center gap-2 text-sm font-semibold">
        <Icon className="size-4 text-fg-subtle" /> {title} <span className="rounded-full bg-surface-3 px-1.5 text-2xs tabular text-fg-muted">{items.length}</span>
      </h3>
      {items.length === 0 ? (
        <p className="mt-2 text-sm text-fg-muted">{empty}</p>
      ) : (
        <ul className="mt-1 divide-y divide-border">
          {items.map((r) => (
            <Row key={r.id} r={r} action={action} />
          ))}
        </ul>
      )}
    </div>
  );
  return (
    <div className="space-y-3">
      <p className="text-sm font-semibold sm:hidden">
        {fmtDate(day, { style: "weekday" })} {k === today ? <span className="ml-1 rounded-full bg-primary/10 px-2 py-0.5 text-2xs text-primary">Today</span> : null}
      </p>
      <div className="grid gap-3 lg:grid-cols-2">
        <Section title="Arrivals" icon={LogIn} items={arrivals} action="checkin" empty="No check-ins scheduled." />
        <Section title="Departures" icon={LogOut} items={departures} action="checkout" empty="No check-outs scheduled." />
        <Section title="In house" icon={Users} items={inHouse} empty="No guests staying over." />
        <div className="surface p-4">
          <h3 className="flex items-center gap-2 text-sm font-semibold">
            <CalendarDays className="size-4 text-fg-subtle" /> Available apartments <span className="rounded-full bg-surface-3 px-1.5 text-2xs tabular text-fg-muted">{free.length}</span>
          </h3>
          {free.length === 0 ? (
            <p className="mt-2 text-sm text-fg-muted">Fully booked.</p>
          ) : (
            <div className="mt-2 flex flex-wrap gap-1.5">
              {free.map((a) => (
                <span key={a.id} className="inline-flex overflow-hidden rounded-md border border-positive-500/30 bg-positive-50 font-mono text-xs font-semibold text-positive-700 dark:bg-positive-500/10">
                  <Link href={perms.create ? `/reservations/new?apartment=${a.id}&checkIn=${k}&checkOut=${dayKey(addDays(day, 1))}` : `/apartments/${a.id}`} className="px-2 py-1 transition hover:bg-positive-100 dark:hover:bg-positive-500/20">
                    {a.code}
                  </Link>
                  {perms.block ? (
                    <button type="button" onClick={() => onBlock(a.id, k)} className="border-l border-positive-500/30 px-1.5 text-positive-700/70 transition hover:bg-positive-100 hover:text-positive-700 dark:hover:bg-positive-500/20" aria-label={`Block ${a.code}`} title="Block this night">
                      <CalendarOff className="size-3" />
                    </button>
                  ) : null}
                </span>
              ))}
            </div>
          )}
          {dayBlocks.length ? (
            <>
              <h4 className="mt-4 text-2xs font-semibold uppercase tracking-wider text-fg-subtle">Blocked tonight</h4>
              <ul className="mt-1.5 space-y-1">
                {dayBlocks.map((b) => {
                  const meta = BLOCK_SOURCE_META[b.source as BlockSource] ?? BLOCK_SOURCE_META.OTHER;
                  return (
                    <li key={b.id} className="flex items-center gap-2 text-xs">
                      <span className="w-9 font-mono font-semibold">{aptOf(b.apartmentId)?.code}</span>
                      <span className="size-2 rounded-full" style={{ background: meta.color }} />
                      <span className="text-fg-muted">
                        {b.type === "HOLD" ? (b.pendingApproval ? "Hold · awaiting approval" : "Hold") : meta.label}
                        {b.guestName ? ` · ${b.guestName}` : ""} · until {fmtDate(b.endDate, { style: "short" })}
                      </span>
                    </li>
                  );
                })}
              </ul>
            </>
          ) : null}
        </div>
      </div>
    </div>
  );
}
