// ThaiHao registration — root App.
// State, navigation, draft persistence, validation, submit.

const SUBMIT_ENDPOINT = "PLACEHOLDER_APPS_SCRIPT_URL";
const HMAC_HEADER = "x-thaihao-sig";
const DRAFT_KEY = "thaihao_register_draft_v1";

const STEPS = [
  { key: "company",   labelKey: "step_company"   },
  { key: "contact",   labelKey: "step_contact"   },
  { key: "documents", labelKey: "step_documents" },
  { key: "products",  labelKey: "step_products"  },
  { key: "readiness", labelKey: "step_readiness" },
  { key: "consent",   labelKey: "step_consent"   },
  { key: "review",    labelKey: "step_review"    },
];

const initialData = () => ({
  // Step 1
  company_th: "", company_en: "", company_cn: "",
  tax_id: "", biz_type: "",
  address: "", province: "", postcode: "",
  year: "", employees: "",
  website: "", facebook: "", lineoa: "",

  // Step 2
  contact_name: "", contact_position: "",
  contact_phone: "", contact_email: "",
  contact_line: "", contact_wechat: "",

  // Step 3
  doc_cert: [], doc_logo: [], doc_id: [], doc_licenses: [],

  // Step 4
  products: [{ id: "p1", certs: [], images: [], pkg_lang: [] }],

  // Step 5
  exported: "", exported_channel: "",
  nmpa: "", china_packaging: "", kol_ready: "",
  volume: "", role: "",

  // Step 6
  pdpa_consent: false, pipl_consent: false,
  signature: "",
});

// ===== Validation per step =====
function validateStep(idx, data) {
  const errs = {};
  if (idx === 0) {
    if (!data.company_th) errs.company_th = "required";
    if (!data.tax_id) errs.tax_id = "required";
    else if (Validators.tax13(data.tax_id)) errs.tax_id = "tax";
    if (!data.biz_type) errs.biz_type = "required";
    if (!data.address) errs.address = "required";
    if (!data.province) errs.province = "required";
    if (data.website && Validators.url(data.website)) errs.website = "url";
    if (data.year && Validators.year(data.year)) errs.year = "year";
  }
  if (idx === 1) {
    if (!data.contact_name) errs.contact_name = "required";
    if (!data.contact_position) errs.contact_position = "required";
    if (!data.contact_phone) errs.contact_phone = "required";
    else if (Validators.phoneTH(data.contact_phone)) errs.contact_phone = "phone";
    if (!data.contact_email) errs.contact_email = "required";
    else if (Validators.email(data.contact_email)) errs.contact_email = "email";
  }
  if (idx === 2) {
    if (!data.doc_cert || data.doc_cert.length === 0) errs.doc_cert = "required";
    if (!data.doc_logo || data.doc_logo.length === 0) errs.doc_logo = "required";
    if (!data.doc_id || data.doc_id.length === 0) errs.doc_id = "required";
  }
  if (idx === 3) {
    const prodErrs = [];
    let any = false;
    (data.products || []).forEach((p, i) => {
      const pe = {};
      if (!p.name_th) pe.name_th = "required";
      if (!p.category) pe.category = "required";
      if (Object.keys(pe).length) any = true;
      prodErrs[i] = pe;
    });
    if (any) errs.products = prodErrs;
    if (!data.products || data.products.length === 0) errs._step = "required";
  }
  if (idx === 4) {
    if (!data.exported) errs.exported = "required";
    if (!data.nmpa) errs.nmpa = "required";
    if (!data.china_packaging) errs.china_packaging = "required";
    if (!data.kol_ready) errs.kol_ready = "required";
    if (!data.volume) errs.volume = "required";
    if (!data.role) errs.role = "required";
  }
  if (idx === 5) {
    if (!data.pdpa_consent) errs.pdpa_consent = "required";
    if (!data.pipl_consent) errs.pipl_consent = "required";
    if (!data.signature) errs.signature = "required";
    else if (data.contact_name && data.signature.trim().toLowerCase() !== data.contact_name.trim().toLowerCase()) {
      errs.signature = "match";
    }
  }
  return errs;
}

const hasErrors = (errs) => {
  if (!errs) return false;
  for (const k of Object.keys(errs)) {
    const v = errs[k];
    if (Array.isArray(v)) {
      if (v.some((x) => x && Object.keys(x).length)) return true;
    } else if (v) return true;
  }
  return false;
};

