// registration-app.jsx — orchestrator: state, navigation, transitions,
// validation, submit.

// ── CSS scoped to the registration root ────────────────────────────────
if (typeof document !== "undefined" && !document.getElementById("reg-app-css")) {
  const s = document.createElement("style");
  s.id = "reg-app-css";
  s.textContent = `
    :root { color-scheme: dark; }
    body { background:#0d0b09; color:#f1ecdd; }
    .reg-root {
      --bg:#0d0b09;
      --fg:#f1ecdd;
      --rule: rgba(241,236,221,0.14);
      --mono: "freight-sans-pro", ui-sans-serif, system-ui, sans-serif;
      --font-body: "minion-3", "freight-text-pro", Iowan Old Style, Georgia, serif;
      --font-display: "minion-3", "freight-text-pro", Iowan Old Style, Georgia, serif;
      min-height: 100vh;
      display: flex; flex-direction: column;
      font-family: var(--font-body);
    }
    .reg-root * {
      min-width: 0;
    }

    /* Top bar */
    .reg-top {
      display: flex; align-items: center; justify-content: space-between;
      padding: 22px clamp(24px, 4vw, 56px);
      border-bottom: 1px solid var(--rule);
      font-family: var(--mono); font-size: 10.5px;
      letter-spacing: .18em; text-transform: uppercase;
      color: rgba(243,239,233,0.65);
    }
    .reg-mark { color: #f3efe9; font-weight: 500; }
    .reg-progress-wrap {
      display: flex; align-items: center; gap: 16px;
      flex: 1; justify-content: center;
    }
    .reg-progress-bar {
      flex: 0 1 320px; height: 1px;
      background: rgba(243,239,233,0.14);
      position: relative; overflow: hidden;
    }
    .reg-progress-fill {
      position: absolute; left: 0; top: 0; bottom: 0;
      background: #f3efe9;
      transition: width .45s cubic-bezier(.16,1,.3,1);
    }
    .reg-progress-text {
      font-variant-numeric: tabular-nums;
      color: rgba(243,239,233,0.78);
    }
    .reg-exit {
      appearance: none; border: 0; background: transparent;
      color: rgba(243,239,233,0.55); cursor: pointer;
      font: inherit; letter-spacing: inherit; text-transform: inherit;
      padding: 4px 8px;
    }
    .reg-exit:hover { color: #f3efe9; }
    .reg-exit:focus-visible,
    .reg-btn:focus-visible {
      outline: 1px solid #f3efe9;
      outline-offset: 4px;
    }

    /* Stage */
    .reg-stage {
      flex: 1; position: relative;
      display: flex; align-items: center; justify-content: center;
      padding: clamp(48px, 6vw, 96px) clamp(24px, 4vw, 56px);
      overflow: hidden;
    }
    .reg-step {
      width: 100%; max-width: 880px;
      display: flex; flex-direction: column; align-items: stretch;
      opacity: 1;
      transition: opacity .35s, transform .45s cubic-bezier(.4,0,.2,1);
    }
    .reg-step.enter-forward { opacity: 0; transform: translateX(60px); }
    .reg-step.enter-back    { opacity: 0; transform: translateX(-60px); }
    .reg-step.idle          { opacity: 1; transform: none; }
    .reg-step.exit-forward  { opacity: 0; transform: translateX(-60px); }
    .reg-step.exit-back     { opacity: 0; transform: translateX(60px); }

    /* For Welcome / Done steps — center align */
    .reg-stage.centered .reg-step {
      align-items: center;
    }

    /* Bottom nav */
    .reg-nav {
      display: flex; align-items: center; justify-content: space-between;
      padding: 20px clamp(24px, 4vw, 56px);
      border-top: 1px solid var(--rule);
      gap: 12px;
    }

    /* Buttons */
    .reg-btn {
      appearance: none; cursor: pointer;
      font-family: var(--font-body); font-weight: 500;
      font-size: 14px; letter-spacing: .02em;
      padding: 13px 22px; border-radius: 999px;
      display: inline-flex; align-items: center; gap: 10px;
      transition: background .15s, color .15s, transform .12s, opacity .15s;
      border: 1px solid transparent;
    }
    .reg-btn:active { transform: translateY(1px); }
    .reg-btn:disabled { opacity: 0.4; cursor: not-allowed; }
    .reg-btn .arrow { transition: transform .18s; }
    .reg-btn.primary {
      background: #f3efe9; color: #0c0c0c;
    }
    .reg-btn.primary:hover:not(:disabled) {
      background: #fff;
    }
    .reg-btn.primary:hover:not(:disabled) .arrow {
      transform: translateX(4px);
    }
    .reg-btn.ghost {
      background: transparent; color: rgba(243,239,233,0.78);
      border-color: rgba(243,239,233,0.18);
    }
    .reg-btn.ghost:hover:not(:disabled) {
      color: #f3efe9; border-color: rgba(243,239,233,0.45);
    }

    /* Toast (form error summary) */
    .reg-toast {
      position: fixed; left: 50%; bottom: 92px; transform: translateX(-50%);
      background: #2a221a; color: #ffe8b3;
      border: 1px solid rgba(231,117,76,0.5);
      padding: 12px 18px; border-radius: 999px;
      font-family: var(--mono); font-size: 11px; letter-spacing: .14em;
      text-transform: uppercase;
      box-shadow: 0 8px 32px rgba(0,0,0,0.45);
      pointer-events: none;
      z-index: 100;
      opacity: 0; transition: opacity .25s, transform .25s;
    }
    .reg-toast[data-show="1"] {
      opacity: 1; transform: translateX(-50%) translateY(0);
    }
    .reg-review-grid {
      width: 100%;
    }
    .reg-review-row span:last-child {
      overflow-wrap: anywhere;
    }
    .reg-submit-progress {
      margin-top: 18px;
      display: grid;
      gap: 10px;
      padding: 14px 16px;
      border: 1px solid rgba(243,239,233,0.14);
      background: rgba(243,239,233,0.045);
      font-family: var(--mono);
      font-size: 10.5px;
      letter-spacing: .16em;
      text-transform: uppercase;
      color: rgba(243,239,233,0.72);
    }
    .reg-submit-progress__top {
      display: flex;
      justify-content: space-between;
      gap: 18px;
    }
    .reg-submit-progress__bar {
      height: 2px;
      background: rgba(243,239,233,0.14);
      overflow: hidden;
    }
    .reg-submit-progress__bar span {
      display: block;
      height: 100%;
      background: #f3efe9;
      transition: width .35s cubic-bezier(.16,1,.3,1);
    }
    @media (max-width: 760px) {
      .reg-top {
        position: sticky;
        top: 0;
        z-index: 20;
        grid-template-columns: 1fr;
        display: grid;
        gap: 14px;
        align-items: start;
        background: rgba(12,12,12,0.92);
        backdrop-filter: blur(14px);
        -webkit-backdrop-filter: blur(14px);
      }
      .reg-progress-wrap {
        width: 100%;
        justify-content: stretch;
      }
      .reg-progress-bar {
        flex: 1 1 auto;
      }
      .reg-exit {
        justify-self: start;
        padding-left: 0;
      }
      .reg-stage {
        align-items: flex-start;
        padding: 40px clamp(18px, 5vw, 28px);
      }
      .reg-nav {
        position: sticky;
        bottom: 0;
        background: rgba(12,12,12,0.94);
        backdrop-filter: blur(14px);
        -webkit-backdrop-filter: blur(14px);
      }
      .reg-review-grid {
        grid-template-columns: 1fr;
      }
      .reg-review-row {
        grid-template-columns: minmax(92px, 34%) 1fr;
      }
      .reg-btn {
        justify-content: center;
      }
    }
  `;
  document.head.appendChild(s);
}

