"use client";

import * as React from "react";
import { useRouter } from "next/navigation";
import { useQuery } from "convex/react";
import { toast } from "sonner";
import { AlertTriangle, ArrowLeft, ArrowRight, Bath, BedDouble, Check, ChevronUp, ImageOff, Search, ShieldAlert, UserPlus, Users } from "lucide-react";
import { api } from "../../../convex/_generated/api";
import { cn } from "@/lib/utils";
import { Button } from "@/components/ui/button";
import { Input, Field, NativeSelect, Textarea } from "@/components/ui/input";
import { Avatar } from "@/components/ui/primitives";
import { StatusBadge, SourceBadge } from "@/components/ui/badge";
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogBody } from "@/components/ui/dialog";
import { CustomerForm } from "@/components/customers/customer-form";
import { RiskBanner } from "@/components/customers/risk-banner";
import { AnimatedText } from "@/components/motion/animated-number";
import { RISK_LEVEL_META, type RiskLevel } from "@/lib/domain";
import { fmtPhone, fmtMoney } from "@/lib/format";
import { nightsBetween, parseDay, fmtDate, addDays, dayKey } from "@/lib/dates";
import { RESERVATION_SOURCES, RESERVATION_SOURCE_META, PAYMENT_METHODS, PAYMENT_METHOD_LABEL } from "@/lib/domain";
import { createReservation } from "@/lib/actions/reservations";

interface CustomerHit {
  id: string;
  code: string;
  fullName: string;
  phone: string;
  idNumber: string | null;
  idType: string | null;
  email: string | null;
  nationality: string | null;
  isBlacklisted: boolean;
  riskLevel?: string;
  riskReason?: string | null;
  verificationStatus?: string;
  lastIncident?: { title: string; occurredAt: string } | null;
  outstanding?: number;
  _count: { reservations: number };
}
interface AptHit {
  id: string;
  code: string;
  name: string;
  building: string | null;
  city: string;
  status: string;
  cleaningStatus: string;
  bedrooms: number;
  beds: number;
  bathrooms: number;
  maxGuests: number;
  basePrice: number;
  weekendPrice: number | null;
  coverImageId?: string | null;
  available: boolean;
  conflicts: { kind: string; label: string; start: string; end: string }[];
  pricing: { nights: number; nightlyPrice: number; subtotal: number; breakdown: { date: string; price: number; weekend: boolean }[] };
}

const STEPS = [
  { label: "Customer", title: "Who is the guest?" },
  { label: "Dates", title: "When is the stay?" },
  { label: "Apartment", title: "Choose an apartment" },
  { label: "Pricing", title: "Pricing & payment" },
  { label: "Confirm", title: "Confirm details" },
];

function useDebounced<T>(value: T, ms: number) {
  const [v, setV] = React.useState(value);
  React.useEffect(() => {
    const t = setTimeout(() => setV(value), ms);
    return () => clearTimeout(t);
  }, [value, ms]);
  return v;
}

/**
 * Five-step booking flow. Customer search and availability are live Convex
 * subscriptions, so a booking made on another device for the same dates
 * removes the apartment from this list while the user is still choosing.
 * On phones the summary lives in a sticky footer with a pull-up sheet.
 */