// Count blocking errors across a step's error object (for required pill)
const countErrors = (errs) => {
  if (!errs) return 0;
  let n = 0;
  for (const k of Object.keys(errs)) {
    const v = errs[k];
    if (Array.isArray(v)) {
      v.forEach((x) => { if (x) n += Object.keys(x).length; });
    } else if (v) n += 1;
  }
  return n;
};

// Validate just one field (used for blur). Returns error code or null.
function validateField(idx, name, data) {
  const stepErrs = validateStep(idx, data);
  return stepErrs[name] || null;
}

// ===== Format relative time for save pill =====
function relTime(ts, lang) {
  if (!ts) return "";
  const diff = Math.floor((Date.now() - ts) / 1000);
  if (diff < 60) return lang === "th" ? "เมื่อสักครู่" : "just now";
  if (diff < 3600) {
    const m = Math.floor(diff / 60);
    return lang === "th" ? `${m} นาทีที่แล้ว` : `${m} min ago`;
  }
  const d = new Date(ts);
  const hh = String(d.getHours()).padStart(2, "0");
  const mm = String(d.getMinutes()).padStart(2, "0");
  return lang === "th" ? `เวลา ${hh}:${mm}` : `at ${hh}:${mm}`;
}

// ===== App =====
const { useState, useEffect, useReducer, useRef, useCallback, useMemo } = React;