// ── State persistence ──────────────────────────────────────────────────
const LS_KEY = "model-registration.v1";

const DEFAULT_DATA = {
  firstName: "",
  lastName: "",
  alias: "",
  dob: "",
  email: "",
  phone: "",
  heightCm: "",
  cupSize: "",
  bandSize: "",
  breasts: "",
  dressSize: "",
  shoeSize: "",
  hairColor: "",
  hairLength: "",
  eyeColor: "",
  tattoos: { has: false, detail: "" },
  piercings: { has: false, detail: "" },
  nationality: "",
  basedInCity: "",
  basedInArea: "",
  basedInCountry: "",
  languages: [],
  availability: "",
  travel: "",
  services: [],
  rates: {
    incall: { "1h": "", "2h": "", "3h": "", overnight: "" },
    outcall: { "1h": "", "2h": "", "3h": "", overnight: "" },
  },
  bio: "",
  personality: "",
  media: [],
};

function loadSaved() {
  try {
    const raw = localStorage.getItem(LS_KEY);
    if (!raw) return DEFAULT_DATA;
    const parsed = JSON.parse(raw);
    // Media isn't persistable (object URLs die on reload) — drop it.
    return { ...DEFAULT_DATA, ...parsed, media: [] };
  } catch { return DEFAULT_DATA; }
}
function saveDraft(data) {
  try {
    const { media, ...rest } = data;
    localStorage.setItem(LS_KEY, JSON.stringify(rest));
  } catch {}
}

