"use client";

/**
 * The card design tab on an event (tasks 8.3, 8.4).
 *
 * Upload → preview → publish, in that order, because publishing is the only step guests
 * feel. A designer iterating on a card uploads five times and publishes once.
 *
 * The preview is a phone frame containing the *published document itself*, not a rendering
 * of it — same shadow root, same stylesheet, same animation. A preview that approximates
 * the card is worse than none: it would be trusted, and it would be wrong about exactly the
 * cases that matter.
 */
import { useCallback, useEffect, useRef, useState } from "react";

import { api, type CardDesign } from "@/lib/api/browser";

import { Button, Card, Empty, ErrorNote, Loading } from "./ui";

/** The whole invitation page has an 800KB budget; the card is the largest thing on it. */
const BUDGET_BYTES = 500 * 1024;

export default function CardDesignPanel({
  eventId,
  canEdit,
}: {
  eventId: string;
  canEdit: boolean;
}) {
  const [designs, setDesigns] = useState<CardDesign[] | null>(null);
  const [error, setError] = useState<unknown>(null);
  const [busy, setBusy] = useState(false);
  const documentRef = useRef<HTMLInputElement>(null);
  const assetsRef = useRef<HTMLInputElement>(null);

  const load = useCallback(async () => {
    try {
      setDesigns(await api.cardDesigns(eventId));
      setError(null);
    } catch (err) {
      setError(err);
    }
  }, [eventId]);

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

  async function upload() {
    const document = documentRef.current?.files?.[0];
    if (!document) return;
    setBusy(true);
    setError(null);
    try {
      await api.uploadCardDesign(eventId, document, [...(assetsRef.current?.files ?? [])]);
      if (documentRef.current) documentRef.current.value = "";
      if (assetsRef.current) assetsRef.current.value = "";
      await load();
    } catch (err) {
      setError(err);
    } finally {
      setBusy(false);
    }
  }

  async function act(action: () => Promise<unknown>) {
    setBusy(true);
    setError(null);
    try {
      await action();
      await load();
    } catch (err) {
      setError(err);
    } finally {
      setBusy(false);
    }
  }

  if (!designs) return <Loading label="Loading card designs…" />;

  const published = designs.find((d) => d.status === "published") ?? null;

  return (
    <div className="space-y-4">
      <ErrorNote error={error} />

      {canEdit && (
        <Card title="Upload a card">
          <div className="space-y-3">
            <div>
              <label
                htmlFor="card-doc"
                className="mb-1 block text-xs font-medium text-stone-600"
              >
                Card file (.html)
              </label>
              <input
                id="card-doc"
                ref={documentRef}
                type="file"
                accept=".html,text/html"
                className="block w-full text-sm file:mr-3 file:min-h-11 file:rounded-md file:border file:border-stone-300 file:bg-white file:px-4 file:text-sm file:font-medium"
              />
            </div>
            <div>
              <label
                htmlFor="card-assets"
                className="mb-1 block text-xs font-medium text-stone-600"
              >
                Everything it references
              </label>
              <input
                id="card-assets"
                ref={assetsRef}
                type="file"
                multiple
                accept="image/png,image/jpeg,image/webp,image/avif,image/svg+xml"
                className="block w-full text-sm file:mr-3 file:min-h-11 file:rounded-md file:border file:border-stone-300 file:bg-white file:px-4 file:text-sm file:font-medium"
              />
              {/* Together in one upload, because the document's references are rewritten
                  against these files — a two-step upload would need an in-between state
                  where the card points at nothing. */}
              <p className="mt-1 text-xs text-stone-500">
                Upload the images and fonts in the same step as the card. A file the card
                references but that is missing here is rejected by name.
              </p>
            </div>
            <div className="flex justify-end">
              <Button onClick={() => void upload()} disabled={busy}>
                {busy ? "Uploading…" : "Upload as draft"}
              </Button>
            </div>
          </div>
        </Card>
      )}

      {designs.length === 0 ? (
        <Card>
          <Empty>No card yet. Guests see the plain invitation until one is published.</Empty>
        </Card>
      ) : (
        <div className="grid gap-4 lg:grid-cols-2">
          <Card title="Versions">
            <ul className="divide-y divide-stone-100">
              {designs.map((design) => (
                <li key={design.id} className="flex items-center gap-3 py-3">
                  <span className="font-mono text-xs text-stone-500">v{design.version}</span>
                  <span
                    className={
                      design.status === "published"
                        ? "rounded-full bg-emerald-50 px-2 py-0.5 text-xs font-medium text-emerald-800"
                        : "rounded-full bg-stone-100 px-2 py-0.5 text-xs text-stone-600"
                    }
                  >
                    {design.status}
                  </span>
                  <Weight bytes={design.total_bytes} />
                  {canEdit && (
                    <span className="ml-auto flex gap-2">
                      {design.status === "published" ? (
                        <Button
                          variant="secondary"
                          disabled={busy}
                          onClick={() => void act(() => api.unpublishCardDesign(design.id))}
                        >
                          Unpublish
                        </Button>
                      ) : (
                        <>
                          <Button
                            disabled={busy}
                            onClick={() => void act(() => api.publishCardDesign(design.id))}
                          >
                            {published ? "Publish (replaces live)" : "Publish"}
                          </Button>
                          <Button
                            variant="secondary"
                            disabled={busy}
                            onClick={() => void act(() => api.deleteCardDesign(design.id))}
                          >
                            Delete
                          </Button>
                        </>
                      )}
                    </span>
                  )}
                </li>
              ))}
            </ul>
            {/* Rolling back is publishing an older version — the same action, so it needs
                no separate button and no separate code path to get wrong. */}
            <p className="mt-3 text-xs text-stone-500">
              To roll back, publish an earlier version. Versions are never reused, so “the
              one from Tuesday” stays identifiable.
            </p>
          </Card>

          <Card title="Preview">
            <PhonePreview design={designs.find((d) => d.status === "published") ?? designs[0]} />
          </Card>
        </div>
      )}
    </div>
  );
}

