"use client";

import { useCallback, useEffect, useRef, useState } from "react";

import {
  api,
  inviteUrl,
  type GuestRead,
  type InvitationSummary,
  type ManualResponse,
} from "@/lib/api/browser";

import InviteCompose from "./InviteCompose";
import Modal from "./Modal";
import { Button, ErrorNote, Loading, StatusBadge, formatDateTime, inputClass } from "./ui";

/**
 * Guest detail: reach this guest now, and correct their answer (tasks 3.11, 3.6).
 *
 * Three distinct jobs live here — copy a link, compose a message, fix an RSVP — and they
 * are given three separately headed, separately contained sections. Undivided, they read as
 * one wall of controls and the admin has to parse the contents to find the one they came
 * for. Colour carries no meaning any of them depends on: containment and headings do the
 * work, so the sections are still told apart without perceiving hue.
 *
 * No QR here (add-guest-invitation-send D10). This screen is about reaching one guest right
 * now; printing is a batch activity and belongs to the QR screen, which still produces the
 * same per-guest artwork.
 *
 * The RSVP override exists because guests phone the couple rather than clicking links. Every
 * one is recorded with actor=admin in `rsvp_history`, so "who marked Rahim as coming?" has
 * an answer six weeks later when the caterer count is being argued about.
 */
export default function GuestDrawer({
  guestId,
  canEdit,
  canSend,
  onClose,
  onChanged,
}: {
  guestId: string;
  canEdit: boolean;
  canSend: boolean;
  onClose: () => void;
  onChanged: () => void;
}) {
  const [guest, setGuest] = useState<GuestRead | null>(null);
  const [error, setError] = useState<unknown>(null);

  const load = useCallback(async () => {
    try {
      setGuest(await api.guest(guestId));
      setError(null);
    } catch (err) {
      setError(err);
    }
  }, [guestId]);

  useEffect(() => {
    void load();
  }, [load]);

  return (
    <Modal title={guest?.full_name ?? "Guest"} onClose={onClose} size="wide">
      <ErrorNote error={error} />
      {!guest ? (
        <Loading />
      ) : (
        <div className="space-y-5">
          <dl className="grid grid-cols-2 gap-3 text-sm sm:grid-cols-4">
            <Detail label="Phone" value={guest.phone_e164 ?? "—"} />
            <Detail
              label="Email"
              value={guest.email ?? "none"}
              warn={!guest.email || guest.email_invalid}
            />
            <Detail label="Side" value={guest.side} />
            <Detail label="Source" value={guest.source.replace(/_/g, " ")} />
          </dl>

          {guest.email_invalid && (
            <p className="rounded-lg bg-amber-50 px-3 py-2 text-sm text-amber-900">
              This address hard-bounced, so it is excluded from every send. Correct it to
              start reaching them again.
            </p>
          )}
          {guest.do_not_contact && (
            <p className="rounded-lg bg-stone-100 px-3 py-2 text-sm text-stone-700">
              Unsubscribed. No further messages will be queued for this guest.
            </p>
          )}

          <div className="space-y-3">
            <h3 className="text-xs font-semibold uppercase tracking-wide text-stone-500">
              Invitations
            </h3>
            {guest.invitations.length === 0 ? (
              <p className="text-sm text-stone-500">Not invited to any event yet.</p>
            ) : (
              guest.invitations.map((invitation) => (
                <InvitationRow
                  key={invitation.id}
                  invitation={invitation}
                  canEdit={canEdit}
                  canSend={canSend}
                  onChanged={() => {
                    void load();
                    onChanged();
                  }}
                />
              ))
            )}
          </div>
        </div>
      )}
    </Modal>
  );
}

