"use client";

/**
 * The interactive layer of the invitation page (FR-2.1, FR-2.6, FR-2.7, FR-1.12, task 7.4).
 *
 * Everything here sits *on top of* server-rendered text — the names, date, venue and
 * contact number are already in the HTML before this mounts, so the invitation is readable
 * even if JavaScript never runs.
 *
 * The `form` stage renders into `RsvpSurface` rather than into the page flow. Every other
 * stage keeps the behaviour it had, in the footer where the call to action lives — and the
 * surface floats *above* whichever of them applies, so a guest updating an existing answer
 * still sees that answer behind the form rather than having it replaced by a blank one.
 */
import { useCallback, useRef, useState } from "react";

import { RsvpForm } from "./RsvpForm";
import { RsvpSurface } from "./RsvpSurface";
import type { Dictionary } from "@/lib/i18n";
import type { InvitationView } from "@/lib/view-models";

type Stage = "idle" | "form" | "success" | "declined";

export function InvitationInteractive({
  invitation,
  dict,
}: {
  invitation: InvitationView;
  dict: Dictionary;
}) {
  const initialStage: Stage =
    invitation.state === "already_responded"
      ? "success"
      : invitation.state === "declined"
        ? "declined"
        : "idle";

  const [stage, setStage] = useState<Stage>(initialStage);
  const [partySize, setPartySize] = useState(invitation.currentRsvp?.partySize ?? 1);
  const [calendarUrl, setCalendarUrl] = useState(`/api/ics/${invitation.token}`);
  const [busy, setBusy] = useState(false);

  // Whether the guest has typed anything, so dismissal can warn instead of discarding.
  // A ref rather than state: it changes on every keystroke and nothing renders from it.
  const touched = useRef(false);

  /** Backdrop and Escape ask first once a field has been touched (task 7.5). A guest who
   * has filled in three fields and brushes the backdrop must not lose them silently. */
  const confirmClose = useCallback(
    () => !touched.current || window.confirm(dict.discardChanges),
    [dict.discardChanges],
  );

  const closeForm = useCallback(() => {
    touched.current = false;
    setStage(initialStage);
  }, [initialStage]);

  const fireConfetti = useCallback(async () => {
    if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return;
    const { default: confetti } = await import("canvas-confetti");
    confetti({ particleCount: 120, spread: 70, origin: { y: 0.6 } });
  }, []);

  async function handleDecline() {
    if (busy) return;
    setBusy(true);
    try {
      const response = await fetch(`/api/rsvp/${encodeURIComponent(invitation.token)}/decline`, {
        method: "POST",
        headers: { "content-type": "application/json" },
        body: JSON.stringify({ reason: null }),
      });
      if (response.ok) setStage("declined");
    } finally {
      setBusy(false);
    }
  }

  return (
    <>
      {/* Called, not rendered as `<Footer />`. A component declared inside another gets a
          new function identity every render, so React unmounts and remounts its whole
          subtree each time — which threw away the trigger button and left focus stranded on
          <body> when the surface closed. Calling it keeps the elements reconciled. */}
      {footer()}

      <RsvpSurface
        open={stage === "form"}
        title={dict.rsvpTitle}
        closeLabel={dict.close}
        onClose={closeForm}
        confirmClose={confirmClose}
      >
        {/* The form itself is unchanged: the surface wraps it rather than replacing it, so
            validation, party-size rules and error handling are the same code as before.
            `onInput` bubbles from every field, which is all the "has this been touched"
            question needs — cheaper and less brittle than threading a callback through. */}
        <div onInput={() => (touched.current = true)}>
          <RsvpForm
            token={invitation.token}
            maxGuests={invitation.maxGuests}
            prefill={invitation.prefill}
            dict={dict}
            onSuccess={(result) => {
              touched.current = false;
              setPartySize(result.partySize);
              if (result.calendarUrl) setCalendarUrl(result.calendarUrl);
              setStage("success");
              void fireConfetti();
            }}
          />
        </div>
      </RsvpSurface>
    </>
  );

  /** What sits in the footer.
   *
   * Rendered from the stage the guest *came from*, not from `form`: while the surface is
   * open the page behind it must keep showing what they were looking at. Falling through to
   * the idle Accept/Decline instead would swap the panel — and because that swaps one
   * element for another, React would remount it, destroying the very button focus has to
   * return to when the surface closes.
   */
  function footer() {
    const behind = stage === "form" ? initialStage : stage;

    if (invitation.state === "closed") {
      return (
        <section className="rounded-2xl border p-6 text-center">
          <h2 className="text-lg font-semibold">{dict.rsvpClosed}</h2>
          {invitation.couple.hostContactPhone && (
            <p className="mt-2 text-sm">
              {dict.rsvpClosedHelp}{" "}
              <a className="underline" href={`tel:${invitation.couple.hostContactPhone}`}>
                {invitation.couple.hostContactPhone}
              </a>
            </p>
          )}
        </section>
      );
    }

    if (behind === "declined") {
      return (
        <section className="rounded-2xl border p-6 text-center">
          <h2 className="text-lg font-semibold">{dict.weWillMissYou}</h2>
          <p className="mt-3 text-sm opacity-70">{dict.changedYourMind}</p>
          <button
            onClick={() => setStage("form")}
            className="mt-3 min-h-11 rounded-full border px-5 text-base"
          >
            {dict.reAccept}
          </button>
        </section>
      );
    }

    if (behind === "success") {
      return (
        <section
          className="rounded-2xl border p-6 text-center"
          aria-live="polite"
          role="status"
        >
          <h2 className="text-xl font-semibold">{dict.alreadyAccepted}</h2>
          <p className="mt-2 text-sm">
            {dict.guestsConfirmed}: <strong>{partySize}</strong>
          </p>

          <div className="mt-4 flex flex-col gap-2">
            <a href={calendarUrl} className="min-h-11 rounded-full border px-5 py-2.5 text-base">
              {dict.addToCalendar}
            </a>
            <button
              onClick={() => setStage("form")}
              className="min-h-11 rounded-full border px-5 text-base"
            >
              {dict.updateResponse}
            </button>
            {invitation.canCancel && (
              <a
                href={`/i/${invitation.token}/cancel`}
                className="min-h-11 px-5 py-2.5 text-sm underline opacity-70"
              >
                {dict.cancelRsvp}
              </a>
            )}
          </div>
        </section>
      );
    }

    // idle — and what sits behind the open surface for a first-time guest. One primary call
    // to action; Decline is retained but visually secondary. It is a real choice, not one to
    // give equal weight to (task 6.3).
    return (
      <div className="flex flex-col gap-2">
        <button
          onClick={() => setStage("form")}
          className="min-h-12 rounded-full bg-black px-6 text-base font-medium text-white"
        >
          {dict.accept}
        </button>
        <button
          onClick={handleDecline}
          disabled={busy}
          className="min-h-11 px-6 text-sm underline opacity-70 disabled:opacity-40"
        >
          {dict.decline}
        </button>
      </div>
    );
  }
}
