import { forwardRef, useEffect, useRef, useState } from "react";
import {
  Eye,
  UploadCloud,
  CheckCircle2,
  AlertTriangle,
  Sparkles,
  ExternalLink,
} from "lucide-react";

export type PublishState = "idle" | "loading" | "success" | "error";

/**
 * Stage events emitted by the backend during publish. Kept in sync with
 * `PublishStage` in `backend/src/publisher/index.ts` and the sub-stages
 * emitted by `pdf-generator.ts`. We accept `string` so an unknown stage
 * never breaks the UI.
 */
export type PublishStage =
  | "extract"
  | "bibliography"
  | "render"
  | "load"
  | "thumbnail"
  | "pdf-setup"
  | "pdf-toc"
  | "rasterize"
  | "pdf-render"
  | "write"
  | "upload"
  | string;

export interface PublishStageEvent {
  stage: PublishStage;
  detail?: {
    index?: number;
    total?: number;
    entries?: number;
  };
}

interface Props {
  state: PublishState;
  error: string;
  docName: string;
  stageEvent: PublishStageEvent | null;
  onClose: () => void;
  onPublish: () => void;
}

const STAGE_LABELS: Record<string, string> = {
  extract: "Extracting content",
  bibliography: "Formatting references",
  render: "Rendering HTML",
  load: "Loading article",
  thumbnail: "Capturing thumbnail",
  "pdf-setup": "Preparing PDF layout",
  "pdf-toc": "Building table of contents",
  rasterize: "Rasterizing embeds",
  "pdf-render": "Rendering PDF",
  write: "Writing files",
  upload: "Uploading to storage",
};

// Canonical order used to compute a progress ratio. Stages not in
// this list still render their label but don't retract the bar.
const STAGE_ORDER: PublishStage[] = [
  "extract",
  "bibliography",
  "render",
  "load",
  "thumbnail",
  "pdf-setup",
  "pdf-toc",
  "rasterize",
  "pdf-render",
  "write",
  "upload",
];

function formatStage(event: PublishStageEvent | null): string {
  if (!event) return "Publishing...";
  const base = STAGE_LABELS[event.stage] ?? "Publishing...";
  if (event.stage === "rasterize" && event.detail?.index && event.detail?.total) {
    return `${base} (${event.detail.index}/${event.detail.total})`;
  }
  return base;
}

/**
 * Compute a 0..1 progress ratio for `event`, blending rasterize
 * sub-progress. Returns `null` for unknown stages so the caller can
 * hold the previous value instead of retracting the bar.
 */
function computeProgress(event: PublishStageEvent | null): number | null {
  if (!event) return 0.04;
  const idx = STAGE_ORDER.indexOf(event.stage);
  if (idx === -1) return null;
  const slots = STAGE_ORDER.length + 1;
  let base = (idx + 1) / slots;
  if (
    event.stage === "rasterize" &&
    event.detail?.index &&
    event.detail?.total
  ) {
    const rasterFrac = Math.min(1, event.detail.index / event.detail.total);
    base = idx / slots + rasterFrac / slots;
  }
  return Math.max(0.04, Math.min(0.98, base));
}

/**
 * Native `<dialog>` shown when the user clicks "Publish" in the top bar.
 * Displays live stage progress during the long PDF pipeline (typically
 * 15-25s) thanks to the Server-Sent Events stream from `/api/publish/stream`.
 */