function InvitationRow({
  invitation,
  canEdit,
  canSend,
  onChanged,
}: {
  invitation: InvitationSummary;
  canEdit: boolean;
  canSend: boolean;
  onChanged: () => void;
}) {
  const [open, setOpen] = useState(false);
  const [response, setResponse] = useState<ManualResponse>("accepted");
  const [partySize, setPartySize] = useState(invitation.party_size ?? 1);
  const [note, setNote] = useState("");
  const [error, setError] = useState<unknown>(null);
  const [saving, setSaving] = useState(false);

  async function override(e: React.FormEvent) {
    e.preventDefault();
    setSaving(true);
    setError(null);
    try {
      await api.overrideRsvp(invitation.id, {
        response,
        party_size: partySize,
        note: note.trim() || null,
      });
      setOpen(false);
      onChanged();
    } catch (err) {
      setError(err);
    } finally {
      setSaving(false);
    }
  }

  return (
    <div className="overflow-hidden rounded-xl border border-stone-300">
      <header className="border-b border-stone-200 bg-stone-100 px-4 py-3">
        <div className="flex flex-wrap items-center gap-2">
          <h4 className="font-medium text-stone-900">{invitation.event_title_en}</h4>
          <StatusBadge status={invitation.status} />
          <span className="text-xs text-stone-500">
            {invitation.party_size ?? 0} of {invitation.max_guests} seat
            {invitation.max_guests === 1 ? "" : "s"}
          </span>
        </div>
        <dl className="mt-1.5 flex flex-wrap gap-x-6 gap-y-1 text-xs text-stone-500">
          <Timeline label="Opened" at={invitation.opened_at} />
          <Timeline label="Responded" at={invitation.responded_at} />
          <div>
            <dt className="inline font-medium">Code </dt>
            <dd className="inline font-mono">{invitation.short_code}</dd>
          </div>
        </dl>
      </header>

      <Section title="Send via copying the link" tone="neutral">
        <InviteShare invitation={invitation} />
      </Section>

      {canSend && (
        <Section title="Send via messaging" tone="accent">
          <InviteCompose
            invitationId={invitation.id}
            eventTitle={invitation.event_title_en}
          />
        </Section>
      )}

      {canEdit && (
        <Section
          title="Record an RSVP"
          tone="neutral"
          action={
            <Button variant="ghost" onClick={() => setOpen((v) => !v)}>
              {open ? "Cancel" : "Set RSVP"}
            </Button>
          }
        >
          {open ? (
            <form onSubmit={override} className="space-y-2">
              <div className="flex flex-wrap gap-2">
                <select
                  value={response}
                  onChange={(e) => setResponse(e.target.value as ManualResponse)}
                  aria-label="Response"
                  className={`${inputClass} max-w-[9rem]`}
                >
                  <option value="accepted">Accepted</option>
                  <option value="declined">Declined</option>
                  <option value="cancelled">Cancelled</option>
                </select>
                <input
                  type="number"
                  min={1}
                  max={invitation.max_guests}
                  value={partySize}
                  onChange={(e) => setPartySize(Number(e.target.value))}
                  aria-label="Party size"
                  className={`${inputClass} max-w-[6rem]`}
                />
                <input
                  value={note}
                  onChange={(e) => setNote(e.target.value)}
                  placeholder="Note (e.g. phoned in)"
                  aria-label="Note"
                  className={`${inputClass} min-w-[10rem] flex-1`}
                />
                <Button type="submit" disabled={saving}>
                  {saving ? "Saving…" : "Record"}
                </Button>
              </div>
              <p className="text-xs text-stone-500">
                Recorded against your account in the guest&apos;s history.
              </p>
              <ErrorNote error={error} />
            </form>
          ) : (
            <p className="text-xs text-stone-500">
              For an answer that arrived by phone rather than through the link.
            </p>
          )}
        </Section>
      )}
    </div>
  );
}

/**
 * One job in the guest detail view, contained and headed.
 *
 * `tone` tints the surface so neighbouring sections separate at a glance; it carries no
 * information of its own, which is why the heading is not optional. Semantic colour in this
 * admin already means something — amber warns, red fails, the status badges carry RSVP
 * state — and spending those hues on decoration is how they stop being noticed.
 */
