// Form field components for the registration form.
// All dark-theme, grotesque-led, editorial. One CSS block at top scopes
// styles to the .reg root so nothing leaks.

if (typeof document !== "undefined" && !document.getElementById("reg-fields-css")) {
  const s = document.createElement("style");
  s.id = "reg-fields-css";
  s.textContent = `
    .reg-row { display: flex; flex-direction: column; gap: 10px; }
    .reg-two-col {
      display: grid;
      grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
      gap: 32px;
      align-items: start;
    }
    .reg-date-grid {
      display: grid;
      grid-template-columns: minmax(0, 1fr) minmax(0, 1.4fr) minmax(0, 1fr);
      gap: 18px;
    }

    .reg-label {
      font-family: var(--mono);
      font-size: 10.5px; letter-spacing: .18em;
      text-transform: uppercase;
      color: rgba(243,239,233,0.70);
      display: flex; align-items: baseline; gap: 10px;
    }
    .reg-label .req {
      color: rgba(243,239,233,0.44); font-weight: 400;
    }
    .reg-help {
      font-size: 12px; color: rgba(243,239,233,0.56);
      font-family: var(--mono); letter-spacing: .02em;
      margin-top: 4px;
    }
    .reg-error {
      color: #e7754c; font-size: 11.5px; letter-spacing: .04em;
      font-family: var(--mono); margin-top: 4px;
    }
    .reg-error[role="alert"] { min-height: 1em; }

    /* Text input — thin underline */
    .reg-input {
      appearance: none; -webkit-appearance: none;
      width: 100%; box-sizing: border-box;
      background: transparent;
      border: none;
      border-bottom: 1px solid rgba(243,239,233,0.22);
      padding: 12px 0 10px;
      font: inherit;
      font-family: var(--font-body);
      font-size: clamp(20px, 1.8vw, 26px);
      color: #f3efe9;
      outline: none;
      transition: border-color .2s;
    }
    .reg-input::placeholder { color: rgba(243,239,233,0.22); }
    .reg-input:focus { border-bottom-color: #f3efe9; }
    .reg-input:focus-visible,
    .reg-textarea:focus-visible,
    .reg-select-button:focus-visible,
    .reg-chip:focus-visible,
    .reg-toggle button:focus-visible,
    .reg-drop:focus-visible,
    .reg-media-x:focus-visible {
      outline: 1px solid #f3efe9;
      outline-offset: 4px;
    }
	    .reg-input.error { border-bottom-color: #e7754c; }

	    .reg-rates {
	      display: grid;
	      gap: 0;
	      border-top: 1px solid rgba(243,239,233,0.16);
	      border-bottom: 1px solid rgba(243,239,233,0.16);
	    }
	    .reg-rate-head,
	    .reg-rate-row {
	      display: grid;
	      grid-template-columns: minmax(104px, 0.9fr) minmax(0, 1fr) minmax(0, 1fr);
	      gap: clamp(14px, 2vw, 28px);
	      align-items: end;
	      padding: 14px 0;
	    }
	    .reg-rate-head {
	      padding-top: 0;
	      font-family: var(--mono);
	      font-size: 10px;
	      letter-spacing: .16em;
	      text-transform: uppercase;
	      color: rgba(243,239,233,0.54);
	    }
	    .reg-rate-row + .reg-rate-row {
	      border-top: 1px solid rgba(243,239,233,0.10);
	    }
	    .reg-rate-label {
	      font-family: var(--mono);
	      font-size: 10.5px;
	      letter-spacing: .16em;
	      text-transform: uppercase;
	      color: rgba(243,239,233,0.72);
	      padding-bottom: 12px;
	    }
	    .reg-rate-input {
	      position: relative;
	    }
	    .reg-rate-input::before {
	      content: "GBP";
	      position: absolute;
	      right: 0;
	      bottom: 13px;
	      color: rgba(243,239,233,0.46);
	      font-family: var(--mono);
	      font-size: 9.5px;
	      letter-spacing: .14em;
	      text-transform: uppercase;
	      pointer-events: none;
	    }
	    .reg-rate-input .reg-input {
	      padding-right: 48px;
	    }

	    /* Number+unit row */
	    .reg-numgroup { display: flex; align-items: baseline; gap: 16px; }
    .reg-numgroup .reg-input { flex: 1 1 auto; min-width: 0; }
    .reg-toggle {
      display: inline-flex; padding: 2px;
      background: rgba(243,239,233,0.06);
      border-radius: 999px;
      font-family: var(--mono); font-size: 11px;
      letter-spacing: .12em; text-transform: uppercase;
    }
    .reg-toggle button {
      appearance: none; border: 0; cursor: pointer;
      padding: 6px 14px;
      background: transparent;
      color: rgba(243,239,233,0.55);
      border-radius: 999px;
      font: inherit;
      transition: background .15s, color .15s;
    }
    .reg-toggle button[data-on="1"] {
      background: #f3efe9; color: #0c0c0c;
    }

    /* Chips */
    .reg-chips { display: flex; flex-wrap: wrap; gap: 8px; }
    .reg-chip {
      appearance: none; border: 1px solid rgba(243,239,233,0.22);
      background: transparent; color: rgba(243,239,233,0.78);
      padding: 9px 14px; border-radius: 999px;
      font-family: var(--font-body); font-size: 13.5px;
      letter-spacing: .01em;
      cursor: pointer;
      transition: background .15s, color .15s, border-color .15s, transform .12s;
    }
    .reg-chip:hover { border-color: rgba(243,239,233,0.5); transform: translateY(-1px); }
    .reg-chip[data-on="1"] {
      background: #f3efe9; color: #0c0c0c; border-color: #f3efe9;
    }
    .reg-chip[data-on="1"]:hover { transform: translateY(-1px); }

    /* Custom dropdown */
    .reg-select {
      position: relative; width: 100%;
    }
    .reg-select-button {
      appearance: none; width: 100%; box-sizing: border-box;
      background: transparent; cursor: pointer;
      border: none;
      border-bottom: 1px solid rgba(243,239,233,0.22);
      padding: 12px 30px 10px 0;
      font-family: var(--font-body);
      font-size: clamp(20px, 1.8vw, 26px);
      color: #f3efe9;
      text-align: left;
      display: flex; align-items: baseline; justify-content: space-between;
      gap: 12px;
      transition: border-color .2s;
    }
    .reg-select-button:focus { outline: none; border-bottom-color: #f3efe9; }
    .reg-select-button.error { border-bottom-color: #e7754c; }
    .reg-select-button.placeholder { color: rgba(243,239,233,0.44); }
    .reg-select-caret {
      flex: 0 0 auto;
      width: 14px; height: 14px;
      color: rgba(243,239,233,0.45);
      transition: transform .2s, color .2s;
    }
    .reg-select.open .reg-select-caret { transform: rotate(180deg); color: #f3efe9; }
    .reg-select-pop {
      position: absolute; top: calc(100% + 8px); left: 0; right: 0;
      max-height: 320px; overflow-y: auto;
      background: #181715;
      border: 1px solid rgba(243,239,233,0.16);
      border-radius: 8px;
      box-shadow: 0 12px 40px rgba(0,0,0,0.45);
      z-index: 50;
      padding: 6px;
      opacity: 0; pointer-events: none;
      transform: translateY(-4px);
      transition: opacity .15s, transform .15s;
    }
    .reg-select.open .reg-select-pop {
      opacity: 1; pointer-events: auto; transform: translateY(0);
    }
    .reg-select-pop::-webkit-scrollbar { width: 8px; }
    .reg-select-pop::-webkit-scrollbar-thumb {
      background: rgba(243,239,233,0.15); border-radius: 4px;
      border: 2px solid transparent; background-clip: content-box;
    }
    .reg-option {
      width: 100%;
      appearance: none; border: 0; background: transparent;
      color: #f3efe9; text-align: left; cursor: pointer;
      font-family: var(--font-body); font-size: 15px;
      padding: 10px 14px; border-radius: 5px;
      display: flex; justify-content: space-between; gap: 12px;
      transition: background .12s;
    }
    .reg-option:hover { background: rgba(243,239,233,0.06); }
    .reg-option[aria-selected="true"] { background: rgba(243,239,233,0.1); }
    .reg-option-sub {
      color: rgba(243,239,233,0.42); font-size: 12px;
      font-family: var(--mono); letter-spacing: .04em; text-transform: uppercase;
    }

    /* TextArea */
    .reg-textarea {
      appearance: none; width: 100%; box-sizing: border-box;
      background: transparent;
      border: 1px solid rgba(243,239,233,0.18);
      border-radius: 4px;
      padding: 16px 18px;
      font: inherit; font-family: var(--font-body);
      font-size: 16px; line-height: 1.55;
      color: #f3efe9;
      resize: vertical; min-height: 120px;
      outline: none;
      transition: border-color .2s;
    }
    .reg-textarea::placeholder { color: rgba(243,239,233,0.22); }
    .reg-textarea:focus { border-color: rgba(243,239,233,0.55); }

    /* File upload */
    .reg-drop {
      border: 1.5px dashed rgba(243,239,233,0.26);
      border-radius: 8px;
      padding: 56px 24px;
      display: flex; flex-direction: column; align-items: center; gap: 14px;
      text-align: center; cursor: pointer;
      transition: border-color .15s, background .15s;
    }
    .reg-drop:hover, .reg-drop[data-active="1"] {
      border-color: #f3efe9;
      background: rgba(243,239,233,0.04);
    }
    .reg-drop.error {
      border-color: rgba(231,117,76,0.74);
      background: rgba(231,117,76,0.08);
    }
    .reg-drop input { display: none; }
    .reg-drop-title { font-family: var(--font-body); font-size: 17px; color: #f3efe9; }
    .reg-drop-sub {
      font-family: var(--mono); font-size: 11px; letter-spacing: .14em;
      text-transform: uppercase; color: rgba(243,239,233,0.5);
    }
    .reg-media-grid {
      display: grid;
      grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
      gap: 8px;
      margin-top: 16px;
    }
    .reg-media-tile {
      position: relative; aspect-ratio: 3 / 4; overflow: hidden;
      background: #1a1816; border-radius: 4px;
    }
    .reg-media-tile img, .reg-media-tile video {
      width: 100%; height: 100%; object-fit: cover; display: block;
    }
    .reg-media-x {
      position: absolute; top: 6px; right: 6px;
      width: 22px; height: 22px; border-radius: 11px;
      background: rgba(10,10,10,0.7); color: #f3efe9;
      border: 0; cursor: pointer;
      display: flex; align-items: center; justify-content: center;
      font-size: 13px; line-height: 1;
      opacity: 0; transition: opacity .15s;
    }
    .reg-media-tile:hover .reg-media-x { opacity: 1; }
    .reg-media-kind {
      position: absolute; bottom: 6px; left: 6px;
      font-family: var(--mono); font-size: 9px; letter-spacing: .14em;
      text-transform: uppercase; color: rgba(243,239,233,0.85);
      background: rgba(10,10,10,0.5); padding: 2px 6px; border-radius: 4px;
    }
    .reg-media-name {
      position: absolute; left: 6px; right: 6px; top: 6px;
      font-family: var(--mono); font-size: 9px; letter-spacing: .08em;
      text-transform: uppercase; color: rgba(243,239,233,0.76);
      overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
      text-shadow: 0 1px 10px rgba(0,0,0,0.8);
    }
    .reg-media-fallback {
      position: absolute; inset: 0;
      display: grid; place-items: center;
      padding: 14px;
      text-align: center;
      color: rgba(243,239,233,0.58);
      font-family: var(--mono); font-size: 10px; letter-spacing: .12em;
      text-transform: uppercase;
      background:
        linear-gradient(135deg, rgba(243,239,233,0.10), rgba(243,239,233,0.025) 42%, rgba(231,117,76,0.10));
    }
    .reg-file-rejections {
      display: flex;
      flex-direction: column;
      gap: 6px;
      padding: 12px 14px;
      border: 1px solid rgba(231,117,76,0.4);
      background: rgba(231,117,76,0.08);
      color: #ffd5c5;
      font-family: var(--mono);
      font-size: 10.5px;
      letter-spacing: .08em;
      text-transform: uppercase;
    }

	    @media (max-width: 760px) {
	      .reg-two-col,
	      .reg-date-grid,
	      .reg-rate-head,
	      .reg-rate-row {
	        grid-template-columns: 1fr;
	        gap: 24px;
	      }
	      .reg-rate-head {
	        display: none;
	      }
	      .reg-rate-row {
	        padding: 22px 0;
	      }
	      .reg-rate-label {
	        padding-bottom: 0;
	      }
      .reg-numgroup {
        flex-wrap: wrap;
      }
      .reg-toggle {
        width: 100%;
        justify-content: space-between;
      }
      .reg-toggle button {
        flex: 1;
      }
      .reg-media-grid {
        grid-template-columns: repeat(2, minmax(0, 1fr));
        gap: 10px;
      }
    }
  `;
  document.head.appendChild(s);
}