function normalizeProfileText(value) {
  return String(value || "")
    .replace(/\r\n?/g, "\n")
    .split(/\n{2,}/)
    .map((paragraph) =>
      paragraph
        .replace(/[ \t]+\n/g, "\n")
        .replace(/\n/g, " ")
        .replace(/[ \t]{2,}/g, " ")
        .trim()
    )
    .filter(Boolean)
    .join("\n\n");
}

function cleanRateInput(value) {
  return String(value || "").replace(/[^\d]/g, "").slice(0, 6);
}

function rateEntries(data) {
  const rates = data?.rates || {};
  return (window.REG_RATE_ROWS || []).flatMap((row) => [
    { row, side: "incall", value: cleanRateInput(rates.incall?.[row.key]) },
    { row, side: "outcall", value: cleanRateInput(rates.outcall?.[row.key]) },
  ]);
}

function hasAnyRate(data) {
  return rateEntries(data).some((entry) => Number(entry.value) > 0);
}

function validateRates(data) {
  if (!hasAnyRate(data)) return "Add at least one rate.";

  const invalid = rateEntries(data).find((entry) => {
    if (!entry.value) return false;
    const number = Number(entry.value);
    return !Number.isInteger(number) || number < 50 || number > 50000;
  });
  return invalid ? "Rates must be whole GBP amounts between 50 and 50000." : "";
}

function calculateAgeFromIsoDate(value, now = new Date()) {
  const [year, month, day] = String(value || "").split("-").map(Number);
  let age = now.getUTCFullYear() - year;
  const monthDelta = now.getUTCMonth() + 1 - month;
  const dayDelta = now.getUTCDate() - day;
  if (monthDelta < 0 || (monthDelta === 0 && dayDelta < 0)) age -= 1;
  return age;
}

function validateDob(value, now = new Date()) {
  if (!value) return "";
  const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(String(value));
  if (!match) return "Choose a complete date of birth.";

  const year = Number(match[1]);
  const month = Number(match[2]);
  const day = Number(match[3]);
  const date = new Date(Date.UTC(year, month - 1, day));
  const valid =
    date.getUTCFullYear() === year &&
    date.getUTCMonth() + 1 === month &&
    date.getUTCDate() === day;

  if (!valid) return "Date of birth is not a real date.";
  return calculateAgeFromIsoDate(value, now) < 18 ? "Applicant must be 18 or older." : "";
}

