"use client";
/* eslint-disable @next/next/no-img-element -- served through /api/media */

import * as React from "react";
import { useRouter } from "next/navigation";
import { useMutation, useQuery } from "convex/react";
import { AnimatePresence, motion } from "motion/react";
import { toast } from "sonner";
import { ArrowLeft, ArrowRight, BadgePercent, Check, Lock, MessageCircle, Phone, Search, Share2, ShieldAlert, ShieldCheck, ShieldX, UserPlus, Users, Zap } from "lucide-react";
import { api } from "../../../convex/_generated/api";
import { cn } from "@/lib/utils";
import { Button } from "@/components/ui/button";
import { Input, Field, NativeSelect } from "@/components/ui/input";
import { Avatar } from "@/components/ui/primitives";
import { Dialog, DialogContent, DialogTitle } from "@/components/ui/dialog";
import { AnimatedText } from "@/components/motion/animated-number";
import { RESERVATION_SOURCES, RESERVATION_SOURCE_META, PAYMENT_METHODS, PAYMENT_METHOD_LABEL, RISK_LEVEL_META, type RiskLevel } from "@/lib/domain";
import { fmtMoney, fmtPhone } from "@/lib/format";
import { fmtDate, parseDay } from "@/lib/dates";
import { createReservation } from "@/lib/actions/reservations";
import { createCustomer } from "@/lib/actions/customers";

export interface QuickTarget {
  apartment: { id: string; code: string; name: string; coverImageId: string | null; maxGuests: number; basePrice: number };
  checkIn: string;
  checkOut: string;
  nights: number;
  subtotal: number;
  guests: number;
  /** pre-selected guest (from the guest finder) */
  guest?: Hit | null;
  /** not a customer yet (website enquiry): search their number first, prefill the new-guest form */
  prefill?: { name: string; phone: string } | null;
}
export interface QuickPerms {
  createCustomer: boolean;
  recordPayment: boolean;
  approveRisky: boolean;
  discount: boolean;
  changePrice: boolean;
  /** largest discount this user may give, as a percentage of the stay */
  maxDiscountPercent: number;
}
export interface Hit {
  id: string;
  code: string;
  fullName: string;
  phone: string;
  riskLevel?: string;
  verificationStatus?: string;
  reservations: number;
}
export interface Screen {
  verdict: "CLEAR" | "WATCH" | "ATTENTION" | "RESTRICTED" | "BLOCKED";
  score: number;
  reasons: string[];
  matches: { id: string; code: string; fullName: string; phone: string; riskLevel: string; riskReason: string | null; matchedBy: string[]; stays: number; noShows: number; unpaid: number; openIncidents: number }[];
  self: { id: string } | null;
  linked: { id: string; code: string; fullName: string; riskLevel: string } | null;
}
interface Check {
  available: boolean;
  conflicts: { kind: string; label: string }[];
  pricing: { nights: number; subtotal: number; nightlyPrice: number };
  heldBy: { userName: string; expiresAt: number }[];
}

const ease = [0.16, 1, 0.3, 1] as const;
const digitsOf = (s: string) => s.replace(/[^\d]/g, "");
const looksLikePhone = (s: string) => /^[+\d][\d\s().-]{5,}$/.test(s.trim()) && digitsOf(s).length >= 6;

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

const VERDICT = {
  CLEAR: { label: "Clear", tone: "text-positive-700 bg-positive-500/10 border-positive-500/25 dark:text-positive-500", icon: ShieldCheck },
  WATCH: { label: "Watchlist", tone: "text-info-700 bg-info-500/10 border-info-500/25 dark:text-info-400", icon: ShieldAlert },
  ATTENTION: { label: "Attention", tone: "text-warning-700 bg-warning-500/10 border-warning-500/30 dark:text-warning-500", icon: ShieldAlert },
  RESTRICTED: { label: "Restricted", tone: "text-negative-700 bg-negative-500/10 border-negative-500/30 dark:text-negative-400", icon: ShieldX },
  BLOCKED: { label: "Blocked", tone: "text-white bg-negative-600 border-negative-700", icon: ShieldX },
} as const;

/**
 * Verdict from the screening engine, rendered as a banner. `compact` is the
 * one-line form used while typing a phone number.
 */