const REG_ALLOWED_IMAGE_EXT = /\.(jpe?g|png|heic|heif)$/i;
const REG_ALLOWED_IMAGE_TYPES = new Set(["image/jpeg", "image/png", "image/heic", "image/heif"]);
const REG_MAX_IMAGE_BYTES = 10 * 1024 * 1024;

function formatBytes(bytes) {
  if (!Number.isFinite(bytes)) return "";
  const mb = bytes / (1024 * 1024);
  return `${mb.toFixed(mb >= 10 ? 0 : 1)} MB`;
}

function imageFileError(file) {
  const type = String(file.type || "").toLowerCase();
  const name = file.name || "file";
  const hasAllowedType = REG_ALLOWED_IMAGE_TYPES.has(type);
  const hasAllowedExtension = REG_ALLOWED_IMAGE_EXT.test(name);

  if (!hasAllowedType && !hasAllowedExtension) {
    return `${name}: use JPG, PNG, HEIC, or HEIF.`;
  }

  if (file.size > REG_MAX_IMAGE_BYTES) {
    return `${name}: ${formatBytes(file.size)} is over the 10 MB limit.`;
  }

  return "";
}

function fieldDescribedBy(id, hasHelp, hasError) {
  return [
    hasHelp ? `${id}-help` : "",
    hasError ? `${id}-error` : "",
  ].filter(Boolean).join(" ") || undefined;
}