function Section({
  title,
  tone,
  action,
  children,
}: {
  title: string;
  tone: "neutral" | "accent";
  action?: React.ReactNode;
  children: React.ReactNode;
}) {
  const surface = tone === "accent" ? "bg-amber-50/40" : "bg-white";
  return (
    <section className={`border-t border-stone-200 px-4 py-3 first:border-t-0 ${surface}`}>
      <div className="mb-2 flex items-center justify-between gap-3">
        {/* h5, not h3: these sit under the invitation's h4, which sits under the
            "Invitations" h3. Navigating by heading should descend into the event, not
            surface its sections as siblings of the list that contains it. */}
        <h5 className="text-xs font-semibold uppercase tracking-wide text-stone-600">
          {title}
        </h5>
        {action}
      </div>
      {children}
    </section>
  );
}

/**
 * The guest's personal link, for pasting into WhatsApp or a message by hand.
 *
 * Always visible rather than behind a toggle: copying it is the common case on the screen
 * used most, and a tap to reveal it would be a tax on every use.
 */
function InviteShare({ invitation }: { invitation: InvitationSummary }) {
  const [copied, setCopied] = useState(false);
  const linkRef = useRef<HTMLInputElement>(null);

  // `inviteUrl` reads `window.location`, which is not available during the server render.
  // Deriving it in an effect keeps the markup identical on both passes; without this the
  // input hydrates with a mismatched value and React discards the client tree.
  const [url, setUrl] = useState("");
  useEffect(() => setUrl(inviteUrl(invitation.token)), [invitation.token]);

  useEffect(() => {
    if (!copied) return;
    const timer = setTimeout(() => setCopied(false), 2000);
    return () => clearTimeout(timer);
  }, [copied]);

  async function copy() {
    try {
      await navigator.clipboard.writeText(url);
      setCopied(true);
      // Audited, because this is the one path by which a guest's link leaves the system
      // with no delivery record attached to it (design D8). Fire-and-forget on purpose: a
      // failed log write must not report a copy that plainly worked as a failure.
      void api.recordLinkCopy(invitation.id).catch(() => {});
    } catch {
      // Clipboard access needs a secure context and can be denied outright. Selecting the
      // text leaves the admin one Ctrl+C away rather than staring at a button that did
      // nothing — and the failure is worth handling, because this runs on venue wifi.
      linkRef.current?.select();
    }
  }

  return (
    <div className="space-y-2">
      {/* Warned, not blocked. Copying is how a host hands a link over in person or lines one
          up to paste later, and an event published five minutes from now makes that link
          work — but a link that 404s today is worth knowing about before it is pasted. */}
      {!invitation.event_published && (
        <p className="rounded-lg bg-amber-50 px-3 py-2 text-sm text-amber-900">
          {invitation.event_title_en} is not published yet, so this link shows “page not
          found” until you publish the event.
        </p>
      )}
      <div className="flex flex-wrap items-center gap-2">
        <label className="sr-only" htmlFor={`invite-link-${invitation.id}`}>
          Invitation link for {invitation.event_title_en}
        </label>
        <input
          id={`invite-link-${invitation.id}`}
          ref={linkRef}
          readOnly
          value={url}
          onFocus={(e) => e.currentTarget.select()}
          className={`${inputClass} min-w-[12rem] flex-1 font-mono text-xs`}
        />
        <Button variant="secondary" onClick={() => void copy()}>
          {copied ? "Copied" : "Copy link"}
        </Button>
      </div>
    </div>
  );
}

function Timeline({ label, at }: { label: string; at: string | null }) {
  return (
    <div>
      <dt className="inline font-medium">{label} </dt>
      <dd className="inline">{at ? formatDateTime(at) : "—"}</dd>
    </div>
  );
}

function Detail({ label, value, warn }: { label: string; value: string; warn?: boolean }) {
  return (
    <div>
      <dt className="text-xs text-stone-500">{label}</dt>
      <dd className={`break-words ${warn ? "text-amber-700" : "text-stone-900"}`}>{value}</dd>
    </div>
  );
}