function money(value) {
  const number = Number(cleanRateInput(value));
  return number > 0 ? `£${number.toLocaleString("en-GB")}` : "";
}

function priceRowsFromRates(data) {
  return (window.REG_RATE_ROWS || [])
    .map((row) => {
      const incall = money(data.rates?.incall?.[row.key]);
      const outcall = money(data.rates?.outcall?.[row.key]);
      return { key: row.key, label: row.label, incall, outcall };
    })
    .filter((row) => row.incall || row.outcall);
}

function priceFromRates(data) {
  const values = rateEntries(data)
    .map((entry) => Number(entry.value))
    .filter((value) => Number.isFinite(value) && value > 0);
  return values.length ? `From ${money(Math.min(...values))}` : "";
}

if (typeof window !== "undefined") {
  window.normalizeProfileText = normalizeProfileText;
}

// ── Validation ─────────────────────────────────────────────────────────
function validateStep(stepIdx, data) {
  const R = window.REG_REQUIRED;
  const errors = {};
  const required = (keys) => keys.forEach((k) => {
    const v = data[k];
    const empty = v === "" || v === undefined || v === null
              || (Array.isArray(v) && v.length === 0);
    if (empty) errors[k] = "Please fill this in";
  });
  switch (stepIdx) {
    case 1: {
      required(R.basics);
      const dobError = validateDob(data.dob);
      if (dobError) errors.dob = dobError;
      // Email shape sanity — full validation is server-side.
      if (data.email && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(data.email))
        errors.email = "Please double-check that email address.";
      // Phone needs to start with + and have at least 8 digits total.
      if (data.phone && !/^\+\d[\d\s\-()]{7,}$/.test(data.phone))
        errors.phone = "Include the country code, e.g. +44 7700 900000.";
      break;
    }
    case 2: required(R.measurements); break;
    case 3: required(R.appearance);   break;
    case 4: required(R.origins);      break;
    case 5: {
      required(R.work);
      const rateError = validateRates(data);
      if (rateError) errors.rates = rateError;
      break;
    }
    case 6: {
      required(R.about);
      // Short-form length floors so the profile doesn't show one-word bios.
      if (data.bio && data.bio.trim().length < 60) errors.bio = "A little longer please — at least a couple of sentences.";
      if (data.personality && data.personality.trim().length < 40) errors.personality = "A touch more — at least one full thought.";
      break;
    }
    case 7: {
      const n = (data.media || []).length;
      if (n < window.REG_MIN_MEDIA) errors.media = `At least ${window.REG_MIN_MEDIA} files needed. You have ${n}.`;
      const video = (data.media || []).find((item) => item.kind === "video" || item.file?.type?.startsWith("video/"));
      if (video) errors.media = "Video opens later on a separate secure lane. Please submit images only.";
      break;
    }
    default: break;
  }
  return errors;
}