// ─── Text ──────────────────────────────────────────────────────────────
function RegText({ label, value, onChange, placeholder, required, autoFocus, error, help }) {
  const id = React.useId();
  const describedBy = fieldDescribedBy(id, help && !error, error);

  return (
    <div className="reg-row">
      <label className="reg-label" htmlFor={id}>
        <span>{label}</span>{required && <span className="req">required</span>}
      </label>
      <input
        id={id}
        type="text"
        className={"reg-input" + (error ? " error" : "")}
        value={value || ""}
        onChange={(e) => onChange(e.target.value)}
        placeholder={placeholder}
        autoFocus={autoFocus}
        aria-invalid={error ? "true" : "false"}
        aria-describedby={describedBy}
      />
      {help && !error && <div className="reg-help" id={`${id}-help`}>{help}</div>}
      {error && <div className="reg-error" id={`${id}-error`} role="alert">{error}</div>}
    </div>
  );
}

// ─── Date ──────────────────────────────────────────────────────────────
function RegDate({ label, value, onChange, required, error, help }) {
  const id = React.useId();
  const describedBy = fieldDescribedBy(id, help && !error, error);

  return (
    <div className="reg-row">
      <label className="reg-label" htmlFor={id}>
        <span>{label}</span>{required && <span className="req">required</span>}
      </label>
      <input
        id={id}
        type="date"
        className={"reg-input" + (error ? " error" : "")}
        value={value || ""}
        onChange={(e) => onChange(e.target.value)}
        style={{ colorScheme: "dark" }}
        aria-invalid={error ? "true" : "false"}
        aria-describedby={describedBy}
      />
      {help && !error && <div className="reg-help" id={`${id}-help`}>{help}</div>}
      {error && <div className="reg-error" id={`${id}-error`} role="alert">{error}</div>}
    </div>
  );
}