function Weight({ bytes }: { bytes: number }) {
  const over = bytes > BUDGET_BYTES;
  return (
    <span className={over ? "text-xs font-medium text-amber-700" : "text-xs text-stone-500"}>
      {(bytes / 1024).toFixed(0)} KB
      {over && " — over budget, the invitation will feel slow on 3G"}
    </span>
  );
}

/**
 * The card at phone width, rendered exactly as a guest gets it (task 8.4).
 *
 * An iframe, and specifically a `srcdoc` iframe with no `allow-same-origin`. Two reasons,
 * and the second is the one that matters: it gives the card a real 375px viewport so its
 * media queries resolve the way they will on a phone, and it keeps an admin-side preview
 * from being able to touch the admin page around it. Previewing a design must not be a way
 * to act on the session of the person previewing it.
 *
 * This renders a draft that has not been published, so unlike the guest page it cannot rely
 * on the document already being live — but it is the same stored, sanitised bytes.
 */
function PhonePreview({ design }: { design: CardDesign }) {
  const document = typeof design.config?.document === "string" ? design.config.document : "";
  if (!document) {
    return <Empty>This design has no document to preview.</Empty>;
  }

  const srcDoc =
    `<!doctype html><meta charset="utf-8">` +
    `<meta name="viewport" content="width=device-width,initial-scale=1">` +
    `<style>html,body{margin:0;padding:12px;font-family:system-ui,sans-serif}</style>` +
    document;

  return (
    <div className="mx-auto w-[375px] max-w-full overflow-hidden rounded-[2rem] border-8 border-stone-800 bg-white">
      <iframe
        title="Card preview"
        srcDoc={srcDoc}
        sandbox=""
        className="block h-[600px] w-full"
      />
    </div>
  );
}