function App() {
  // Language
  const [lang, setLang] = useState(() => {
    const saved = localStorage.getItem("thaihao_lang");
    return saved || "th";
  });
  const t = STRINGS[lang];

  // Current step (0-6: 6 = review). Success is a separate phase.
  const [phase, setPhase] = useState("wizard"); // 'resume' | 'wizard' | 'submitting' | 'success' | 'error'
  const [stepIdx, setStepIdx] = useState(0);
  const [completed, setCompleted] = useState(() => new Set());
  const [data, setData] = useState(initialData);
  const [errors, setErrors] = useState({});
  const [registrationId, setRegistrationId] = useState(null);
  const [saveState, setSaveState] = useState({ state: "idle", at: null });
  const [showPrivacy, setShowPrivacy] = useState(false);
  const [submitProgress, setSubmitProgress] = useState(null);
  const [submitError, setSubmitError] = useState(null);  // {kind, ...}

  // Resume banner
  const [resumeMeta, setResumeMeta] = useState(null); // { savedAt, snapshot }

  // ----- On mount: read draft -----
  useEffect(() => {
    try {
      const raw = localStorage.getItem(DRAFT_KEY);
      if (raw) {
        const parsed = JSON.parse(raw);
        if (parsed && parsed.data && parsed.savedAt) {
          // Only show resume if there's some non-default data
          const hasContent = parsed.data.company_th || parsed.data.contact_name || parsed.data.tax_id;
          if (hasContent) {
            setResumeMeta(parsed);
            setPhase("resume");
          }
        }
      }
    } catch (e) { /* noop */ }
  }, []);

  // ----- Auto-save -----
  const dataRef = useRef(data);
  const stepRef = useRef(stepIdx);
  useEffect(() => { dataRef.current = data; }, [data]);
  useEffect(() => { stepRef.current = stepIdx; }, [stepIdx]);

  const persistDraft = useCallback(() => {
    try {
      const payload = {
        savedAt: Date.now(),
        stepIdx: stepRef.current,
        data: dataRef.current,
      };
      localStorage.setItem(DRAFT_KEY, JSON.stringify(payload));
      setSaveState({ state: "saving", at: Date.now() });
      setTimeout(() => setSaveState({ state: "saved", at: payload.savedAt }), 350);
    } catch (e) { /* noop */ }
  }, []);

  // Save on data change (debounced), and every 30s
  useEffect(() => {
    if (phase !== "wizard") return;
    const id = setTimeout(persistDraft, 1500);
    return () => clearTimeout(id);
  }, [data, persistDraft, phase]);

  useEffect(() => {
    if (phase !== "wizard") return;
    const id = setInterval(persistDraft, 30000);
    return () => clearInterval(id);
  }, [persistDraft, phase]);

  // Save lang
  useEffect(() => { localStorage.setItem("thaihao_lang", lang); }, [lang]);

  // ----- Resume actions -----
  const resumeDraft = () => {
    if (resumeMeta) {
      setData({ ...initialData(), ...resumeMeta.data });
      setStepIdx(resumeMeta.stepIdx || 0);
      // Mark steps before resumed step as completed
      const c = new Set();
      for (let i = 0; i < (resumeMeta.stepIdx || 0); i++) c.add(i);
      setCompleted(c);
    }
    setResumeMeta(null);
    setPhase("wizard");
  };
  const discardDraft = () => {
    localStorage.removeItem(DRAFT_KEY);
    setData(initialData());
    setStepIdx(0);
    setCompleted(new Set());
    setResumeMeta(null);
    setPhase("wizard");
  };

  // ----- Updates -----
  const update = useCallback((key, value) => {
    setData((d) => ({ ...d, [key]: value }));
    // Clear error for this field on edit
    setErrors((errs) => {
      if (!(key in errs)) return errs;
      const next = { ...errs };
      delete next[key];
      return next;
    });
  }, []);

  // Top-of-form ref for scroll on step change
  const mainRef = useRef(null);
  const scrollToTop = () => {
    if (mainRef.current) {
      window.scrollTo({ top: 0, behavior: "smooth" });
    }
  };

  // ----- Step nav -----
  const stepErrorsLive = useMemo(() => stepIdx < 6 ? validateStep(stepIdx, data) : {}, [stepIdx, data]);
  const missingCount = countErrors(stepErrorsLive);
  const canProceed = missingCount === 0;

  const goNext = () => {
    if (stepIdx < 6) {
      if (!canProceed) {
        setErrors(stepErrorsLive);
        return;
      }
      setErrors({});
      setCompleted((c) => new Set([...c, stepIdx]));
      setStepIdx(stepIdx + 1);
      setTimeout(scrollToTop, 0);
    }
  };
  const goBack = () => {
    if (stepIdx > 0) {
      setErrors({});
      setStepIdx(stepIdx - 1);
      setTimeout(scrollToTop, 0);
    }
  };
  const jumpTo = (i) => {
    setErrors({});
    setStepIdx(i);
    setTimeout(scrollToTop, 0);
  };

  // Field-level blur validation. Only sets error if that one field is invalid;
  // never raises errors for other unfilled required fields.
  const onFieldBlur = useCallback((name) => {
    setErrors((errs) => {
      const code = validateField(stepRef.current, name, dataRef.current);
      const next = { ...errs };
      if (code) next[name] = code;
      else delete next[name];
      return next;
    });
  }, []);

  // ----- Submit -----
  const submit = async () => {
    setPhase("submitting");
    setErrors({});
    setSubmitError(null);
    setSubmitProgress({ phase: "init", current: 0, total: 0, label: t.progress_init });

    try {
      const res = await submitRegistration(data, lang, (p) => setSubmitProgress(p));
      if (res.ok) {
        setRegistrationId(res.registration_id);
        setPhase("success");
        localStorage.removeItem(DRAFT_KEY);
      } else {
        setSubmitError({ kind: res.error || "server", ...res.detail });
        setPhase("error");
      }
    } catch (e) {
      setSubmitError({ kind: "server", message: String(e && e.message || e) });
      setPhase("error");
    } finally {
      setSubmitProgress(null);
    }
  };

  // ----- Steps config (with translated labels) -----
  const stepsConfig = STEPS.slice(0, 6).map((s) => ({ key: s.key, label: t[s.labelKey] }));

  // ----- Renderers -----
  const renderStep = () => {
    const common = { data, update, errors, t, onFieldBlur };
    if (stepIdx === 0) return <Step1Company {...common} />;
    if (stepIdx === 1) return <Step2Contact {...common} />;
    if (stepIdx === 2) return <Step3Documents {...common} />;
    if (stepIdx === 3) return <Step4Products {...common} />;
    if (stepIdx === 4) return <Step5Readiness {...common} />;
    if (stepIdx === 5) return <Step6Consent {...common} onOpenPrivacy={() => setShowPrivacy(true)} />;
    return null;
  };

  const stepHead = (() => {
    if (stepIdx === 0) return { eyebrow: t.step_company, title: t.s1_title, intro: t.s1_intro };
    if (stepIdx === 1) return { eyebrow: t.step_contact, title: t.s2_title, intro: t.s2_intro };
    if (stepIdx === 2) return { eyebrow: t.step_documents, title: t.s3_title, intro: t.s3_intro };
    if (stepIdx === 3) return { eyebrow: t.step_products, title: t.s4_title, intro: t.s4_intro };
    if (stepIdx === 4) return { eyebrow: t.step_readiness, title: t.s5_title, intro: t.s5_intro };
    if (stepIdx === 5) return { eyebrow: t.step_consent, title: t.s6_title, intro: t.s6_intro };
    return null;
  })();

  // ----- Render -----
  return (
    <div className="app-shell">
      <header className="app-top reveal" style={{animationDelay: "0ms"}}>
        <div className="app-top-inner">
          <div className="brand-mark">
            <div className="brand-square">
              <img src={(typeof window !== "undefined" && window.__resources && window.__resources.logoIcon) || "assets/logo-icon.png"} alt="ThaiHao" />
            </div>
            <div className="brand-word">{t.brand_word}</div>
            <div className="brand-sub">{t.brand_sub}</div>
          </div>
          <div className="top-actions">
            {phase === "wizard" && <SavePill state={saveState.state} when={relTime(saveState.at, lang)} t={t} />}
            <LanguageToggle lang={lang} onChange={setLang} />
          </div>
        </div>
      </header>

      <main className="app-main" ref={mainRef}>
        {phase === "resume" && resumeMeta && (
          <ResumeView
            meta={resumeMeta}
            lang={lang}
            t={t}
            onResume={resumeDraft}
            onDiscard={discardDraft}
            stepsConfig={stepsConfig}
          />
        )}

        {(phase === "wizard" || phase === "submitting" || phase === "error") && (
          <>
            {/* Intro/landing on step 0 (and not after returning) */}
            {stepIdx === 0 && completed.size === 0 && (
              <div className="intro-card reveal" style={{animationDelay: "80ms"}}>
                <div className="display-lg" style={{marginBottom: 8}}>{t.page_title}</div>
                <p className="body-md" style={{maxWidth: "60ch"}}>{t.page_intro}</p>
                <div className="intro-meta">
                  <span className="intro-meta-item"><i className="ti ti-clock"></i><b>{t.meta_time}</b></span>
                  <span className="intro-meta-item"><i className="ti ti-list-numbers"></i><b>{t.meta_steps}</b></span>
                  <span className="intro-meta-item"><i className="ti ti-device-floppy"></i>{t.meta_save}</span>
                </div>
              </div>
            )}

            <div className="reveal" style={{animationDelay: "180ms"}}>
              <StepIndicator
                steps={stepsConfig}
                current={Math.min(stepIdx, 5)}
                completed={completed}
                onJump={jumpTo}
                t={t}
              />
            </div>

            <div key={stepIdx} className="step-in reveal" style={{animationDelay: "260ms"}}>
              {stepIdx < 6 ? (
                <div className="card">
                  <div className="step-head">
                    <div className="eyebrow"><span className="dot"></span><span>{stepHead.eyebrow}</span></div>
                    <h1 className="headline-md">{stepHead.title}</h1>
                    <p className="body-md" style={{marginTop: 6, color: "var(--on-surface-variant)"}}>{stepHead.intro}</p>
                  </div>
                  {renderStep()}

                  {/* Error summary */}
                  {hasErrors(errors) && (
                    <div style={{
                      marginTop: 20,
                      padding: "10px 12px",
                      borderRadius: "var(--r)",
                      background: "var(--bg-danger)",
                      color: "var(--on-danger)",
                      display: "flex", alignItems: "center", gap: 8,
                      fontSize: 13,
                    }} role="alert">
                      <i className="ti ti-alert-circle"></i>
                      <span>{t.err_step_summary}</span>
                    </div>
                  )}

                  <div className="step-nav">
                    {stepIdx > 0 ? (
                      <Button variant="secondary" icon="arrow-left" onClick={goBack}>{t.back}</Button>
                    ) : <span />}
                    <div className="step-nav-right" style={{display: "flex", alignItems: "center", gap: 10}}>
                      {missingCount > 0 ? (
                        <span className="required-pill" aria-live="polite">
                          <i className="ti ti-circle-dot"></i>
                          <span>{lang === "th" ? `ต้องกรอกอีก ${missingCount} ช่อง` : `${missingCount} field${missingCount === 1 ? "" : "s"} remaining`}</span>
                        </span>
                      ) : (
                        <span className="required-pill is-ready" aria-live="polite">
                          <i className="ti ti-check"></i>
                          <span>{lang === "th" ? "พร้อมไปขั้นตอนถัดไป" : "Ready to continue"}</span>
                        </span>
                      )}
                      <Button variant="primary" iconRight="arrow-right" onClick={goNext} disabled={!canProceed}>{t.next}</Button>
                    </div>
                  </div>
                </div>
              ) : (
                <>
                  <Review data={data} t={t} jumpTo={jumpTo} />
                  <div className="step-nav" style={{
                    marginTop: 16,
                    padding: "16px 24px",
                    background: "var(--surface-container-lowest)",
                    border: "0.5px solid var(--outline-variant)",
                    borderRadius: "var(--r-lg)",
                  }}>
                    <Button variant="secondary" icon="arrow-left" onClick={goBack}>{t.back}</Button>
                    <Button variant="primary" size="lg" iconRight="circle-check" onClick={submit}>{t.submit}</Button>
                  </div>
                </>
              )}
            </div>

            {phase === "error" && submitError && (
              <SubmitErrorView error={submitError} t={t} lang={lang} onRetry={submit} />
            )}
          </>
        )}

        {phase === "success" && (
          <SuccessScreen
            registrationId={registrationId}
            t={t}
            lang={lang}
            onRestart={discardDraft}
          />
        )}
      </main>

      <footer className="app-foot">
        ThaiHao · <a href="#">นโยบายความเป็นส่วนตัว</a> · <a href="#">เงื่อนไขการใช้งาน</a> · <a href="#">ติดต่อทีมงาน</a>
      </footer>

      {/* Submit overlay */}
      {phase === "submitting" && (
        <div className="submit-overlay" role="dialog" aria-modal="true" aria-live="polite">
          <div className="submit-overlay-card submit-progress">
            <div className="spinner" aria-hidden="true"></div>
            <div>
              <div className="submit-progress-title">
                {(submitProgress && submitProgress.label) || t.submitting}
              </div>
              {submitProgress && submitProgress.phase === "upload" && submitProgress.total > 0 && (
                <div className="submit-progress-sub" style={{marginTop: 4}}>
                  {submitProgress.fileName}
                </div>
              )}
            </div>
            <div className="submit-progress-bar" aria-hidden="true">
              <span style={{
                width: submitProgress && submitProgress.phase === "upload" && submitProgress.total > 0
                  ? `${Math.min(100, (submitProgress.current / submitProgress.total) * 100)}%`
                  : submitProgress && submitProgress.phase === "finalize" ? "95%"
                  : "15%"
              }} />
            </div>
          </div>
        </div>
      )}

      {/* Privacy modal */}
      {showPrivacy && (
        <Modal
          title={t.privacy_title}
          onClose={() => setShowPrivacy(false)}
          footer={<Button variant="primary" onClick={() => setShowPrivacy(false)}>{t.close}</Button>}
        >
          <div className="field-stack body-md" style={{color: "var(--on-surface)"}}>
            <p>{lang === "th"
              ? "ThaiHao เก็บข้อมูลของท่านเพื่อวัตถุประสงค์ในการตรวจสอบคุณสมบัติผู้ประกอบการและจัดทำสัญญาความร่วมมือเท่านั้น เราจะไม่ใช้ข้อมูลของท่านเพื่อการตลาดโดยปราศจากความยินยอม"
              : "ThaiHao collects your data solely to verify your business and prepare partnership agreements. We will not use your data for marketing without separate consent."}
            </p>
            <p>{lang === "th"
              ? "ข้อมูลของท่านจะถูกเก็บแบบเข้ารหัส (AES-256) บนเซิร์ฟเวอร์ในประเทศไทย ข้อมูลบางส่วนที่จำเป็นต่อการขายข้ามพรมแดน อาทิ ชื่อกิจการ ที่อยู่ และข้อมูลสินค้า จะถูกส่งไปยังพันธมิตรในจีนภายใต้ข้อตกลงคุ้มครองข้อมูล (DPA) ที่ปฏิบัติตามมาตรฐาน PIPL"
              : "Data is encrypted (AES-256) on servers in Thailand. Information required for cross-border commerce — company name, address, product details — is shared with our China partners under a PIPL-compliant Data Protection Agreement."}
            </p>
            <p>{lang === "th"
              ? "ท่านสามารถใช้สิทธิเข้าถึง แก้ไข ลบ หรือถอนความยินยอมได้ตลอดเวลาผ่าน Producer Portal หรืออีเมล dpo@thaihao.co"
              : "You may exercise your rights to access, rectify, delete, or withdraw consent at any time via the Producer Portal or at dpo@thaihao.co."}
            </p>
          </div>
        </Modal>
      )}
    </div>
  );
}