export function ScreenBanner({ screen, pending, compact, onUse }: { screen: Screen | null | undefined; pending?: boolean; compact?: boolean; onUse?: (m: Screen["matches"][number]) => void }) {
  if (pending && screen === undefined) return <p className="flex items-center gap-2 rounded-lg border border-border bg-surface-2 px-3 py-2 text-xs text-fg-muted"><span className="size-3 animate-spin rounded-full border-2 border-fg-subtle border-t-transparent" /> Checking this number…</p>;
  if (!screen) return null;
  const meta = VERDICT[screen.verdict];
  const Icon = meta.icon;
  const others = screen.matches.filter((m) => !m.matchedBy.includes("self"));
  return (
    <motion.div key={screen.verdict} initial={{ opacity: 0, y: -6 }} animate={{ opacity: 1, y: 0 }} className={cn("rounded-xl border px-3 py-2 text-xs", meta.tone, screen.verdict === "BLOCKED" && "animate-glow-flash")}>
      <p className="flex items-center gap-2 font-semibold">
        <Icon className="size-4 shrink-0" />
        {screen.verdict === "CLEAR" ? (others.length ? `Known number · ${others[0].fullName}` : "Number clear · no history of problems") : `${meta.label}${screen.score ? ` · risk ${screen.score}/100` : ""}`}
        {onUse && others[0] && !compact ? (
          <button type="button" onClick={() => onUse(others[0])} className="ml-auto rounded-full bg-white/70 px-2 py-0.5 text-2xs font-semibold text-fg shadow-xs transition hover:bg-white dark:bg-white/15 dark:text-white">
            Use {others[0].code}
          </button>
        ) : null}
      </p>
      {!compact && screen.reasons.length ? (
        <ul className="mt-1.5 flex flex-wrap gap-1">
          {screen.reasons.map((r) => (
            <li key={r} className="rounded-full bg-black/8 px-2 py-0.5 dark:bg-white/12">{r}</li>
          ))}
        </ul>
      ) : null}
      {!compact && others.length && screen.verdict === "CLEAR" ? <p className="mt-1 opacity-80">This number already has a profile ({others[0].code}, {others[0].stays} stay{others[0].stays === 1 ? "" : "s"}). Use it instead of creating a duplicate.</p> : null}
    </motion.div>
  );
}

/**
 * Two-step booking sheet for the front desk. While it is open the apartment
 * is held for this worker (other desks see "being booked"), availability
 * and the guest's screening verdict stay live, and the price can be
 * adjusted within the worker's discount allowance.
 */