// ─── Height (number + cm/ft toggle, stored as cm) ──────────────────────
function RegHeight({ value, onChange, required, error }) {
  // value is always stored in cm. Display can be cm or ft/in.
  const [unit, setUnit] = React.useState("cm");
  const cm = value || "";

  // Local "feet, inches" representation for the ft mode.
  const ftIn = (() => {
    if (!cm) return { ft: "", in: "" };
    const totalIn = Number(cm) / 2.54;
    const ft = Math.floor(totalIn / 12);
    const inch = Math.round(totalIn - ft * 12);
    return { ft, in: inch };
  })();

  const setCm = (raw) => {
    const n = Number(String(raw).replace(/[^0-9.]/g, ""));
    if (!Number.isFinite(n) || n === 0) { onChange(""); return; }
    onChange(Math.round(n));
  };
  const setFromFt = (ft, inch) => {
    const f = Number(ft) || 0;
    const i = Number(inch) || 0;
    if (!f && !i) { onChange(""); return; }
    onChange(Math.round((f * 12 + i) * 2.54));
  };
  const id = React.useId();

  return (
    <div className="reg-row">
      <label className="reg-label" htmlFor={`${id}-${unit === "cm" ? "cm" : "ft"}`}>
        <span>Height</span>{required && <span className="req">required</span>}
      </label>
      <div className="reg-numgroup">
        {unit === "cm" ? (
          <input
            id={`${id}-cm`}
            type="text" inputMode="numeric"
            className={"reg-input" + (error ? " error" : "")}
            value={cm}
            onChange={(e) => setCm(e.target.value)}
            placeholder="178"
            style={{ maxWidth: 160 }}
            aria-invalid={error ? "true" : "false"}
            aria-describedby={`${id}-help${error ? ` ${id}-error` : ""}`}
          />
        ) : (
          <>
            <input
              id={`${id}-ft`}
              type="text" inputMode="numeric"
              className={"reg-input" + (error ? " error" : "")}
              value={ftIn.ft}
              onChange={(e) => setFromFt(e.target.value, ftIn.in)}
              placeholder="5"
              style={{ maxWidth: 80 }}
              aria-label="Feet"
              aria-invalid={error ? "true" : "false"}
            />
            <span style={{ color:"rgba(243,239,233,0.5)", fontFamily:"var(--mono)", fontSize:13, letterSpacing:".14em", textTransform:"uppercase" }}>ft</span>
            <input
              id={`${id}-in`}
              type="text" inputMode="numeric"
              className={"reg-input" + (error ? " error" : "")}
              value={ftIn.in}
              onChange={(e) => setFromFt(ftIn.ft, e.target.value)}
              placeholder="10"
              style={{ maxWidth: 80 }}
              aria-label="Inches"
              aria-invalid={error ? "true" : "false"}
            />
            <span style={{ color:"rgba(243,239,233,0.5)", fontFamily:"var(--mono)", fontSize:13, letterSpacing:".14em", textTransform:"uppercase" }}>in</span>
          </>
        )}
        <div className="reg-toggle" style={{ marginLeft:"auto" }}>
          <button data-on={unit === "cm" ? "1" : "0"} onClick={() => setUnit("cm")}>cm</button>
          <button data-on={unit === "ft" ? "1" : "0"} onClick={() => setUnit("ft")}>ft / in</button>
        </div>
      </div>
      <div className="reg-help" id={`${id}-help`}>
        Saved as centimetres ({cm ? `${cm} cm` : "—"}). The profile shows it formatted automatically.
      </div>
      {error && <div className="reg-error" id={`${id}-error`} role="alert">{error}</div>}
    </div>
  );
}