// ── Submit → emit profile-data.js shape ────────────────────────────────
function emitProfileDataFile(data) {
  const F = window.REG_FORMATTERS;
  const age    = F.age(data.dob);
  const cmH    = data.heightCm ? `${data.heightCm} cm` : "";
  const ftIn   = data.heightCm
    ? (() => { const t = data.heightCm/2.54; const ft = Math.floor(t/12); const i = Math.round(t - ft*12); return `${ft} ft ${i} in`; })()
    : "";
  const marks  = (() => {
    if (data.tattoos?.has || data.piercings?.has) {
      const parts = [];
      if (data.tattoos?.has)   parts.push(data.tattoos.detail ? `Tattoos: ${data.tattoos.detail}` : "Has tattoos");
      if (data.piercings?.has) parts.push(data.piercings.detail || "Piercings");
      return { v: data.tattoos?.has ? "Tattoos" : "Piercings", sub: parts.join(" · ") };
    }
    return { v: "No tattoos", sub: "no piercings" };
  })();

  const stats = [
    { k: "Age",         v: age,                                 sub: data.dob ? `b. ${new Date(data.dob).getFullYear()}` : "" },
    { k: "Height",      v: cmH,                                 sub: ftIn },
    { k: "Cup size",    v: data.cupSize,                         sub: data.bandSize },
    { k: "Breasts",     v: data.breasts },
    { k: "Dress",       v: data.dressSize ? `EU ${data.dressSize}` : "" },
    { k: "Shoe",        v: data.shoeSize ? `EU ${data.shoeSize}` : "" },
    { k: "Hair",        v: data.hairColor,                       sub: data.hairLength },
    { k: "Eyes",        v: data.eyeColor },
    { k: "Nationality", v: data.nationality },
    { k: "Based in",    v: [data.basedInArea, data.basedInCity, data.basedInCountry].filter(Boolean).join(", ") },
    { k: "Languages",   v: (data.languages || []).slice(0,3).map((l) => ({English:"EN",French:"FR",Italian:"IT",Spanish:"ES",German:"DE",Portuguese:"PT",Russian:"RU",Japanese:"JP",Mandarin:"ZH",Korean:"KO",Arabic:"AR"})[l] || l.slice(0,2).toUpperCase()).join(" · "),
                                                                 sub: (data.languages || []).join(", ") },
    { k: "Marks",       v: marks.v, sub: marks.sub },
    { k: "Availability", v: data.availability },
    { k: "Travel",      v: data.travel },
    { k: "Services",    v: (data.services || []).join(", ") },
  ];

  const media = (data.media || []).map((m, idx) => {
    const out = {
      i: idx + 1,
      src: `assets/${String(idx+1).padStart(2,"0")}.${m.kind === "video" ? "mp4" : "jpg"}`,
      w: m.w, h: m.h,
    };
    if (m.kind === "video") {
      out.kind = "video";
      out.poster = `assets/${String(idx+1).padStart(2,"0")}.jpg`;
    }
    return out;
  });

  const payload = {
    firstName: data.firstName,
    lastName: data.lastName,
    agency: "ELARA",
    city: data.basedInCity,
    area: data.basedInArea,
    stats,
    bio: normalizeProfileText(data.bio),
    personality: normalizeProfileText(data.personality),
    availability: { notice: data.availability, travel: data.travel },
    services: data.services || [],
    prices: priceRowsFromRates(data),
    priceFrom: priceFromRates(data),
    media,
  };

  const body = JSON.stringify(payload, null, 2);
  return `// Generated by the registration form on ${new Date().toISOString().slice(0,10)}.\n// Drop the applicant's photos into ./assets/ matching the src paths below.\n\nwindow.MODEL = ${body};\n`;
}

function downloadFile(filename, contents, mime = "text/plain") {
  const blob = new Blob([contents], { type: mime });
  const url = URL.createObjectURL(blob);
  const a = document.createElement("a");
  a.href = url; a.download = filename; a.click();
  setTimeout(() => URL.revokeObjectURL(url), 1000);
}

// ── Step registry ──────────────────────────────────────────────────────
// Each entry: { key, component, isForm } — isForm means we run validation
// on Next and show the progress bar.
const STEPS = [
  { key: "welcome",      isForm: false },
  { key: "basics",       isForm: true  },
  { key: "measurements", isForm: true  },
  { key: "appearance",   isForm: true  },
  { key: "origins",      isForm: true  },
  { key: "work",         isForm: true  },
  { key: "words",        isForm: true  },
  { key: "portfolio",    isForm: true  },
  { key: "review",       isForm: false },
  { key: "done",         isForm: false },
];