export const PublishDialog = forwardRef<HTMLDialogElement, Props>(function PublishDialog(
  { state, error, docName, stageEvent, onClose, onPublish },
  ref
) {
  const stageLabel = formatStage(stageEvent);

  // Monotonic progress. The backend can emit unknown stages or
  // re-emit earlier ones during retries; the bar should never walk
  // backwards on camera.
  const progressRef = useRef<number>(0.04);
  const [progress, setProgress] = useState<number>(0.04);
  useEffect(() => {
    if (state === "idle") {
      progressRef.current = 0.04;
      setProgress(0.04);
      return;
    }
    if (state === "success") {
      progressRef.current = 1;
      setProgress(1);
      return;
    }
    if (state === "error") return;
    const next = computeProgress(stageEvent);
    if (next == null) return;
    if (next > progressRef.current) {
      progressRef.current = next;
      setProgress(next);
    }
  }, [stageEvent, state]);

  // Readers land on the Space root: when a published article exists,
  // `backend/src/create-app.ts` (`app.get("*")`) serves it at `/`. The
  // `/published/<docName>/...` path is an implementation detail used by
  // the publisher pipeline (PDF generation, og:image, ...), not the
  // canonical reader URL. So we surface the Space URL itself.
  const origin = typeof window !== "undefined" ? window.location.origin : "";
  const publishedUrl = origin ? `${origin}/` : "/";
  const displayUrl = origin || "/";

  return (
    <dialog
      ref={ref}
      className={`ed-dialog ed-dialog--publish ed-dialog--publish-${state}`}
      onClose={onClose}
    >
      <header className="publish-dialog__header">
        <div className={`publish-dialog__icon publish-dialog__icon--${state}`}>
          {state === "idle" && <UploadCloud size={22} strokeWidth={1.75} />}
          {state === "loading" && <span className="spinner" aria-hidden />}
          {state === "success" && <CheckCircle2 size={22} strokeWidth={1.75} />}
          {state === "error" && <AlertTriangle size={22} strokeWidth={1.75} />}
        </div>
        <div className="publish-dialog__heading">
          <h2 className="publish-dialog__title">
            {state === "idle" && "Publish article"}
            {state === "loading" && "Publishing your article"}
            {state === "success" && "Published!"}
            {state === "error" && "Publish failed"}
          </h2>
          <p className="publish-dialog__subtitle">
            {state === "idle" && "Static HTML, print-ready PDF and thumbnail."}
            {state === "loading" && stageLabel}
            {state === "success" && "Your update is live for every reader."}
            {state === "error" && "We couldn't finish the publish run."}
          </p>
        </div>
      </header>

      <div className="publish-dialog__body">
        {state === "idle" && (
          <div className="publish-dialog__intro">
            <ul className="publish-dialog__checklist">
              <li>
                <span className="publish-dialog__bullet" aria-hidden />
                <span>
                  <strong>Static HTML</strong> page ready to share.
                </span>
              </li>
              <li>
                <span className="publish-dialog__bullet" aria-hidden />
                <span>
                  <strong>Print-ready PDF</strong> with full typography.
                </span>
              </li>
              <li>
                <span className="publish-dialog__bullet" aria-hidden />
                <span>
                  <strong>Social thumbnail</strong> captured automatically.
                </span>
              </li>
            </ul>
            <p className="publish-dialog__note">
              <Sparkles size={12} strokeWidth={2} aria-hidden />
              Takes 15-25s. Collaborators can keep editing in the meantime.
            </p>
          </div>
        )}

        {state === "loading" && (
          <div className="publish-dialog__progress">
            <div
              className="publish-dialog__bar"
              role="progressbar"
              aria-valuemin={0}
              aria-valuemax={100}
              aria-valuenow={Math.round(progress * 100)}
            >
              <span
                className="publish-dialog__bar-fill"
                style={{ width: `${Math.round(progress * 100)}%` }}
              />
            </div>
            <p className="publish-dialog__hint">
              Rasterizing embeds and rendering the PDF can take a moment. Keep
              this tab open until it finishes.
            </p>
          </div>
        )}

        {state === "success" && (
          <div className="publish-dialog__success">
            <div className="publish-dialog__success-url">
              <span className="publish-dialog__url-label">Live at</span>
              <code className="publish-dialog__url-code">{displayUrl}</code>
            </div>
            <a
              href={publishedUrl}
              target="_blank"
              rel="noopener noreferrer"
              className="btn btn--primary publish-dialog__view-btn"
            >
              <Eye size={16} />
              View published article
              <ExternalLink size={13} strokeWidth={2} aria-hidden />
            </a>
          </div>
        )}

        {state === "error" && (
          <p className="publish-dialog__error">
            {error || "An error occurred while publishing."}
          </p>
        )}
      </div>

      <footer className="publish-dialog__actions">
        {state === "idle" && (
          <>
            <button className="btn" onClick={onClose}>Cancel</button>
            <button className="btn btn--primary" onClick={onPublish}>
              <UploadCloud size={14} strokeWidth={2} aria-hidden />
              Publish
            </button>
          </>
        )}
        {(state === "success" || state === "error") && (
          <button className="btn" onClick={onClose}>Close</button>
        )}
      </footer>
    </dialog>
  );
});