// ─── Dropdown — accepts strings or {value, sub} ────────────────────────
function RegDropdown({ label, value, options, onChange, placeholder = "Select…", required, error, help }) {
  const [open, setOpen] = React.useState(false);
  const ref = React.useRef(null);
  const id = React.useId();
  React.useEffect(() => {
    if (!open) return;
    const off = (e) => { if (!ref.current?.contains(e.target)) setOpen(false); };
    const onKey = (e) => { if (e.key === "Escape") setOpen(false); };
    document.addEventListener("pointerdown", off, true);
    document.addEventListener("keydown", onKey);
    return () => {
      document.removeEventListener("pointerdown", off, true);
      document.removeEventListener("keydown", onKey);
    };
  }, [open]);

  const norm = options.map((o) => typeof o === "string" ? { value: o } : o);
  const display = value || placeholder;
  const placeholderState = !value;

  return (
    <div className="reg-row">
      <label className="reg-label" id={`${id}-label`}>
        <span>{label}</span>{required && <span className="req">required</span>}
      </label>
      <div className={"reg-select" + (open ? " open" : "")} ref={ref}>
        <button
          type="button"
          className={"reg-select-button" + (placeholderState ? " placeholder" : "") + (error ? " error" : "")}
          onClick={() => setOpen((o) => !o)}
          aria-haspopup="listbox"
          aria-expanded={open ? "true" : "false"}
          aria-labelledby={`${id}-label`}
          aria-controls={`${id}-listbox`}
          aria-invalid={error ? "true" : "false"}
          aria-describedby={fieldDescribedBy(id, help && !error, error)}
        >
          <span>{display}</span>
          <svg className="reg-select-caret" viewBox="0 0 14 14" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round">
            <path d="M3 5l4 4 4-4"/>
          </svg>
        </button>
        <div className="reg-select-pop" role="listbox" id={`${id}-listbox`} aria-labelledby={`${id}-label`}>
          {norm.map((o) => (
            <button
              key={o.value}
              type="button"
              className="reg-option"
              role="option"
              aria-selected={o.value === value}
              onClick={() => { onChange(o.value); setOpen(false); }}
            >
              <span>{o.value}</span>
              {o.sub && <span className="reg-option-sub">{o.sub}</span>}
            </button>
          ))}
        </div>
      </div>
      {help && !error && <div className="reg-help" id={`${id}-help`}>{help}</div>}
      {error && <div className="reg-error" id={`${id}-error`} role="alert">{error}</div>}
    </div>
  );
}

// ─── Multi-chip select ─────────────────────────────────────────────────
function RegMultiChip({ label, value, options, onChange, required, error, help }) {
  const list = value || [];
  const id = React.useId();
  const toggle = (v) => {
    if (list.includes(v)) onChange(list.filter((x) => x !== v));
    else onChange([...list, v]);
  };
  return (
    <div className="reg-row">
      <label className="reg-label">
        <span>{label}</span>{required && <span className="req">required</span>}
      </label>
      <div className="reg-chips" role="group" aria-label={label} aria-describedby={fieldDescribedBy(id, help && !error, error)}>
        {options.map((o) => (
          <button
            key={o}
            type="button"
            className="reg-chip"
            data-on={list.includes(o) ? "1" : "0"}
            aria-pressed={list.includes(o) ? "true" : "false"}
            onClick={() => toggle(o)}
          >{o}</button>
        ))}
      </div>
      {help && !error && <div className="reg-help" id={`${id}-help`}>{help}</div>}
      {error && <div className="reg-error" id={`${id}-error`} role="alert">{error}</div>}
    </div>
  );
}

// ─── Radio (single-choice chip group) ──────────────────────────────────
function RegRadio({ label, value, options, onChange, required, error, help }) {
  const id = React.useId();

  return (
    <div className="reg-row">
      <label className="reg-label">
        <span>{label}</span>{required && <span className="req">required</span>}
      </label>
      <div className="reg-chips" role="radiogroup" aria-label={label} aria-describedby={fieldDescribedBy(id, help && !error, error)}>
        {options.map((o) => (
          <button
            key={o}
            type="button"
            className="reg-chip"
            role="radio"
            aria-checked={value === o ? "true" : "false"}
            data-on={value === o ? "1" : "0"}
            onClick={() => onChange(o)}
          >{o}</button>
        ))}
      </div>
      {help && !error && <div className="reg-help" id={`${id}-help`}>{help}</div>}
      {error && <div className="reg-error" id={`${id}-error`} role="alert">{error}</div>}
    </div>
  );
}

// ─── TextArea ──────────────────────────────────────────────────────────
function RegTextArea({ label, value, onChange, placeholder, required, error, help, rows = 5 }) {
  const id = React.useId();

  return (
    <div className="reg-row">
      <label className="reg-label" htmlFor={id}>
        <span>{label}</span>{required && <span className="req">required</span>}
      </label>
      <textarea
        id={id}
        className="reg-textarea"
        value={value || ""}
        onChange={(e) => onChange(e.target.value)}
        placeholder={placeholder}
        rows={rows}
        aria-invalid={error ? "true" : "false"}
        aria-describedby={fieldDescribedBy(id, help && !error, error)}
      />
      {help && !error && <div className="reg-help" id={`${id}-help`}>{help}</div>}
      {error && <div className="reg-error" id={`${id}-error`} role="alert">{error}</div>}
    </div>
  );
}