export function QuickBook({ target, currency, perms, me, onClose, onShare }: { target: QuickTarget | null; currency: string; perms: QuickPerms; me: { id: string; name: string }; onClose: () => void; onShare?: (t: QuickTarget) => void }) {
  const router = useRouter();
  const [step, setStep] = React.useState<0 | 1>(0);
  const [q, setQ] = React.useState("");
  const term = useDebounced(q.trim(), 160);
  const raw = useQuery(api.customers.search, term.length >= 2 ? { q: term, limit: 6 } : "skip") as Hit[] | undefined;
  const [guest, setGuest] = React.useState<Hit | null>(null);
  const [creating, setCreating] = React.useState(false);
  const [nf, setNf] = React.useState({ firstName: "", lastName: "", phone: "" });
  const [source, setSource] = React.useState("DIRECT");
  const [paid, setPaid] = React.useState("");
  const [method, setMethod] = React.useState("CASH");
  const [discMode, setDiscMode] = React.useState<"none" | "custom" | number>("none");
  const [discCustom, setDiscCustom] = React.useState("");
  const [nightly, setNightly] = React.useState("");
  const [busy, setBusy] = React.useState(false);
  const [error, setError] = React.useState<string | null>(null);
  const [dup, setDup] = React.useState<{ code: string; apartment: string; checkIn: string; checkOut: string } | null>(null);
  const [done, setDone] = React.useState<{ id: string; code: string } | null>(null);

  // ── Hold: "I'm booking this" ─────────────────────────────────
  const holdStart = useMutation(api.frontDesk.holdStart);
  const holdPing = useMutation(api.frontDesk.holdPing);
  const holdRelease = useMutation(api.frontDesk.holdRelease);
  const holdRef = React.useRef<string | null>(null);
  const [heldBy, setHeldBy] = React.useState<string | null>(null);
  React.useEffect(() => {
    if (!target) return;
    let alive = true;
    setHeldBy(null);
    holdStart({ apartmentId: target.apartment.id as never, checkIn: target.checkIn, checkOut: target.checkOut })
      .then((r) => {
        if (!alive) {
          if (r.id) void holdRelease({ id: r.id });
          return;
        }
        holdRef.current = r.id;
        setHeldBy(r.heldBy);
      })
      .catch(() => {});
    const t = setInterval(() => {
      if (holdRef.current) void holdPing({ id: holdRef.current as never }).catch(() => {});
    }, 60_000);
    return () => {
      alive = false;
      clearInterval(t);
      if (holdRef.current) void holdRelease({ id: holdRef.current as never }).catch(() => {});
      holdRef.current = null;
    };
  }, [target, holdStart, holdPing, holdRelease]);

  // ── Live availability for this apartment & stay ──────────────
  const check = useQuery(api.frontDesk.check, target && !done ? { apartmentId: target.apartment.id as never, checkIn: target.checkIn, checkOut: target.checkOut } : "skip") as Check | null | undefined;
  const taken = !!check && !check.available;
  const otherHold = heldBy ?? check?.heldBy?.[0]?.userName ?? null;

  // ── Screening ───────────────────────────────────────────────
  const phoneTyped = creating ? nf.phone : looksLikePhone(q) ? q : "";
  const phoneTerm = useDebounced(digitsOf(phoneTyped).length >= 8 ? phoneTyped.trim() : "", 220);
  const screenArgs = step === 1 && guest ? { customerId: guest.id as never } : phoneTerm ? { phone: phoneTerm } : ("skip" as const);
  const screen = useQuery(api.frontDesk.screen, screenArgs) as Screen | null | undefined;
  const gated = !!screen && (screen.verdict === "BLOCKED" || screen.verdict === "RESTRICTED");
  const blocked = gated && !perms.approveRisky;

  React.useEffect(() => {
    if (!target) return;
    setStep(target.guest ? 1 : 0);
    setQ(target.prefill?.phone ?? "");
    setGuest(target.guest ?? null);
    setCreating(false);
    const parts = (target.prefill?.name ?? "").trim().split(" ");
    setNf({ firstName: parts[0] ?? "", lastName: parts.slice(1).join(" "), phone: target.prefill?.phone ?? "" });
    setSource("DIRECT");
    setPaid("");
    setDiscMode("none");
    setDiscCustom("");
    setNightly("");
    setError(null);
    setDup(null);
    setDone(null);
  }, [target]);

  // ── Pricing ─────────────────────────────────────────────────
  const t = target;
  const nights = t?.nights ?? 0;
  const baseSubtotal = check?.pricing.subtotal ?? t?.subtotal ?? 0;
  const nightlyNum = perms.changePrice && Number(nightly) > 0 ? Number(nightly) : null;
  const subtotal = nightlyNum ? Math.round(nightlyNum * nights * 100) / 100 : baseSubtotal;
  const maxDisc = Math.floor((subtotal * perms.maxDiscountPercent) / 100);
  const discount = discMode === "none" ? 0 : discMode === "custom" ? Math.max(0, Math.round(Number(discCustom) || 0)) : Math.round((subtotal * discMode) / 100);
  const discTooHigh = discount > maxDisc + 0.5;
  const total = Math.max(0, subtotal - discount);
  const pctChips = [5, 10, 15, 20].filter((p) => p <= perms.maxDiscountPercent);

  async function quickCreate() {
    if (!nf.firstName.trim() || !nf.phone.trim()) return setError("First name and phone are required.");
    if (screen?.linked && !perms.approveRisky) return setError(`This number belongs to ${screen.linked.fullName} (${screen.linked.code}), who is ${RISK_LEVEL_META[screen.linked.riskLevel as RiskLevel]?.label.toLowerCase()}.`);
    setBusy(true);
    setError(null);
    const res = await createCustomer({ firstName: nf.firstName.trim(), lastName: nf.lastName.trim(), phone: nf.phone.trim() }, { ignoreDuplicates: true });
    setBusy(false);
    if (!res.ok) return setError(res.error);
    setGuest({ id: res.data.id, code: res.data.code, fullName: `${nf.firstName.trim()} ${nf.lastName.trim()}`.trim(), phone: nf.phone.trim(), riskLevel: "NORMAL", reservations: 0 });
    setCreating(false);
    setStep(1);
  }

  const useExisting = (m: Screen["matches"][number]) => {
    setGuest({ id: m.id, code: m.code, fullName: m.fullName, phone: m.phone, riskLevel: m.riskLevel, reservations: m.stays });
    setCreating(false);
    setStep(1);
  };

  async function book(allowSecondStay = false) {
    if (!t || !guest || discTooHigh) return;
    setBusy(true);
    setError(null);
    const paidNum = Math.max(0, Number(paid) || 0);
    const res = await createReservation({ customerId: guest.id, apartmentId: t.apartment.id, checkIn: t.checkIn, checkOut: t.checkOut, adults: Math.max(1, t.guests), children: 0, source, status: "CONFIRMED", nightlyPrice: nightlyNum ?? undefined, discount: perms.discount ? discount : 0, amountPaid: perms.recordPayment ? paidNum : 0, paymentMethod: perms.recordPayment && paidNum > 0 ? method : null, assignedToId: me.id, riskOverride: gated && perms.approveRisky ? true : undefined, allowSecondStay });
    setBusy(false);
    if (!res.ok) {
      const d = res.fields?.__duplicate;
      if (d) {
        try {
          setDup(JSON.parse(d));
        } catch {
          setError(res.error);
        }
        return;
      }
      setError(res.error);
      toast.error(res.error);
      return;
    }
    setDup(null);
    setDone(res.data);
    holdRef.current = null;
    toast.success(`Reservation ${res.data.code} created`);
    if ("vibrate" in navigator) navigator.vibrate?.([10, 40, 10]);
  }

  const confirmText = t && guest && done ? `Hello ${guest.fullName.split(" ")[0]}, your booking is confirmed ✅\n${t.apartment.name} (${t.apartment.code})\n${fmtDate(parseDay(t.checkIn), { style: "weekday" })} → ${fmtDate(parseDay(t.checkOut), { style: "weekday" })} · ${nights} night${nights > 1 ? "s" : ""}\nTotal ${fmtMoney(total, currency, { whole: true })}${Number(paid) > 0 ? ` · paid ${fmtMoney(Number(paid), currency, { whole: true })}` : ""}\nReference ${done.code}` : "";

  return (
    <Dialog open={!!t} onOpenChange={(o) => !o && onClose()}>
      <DialogContent size="md" className="overflow-hidden p-0">
        {t ? (
          <>
            <DialogTitle className="sr-only">Quick booking</DialogTitle>
            {/* Apartment strip */}
            <div className="relative flex items-center gap-3 border-b border-border bg-surface-2/60 p-4 pr-12">
              <span className="relative h-14 w-20 shrink-0 overflow-hidden rounded-lg bg-surface-3">
                {t.apartment.coverImageId ? <img src={`/api/media/${t.apartment.coverImageId}?w=320`} alt="" className="size-full object-cover" /> : null}
                <span className="absolute left-1 top-1 rounded bg-stone-950/60 px-1 font-mono text-[10px] font-bold text-white">{t.apartment.code}</span>
              </span>
              <div className="min-w-0 flex-1">
                <p className="truncate font-semibold">{t.apartment.name}</p>
                <p className="flex flex-wrap items-center gap-x-1.5 gap-y-0.5 text-xs text-fg-muted">
                  {!done ? (
                    <span key={String(taken)} className={cn("inline-flex items-center gap-1 rounded-full px-1.5 py-0.5 text-[10px] font-semibold animate-badge-pop", check === undefined ? "bg-surface-3 text-fg-muted" : taken ? "bg-negative-500/15 text-negative-600" : "bg-positive-500/15 text-positive-700 dark:text-positive-500")}>
                      <span className={cn("size-1.5 rounded-full", check === undefined ? "bg-fg-subtle" : taken ? "bg-negative-500" : "bg-positive-500 live-dot")} /> {check === undefined ? "checking" : taken ? "just taken" : "free · live"}
                    </span>
                  ) : null}
                  <span>{fmtDate(parseDay(t.checkIn), { style: "short" })} → {fmtDate(parseDay(t.checkOut), { style: "short" })} · {nights} night{nights > 1 ? "s" : ""} · {t.guests} guest{t.guests > 1 ? "s" : ""}</span>
                </p>
              </div>
              <div className="flex shrink-0 flex-col items-end">
                <span className="font-display text-xl font-medium leading-tight tabular">
                  <AnimatedText text={fmtMoney(total, currency, { whole: true })} />
                </span>
                {discount > 0 ? <s className="text-2xs text-fg-subtle">{fmtMoney(subtotal, currency, { whole: true })}</s> : null}
                {onShare ? (
                  <button type="button" onClick={() => onShare(t)} className="mt-1 inline-flex items-center gap-1 rounded-full border border-border bg-surface px-2 py-0.5 text-2xs font-medium text-fg-muted transition hover:border-border-strong hover:text-fg active:scale-95">
                    <Share2 className="size-3" /> Share
                  </button>
                ) : null}
              </div>
            </div>

            <AnimatePresence>
              {otherHold && !done ? (
                <motion.p key="hold" initial={{ height: 0, opacity: 0 }} animate={{ height: "auto", opacity: 1 }} exit={{ height: 0, opacity: 0 }} className="flex items-center gap-2 overflow-hidden border-b border-warning-500/30 bg-warning-500/10 px-4 py-2 text-xs font-medium text-warning-700 dark:text-warning-500">
                  <Lock className="size-3.5 shrink-0" /> {otherHold} is booking this apartment right now. If they confirm first, this booking will be refused.
                </motion.p>
              ) : null}
              {taken && !done ? (
                <motion.p key="taken" initial={{ height: 0, opacity: 0 }} animate={{ height: "auto", opacity: 1 }} exit={{ height: 0, opacity: 0 }} className="flex items-center gap-2 overflow-hidden border-b border-negative-500/30 bg-negative-500/10 px-4 py-2 text-xs font-medium text-negative-700 dark:text-negative-400">
                  <ShieldX className="size-3.5 shrink-0" /> No longer free: {check?.conflicts.map((c) => c.label).join(", ")}.
                </motion.p>
              ) : null}
            </AnimatePresence>

            <div className="p-4 sm:p-5">
              <AnimatePresence mode="wait" initial={false}>
                {done ? (
                  <motion.div key="done" initial={{ opacity: 0, scale: 0.95 }} animate={{ opacity: 1, scale: 1 }} className="flex flex-col items-center py-2 text-center">
                    <motion.span initial={{ scale: 0 }} animate={{ scale: 1 }} transition={{ type: "spring", stiffness: 400, damping: 18, delay: 0.1 }} className="flex size-16 items-center justify-center rounded-full bg-positive-500/15 text-positive-600">
                      <Check className="size-8" />
                    </motion.span>
                    <p className="mt-3 font-display text-2xl font-medium">Booked · {done.code}</p>
                    <p className="mt-1 text-sm text-fg-muted">{guest?.fullName} · {t.apartment.code} · {nights} night{nights > 1 ? "s" : ""} · {fmtMoney(total, currency, { whole: true })}</p>
                    {guest?.phone ? (
                      <Button variant="secondary" className="mt-4 w-full border-positive-500/30 bg-positive-500/10 text-positive-700 hover:bg-positive-500/15 dark:text-positive-500" asChild>
                        <a href={`https://wa.me/${digitsOf(guest.phone)}?text=${encodeURIComponent(confirmText)}`} target="_blank" rel="noreferrer">
                          <MessageCircle /> Send confirmation on WhatsApp
                        </a>
                      </Button>
                    ) : null}
                    <div className="mt-2 flex w-full gap-2">
                      <Button variant="secondary" className="flex-1" onClick={onClose}>
                        Book another
                      </Button>
                      <Button className="flex-1" onClick={() => { onClose(); router.push(`/reservations/${done.id}`); }}>
                        Open reservation <ArrowRight />
                      </Button>
                    </div>
                  </motion.div>
                ) : step === 0 ? (
                  <motion.div key="guest" initial={{ opacity: 0, x: -16 }} animate={{ opacity: 1, x: 0 }} exit={{ opacity: 0, x: -16 }} transition={{ duration: 0.25, ease }} className="space-y-3">
                    <p className="eyebrow">Step 1 of 2 · Who is staying?</p>
                    {!creating ? (
                      <>
                        <div className="relative">
                          <Search className="absolute left-3 top-1/2 size-4 -translate-y-1/2 text-fg-subtle" />
                          <Input
                            value={q}
                            onChange={(e) => setQ(e.target.value)}
                            onKeyDown={(e) => {
                              if (e.key === "Enter" && raw?.[0]) {
                                setGuest(raw[0]);
                                setStep(1);
                              }
                            }}
                            placeholder="Phone number, name or ID…"
                            inputMode="search"
                            className="h-12 rounded-xl pl-10 text-base"
                            autoFocus
                          />
                        </div>
                        {looksLikePhone(q) && digitsOf(q).length >= 8 ? <ScreenBanner screen={screen} pending={!!phoneTerm} compact /> : null}
                        <ul className="stagger-fast max-h-64 divide-y divide-border overflow-y-auto rounded-xl border border-border scrollbar-thin">
                          {term.length < 2 ? (
                            <li className="px-3 py-4 text-center text-xs text-fg-muted">Start with the phone number — the engine recognises returning and blocked guests instantly.</li>
                          ) : raw === undefined ? (
                            <li className="space-y-2 p-3">{[0, 1].map((i) => <div key={i} className="skeleton h-9" />)}</li>
                          ) : raw.length === 0 ? (
                            <li className="px-3 py-4 text-center text-xs text-fg-muted">No guest matches “{term}”.</li>
                          ) : (
                            raw.map((c) => {
                              const r = c.riskLevel ? RISK_LEVEL_META[c.riskLevel as RiskLevel] : null;
                              return (
                                <li key={c.id}>
                                  <button type="button" onClick={() => { setGuest(c); setStep(1); }} className={cn("flex w-full items-center gap-3 px-3 py-2.5 text-left transition hover:bg-surface-2 active:bg-surface-3", guest?.id === c.id && "bg-primary/5")}>
                                    <Avatar name={c.fullName} size="md" seed={c.id} />
                                    <span className="min-w-0 flex-1">
                                      <span className="flex flex-wrap items-center gap-x-2 gap-y-0.5">
                                        <span className="font-medium">{c.fullName}</span>
                                        {c.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.reservations}</span> : null}
                                        {r && c.riskLevel !== "NORMAL" ? <span className={cn("inline-flex items-center gap-1 rounded-full px-1.5 py-0.5 text-2xs font-semibold", r.blocksBooking || r.needsApproval ? "bg-negative-500/12 text-negative-700 dark:text-negative-400" : "bg-warning-50 text-warning-700 dark:bg-warning-500/10")}><ShieldAlert className="size-3" /> {r.label}</span> : null}
                                      </span>
                                      <span className="block truncate text-xs text-fg-muted">{fmtPhone(c.phone)}</span>
                                    </span>
                                    <ArrowRight className="size-4 text-fg-subtle" />
                                  </button>
                                </li>
                              );
                            })
                          )}
                        </ul>
                        {perms.createCustomer ? (
                          <Button variant="secondary" className="w-full" onClick={() => { setCreating(true); const parts = q.trim().split(" "); const isPhone = looksLikePhone(q); if (!(t.prefill && isPhone)) setNf({ firstName: isPhone ? "" : parts[0] ?? "", lastName: isPhone ? "" : parts.slice(1).join(" "), phone: isPhone ? q.trim() : "" }); }}>
                            <UserPlus /> New guest in 10 seconds
                          </Button>
                        ) : null}
                      </>
                    ) : (
                      <div className="space-y-3">
                        <Field label="Phone" required hint="Checked live against every profile, blacklist and incident">
                          <Input value={nf.phone} onChange={(e) => setNf({ ...nf, phone: e.target.value })} inputMode="tel" placeholder="+212 6 XX XX XX XX" className="h-11" autoFocus />
                        </Field>
                        <ScreenBanner screen={screen} pending={!!phoneTerm} onUse={useExisting} />
                        <div className="grid grid-cols-2 gap-2">
                          <Field label="First name" required>
                            <Input value={nf.firstName} onChange={(e) => setNf({ ...nf, firstName: e.target.value })} className="h-11" />
                          </Field>
                          <Field label="Last name">
                            <Input value={nf.lastName} onChange={(e) => setNf({ ...nf, lastName: e.target.value })} className="h-11" onKeyDown={(e) => e.key === "Enter" && quickCreate()} />
                          </Field>
                        </div>
                        {error ? <p 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">{error}</p> : null}
                        <div className="flex gap-2">
                          <Button variant="secondary" onClick={() => { setCreating(false); setError(null); }}>
                            <ArrowLeft /> Back
                          </Button>
                          <Button className="flex-1" onClick={quickCreate} loading={busy} disabled={!!screen?.linked && !perms.approveRisky}>
                            <Zap /> Create & continue
                          </Button>
                        </div>
                      </div>
                    )}
                  </motion.div>
                ) : (
                  <motion.div key="confirm" initial={{ opacity: 0, x: 16 }} animate={{ opacity: 1, x: 0 }} exit={{ opacity: 0, x: 16 }} transition={{ duration: 0.25, ease }} className="space-y-3.5">
                    <p className="eyebrow">Step 2 of 2 · Confirm</p>
                    <button type="button" onClick={() => { setStep(0); setGuest(null); setDup(null); setError(null); }} className="flex w-full items-center gap-3 rounded-xl border border-primary/40 bg-primary/5 p-3 text-left transition hover:bg-primary/10">
                      <Avatar name={guest!.fullName} size="lg" seed={guest!.id} />
                      <span className="min-w-0 flex-1">
                        <span className="block font-semibold">{guest!.fullName}</span>
                        <span className="flex items-center gap-1 text-xs text-fg-muted"><Phone className="size-3" /> {fmtPhone(guest!.phone)} · {guest!.reservations} previous stay{guest!.reservations === 1 ? "" : "s"}</span>
                      </span>
                      <span className="text-2xs font-medium text-primary">Change</span>
                    </button>
                    <ScreenBanner screen={screen} pending />
                    {gated && perms.approveRisky ? <p className="text-2xs text-fg-muted">You can approve this guest: the booking is recorded as a risk override in the audit trail.</p> : null}

                    <div>
                      <p className="mb-1.5 text-xs font-medium text-fg-muted">Source</p>
                      <div className="flex flex-wrap gap-1.5">
                        {RESERVATION_SOURCES.map((s) => (
                          <button key={s} type="button" onClick={() => setSource(s)} className={cn("rounded-full border px-3 py-1.5 text-xs font-medium transition-all active:scale-95", 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>
                    </div>

                    {perms.discount || perms.changePrice ? (
                      <div className="rounded-xl border border-border p-3">
                        <div className="flex items-center justify-between">
                          <p className="flex items-center gap-1.5 text-xs font-medium text-fg-muted"><BadgePercent className="size-3.5" /> Price & discount</p>
                          {perms.discount ? <span className="text-2xs text-fg-subtle">up to {perms.maxDiscountPercent}% · {fmtMoney(maxDisc, currency, { whole: true })}</span> : null}
                        </div>
                        <div className="mt-2 flex flex-wrap items-center gap-1.5">
                          {perms.changePrice ? (
                            <Input type="number" inputMode="decimal" min={0} value={nightly} onChange={(e) => setNightly(e.target.value)} placeholder={String(Math.round(baseSubtotal / Math.max(1, nights)))} suffix={`${currency}/night`} className="h-9 w-40" aria-label="Nightly price" />
                          ) : null}
                          {perms.discount ? (
                            <>
                              <button type="button" onClick={() => setDiscMode("none")} className={cn("rounded-full border px-2.5 py-1 text-xs font-medium transition-all active:scale-95", discMode === "none" ? "border-primary bg-primary/10 text-primary" : "border-border text-fg-muted")}>None</button>
                              {pctChips.map((p) => (
                                <button key={p} type="button" onClick={() => setDiscMode(p)} className={cn("rounded-full border px-2.5 py-1 text-xs font-medium transition-all active:scale-95", discMode === p ? "border-primary bg-primary/10 text-primary" : "border-border text-fg-muted")}>−{p}%</button>
                              ))}
                              <button type="button" onClick={() => setDiscMode("custom")} className={cn("rounded-full border px-2.5 py-1 text-xs font-medium transition-all active:scale-95", discMode === "custom" ? "border-primary bg-primary/10 text-primary" : "border-border text-fg-muted")}>Amount</button>
                              {discMode === "custom" ? <Input type="number" inputMode="decimal" min={0} max={maxDisc} value={discCustom} onChange={(e) => setDiscCustom(e.target.value)} placeholder="0" suffix={currency} className="h-9 w-32 animate-slide-up" autoFocus aria-label="Discount amount" /> : null}
                            </>
                          ) : null}
                        </div>
                        {discTooHigh ? <p className="mt-1.5 text-xs text-negative-600">Maximum discount for you is {fmtMoney(maxDisc, currency, { whole: true })} ({perms.maxDiscountPercent}%). Ask a manager for more.</p> : null}
                      </div>
                    ) : null}

                    {perms.recordPayment ? (
                      <div className="grid grid-cols-2 gap-2">
                        <Field label="Paid now" hint="Optional">
                          <Input type="number" inputMode="decimal" min={0} value={paid} onChange={(e) => setPaid(e.target.value)} placeholder="0" suffix={currency} className="h-11" />
                        </Field>
                        <Field label="Method">
                          <NativeSelect value={method} onChange={(e) => setMethod(e.target.value)} className="h-11" disabled={!Number(paid)}>
                            {PAYMENT_METHODS.map((m) => (
                              <option key={m} value={m}>{PAYMENT_METHOD_LABEL[m]}</option>
                            ))}
                          </NativeSelect>
                        </Field>
                      </div>
                    ) : null}

                    <div className="rounded-xl bg-surface-2 p-3 text-sm">
                      <div className="flex justify-between text-fg-muted"><span>{nights} night{nights > 1 ? "s" : ""} · {fmtMoney(subtotal / Math.max(1, nights), currency, { whole: true })} avg</span><span className="tabular">{fmtMoney(subtotal, currency, { whole: true })}</span></div>
                      {discount > 0 ? <div className="mt-1 flex justify-between text-xs text-positive-700 dark:text-positive-500"><span>Discount{discMode !== "custom" && discMode !== "none" ? ` ${discMode}%` : ""}</span><span className="tabular">−{fmtMoney(discount, currency, { whole: true })}</span></div> : null}
                      {Number(paid) > 0 ? <div className="mt-1 flex justify-between text-xs text-fg-muted"><span>Remaining after payment</span><span className="tabular">{fmtMoney(Math.max(0, total - Number(paid)), currency, { whole: true })}</span></div> : null}
                      <div className="mt-2 flex items-center justify-between border-t border-border pt-2 font-semibold"><span className="flex items-center gap-1.5"><Users className="size-4 text-fg-muted" /> {t.guests} guest{t.guests > 1 ? "s" : ""}</span><span className="tabular"><AnimatedText text={fmtMoney(total, currency, { whole: true })} /></span></div>
                    </div>

                    <AnimatePresence>
                      {dup ? (
                        <motion.div key="dup" initial={{ opacity: 0, y: 6 }} animate={{ opacity: 1, y: 0 }} exit={{ opacity: 0 }} className="rounded-xl border border-warning-500/40 bg-warning-500/10 p-3 text-xs text-warning-800 dark:text-warning-400">
                          <p className="flex items-center gap-2 font-semibold"><ShieldAlert className="size-4" /> Already booked on these nights</p>
                          <p className="mt-1">{guest!.fullName} has <b>{dup.code}</b> in <b>{dup.apartment}</b> for {fmtDate(parseDay(dup.checkIn), { style: "short" })} → {fmtDate(parseDay(dup.checkOut), { style: "short" })}. Booking twice by mistake is blocked.</p>
                          <div className="mt-2 flex flex-wrap gap-2">
                            <Button size="xs" variant="secondary" onClick={() => setDup(null)}>Cancel</Button>
                            <Button size="xs" onClick={() => book(true)} loading={busy}>Book anyway · 2nd apartment</Button>
                          </div>
                        </motion.div>
                      ) : null}
                    </AnimatePresence>
                    {error ? <p 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">{error}</p> : null}
                    <div className="flex gap-2">
                      <Button variant="secondary" size="lg" onClick={() => { setStep(0); setGuest(null); setDup(null); setError(null); }} disabled={busy}>
                        <ArrowLeft />
                      </Button>
                      <Button size="lg" className={cn("flex-1", !blocked && !taken && "shine")} onClick={() => book(false)} loading={busy} disabled={blocked || taken || discTooHigh || !!dup}>
                        {blocked ? <><ShieldX /> Booking refused</> : taken ? "No longer available" : <><Check /> Confirm booking</>}
                      </Button>
                    </div>
                  </motion.div>
                )}
              </AnimatePresence>
            </div>
          </>
        ) : null}
      </DialogContent>
    </Dialog>
  );
}