function App() {
  const [step, setStep] = React.useState(0);
  const [data, setData] = React.useState(() => loadSaved());
  const [errors, setErrors] = React.useState({});
  const [direction, setDirection] = React.useState(1);
  const [submitting, setSubmitting] = React.useState(false);
  const [uploadProgress, setUploadProgress] = React.useState(null);
  const [toast, setToast] = React.useState("");
  const toastTimerRef = React.useRef(null);
  // For mounting the slide-in animation each step.
  const [stepKey, setStepKey] = React.useState(0);

  React.useEffect(() => { saveDraft(data); }, [data]);
  React.useEffect(() => () => {
    if (toastTimerRef.current) window.clearTimeout(toastTimerRef.current);
  }, []);

  const clearToast = () => {
    if (toastTimerRef.current) {
      window.clearTimeout(toastTimerRef.current);
      toastTimerRef.current = null;
    }
    setToast("");
  };

  const showToast = (message, duration) => {
    clearToast();
    setToast(message);
    toastTimerRef.current = window.setTimeout(() => {
      setToast("");
      toastTimerRef.current = null;
    }, duration);
  };

  const update = (k, v) => {
    setData((d) => ({ ...d, [k]: v }));
    // Clear the error for this field as the user edits.
    if (errors[k]) setErrors((e) => { const n = { ...e }; delete n[k]; return n; });
  };

  const goTo = (idx, dir = 1) => {
    if (idx < 0 || idx >= STEPS.length) return;
    setDirection(dir);
    setStep(idx);
    setStepKey((k) => k + 1);
    setErrors({});
  };

  const next = () => {
    const errs = validateStep(step, data);
    if (Object.keys(errs).length) {
      setErrors(errs);
      showToast("Please complete the highlighted fields", 2500);
      return;
    }
    goTo(step + 1, 1);
  };
  const back = () => goTo(step - 1, -1);

  const submit = async () => {
    setSubmitting(true);
    setUploadProgress({
      label: "Preparing secure upload",
      done: 0,
      total: Math.max(1, (data.media || []).length),
      percent: 4,
    });
    clearToast();
    try {
      if (!window.ELARA_REGISTRATION_SUBMIT) {
        throw new Error("Registration service is not ready yet.");
      }
      const submitData = {
        ...data,
        bio: normalizeProfileText(data.bio),
        personality: normalizeProfileText(data.personality),
      };
      const result = await window.ELARA_REGISTRATION_SUBMIT(submitData, {
        onProgress(progress) {
          const total = Math.max(1, progress.total || (data.media || []).length);
          const done = Math.min(total, progress.done || 0);
          const phaseOffset = progress.stage === "submitting" ? 92 : progress.stage === "complete" ? 100 : 8;
          const uploadPct = total ? (done / total) * 84 : 0;
          setUploadProgress({
            label: progress.label || "Uploading securely",
            done,
            total,
            percent: Math.max(4, Math.min(100, phaseOffset + uploadPct)),
          });
        },
      });
      const serverMedia = Array.isArray(result?.previewMedia) ? result.previewMedia : [];
      if (serverMedia.length) {
        setData({
          ...submitData,
          media: serverMedia.map((item, index) => ({
            i: index + 1,
            kind: item.kind || "image",
            provider: item.provider,
            providerAssetId: item.providerAssetId,
            src: item.previewUrl || item.url || item.urls?.public || "",
            urls: item.urls,
            w: item.width || item.w,
            h: item.height || item.h,
            aspectRatio: item.width && item.height ? item.width / item.height : item.aspectRatio,
            alt: item.alt,
          })),
        });
      }
      try { localStorage.removeItem(LS_KEY); } catch {}
      setSubmitting(false);
      setUploadProgress(null);
      goTo(STEPS.findIndex((s) => s.key === "done"), 1);
    } catch (err) {
      setSubmitting(false);
      setUploadProgress(null);
      showToast(err?.message || "Submission failed. Please try again.", 5000);
    }
  };

  const downloadDataFile = () => {
    const content = emitProfileDataFile(data);
    downloadFile("profile-data.js", content, "application/javascript");
  };

  const current = STEPS[step];
  const formIdx = STEPS.filter((s) => s.isForm).findIndex((s) => s.key === current.key);
  const formTotal = STEPS.filter((s) => s.isForm).length;
  const showProgress = current.isForm || current.key === "review";
  const progressIdx = current.isForm ? formIdx + 1 : formTotal;
  const progressPct = (progressIdx / formTotal) * 100;

  const isCentered = current.key === "welcome" || current.key === "done";

  return (
    <div className="reg-root">
      <header className="reg-top">
        <span className="reg-mark">ELARA</span>
        {showProgress ? (
          <div className="reg-progress-wrap">
            <span className="reg-progress-text">
              Step {String(progressIdx).padStart(2,"0")} / {String(formTotal).padStart(2,"0")}
            </span>
            <div className="reg-progress-bar">
              <div className="reg-progress-fill" style={{ width: `${progressPct}%` }}/>
            </div>
          </div>
        ) : (
          <span style={{ flex: 1, textAlign: "center" }}>
            {current.key === "welcome" ? "Talent application" : "Submission complete"}
          </span>
        )}
        {current.key !== "done" && (
          <button className="reg-exit" onClick={() => {
            if (confirm("Discard this draft and start over?")) {
              try { localStorage.removeItem(LS_KEY); } catch {}
              setData(DEFAULT_DATA);
              goTo(0, -1);
            }
          }}>Reset</button>
        )}
      </header>

      <main className={"reg-stage" + (isCentered ? " centered" : "")}>
        <StepView
          key={stepKey}
          stepKey={current.key}
          direction={direction}
          data={data}
          update={update}
          errors={errors}
          onStart={() => goTo(1, 1)}
          onEditStep={(idx) => goTo(idx, -1)}
          onSubmit={submit}
          submitting={submitting}
          uploadProgress={uploadProgress}
          onDownloadData={downloadDataFile}
        />
      </main>

      {current.key !== "welcome" && current.key !== "done" && current.key !== "review" && (
        <nav className="reg-nav">
          <button className="reg-btn ghost" onClick={back}>← Back</button>
          <button className="reg-btn primary" onClick={next}>
            Continue <span className="arrow">→</span>
          </button>
        </nav>
      )}

      <div className="reg-toast" data-show={toast ? "1" : "0"} role="status" aria-live="polite">{toast}</div>
    </div>
  );
}