// ─── File upload (drag-drop, multi, with previews) ─────────────────────
//
// Returns items shaped { src (objectURL), file, w, h, kind, name }. The
// caller persists in state. On submit we hand over the file blobs to the
// agency; image dimensions are measured client-side so the gallery packs
// rows without ever cropping.
function RegFileUpload({ label, value, onChange, min = 5, max = 15, required, error, help }) {
  const list = value || [];
  const [active, setActive] = React.useState(false);
  const [rejections, setRejections] = React.useState([]);
  const [busy, setBusy] = React.useState(false);
  const inputRef = React.useRef(null);
  const listRef = React.useRef(list);
  const objectUrlsRef = React.useRef(new Set());
  const id = React.useId();

  const measure = (file) => new Promise((res) => {
    const url = URL.createObjectURL(file);
    objectUrlsRef.current.add(url);
    const img = new Image();
    img.onload = () => res({
      src: url,
      file,
      w: img.naturalWidth,
      h: img.naturalHeight,
      kind: "image",
      name: file.name,
      previewable: true,
    });
    img.onerror = () => res({
      src: url,
      file,
      w: 1200,
      h: 1600,
      kind: "image",
      name: file.name,
      previewable: false,
    });
    img.src = url;
  });

  React.useEffect(() => {
    listRef.current = list;
    const activeUrls = new Set(list.map((item) => item.src).filter(Boolean));

    for (const url of Array.from(objectUrlsRef.current)) {
      if (!activeUrls.has(url)) {
        URL.revokeObjectURL(url);
        objectUrlsRef.current.delete(url);
      }
    }
  }, [list]);

  React.useEffect(() => () => {
    for (const url of objectUrlsRef.current) URL.revokeObjectURL(url);
    objectUrlsRef.current.clear();
  }, []);

  const addFiles = async (files) => {
    const currentList = listRef.current;
    const remaining = max - currentList.length;
    const incoming = Array.from(files);
    if (remaining <= 0) {
      setRejections([`Maximum ${max} images already selected.`]);
      return;
    }

    const messages = [];
    const arr = [];
    for (const file of incoming) {
      const reason = imageFileError(file);
      if (reason) messages.push(reason);
      else arr.push(file);
    }

    if (arr.length > remaining) {
      messages.push(`Only ${remaining} more ${remaining === 1 ? "image" : "images"} can be added.`);
    }

    const accepted = arr.slice(0, remaining);
    setRejections(messages.slice(0, 4));
    if (!accepted.length) return;

    setBusy(true);
    const measured = await Promise.all(accepted.map(measure));
    setBusy(false);

    const latestList = listRef.current;
    const latestRemaining = max - latestList.length;
    const nextMeasured = measured.slice(0, latestRemaining);
    const unusedMeasured = measured.slice(latestRemaining);

    for (const item of unusedMeasured) {
      URL.revokeObjectURL(item.src);
      objectUrlsRef.current.delete(item.src);
    }

    if (!nextMeasured.length) {
      setRejections([`Maximum ${max} images already selected.`]);
      return;
    }

    const nextList = [...latestList, ...nextMeasured];
    listRef.current = nextList;
    onChange(nextList);
  };

  const removeAt = (i) => {
    const next = list.slice();
    URL.revokeObjectURL(next[i].src);
    objectUrlsRef.current.delete(next[i].src);
    next.splice(i, 1);
    listRef.current = next;
    onChange(next);
  };

  return (
    <div className="reg-row">
      <label className="reg-label">
        <span>{label}</span>
        {required && <span className="req">min {min}</span>}
      </label>
      <div
        className={"reg-drop" + (error ? " error" : "")}
        data-active={active ? "1" : "0"}
        role="button"
        tabIndex={0}
        aria-label="Upload registration images"
        aria-describedby={`${id}-help${error ? ` ${id}-error` : ""}`}
        onClick={() => inputRef.current?.click()}
        onKeyDown={(e) => {
          if (e.key === "Enter" || e.key === " ") {
            e.preventDefault();
            inputRef.current?.click();
          }
        }}
        onDragOver={(e) => { e.preventDefault(); setActive(true); }}
        onDragLeave={() => setActive(false)}
        onDrop={(e) => {
          e.preventDefault();
          setActive(false);
          if (e.dataTransfer?.files?.length) addFiles(e.dataTransfer.files);
        }}
      >
        <svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" style={{color:"rgba(243,239,233,0.7)"}}>
          <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/>
          <polyline points="17 8 12 3 7 8"/>
          <line x1="12" y1="3" x2="12" y2="15"/>
        </svg>
        <div className="reg-drop-title">
          {list.length === 0
            ? "Drop your images here"
            : busy ? "Reading image files"
              : `Add more (${list.length} / ${max})`}
        </div>
        <div className="reg-drop-sub">
          {list.length === 0
            ? `Minimum ${min} · maximum ${max} · JPG, PNG, HEIC, HEIF · 10 MB each`
            : "JPG, PNG, HEIC, HEIF · 10 MB each"}
        </div>
        <input
          id={id}
          ref={inputRef}
          type="file"
          accept="image/jpeg,image/png,image/heic,image/heif,.jpg,.jpeg,.png,.heic,.heif"
          multiple
          onChange={(e) => { if (e.target.files?.length) addFiles(e.target.files); e.target.value = ""; }}
        />
      </div>

      {rejections.length > 0 && (
        <div className="reg-file-rejections" role="alert">
          {rejections.map((message) => <span key={message}>{message}</span>)}
        </div>
      )}

      {list.length > 0 && (
        <div className="reg-media-grid">
          {list.map((item, i) => (
            <div key={item.src} className="reg-media-tile">
              {item.previewable === false ? (
                <div className="reg-media-fallback">
                  Preview pending
                </div>
              ) : (
                <img src={item.src} alt={`Selected image ${i + 1}`} loading="lazy" decoding="async"/>
              )}
              <span className="reg-media-name">{item.name}</span>
              <span className="reg-media-kind">
                {item.previewable === false ? "Secure image" : `${item.w}×${item.h}`}
              </span>
              <button
                type="button"
                className="reg-media-x"
                aria-label="Remove"
                onClick={(e) => { e.stopPropagation(); removeAt(i); }}
              >×</button>
            </div>
          ))}
        </div>
      )}

      {help && !error && <div className="reg-help" id={`${id}-help`}>{help}</div>}
      {error && <div className="reg-error" id={`${id}-error`} role="alert">{error}</div>}
    </div>
  );
}