export function ReservationWizard({ me, perms, settings, workers, preset }: { me: { id: string; fullName: string }; perms: { changePrice: boolean; discount: boolean; recordPayment: boolean; createCustomer: boolean; approveRisky: boolean }; settings: { currency: string; weekendDays: number[]; maxDiscountPercent: number; checkInTime: string; checkOutTime: string }; workers: { id: string; fullName: string }[]; preset: { customer: CustomerHit | null; apartmentId?: string; checkIn: string; checkOut: string } }) {
  const router = useRouter();
  const [step, setStep] = React.useState(preset.customer ? 1 : 0);
  const [dir, setDir] = React.useState<1 | -1>(1);
  const [customer, setCustomer] = React.useState<CustomerHit | null>(preset.customer);
  const [riskAck, setRiskAck] = React.useState(false);
  const riskMeta = customer?.riskLevel ? RISK_LEVEL_META[customer.riskLevel as RiskLevel] : null;
  const riskHard = !!riskMeta && (riskMeta.blocksBooking || riskMeta.needsApproval);
  const riskOk = !riskMeta || customer?.riskLevel === "NORMAL" || (riskHard ? perms.approveRisky && riskAck : riskAck);
  const [checkIn, setCheckIn] = React.useState(preset.checkIn);
  const [checkOut, setCheckOut] = React.useState(preset.checkOut);
  const [adults, setAdults] = React.useState(2);
  const [children, setChildren] = React.useState(0);
  const [apartment, setApartment] = React.useState<AptHit | null>(null);
  const [nightly, setNightly] = React.useState<string>("");
  const [discount, setDiscount] = React.useState<string>("0");
  const [deposit, setDeposit] = React.useState<string>("0");
  const [paid, setPaid] = React.useState<string>("0");
  const [method, setMethod] = React.useState<string>("CASH");
  const [source, setSource] = React.useState<string>("DIRECT");
  const [status, setStatus] = React.useState<"CONFIRMED" | "PENDING" | "INQUIRY">("CONFIRMED");
  const [assignedTo, setAssignedTo] = React.useState<string>(me.id);
  const [notes, setNotes] = React.useState("");
  const [requests, setRequests] = React.useState("");
  const [externalRef, setExternalRef] = React.useState("");
  const [submitting, setSubmitting] = React.useState(false);
  const [error, setError] = React.useState<string | null>(null);
  const [summaryOpen, setSummaryOpen] = React.useState(false);

  const nights = React.useMemo(() => (checkIn && checkOut ? nightsBetween(parseDay(checkIn), parseDay(checkOut)) : 0), [checkIn, checkOut]);
  const guests = adults + children;

  // Live availability for the chosen dates (re-runs whenever inventory changes).
  const board = useQuery(api.inventoryOps.availabilityBoard, step >= 2 && nights > 0 ? { checkIn, checkOut } : "skip");
  const apts = (board?.items as AptHit[] | undefined) ?? null;
  const aptLoading = step >= 2 && nights > 0 && board === undefined;
  React.useEffect(() => {
    if (!apts) return;
    if (apartment) {
      const fresh = apts.find((a) => a.id === apartment.id);
      if (fresh && (fresh.available !== apartment.available || fresh.pricing.subtotal !== apartment.pricing.subtotal)) setApartment(fresh);
      if (fresh && !fresh.available && apartment.available) toast.warning(`${fresh.code} was just booked by someone else for these dates.`);
      return;
    }
    if (preset.apartmentId) {
      const pre = apts.find((a) => a.id === preset.apartmentId);
      if (pre?.available) setApartment(pre);
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [apts]);

  // Pricing
  const basePricing = apartment?.pricing;
  const override = nightly.trim() !== "" ? Number(nightly) : null;
  const subtotal = override != null && override > 0 ? override * nights : (basePricing?.subtotal ?? 0);
  const disc = Math.max(0, Number(discount) || 0);
  const total = Math.max(0, subtotal - disc);
  const discPct = subtotal ? (disc / subtotal) * 100 : 0;
  const discTooHigh = discPct > settings.maxDiscountPercent + 0.01;
  const paidNum = Math.max(0, Number(paid) || 0);
  const remaining = Math.max(0, total - paidNum);

  const canNext = [!!customer && riskOk, nights > 0 && guests > 0, !!apartment && apartment.available && guests <= apartment.maxGuests, !discTooHigh && total >= 0, true][step];
  const goTo = (n: number) => {
    setDir(n > step ? 1 : -1);
    setStep(Math.max(0, Math.min(STEPS.length - 1, n)));
    if (typeof window !== "undefined" && window.innerWidth < 768) window.scrollTo({ top: 0, behavior: "smooth" });
  };

  async function submit() {
    if (!customer || !apartment) return;
    setSubmitting(true);
    setError(null);
    const res = await createReservation({
      customerId: customer.id,
      apartmentId: apartment.id,
      checkIn,
      checkOut,
      adults,
      children,
      source: source as (typeof RESERVATION_SOURCES)[number],
      status,
      nightlyPrice: override ?? undefined,
      discount: disc,
      deposit: Math.max(0, Number(deposit) || 0),
      amountPaid: paidNum,
      paymentMethod: paidNum > 0 ? (method as (typeof PAYMENT_METHODS)[number]) : null,
      assignedToId: assignedTo || null,
      internalNotes: notes || null,
      customerRequests: requests || null,
      externalRef: externalRef || null,
      riskOverride: riskHard && riskAck ? true : undefined,
    });
    setSubmitting(false);
    if (!res.ok) {
      setError(res.error);
      toast.error(res.error);
      if (res.code === "CONFLICT") goTo(2);
      return;
    }
    toast.success(`Reservation ${res.data.code} created`);
    router.push(`/reservations/${res.data.id}`);
  }

  const summary = (
    <dl className="space-y-3">
      <SummaryRow label="Guest" value={customer ? customer.fullName : "—"} sub={customer ? fmtPhone(customer.phone) : undefined} />
      <SummaryRow label="Dates" value={nights > 0 ? `${fmtDate(parseDay(checkIn), { style: "weekday" })} → ${fmtDate(parseDay(checkOut), { style: "weekday" })}` : "—"} sub={nights > 0 ? `${nights} night${nights > 1 ? "s" : ""} · ${guests} guest${guests > 1 ? "s" : ""}` : undefined} />
      <SummaryRow label="Apartment" value={apartment ? `${apartment.code} · ${apartment.name}` : "—"} sub={apartment ? `${apartment.building ?? ""} ${apartment.city}` : undefined} />
      <div className="border-t border-border pt-3">
        <div className="flex justify-between text-fg-muted">
          <span>
            {nights} × {fmtMoney(nights ? subtotal / nights : 0, settings.currency)}
          </span>
          <span className="tabular">{fmtMoney(subtotal, settings.currency)}</span>
        </div>
        {disc > 0 ? (
          <div className="flex justify-between text-positive-600">
            <span>Discount</span>
            <span className="tabular">−{fmtMoney(disc, settings.currency)}</span>
          </div>
        ) : null}
        <div className="mt-1 flex justify-between text-base font-semibold">
          <span>Total</span>
          <span className="tabular">
            <AnimatedText text={fmtMoney(total, settings.currency)} />
          </span>
        </div>
        {paidNum > 0 ? (
          <div className="mt-1 flex justify-between text-xs text-fg-muted">
            <span>Paid now · remaining</span>
            <span className="tabular">
              {fmtMoney(paidNum, settings.currency)} · {fmtMoney(remaining, settings.currency)}
            </span>
          </div>
        ) : null}
      </div>
    </dl>
  );

  return (
    <div className="grid min-w-0 gap-5 lg:grid-cols-[minmax(0,1fr)_300px]">
      <div className="min-w-0 space-y-4 sm:space-y-5">
        {/* Phone: compact progress header */}
        <div className="sm:hidden">
          <div className="flex items-end justify-between">
            <div>
              <p className="eyebrow">
                Step {step + 1} of {STEPS.length}
              </p>
              <p key={step} className="font-display text-xl font-medium leading-tight animate-slide-up">
                {STEPS[step].title}
              </p>
            </div>
            <p className="text-xs text-fg-subtle">{step < STEPS.length - 1 ? `Next: ${STEPS[step + 1].label}` : "Last step"}</p>
          </div>
          <div className="mt-2.5 flex gap-1" aria-hidden>
            {STEPS.map((s, i) => (
              <span key={s.label} className="h-1 flex-1 overflow-hidden rounded-full bg-surface-3">
                <span className={cn("block h-full rounded-full bg-primary transition-[width] duration-500 [transition-timing-function:var(--ease-out-expo)]", i < step ? "w-full" : i === step ? "w-1/2 animate-pulse-soft" : "w-0")} />
              </span>
            ))}
          </div>
        </div>
        {/* Larger screens: pill stepper */}
        <ol className="hidden items-center gap-1 overflow-x-auto scrollbar-none sm:flex" aria-label="Progress">
          {STEPS.map((s, i) => {
            const done = i < step;
            const active = i === step;
            return (
              <li key={s.label} className="flex items-center gap-1">
                <button type="button" disabled={i > step} onClick={() => goTo(i)} className={cn("flex items-center gap-2 rounded-full px-3 py-1.5 text-xs font-medium transition-all duration-300 [transition-timing-function:var(--ease-out-expo)]", active ? "bg-primary text-primary-fg shadow-sm shadow-primary/30" : done ? "bg-primary/10 text-primary hover:bg-primary/15" : "bg-surface-2 text-fg-subtle")}>
                  <span className={cn("flex size-4 items-center justify-center rounded-full text-2xs transition-colors", active ? "bg-primary-fg/20" : done ? "bg-primary text-primary-fg" : "bg-surface-3")}>{done ? <Check className="size-3 animate-badge-pop" /> : i + 1}</span>
                  {s.label}
                </button>
                {i < STEPS.length - 1 ? <span className={cn("h-px w-3 transition-colors duration-500 sm:w-6", i < step ? "bg-primary/50" : "bg-border")} /> : null}
              </li>
            );
          })}
        </ol>

        <div className={cn("surface p-4 sm:p-5", dir === 1 ? "animate-slide-up" : "animate-scale-in")} key={step}>
          {step === 0 ? (
            <div className="space-y-4">
              <StepCustomer customer={customer} onSelect={(c) => { setCustomer(c); setRiskAck(false); }} canCreate={perms.createCustomer} />
              {customer && customer.riskLevel && customer.riskLevel !== "NORMAL" ? <RiskBanner customer={{ id: customer.id, code: customer.code, fullName: customer.fullName, riskLevel: customer.riskLevel, riskReason: customer.riskReason ?? null, lastIncident: customer.lastIncident ?? null, outstanding: customer.outstanding }} canApprove={perms.approveRisky} acknowledged={riskAck} onAcknowledge={() => setRiskAck(true)} onBlock={() => setCustomer(null)} dates={{ checkIn, checkOut, apartmentId: apartment?.id }} /> : null}
            </div>
          ) : null}
          {step === 1 ? (
            <StepDates checkIn={checkIn} checkOut={checkOut} setCheckIn={setCheckIn} setCheckOut={setCheckOut} adults={adults} kids={children} setAdults={setAdults} setChildren={setChildren} nights={nights} />
          ) : null}
          {step === 2 ? <StepApartment apts={apts} loading={aptLoading} selected={apartment} onSelect={setApartment} guests={guests} currency={settings.currency} /> : null}
          {step === 3 && apartment ? (
            <StepPricing
              apartment={apartment}
              nights={nights}
              currency={settings.currency}
              perms={perms}
              nightly={nightly}
              setNightly={setNightly}
              discount={discount}
              setDiscount={setDiscount}
              deposit={deposit}
              setDeposit={setDeposit}
              paid={paid}
              setPaid={setPaid}
              method={method}
              setMethod={setMethod}
              subtotal={subtotal}
              total={total}
              discTooHigh={discTooHigh}
              maxDiscountPercent={settings.maxDiscountPercent}
              remaining={remaining}
            />
          ) : null}
          {step === 4 && apartment && customer ? (
            <StepConfirm
              source={source}
              setSource={setSource}
              status={status}
              setStatus={setStatus}
              assignedTo={assignedTo}
              setAssignedTo={setAssignedTo}
              workers={workers}
              notes={notes}
              setNotes={setNotes}
              requests={requests}
              setRequests={setRequests}
              externalRef={externalRef}
              setExternalRef={setExternalRef}
              error={error}
            />
          ) : null}
        </div>

        {/* Action bar: sticky glass footer above the phone tab bar, inline on desktop */}
        <div className="glass sticky bottom-[calc(5.25rem+env(safe-area-inset-bottom))] z-10 flex items-center gap-2 rounded-2xl p-2.5 shadow-xl md:static md:border-0 md:bg-transparent md:p-0 md:shadow-none md:backdrop-blur-none">
          <Button variant="secondary" size="icon" className="shrink-0 md:size-9 md:w-auto md:px-4" onClick={() => goTo(step - 1)} disabled={step === 0 || submitting} aria-label="Back">
            <ArrowLeft />
            <span className="hidden md:inline">Back</span>
          </Button>
          <button type="button" onClick={() => setSummaryOpen(true)} className="flex min-w-0 flex-1 flex-col items-start rounded-xl px-2 py-1 text-left transition hover:bg-surface-2 active:scale-[0.98] lg:hidden" aria-label="Show summary">
            <span className="flex items-center gap-1 text-2xs uppercase tracking-wider text-fg-subtle">
              {nights > 0 ? `${nights} night${nights > 1 ? "s" : ""}` : "Summary"} <ChevronUp className="size-3" />
            </span>
            <span className="text-sm font-semibold tabular text-fg">
              <AnimatedText text={fmtMoney(total, settings.currency)} />
            </span>
          </button>
          <span className="hidden flex-1 lg:block" />
          {step < STEPS.length - 1 ? (
            <Button onClick={() => goTo(step + 1)} disabled={!canNext} size="lg" className="shrink-0">
              Continue <ArrowRight />
            </Button>
          ) : (
            <Button onClick={submit} loading={submitting} disabled={!canNext} size="lg" className="shrink-0">
              <Check /> Confirm
            </Button>
          )}
        </div>
      </div>

      {/* Summary rail (desktop) */}
      <aside className="hidden lg:block">
        <div className="sticky top-20 surface p-4 text-sm">
          <p className="eyebrow flex items-center gap-2">
            Summary <span className="live-dot" aria-label="Live" />
          </p>
          <div className="mt-3">{summary}</div>
        </div>
      </aside>

      {/* Summary sheet (phone / tablet) */}
      <Dialog open={summaryOpen} onOpenChange={setSummaryOpen}>
        <DialogContent size="sm">
          <DialogHeader>
            <DialogTitle>Booking summary</DialogTitle>
          </DialogHeader>
          <DialogBody className="pb-6 text-sm">{summary}</DialogBody>
        </DialogContent>
      </Dialog>
    </div>
  );
}

function SummaryRow({ label, value, sub }: { label: string; value: string; sub?: string }) {
  return (
    <div>
      <dt className="text-2xs uppercase tracking-wider text-fg-subtle">{label}</dt>
      <dd className="font-medium text-fg">{value}</dd>
      {sub ? <dd className="text-xs text-fg-muted">{sub}</dd> : null}
    </div>
  );
}

type RawCustomer = Omit<CustomerHit, "_count"> & { reservations: number };
const toHit = (c: RawCustomer): CustomerHit => ({ ...c, _count: { reservations: c.reservations } });

// ── Step 1: customer ──────────────────────────────────────────
function StepCustomer({ customer, onSelect, canCreate }: { customer: CustomerHit | null; onSelect: (c: CustomerHit) => void; canCreate: boolean }) {
  const [q, setQ] = React.useState("");
  const [createOpen, setCreateOpen] = React.useState(false);
  const term = useDebounced(q.trim(), 160);
  const raw = useQuery(api.customers.search, term.length >= 2 ? { q: term, limit: 8 } : "skip") as RawCustomer[] | undefined;
  const hits = React.useMemo(() => (term.length >= 2 && raw ? raw.map(toHit) : []), [raw, term]);
  const loading = term.length >= 2 && raw === undefined;
  const [pendingName, setPendingName] = React.useState<{ id: string; name: string } | null>(null);
  const refetch = useQuery(api.customers.search, pendingName ? { q: pendingName.name, limit: 4 } : "skip") as RawCustomer[] | undefined;
  React.useEffect(() => {
    if (!pendingName || !refetch) return;
    const full = refetch.find((c) => c.id === pendingName.id);
    if (full) onSelect(toHit(full));
    setPendingName(null);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [refetch]);

  return (
    <div className="space-y-4">
      <div className="hidden sm:block">
        <h2 className="text-base font-semibold">Who is the guest?</h2>
        <p className="text-sm text-fg-muted">Search by name, phone or ID number first — returning customers keep their profile and history.</p>
      </div>
      <p className="text-sm text-fg-muted sm:hidden">Search by name, phone or ID — returning guests keep their history.</p>
      <div className="relative">
        <Search className={cn("absolute left-3 top-1/2 size-4 -translate-y-1/2 text-fg-subtle transition-colors", loading && "animate-wiggle text-primary")} />
        <Input value={q} onChange={(e) => setQ(e.target.value)} placeholder="Name, phone, CIN / passport number…" className="h-12 rounded-xl pl-10 text-base" autoFocus />
        {loading ? (
          <span className="absolute inset-x-3 bottom-0 h-0.5 overflow-hidden rounded-full" aria-hidden>
            <span className="route-bar block h-full w-1/4 rounded-full animate-indeterminate" />
          </span>
        ) : null}
      </div>
      {customer ? (
        <div className="flex items-center gap-3 rounded-xl border border-primary/40 bg-primary/5 p-3 animate-scale-in">
          <Avatar name={customer.fullName} size="lg" />
          <div className="min-w-0 flex-1">
            <p className="font-semibold">{customer.fullName}</p>
            <p className="text-xs text-fg-muted">
              {fmtPhone(customer.phone)} {customer.idNumber ? `· ${customer.idType ?? "ID"} ${customer.idNumber}` : ""} · {customer._count.reservations} previous stay{customer._count.reservations === 1 ? "" : "s"}
            </p>
          </div>
          <Check className="size-5 text-primary animate-badge-pop" />
        </div>
      ) : null}
      {hits.length ? (
        <ul className="stagger-fast divide-y divide-border overflow-hidden rounded-xl border border-border">
          {hits.map((c) => (
            <li key={c.id}>
              <button type="button" onClick={() => onSelect(c)} className={cn("flex w-full items-center gap-3 px-3 py-3 text-left transition-colors hover:bg-surface-2 active:bg-surface-3", customer?.id === c.id && "bg-primary/5")}>
                <Avatar name={c.fullName} size="md" />
                <span className="min-w-0 flex-1">
                  <span className="flex flex-wrap items-center gap-x-2 gap-y-1">
                    <span className="font-medium">{c.fullName}</span>
                    {c.riskLevel && c.riskLevel !== "NORMAL" ? (
                      <span className={cn("inline-flex items-center gap-1 rounded-full px-1.5 py-0.5 text-2xs font-medium", c.riskLevel === "WATCHLIST" ? "bg-info-50 text-info-700 dark:bg-info-500/10" : c.riskLevel === "HIGH_ATTENTION" ? "bg-warning-50 text-warning-700 dark:bg-warning-500/10" : "bg-negative-50 text-negative-700 dark:bg-negative-500/10")}>
                        <ShieldAlert className="size-3" /> {RISK_LEVEL_META[c.riskLevel as RiskLevel]?.label ?? c.riskLevel}
                      </span>
                    ) : null}
                    {c.verificationStatus === "VERIFIED" ? <span className="rounded-full bg-positive-50 px-1.5 py-0.5 text-2xs font-medium text-positive-700 dark:bg-positive-500/10">Verified</span> : null}
                    {c._count.reservations > 0 ? <span className="rounded-full bg-brand-50 px-1.5 py-0.5 text-2xs font-medium text-brand-700 dark:bg-brand-500/10 dark:text-brand-300">Returning · {c._count.reservations}</span> : null}
                  </span>
                  <span className="block truncate text-xs text-fg-muted">
                    {fmtPhone(c.phone)} {c.idNumber ? `· ${c.idNumber}` : ""} {c.nationality ? `· ${c.nationality}` : ""}
                  </span>
                </span>
                <span className="hidden font-mono text-2xs text-fg-subtle sm:block">{c.code}</span>
              </button>
            </li>
          ))}
        </ul>
      ) : term.length >= 2 && !loading ? (
        <p className="rounded-xl border border-dashed border-border-strong px-4 py-6 text-center text-sm text-fg-muted animate-fade-in">No customer matches “{term}”.</p>
      ) : null}
      {canCreate ? (
        <div className="flex items-center justify-between gap-3 rounded-xl border border-dashed border-border-strong p-3">
          <p className="text-sm text-fg-muted">New guest?</p>
          <Button variant="secondary" size="sm" onClick={() => setCreateOpen(true)}>
            <UserPlus /> Create customer
          </Button>
        </div>
      ) : null}
      <Dialog open={createOpen} onOpenChange={setCreateOpen}>
        <DialogContent size="lg">
          <DialogHeader>
            <DialogTitle>New customer</DialogTitle>
          </DialogHeader>
          <DialogBody className="pb-5">
            <CustomerForm
              compact
              initial={{ phone: /^\+?\d[\d\s]{6,}$/.test(q) ? q : "", firstName: /^\+?\d/.test(q) ? "" : q.split(" ")[0] ?? "", lastName: /^\+?\d/.test(q) ? "" : q.split(" ").slice(1).join(" ") }}
              onCancel={() => setCreateOpen(false)}
              onSaved={(c) => {
                setCreateOpen(false);
                onSelect({ id: c.id, code: c.code, fullName: c.fullName, phone: "", idNumber: null, idType: null, email: null, nationality: null, isBlacklisted: false, riskLevel: "NORMAL", _count: { reservations: 0 } });
                setPendingName({ id: c.id, name: c.fullName });
              }}
            />
          </DialogBody>
        </DialogContent>
      </Dialog>
    </div>
  );
}

// ── Step 2: dates ─────────────────────────────────────────────
function StepDates({ checkIn, checkOut, setCheckIn, setCheckOut, adults, kids, setAdults, setChildren, nights }: { checkIn: string; checkOut: string; setCheckIn: (v: string) => void; setCheckOut: (v: string) => void; adults: number; kids: number; setAdults: (n: number) => void; setChildren: (n: number) => void; nights: number }) {
  const quick = [1, 2, 3, 7];
  return (
    <div className="space-y-5">
      <div className="hidden sm:block">
        <h2 className="text-base font-semibold">When is the stay?</h2>
        <p className="text-sm text-fg-muted">Nights are calculated automatically. Only apartments free for the whole period will be offered next.</p>
      </div>
      <div className="grid grid-cols-2 gap-3 sm:grid-cols-[1fr_1fr_auto] sm:gap-4">
        <Field label="Check-in" required>
          <Input type="date" value={checkIn} onChange={(e) => setCheckIn(e.target.value)} className="h-11 min-w-0" />
        </Field>
        <Field label="Check-out" required>
          <Input type="date" value={checkOut} min={checkIn} onChange={(e) => setCheckOut(e.target.value)} className="h-11 min-w-0" />
        </Field>
        <div className="col-span-2 flex items-center justify-center gap-2 rounded-xl bg-surface-2 px-5 py-2 sm:col-span-1 sm:flex-col sm:gap-0">
          <span key={nights} className="text-2xl font-semibold tabular animate-badge-pop">
            {nights}
          </span>
          <span className="text-2xs uppercase tracking-wider text-fg-subtle">night{nights === 1 ? "" : "s"}</span>
        </div>
      </div>
      <div className="flex flex-wrap items-center gap-1.5 text-xs text-fg-muted">
        Quick:
        {quick.map((n) => (
          <Button key={n} type="button" size="xs" variant="subtle" onClick={() => setCheckOut(dayKey(addDays(parseDay(checkIn), n)))}>
            {n} night{n > 1 ? "s" : ""}
          </Button>
        ))}
      </div>
      {nights <= 0 ? (
        <p className="flex items-center gap-2 text-sm text-negative-600 animate-slide-up">
          <AlertTriangle className="size-4" /> Check-out must be after check-in.
        </p>
      ) : null}
      <div className="grid gap-3 sm:grid-cols-2 sm:gap-4">
        <Counter label="Adults" value={adults} min={1} onChange={setAdults} />
        <Counter label="Children" value={kids} min={0} onChange={setChildren} />
      </div>
    </div>
  );
}

function Counter({ label, value, min, onChange }: { label: string; value: number; min: number; onChange: (n: number) => void }) {
  return (
    <div className="flex items-center justify-between rounded-xl border border-border px-4 py-3">
      <span className="text-sm font-medium">{label}</span>
      <div className="flex items-center gap-2">
        <Button type="button" variant="secondary" size="iconSm" onClick={() => onChange(Math.max(min, value - 1))} aria-label={`Fewer ${label}`}>
          −
        </Button>
        <span key={value} className="w-6 text-center text-sm font-semibold tabular animate-badge-pop">
          {value}
        </span>
        <Button type="button" variant="secondary" size="iconSm" onClick={() => onChange(Math.min(20, value + 1))} aria-label={`More ${label}`}>
          +
        </Button>
      </div>
    </div>
  );
}

// ── Step 3: apartment ─────────────────────────────────────────
function StepApartment({ apts, loading, selected, onSelect, guests, currency }: { apts: AptHit[] | null; loading: boolean; selected: AptHit | null; onSelect: (a: AptHit) => void; guests: number; currency: string }) {
  const [showUnavailable, setShowUnavailable] = React.useState(false);
  const list = (apts ?? []).filter((a) => showUnavailable || a.available);
  const unavailableCount = (apts ?? []).filter((a) => !a.available).length;
  return (
    <div className="space-y-4">
      <div className="flex flex-wrap items-end justify-between gap-2">
        <div>
          <h2 className="hidden text-base font-semibold sm:block">Choose an apartment</h2>
          <p className="flex items-center gap-2 text-sm text-fg-muted">
            {apts ? `${apts.length - unavailableCount} of ${apts.length} apartments are free for these dates.` : "Checking availability…"}
            {apts ? <span className="live-dot" aria-label="Live availability" /> : null}
          </p>
        </div>
        {unavailableCount ? (
          <label className="flex items-center gap-2 text-xs text-fg-muted">
            <input type="checkbox" checked={showUnavailable} onChange={(e) => setShowUnavailable(e.target.checked)} /> Show unavailable ({unavailableCount})
          </label>
        ) : null}
      </div>
      {loading || !apts ? (
        <div className="grid gap-3 sm:grid-cols-2">
          {[0, 1, 2, 3].map((i) => (
            <div key={i} className="skeleton h-28" />
          ))}
        </div>
      ) : list.length === 0 ? (
        <p className="rounded-xl border border-dashed border-border-strong p-6 text-center text-sm text-fg-muted">No apartment is available for these dates. Try different dates.</p>
      ) : (
        <div className="stagger-fast grid gap-3 sm:grid-cols-2">
          {list.map((a) => {
            const tooSmall = guests > a.maxGuests;
            const disabled = !a.available || tooSmall;
            const active = selected?.id === a.id;
            return (
              <button key={a.id} type="button" disabled={disabled} onClick={() => onSelect(a)} className={cn("group relative overflow-hidden rounded-xl border text-left transition-all duration-200 [transition-timing-function:var(--ease-out-expo)]", active ? "border-primary bg-primary/5 shadow-md ring-2 ring-primary/30" : "border-border hover:-translate-y-0.5 hover:border-border-strong hover:shadow-md", disabled && "cursor-not-allowed opacity-55 hover:translate-y-0 hover:border-border hover:shadow-none")}>
                <div className="flex gap-3 p-3">
                  <span className="relative h-20 w-24 shrink-0 overflow-hidden rounded-lg bg-surface-2">
                    {a.coverImageId ? (
                      // eslint-disable-next-line @next/next/no-img-element
                      <img src={`/api/media/${a.coverImageId}?w=320`} alt="" className="size-full object-cover transition-transform duration-500 group-hover:scale-105" loading="lazy" />
                    ) : (
                      <span className="flex size-full items-center justify-center text-fg-subtle">
                        <ImageOff className="size-5" />
                      </span>
                    )}
                    <span className="absolute left-1.5 top-1.5 rounded-md bg-stone-950/60 px-1.5 py-0.5 font-mono text-2xs font-semibold text-white backdrop-blur">{a.code}</span>
                  </span>
                  <div className="min-w-0 flex-1">
                    <div className="flex items-start justify-between gap-2">
                      <p className="truncate font-semibold">{a.name}</p>
                      <StatusBadge status={a.status} size="sm" />
                    </div>
                    <p className="truncate text-xs text-fg-muted">
                      {a.building ? `${a.building} · ` : ""}
                      {a.city}
                    </p>
                    <div className="mt-2 flex items-center gap-3 text-xs text-fg-muted">
                      <span className="flex items-center gap-1">
                        <BedDouble className="size-3.5" /> {a.bedrooms || "Studio"}
                      </span>
                      <span className="flex items-center gap-1">
                        <Bath className="size-3.5" /> {a.bathrooms}
                      </span>
                      <span className={cn("flex items-center gap-1", tooSmall && "font-medium text-negative-600")}>
                        <Users className="size-3.5" /> {a.maxGuests}
                      </span>
                    </div>
                    <div className="mt-2 flex items-end justify-between gap-2">
                      <span className="text-xs text-fg-muted">
                        <span className="font-medium text-fg">{fmtMoney(a.basePrice, currency, { whole: true })}</span>/night
                      </span>
                      <span className="text-sm font-semibold tabular">{fmtMoney(a.pricing.subtotal, currency, { whole: true })}</span>
                    </div>
                  </div>
                </div>
                {!a.available ? (
                  <p className="flex items-center gap-1 border-t border-negative-500/20 bg-negative-50 px-3 py-1.5 text-2xs text-negative-700 dark:bg-negative-500/10">
                    <AlertTriangle className="size-3" /> {a.conflicts[0]?.label}
                  </p>
                ) : tooSmall ? (
                  <p className="border-t border-warning-500/20 bg-warning-50 px-3 py-1.5 text-2xs text-warning-700 dark:bg-warning-500/10">Too small for {guests} guests</p>
                ) : null}
                {active ? (
                  <span className="absolute right-2 top-2 flex size-5 items-center justify-center rounded-full bg-primary text-primary-fg animate-badge-pop">
                    <Check className="size-3" />
                  </span>
                ) : null}
              </button>
            );
          })}
        </div>
      )}
    </div>
  );
}

// ── Step 4: pricing ───────────────────────────────────────────
function StepPricing(p: { apartment: AptHit; nights: number; currency: string; perms: { changePrice: boolean; discount: boolean; recordPayment: boolean }; nightly: string; setNightly: (v: string) => void; discount: string; setDiscount: (v: string) => void; deposit: string; setDeposit: (v: string) => void; paid: string; setPaid: (v: string) => void; method: string; setMethod: (v: string) => void; subtotal: number; total: number; discTooHigh: boolean; maxDiscountPercent: number; remaining: number }) {
  const base = p.apartment.pricing;
  const weekendNights = base.breakdown.filter((b) => b.weekend).length;
  return (
    <div className="space-y-5">
      <div>
        <h2 className="hidden text-base font-semibold sm:block">Pricing & payment</h2>
        <p className="text-sm text-fg-muted">
          Default rate {fmtMoney(p.apartment.basePrice, p.currency)}/night{p.apartment.weekendPrice ? `, ${fmtMoney(p.apartment.weekendPrice, p.currency)} on weekend nights (${weekendNights} of ${p.nights})` : ""}.
        </p>
      </div>
      <div className="grid gap-4 sm:grid-cols-2">
        <Field label="Nightly price" hint={p.perms.changePrice ? "Leave empty to keep the default rate" : "Only authorised staff can change the price"}>
          <Input type="number" inputMode="decimal" min={0} step="10" value={p.nightly} onChange={(e) => p.setNightly(e.target.value)} placeholder={String(base.nightlyPrice)} disabled={!p.perms.changePrice} suffix={p.currency} className="h-11" />
        </Field>
        <Field label="Discount" error={p.discTooHigh ? `Maximum ${p.maxDiscountPercent}% (${fmtMoney((p.subtotal * p.maxDiscountPercent) / 100, p.currency)})` : undefined} hint={p.perms.discount ? "Fixed amount off the total" : "You are not allowed to apply discounts"}>
          <Input type="number" inputMode="decimal" min={0} step="10" value={p.discount} onChange={(e) => p.setDiscount(e.target.value)} disabled={!p.perms.discount} suffix={p.currency} aria-invalid={p.discTooHigh} className="h-11" />
        </Field>
        <Field label="Security deposit" hint="Refundable, collected at check-in">
          <Input type="number" inputMode="decimal" min={0} step="100" value={p.deposit} onChange={(e) => p.setDeposit(e.target.value)} suffix={p.currency} className="h-11" />
        </Field>
        <Field label="Amount paid now" hint={p.perms.recordPayment ? "Creates a payment record" : "You are not allowed to record payments"}>
          <Input type="number" inputMode="decimal" min={0} step="50" value={p.paid} onChange={(e) => p.setPaid(e.target.value)} disabled={!p.perms.recordPayment} suffix={p.currency} className="h-11" />
        </Field>
        {Number(p.paid) > 0 ? (
          <Field label="Payment method" className="animate-slide-up">
            <NativeSelect value={p.method} onChange={(e) => p.setMethod(e.target.value)} className="h-11">
              {PAYMENT_METHODS.map((m) => (
                <option key={m} value={m}>
                  {PAYMENT_METHOD_LABEL[m]}
                </option>
              ))}
            </NativeSelect>
          </Field>
        ) : null}
      </div>
      <div className="rounded-xl bg-surface-2 p-4 text-sm">
        <div className="flex justify-between text-fg-muted">
          <span>
            {p.nights} night{p.nights > 1 ? "s" : ""} × {fmtMoney(p.nights ? p.subtotal / p.nights : 0, p.currency)}
          </span>
          <span className="tabular">{fmtMoney(p.subtotal, p.currency)}</span>
        </div>
        {Number(p.discount) > 0 ? (
          <div className="flex justify-between text-positive-600 animate-slide-up">
            <span>Discount</span>
            <span className="tabular">−{fmtMoney(Number(p.discount), p.currency)}</span>
          </div>
        ) : null}
        <div className="mt-2 flex justify-between border-t border-border pt-2 text-base font-semibold">
          <span>Total</span>
          <span className="tabular">
            <AnimatedText text={fmtMoney(p.total, p.currency)} />
          </span>
        </div>
        {Number(p.paid) > 0 ? (
          <div className="mt-1 flex justify-between text-xs text-fg-muted animate-slide-up">
            <span>Remaining after payment</span>
            <span className="tabular">{fmtMoney(p.remaining, p.currency)}</span>
          </div>
        ) : null}
      </div>
      <details className="group text-xs text-fg-muted">
        <summary className="cursor-pointer select-none transition-colors hover:text-fg">Per-night breakdown</summary>
        <ul className="stagger-fast mt-2 grid grid-cols-2 gap-x-6 gap-y-1 sm:grid-cols-3">
          {base.breakdown.map((b) => (
            <li key={b.date} className="flex justify-between">
              <span>
                {fmtDate(parseDay(b.date), { style: "weekday" })}
                {b.weekend ? <span className="ml-1 text-gold-700">wknd</span> : null}
              </span>
              <span className="tabular">{fmtMoney(p.nightly ? Number(p.nightly) : b.price, p.currency)}</span>
            </li>
          ))}
        </ul>
      </details>
    </div>
  );
}

// ── Step 5: confirm ───────────────────────────────────────────
function StepConfirm(p: { source: string; setSource: (v: string) => void; status: "CONFIRMED" | "PENDING" | "INQUIRY"; setStatus: (v: "CONFIRMED" | "PENDING" | "INQUIRY") => void; assignedTo: string; setAssignedTo: (v: string) => void; workers: { id: string; fullName: string }[]; notes: string; setNotes: (v: string) => void; requests: string; setRequests: (v: string) => void; externalRef: string; setExternalRef: (v: string) => void; error: string | null }) {
  return (
    <div className="space-y-5">
      <div>
        <h2 className="hidden text-base font-semibold sm:block">Confirm details</h2>
        <p className="text-sm text-fg-muted">Where did this booking come from, and who is responsible for it?</p>
      </div>
      <div className="grid gap-4 sm:grid-cols-2">
        <Field label="Source" required className="sm:col-span-2">
          <div className="flex flex-wrap gap-1.5">
            {RESERVATION_SOURCES.map((s) => (
              <button key={s} type="button" onClick={() => p.setSource(s)} className={cn("rounded-full border px-3 py-1.5 text-xs font-medium transition-all duration-200 active:scale-95", p.source === s ? "border-primary bg-primary/10 text-primary shadow-sm" : "border-border text-fg-muted hover:border-border-strong")}>
                <span className="mr-1.5 inline-block size-1.5 rounded-full align-middle" style={{ background: RESERVATION_SOURCE_META[s].color }} />
                {RESERVATION_SOURCE_META[s].label}
              </button>
            ))}
          </div>
        </Field>
        <Field label="Initial status">
          <NativeSelect value={p.status} onChange={(e) => p.setStatus(e.target.value as typeof p.status)} className="h-11">
            <option value="CONFIRMED">Confirmed — blocks the calendar</option>
            <option value="PENDING">Pending — awaiting deposit (blocks the calendar)</option>
            <option value="INQUIRY">Inquiry — does not block the calendar</option>
          </NativeSelect>
        </Field>
        {(p.source === "AIRBNB" || p.source === "BOOKING") && (
          <Field label="Channel reference" className="animate-slide-up">
            <Input value={p.externalRef} onChange={(e) => p.setExternalRef(e.target.value)} placeholder={p.source === "AIRBNB" ? "HMXXXXXXXX" : "Booking number"} className="h-11 font-mono" />
          </Field>
        )}
        <Field label="Assigned worker">
          <NativeSelect value={p.assignedTo} onChange={(e) => p.setAssignedTo(e.target.value)} className="h-11">
            <option value="">Unassigned</option>
            {p.workers.map((w) => (
              <option key={w.id} value={w.id}>
                {w.fullName}
              </option>
            ))}
          </NativeSelect>
        </Field>
        <Field label="Customer requests" className="sm:col-span-2">
          <Textarea value={p.requests} onChange={(e) => p.setRequests(e.target.value)} rows={2} placeholder="Early check-in, baby cot, airport transfer…" />
        </Field>
        <Field label="Internal notes" className="sm:col-span-2" hint="Visible to staff only">
          <Textarea value={p.notes} onChange={(e) => p.setNotes(e.target.value)} rows={2} />
        </Field>
      </div>
      <div className="flex items-center gap-2 text-xs text-fg-muted">
        <SourceBadge source={p.source} /> <StatusBadge status={p.status} />
      </div>
      {p.error ? (
        <div role="alert" className="rounded-md border border-negative-500/25 bg-negative-50 px-3 py-2 text-sm text-negative-700 animate-slide-up dark:bg-negative-500/10">
          {p.error}
        </div>
      ) : null}
    </div>
  );
}