// Resume view — shown when a draft is found
function ResumeView({ meta, lang, t, onResume, onDiscard, stepsConfig }) {
  const savedAt = new Date(meta.savedAt);
  const months = lang === "th"
    ? ["ม.ค.","ก.พ.","มี.ค.","เม.ย.","พ.ค.","มิ.ย.","ก.ค.","ส.ค.","ก.ย.","ต.ค.","พ.ย.","ธ.ค."]
    : ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];
  const when = `${savedAt.getDate()} ${months[savedAt.getMonth()]} ${savedAt.getFullYear() + (lang === "th" ? 543 : 0)} · ${String(savedAt.getHours()).padStart(2,"0")}:${String(savedAt.getMinutes()).padStart(2,"0")}`;
  const stepName = stepsConfig[meta.stepIdx || 0]?.label || stepsConfig[0].label;

  return (
    <div className="resume-banner">
      <div className="resume-banner-text">
        <i className="ti ti-device-floppy" aria-hidden="true"></i>
        <div>
          <div className="body-md" style={{fontWeight: 500}}>{t.resume_title}</div>
          <div className="body-sm">{t.resume_sub(when)} · {stepName}</div>
        </div>
      </div>
      <div className="resume-banner-actions">
        <Button variant="tertiary" onClick={onDiscard}>{t.resume_restart}</Button>
        <Button variant="primary" icon="arrow-right" onClick={onResume}>{t.resume_continue}</Button>
      </div>
    </div>
  );
}

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