// ─── Yes/No + optional details ─────────────────────────────────────────
function RegYesNoDetails({ label, value, onChange, detailLabel, required }) {
  // value shape: { has: boolean, detail: string }
  const v = value || { has: false, detail: "" };
  const id = React.useId();

  return (
    <div className="reg-row">
      <label className="reg-label" id={`${id}-label`}>
        <span>{label}</span>{required && <span className="req">required</span>}
      </label>
      <div className="reg-chips" role="radiogroup" aria-labelledby={`${id}-label`}>
        <button type="button" className="reg-chip" role="radio" aria-checked={v.has === false ? "true" : "false"} data-on={v.has === false ? "1" : "0"} onClick={() => onChange({ has: false, detail: "" })}>None</button>
        <button type="button" className="reg-chip" role="radio" aria-checked={v.has === true ? "true" : "false"} data-on={v.has === true  ? "1" : "0"} onClick={() => onChange({ has: true, detail: v.detail })}>Yes</button>
      </div>
      {v.has && (
        <div style={{ marginTop: 14 }}>
          <input
            id={`${id}-detail`}
            type="text"
            className="reg-input"
            value={v.detail || ""}
            onChange={(e) => onChange({ has: true, detail: e.target.value })}
            placeholder={detailLabel || "Describe briefly"}
            style={{ fontSize: 18 }}
          />
        </div>
      )}
    </div>
  );
}

// ─── Date — three dropdowns (day / month / year) ───────────────────────
// Stores an ISO date string ("YYYY-MM-DD") so the rest of the form is
// unchanged. Native <input type=date> looks too utilitarian against the
// rest of this form's editorial chrome.
const REG_MONTHS = [
  "January", "February", "March", "April", "May", "June",
  "July", "August", "September", "October", "November", "December",
];

function parseDobParts(value) {
  if (!value) return { y: "", m: "", d: "" };
  const [y = "", m = "", d = ""] = value.split("-");
  return {
    y,
    m: m ? String(Number(m)).padStart(2, "0") : "",
    d: d ? String(Number(d)) : "",
  };
}

function RegDateParts({ label, value, onChange, required, error, help, minAge = 16, maxAge = 65 }) {
  const [draft, setDraft] = React.useState(() => parseDobParts(value));

  React.useEffect(() => {
    if (value) setDraft(parseDobParts(value));
  }, [value]);

  const updatePart = (key, nextValue) => {
    const next = { ...draft, [key]: nextValue };
    if (next.y && next.m && next.d) {
      // Clamp day to month's length so e.g. picking Feb 30 falls back to Feb 28.
      const last = new Date(Number(next.y), Number(next.m), 0).getDate();
      const dN = Math.min(Number(next.d), last);
      next.d = String(dN);
      onChange(`${next.y}-${String(next.m).padStart(2, "0")}-${String(dN).padStart(2, "0")}`);
    }
    setDraft(next);
  };

  const days = Array.from({ length: 31 }, (_, i) => String(i + 1));
  const months = REG_MONTHS.map((name, i) => ({ value: String(i + 1).padStart(2, "0"), label: name }));
  const thisYear = new Date().getFullYear();
  const years = Array.from({ length: maxAge - minAge + 1 },
    (_, i) => String(thisYear - minAge - i));

  return (
    <div className="reg-row">
      <label className="reg-label">
        <span>{label}</span>{required && <span className="req">required</span>}
      </label>
      <div className="reg-date-grid">
        <DateDropdown
          placeholder="Day"
          value={draft.d}
          options={days}
          onChange={(v) => updatePart("d", v)}
        />
        <DateDropdown
          placeholder="Month"
          value={draft.m}
          renderValue={(v) => v ? REG_MONTHS[Number(v) - 1] : ""}
          options={months.map((m) => ({ value: m.value, label: m.label }))}
          onChange={(v) => updatePart("m", v)}
        />
        <DateDropdown
          placeholder="Year"
          value={draft.y}
          options={years}
          onChange={(v) => updatePart("y", v)}
        />
      </div>
      {help && !error && <div className="reg-help">{help}</div>}
      {error && <div className="reg-error">{error}</div>}
    </div>
  );
}

