"use client";

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

import {
  ApiError,
  api,
  type ComposedMessage,
  type SendInvitationResult,
} from "@/lib/api/browser";

import { LinkPreviewCard } from "./LinkPreviewCard";
import { Button, ErrorNote, Loading, formatDateTime, inputClass } from "./ui";

/**
 * Compose and send one guest's invitation email (add-guest-invitation-send D4, D5).
 *
 * The whole message is shown as editable text rather than as a preview of a template,
 * because it *is* the message — an admin approving a summary and a guest receiving something
 * else is the failure this panel exists to prevent.
 *
 * Composition happens on the server. The sentence in the body resolves event → wedding →
 * built-in default, by locale and invitation type, and reimplementing that here is how this
 * panel and the guest's invitation page would drift apart.
 */

/** Why the Send button is inert for a draft event, for the control's own tooltip. */
const DRAFT_HINT = "This event is not published, so its invitation link returns 404";

export default function InviteCompose({
  invitationId,
  eventTitle,
}: {
  invitationId: string;
  eventTitle: string;
}) {
  const [composed, setComposed] = useState<ComposedMessage | null>(null);
  const [subject, setSubject] = useState("");
  const [header, setHeader] = useState("");
  const [body, setBody] = useState("");
  const [footer, setFooter] = useState("");

  const [loadError, setLoadError] = useState<unknown>(null);
  const [sendError, setSendError] = useState<unknown>(null);
  const [sending, setSending] = useState(false);
  const [result, setResult] = useState<SendInvitationResult | null>(null);
  const [lastSentAt, setLastSentAt] = useState<string | null>(null);

  const load = useCallback(async () => {
    try {
      const message = await api.invitationMessage(invitationId);
      setComposed(message);
      setSubject(message.subject);
      setHeader(message.header);
      setBody(message.body);
      setFooter(message.footer);
      setLastSentAt(message.last_sent_at);
      setLoadError(null);
    } catch (err) {
      setLoadError(err);
    }
  }, [invitationId]);

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

  /**
   * Send, asking about quiet hours only if the server says to.
   *
   * The confirmation is driven by the API's 409 rather than by the `in_quiet_hours` flag
   * from the initial load: that flag is a hint that goes stale while the admin types, and
   * the browser's clock is not Asia/Dhaka in any case. The server owns the question; this
   * relays it and sends the answer back.
   */
  async function send(confirmedQuietHours = false) {
    setSending(true);
    setSendError(null);
    setResult(null);
    try {
      const sent = await api.sendInvitation(invitationId, {
        subject,
        header,
        body,
        footer,
        confirmed_quiet_hours: confirmedQuietHours,
      });
      setResult(sent);
      setLastSentAt(sent.sent_at);
    } catch (err) {
      if (err instanceof ApiError && err.code === "quiet_hours" && !confirmedQuietHours) {
        if (confirm(err.message)) {
          setSending(false);
          return send(true);
        }
      } else {
        setSendError(err);
      }
    } finally {
      setSending(false);
    }
  }

  if (loadError) return <ErrorNote error={loadError} />;
  if (!composed) return <Loading label="Composing…" />;

  // A draft event's invitation page answers 404 to everyone, so the link in this message is
  // dead until it is published. Stated before the fields rather than after Send: the API
  // refuses the send either way, but by then the admin has written the message.
  const draft = !composed.event_published;
  const blocked = composed.blocked_reason;
  // Both reasons stop a send, and they stop it for unrelated reasons — one is the guest's
  // consent, the other is the event's state — so they are shown separately and combined only
  // where the controls need a single answer.
  const cannotSend = !!blocked || draft;

  return (
    <div className="space-y-3">
      {draft && (
        <p className="rounded-lg bg-amber-50 px-3 py-2 text-sm text-amber-900">
          {eventTitle} is still a draft, so its invitation link shows “page not found”.
          Publish the event on the Events screen, then send.
        </p>
      )}
      {blocked ? (
        <p className="rounded-lg bg-amber-50 px-3 py-2 text-sm text-amber-900">{blocked}</p>
      ) : (
        <p className="text-xs text-stone-600">
          Sends to <span className="font-medium text-stone-900">{composed.to_email}</span>.
          Replies go to the couple.
        </p>
      )}

      {/* Above the editable fields on purpose: the block is chrome the guest receives
          whatever the admin types, so it belongs outside the part they are editing. */}
      <section className="space-y-2">
        <p className="text-xs font-medium text-stone-500">What this link will look like</p>
        <LinkPreviewCard
          title={composed.preview.title}
          description={composed.preview.description}
          guestName={composed.preview.guest_name}
          imageUrl={composed.preview.image_url}
        />
        {!composed.preview.has_image && (
          <p className="text-xs text-stone-500">
            Publish a card design with a preview image for {eventTitle} to show artwork
            here and in the guest&apos;s email.
          </p>
        )}
      </section>

      {lastSentAt && (
        // Repeat sends are allowed on purpose (D2), so the guard against an accidental
        // duplicate is showing the admin that one already went out.
        <p className="text-xs text-stone-500">Last emailed {formatDateTime(lastSentAt)}.</p>
      )}

      <Labelled label="Subject" htmlFor={`subject-${invitationId}`}>
        <input
          id={`subject-${invitationId}`}
          value={subject}
          onChange={(e) => setSubject(e.target.value)}
          className={inputClass}
          disabled={cannotSend}
        />
      </Labelled>

      <Labelled label="Greeting" htmlFor={`header-${invitationId}`}>
        <input
          id={`header-${invitationId}`}
          value={header}
          onChange={(e) => setHeader(e.target.value)}
          className={inputClass}
          disabled={cannotSend}
        />
      </Labelled>

      <Labelled label="Message" htmlFor={`body-${invitationId}`}>
        <textarea
          id={`body-${invitationId}`}
          rows={6}
          value={body}
          onChange={(e) => setBody(e.target.value)}
          className={`${inputClass} font-normal`}
          disabled={cannotSend}
        />
      </Labelled>

      <Labelled label="Sign-off" htmlFor={`footer-${invitationId}`}>
        <textarea
          id={`footer-${invitationId}`}
          rows={2}
          value={footer}
          onChange={(e) => setFooter(e.target.value)}
          className={inputClass}
          disabled={cannotSend}
        />
      </Labelled>

      <p className="text-xs text-stone-500">
        An unsubscribe line for {eventTitle} is added automatically.
      </p>

      <div className="flex flex-wrap items-center gap-3">
        <Button
          onClick={() => void send()}
          disabled={sending || cannotSend}
          title={blocked ?? (draft ? DRAFT_HINT : undefined)}
        >
          {sending ? "Sending…" : "Send via email"}
        </Button>
        {result && <Outcome result={result} />}
      </div>

      <ErrorNote error={sendError} />
    </div>
  );
}

/** What actually happened, in the words the admin needs — never dressed up as a delivery. */
function Outcome({ result }: { result: SendInvitationResult }) {
  if (result.dry_run) {
    return (
      <span role="status" className="text-sm text-amber-800">
        Rehearsed only — dry run is on, so no email was sent.
      </span>
    );
  }
  if (result.status === "sent") {
    return (
      <span role="status" className="text-sm text-green-800">
        Sent {result.sent_at ? formatDateTime(result.sent_at) : ""}.
      </span>
    );
  }
  return (
    <span role="status" className="text-sm text-red-700">
      {result.skip_reason ?? result.error_message ?? `Not sent (${result.status}).`}
    </span>
  );
}

function Labelled({
  label,
  htmlFor,
  children,
}: {
  label: string;
  htmlFor: string;
  children: React.ReactNode;
}) {
  return (
    <div>
      <label
        htmlFor={htmlFor}
        className="mb-1 block text-xs font-medium uppercase tracking-wide text-stone-500"
      >
        {label}
      </label>
      {children}
    </div>
  );
}
