/**
 * Personalised invitation, `/i/{token}` (task 2.3, FR-1.1).
 *
 * Dynamic and never cached: the token identifies one guest, so a shared cache entry would
 * show the wrong person's details. The fetch happens here on the server, which is why the
 * token never appears in a request the browser makes.
 */
import type { Metadata } from "next";
import { headers } from "next/headers";
import { notFound } from "next/navigation";

import { InvitationInteractive } from "@/components/InvitationInteractive";
import { InvitationShell } from "@/components/InvitationShell";
import { fetchInvitationByToken } from "@/lib/api/server";
import { isPreviewCrawler } from "@/lib/crawlers";
import { getDictionary } from "@/lib/i18n";
import { toInvitationView, type Locale } from "@/lib/view-models";
import { linkPreviewMetadata } from "@/lib/link-preview-metadata";
import { appBaseUrl } from "@/lib/public-config";

export const dynamic = "force-dynamic";
export const revalidate = 0;

/**
 * Whether this request is a chat application building a preview rather than a guest.
 *
 * Reading `headers()` costs nothing here — this route is already `force-dynamic`, because
 * the page is personalised. On `/e/[slug]` the same call would opt that route out of ISR,
 * which is why the open route does not do this.
 */
async function requestIsCrawler(): Promise<boolean> {
  return isPreviewCrawler((await headers()).get("user-agent"));
}

export default async function InvitationPage({
  params,
  searchParams,
}: {
  params: Promise<{ token: string }>;
  searchParams: Promise<{ lang?: string }>;
}) {
  const { token } = await params;
  const { lang } = await searchParams;

  // A crawler's fetch must not be recorded as the guest opening their invitation (D9).
  const data = await fetchInvitationByToken(token, await requestIsCrawler());
  // Generic not-found: the page must never reveal whether a token exists (PRD §7.6).
  if (!data) notFound();

  const override: Locale | undefined = lang === "bn" || lang === "en" ? lang : undefined;
  const invitation = toInvitationView(data, token, override);
  const dict = getDictionary(invitation.locale);

  return (
    <InvitationShell
      event={invitation.event}
      couple={invitation.couple}
      locale={invitation.locale}
      dict={dict}
      greeting={invitation.greeting}
      guestName={invitation.prefill?.name}
      card={invitation.card}
    >
      <InvitationInteractive invitation={invitation} dict={dict} />
    </InvitationShell>
  );
}

export async function generateMetadata({
  params,
}: {
  params: Promise<{ token: string }>;
}): Promise<Metadata> {
  const { token } = await params;
  // Always the non-marking read: metadata generation is not a guest opening anything, and
  // Next calls this alongside the page render on every request.
  const data = await fetchInvitationByToken(token, true);
  if (!data) return { title: "Invitation" };

  // Through the view model like every other read, so the API's snake_case never reaches a
  // component or a tag by accident.
  return {
    ...linkPreviewMetadata(toInvitationView(data, token).preview, appBaseUrl()),
    // Personalised and token-bearing: keep it out of search results entirely. The preview
    // tags above are guest-free (design D3), so they are safe to hand a crawler — being
    // indexed is a different question from being unfurled, and the answer is still no.
    robots: { index: false, follow: false },
    referrer: "no-referrer",
  };
}