// Compact dropdown variant used by the date picker. Same chrome as RegDropdown
// but renders just the bare value (no label row), accepts string or
// {value,label} options, and supports a renderValue for displaying month names.
function DateDropdown({ value, placeholder, options, onChange, renderValue }) {
  const [open, setOpen] = React.useState(false);
  const ref = React.useRef(null);
  const id = React.useId();
  React.useEffect(() => {
    if (!open) return;
    const off = (e) => { if (!ref.current?.contains(e.target)) setOpen(false); };
    const onKey = (e) => { if (e.key === "Escape") setOpen(false); };
    document.addEventListener("pointerdown", off, true);
    document.addEventListener("keydown", onKey);
    return () => {
      document.removeEventListener("pointerdown", off, true);
      document.removeEventListener("keydown", onKey);
    };
  }, [open]);

  const norm = options.map((o) =>
    typeof o === "string" ? { value: o, label: o } : o
  );
  const selected = norm.find((o) => o.value === value);
  const display = selected
    ? (renderValue ? renderValue(value) : selected.label)
    : placeholder;

  return (
    <div className={"reg-select" + (open ? " open" : "")} ref={ref}>
      <button
        type="button"
        className={"reg-select-button" + (!value ? " placeholder" : "")}
        onClick={() => setOpen((o) => !o)}
        aria-haspopup="listbox"
        aria-expanded={open ? "true" : "false"}
        aria-controls={`${id}-listbox`}
      >
        <span>{display}</span>
        <svg className="reg-select-caret" viewBox="0 0 14 14" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round">
          <path d="M3 5l4 4 4-4"/>
        </svg>
      </button>
      <div className="reg-select-pop" role="listbox" id={`${id}-listbox`}>
        {norm.map((o) => (
          <button
            key={o.value}
            type="button"
            className="reg-option"
            role="option"
            aria-selected={o.value === value}
            onClick={() => { onChange(o.value); setOpen(false); }}
          >
            <span>{o.label}</span>
          </button>
        ))}
      </div>
    </div>
  );
}

// ─── Size with region toggle (dress / shoe) ────────────────────────────
//
// Stores the canonical EU value (a string like "36" or "39.5"); the
// component handles converting on the fly when the user toggles to US / UK.
// Sizing tables ship with the component as window.REG_SIZE_TABLES.
window.REG_SIZE_TABLES = {
  dress: {
    EU: ["32", "34", "36", "38", "40", "42", "44", "46", "48"],
    US: ["0",  "2",  "4",  "6",  "8",  "10", "12", "14", "16"],
    UK: ["4",  "6",  "8",  "10", "12", "14", "16", "18", "20"],
  },
  shoe: {
    EU: ["35", "36", "37", "38", "39", "40", "41", "42", "43"],
    US: ["5",  "6",  "7",  "8",  "8.5","9.5","10", "11", "12"],
    UK: ["2.5","3.5","4.5","5.5","6",  "7",  "7.5","8.5","9.5"],
  },
};

function RegSizeWithRegion({ label, value, table, onChange, required, error, help }) {
  // Region used purely for display — the canonical stored value is always EU.
  const [region, setRegion] = React.useState("EU");
  const eu = value || ""; // canonical EU string
  const idx = table.EU.indexOf(eu);
  const displayValue = idx >= 0 ? table[region][idx] : "";

  return (
    <div className="reg-row">
      <label className="reg-label">
        <span>{label}</span>{required && <span className="req">required</span>}
      </label>
      <div style={{ display:"flex", alignItems:"baseline", gap: 16 }}>
        <div style={{ flex: "1 1 auto", minWidth: 0 }}>
          <DateDropdown
            placeholder={`Select… (${region})`}
            value={displayValue}
            options={table[region]}
            onChange={(v) => {
              // Reverse-lookup the chosen value's index in the current region
              // and emit the EU equivalent so storage stays canonical.
              const i = table[region].indexOf(v);
              onChange(i >= 0 ? table.EU[i] : "");
            }}
          />
        </div>
        <div className="reg-toggle" style={{ flex:"0 0 auto" }}>
          {["EU", "US", "UK"].map((r) => (
            <button
              key={r}
              type="button"
              data-on={region === r ? "1" : "0"}
              onClick={() => setRegion(r)}
            >{r}</button>
          ))}
        </div>
      </div>
      <div className="reg-help">
        {eu
          ? `EU ${table.EU[idx]} · US ${table.US[idx]} · UK ${table.UK[idx]}`
          : "The profile shows all three regions side by side."}
      </div>
      {error && <div className="reg-error">{error}</div>}
    </div>
  );
}

Object.assign(window, {
  RegText, RegDate, RegHeight, RegDropdown, RegMultiChip, RegRadio,
  RegTextArea, RegFileUpload, RegYesNoDetails,
  RegDateParts, RegSizeWithRegion,
});