// ============================================================
// SubmitErrorView — renders different cards for known error types
// ============================================================
function SubmitErrorView({ error, t, lang, onRetry }) {
  const kind = error.kind;

  // Duplicate — special UX
  if (kind === "duplicate") {
    const months = lang === "th"
      ? ["ม.ค.","ก.พ.","มี.ค.","เม.ย.","พ.ค.","มิ.ย.","ก.ค.","ส.ค.","ก.ย.","ต.ค.","พ.ย.","ธ.ค."]
      : ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];
    let when = "—";
    if (error.existing_date) {
      const d = new Date(error.existing_date);
      when = `${d.getDate()} ${months[d.getMonth()]} ${d.getFullYear() + (lang === "th" ? 543 : 0)}`;
    }
    return (
      <div className="dup-card" role="alert">
        <div className="dup-card-head">
          <i className="ti ti-info-circle"></i>
          <strong>{t.err_duplicate_title}</strong>
        </div>
        <p className="body-sm" style={{color:"var(--on-warning)"}}>{t.err_duplicate_body(when)}</p>
        {error.existing_id && (
          <>
            <div className="body-sm" style={{marginTop: 10, color:"var(--on-warning)"}}>{t.err_duplicate_id_label}</div>
            <div className="dup-card-id">{error.existing_id}</div>
          </>
        )}
      </div>
    );
  }

  // Friendly messages per error kind
  let body = t.submit_error_body;
  if (kind === "rate_limit") body = t.err_rate_limit;
  else if (kind === "recaptcha") body = t.err_recaptcha;
  else if (kind === "origin") body = t.err_origin;
  else if (kind === "file_too_large" && error.name) body = t.err_file_too_large(error.name);
  else if (kind === "upload_failed" && error.name) body = t.err_upload(error.name);
  else if (kind === "file_read" && error.name) body = t.err_upload(error.name);

  const retryable = kind !== "origin" && kind !== "recaptcha";

  return (
    <div className="error-card" role="alert">
      <div className="error-card-head">
        <i className="ti ti-alert-triangle"></i>
        <strong>{t.submit_error_title}</strong>
      </div>
      <p className="body-sm" style={{color: "var(--on-danger)", marginBottom: retryable ? 12 : 0}}>{body}</p>
      {retryable && (
        <Button variant="primary" icon="refresh" onClick={onRetry}>{t.submit_retry}</Button>
      )}
    </div>
  );
}