// StepView — wraps content with the slide animation lifecycle.
function StepView({ stepKey, direction, data, update, errors, onStart, onEditStep, onSubmit, submitting, uploadProgress, onDownloadData }) {
  const [phase, setPhase] = React.useState(direction > 0 ? "enter-forward" : "enter-back");
  React.useEffect(() => {
    const t = requestAnimationFrame(() => setPhase("idle"));
    return () => cancelAnimationFrame(t);
  }, []);

  let content;
  switch (stepKey) {
    case "welcome":      content = <StepWelcome onStart={onStart}/>; break;
    case "basics":       content = <StepBasics       data={data} update={update} errors={errors}/>; break;
    case "measurements": content = <StepMeasurements data={data} update={update} errors={errors}/>; break;
    case "appearance":   content = <StepAppearance   data={data} update={update} errors={errors}/>; break;
    case "origins":      content = <StepOrigins      data={data} update={update} errors={errors}/>; break;
    case "work":         content = <StepWork         data={data} update={update} errors={errors}/>; break;
    case "words":        content = <StepWords        data={data} update={update} errors={errors}/>; break;
    case "portfolio":    content = <StepPortfolio    data={data} update={update} errors={errors}/>; break;
    case "review":       content = <StepReview       data={data} onEditStep={onEditStep} onSubmit={onSubmit} submitting={submitting} uploadProgress={uploadProgress}/>; break;
    case "done":         content = <StepDone         data={data} onDownloadData={onDownloadData}/>; break;
    default:             content = null;
  }

  return <div className={"reg-step " + phase}>{content}</div>;
}

ReactDOM.createRoot(document.getElementById("root")).render(<App/>);
