// EditMode.jsx — storefront inline WYSIWYG edit mode.
//
// Entered by opening the storefront with ?edit=1 (the admin has a link for it).
// When on, the admin can edit text, prices, and images directly on the page;
// every change saves live to the admin API. Browser-cached basic-auth (from
// having signed into /admin) authorises those calls — no passcode needed.
//
// This module exposes, on window:
//   EditCtx        — React context: { edit, saveSku, saveSetting, saveSeries,
//                    saveLook, upload, status }. Components read `edit` to decide
//                    whether to render editable affordances.
//   EditableText   — inline-editable text (single or multiline) when edit is on,
//                    plain text otherwise.
//   EditableImage  — click/drop-to-replace overlay for a photo (edit only).
//   EditBar        — floating status/exit bar.
//   sbAdmin        — fetch helpers for the admin API (patch/put/post/upload).
//   SB_EDIT        — boolean, true when ?edit=1 is present.

window.SB_EDIT = (() => {
  try { return new URLSearchParams(window.location.search).get("edit") === "1"; }
  catch { return false; }
})();

window.EditCtx = React.createContext({ edit: false });

// ── Admin API helpers (same-origin; cached basic-auth is sent automatically) ──
async function adminReq(method, path, body) {
  const opts = { method, credentials: "same-origin", headers: {} };
  if (body !== undefined) {
    opts.headers["Content-Type"] = "application/json";
    opts.body = JSON.stringify(body);
  }
  const res = await fetch(path, opts);
  const text = await res.text();
  let data; try { data = text ? JSON.parse(text) : {}; } catch { data = { error: text }; }
  if (!res.ok) throw Object.assign(new Error(data.error || res.statusText), { status: res.status });
  return data;
}
window.sbAdmin = {
  get:   (p)    => adminReq("GET", p),
  patch: (p, b) => adminReq("PATCH", p, b),
  put:   (p, b) => adminReq("PUT", p, b),
  post:  (p, b) => adminReq("POST", p, b),
  del:   (p)    => adminReq("DELETE", p),
  upload: async (file) => {
    const fd = new FormData();
    fd.append("file", file);
    const res = await fetch("/api/admin/upload", { method: "POST", credentials: "same-origin", body: fd });
    const data = await res.json().catch(() => ({}));
    if (!res.ok) throw new Error(data.error || res.statusText);
    return data; // { path, size }
  },
};

// ── Inline-editable text ──────────────────────────────────────────────
// Uncontrolled contentEditable: we seed the DOM text via a ref (so the caret
// never jumps mid-type) and read it back on blur. The parent only re-renders
// after blur → save, so editing is smooth.
function EditableText({ value, onSave, multiline, className, style, placeholder, tag }) {
  const ctx = React.useContext(window.EditCtx);
  const ref = React.useRef(null);
  const Tag = tag || "span";
  const editing = ctx && ctx.edit;

  React.useEffect(() => {
    if (editing && ref.current && document.activeElement !== ref.current) {
      ref.current.textContent = value == null ? "" : String(value);
    }
  }, [value, editing]);

  if (!editing) return <Tag className={className} style={style}>{value}</Tag>;

  const commit = () => {
    const next = (ref.current.textContent || "").trim();
    if (next !== (value == null ? "" : String(value)).trim()) onSave(next);
  };
  return (
    <Tag
      ref={ref}
      className={(className ? className + " " : "") + "sb-editable" + (multiline ? " sb-editable-ml" : "")}
      style={multiline ? { whiteSpace: "pre-wrap", ...style } : style}
      contentEditable
      suppressContentEditableWarning
      spellCheck={false}
      data-ph={placeholder || "—"}
      onClick={(e) => e.stopPropagation()}
      onMouseDown={(e) => e.stopPropagation()}
      onBlur={commit}
      onKeyDown={(e) => {
        if (e.key === "Enter" && !multiline) { e.preventDefault(); e.currentTarget.blur(); }
        if (e.key === "Escape") { e.currentTarget.textContent = value == null ? "" : String(value); e.currentTarget.blur(); }
      }}
    />
  );
}
window.EditableText = EditableText;

// ── Click / drop-to-replace photo overlay ─────────────────────────────
// Renders nothing unless edit is on. When on, it's an absolutely-positioned
// layer that fills its (position:relative) parent — drop a file or click to
// upload, then onPick(path) is called with the new image path.
// Rendered as a small corner chip (not a full-cover layer) so any editable text
// sitting on top of the image — e.g. the cover letter — stays clickable. The
// chip is both the click target and a drop target.
function EditableImage({ onPick, title }) {
  const ctx = React.useContext(window.EditCtx);
  const [over, setOver] = React.useState(false);
  const inputRef = React.useRef(null);
  if (!ctx || !ctx.edit) return null;

  const handle = (files) => { const f = files && files[0]; if (f) ctx.upload(f, onPick); };
  return (
    <div className="sb-img-edit-wrap">
      <button type="button" className={"sb-img-chip" + (over ? " is-over" : "")}
        onClick={(e) => { e.stopPropagation(); inputRef.current && inputRef.current.click(); }}
        onMouseDown={(e) => e.stopPropagation()}
        onDragOver={(e) => { e.preventDefault(); e.stopPropagation(); if (!over) setOver(true); }}
        onDragLeave={(e) => { if (e.currentTarget.contains(e.relatedTarget)) return; setOver(false); }}
        onDrop={(e) => { e.preventDefault(); e.stopPropagation(); setOver(false); if (e.dataTransfer.files.length) handle(e.dataTransfer.files); }}>
        {title || "⤓ replace"}
      </button>
      <input ref={inputRef} type="file" accept="image/*" style={{ display: "none" }}
        onChange={(e) => { handle(e.target.files); e.target.value = ""; }} />
    </div>
  );
}
window.EditableImage = EditableImage;

// Drop-to-replace: spread onto any image container in edit mode so dropping a
// file uploads it and calls onPick(path). No-op when not editing.
window.dropProps = (ec, onPick) => (ec && ec.edit) ? {
  onDragOver: (e) => { if (e.dataTransfer && Array.from(e.dataTransfer.types || []).includes("Files")) { e.preventDefault(); } },
  onDrop: (e) => {
    if (e.dataTransfer && e.dataTransfer.files && e.dataTransfer.files.length) {
      e.preventDefault(); e.stopPropagation();
      ec.upload(e.dataTransfer.files[0], onPick);
    }
  },
} : {};

// Small array-move helper shared by the storefront editors.
window.sbMove = (arr, from, to) => {
  const next = arr.slice();
  const [m] = next.splice(from, 1);
  next.splice(to, 0, m);
  return next;
};

// ── Floating edit-mode bar ────────────────────────────────────────────
function EditBar({ status, onExit, device, onDevice, preview, onPreview, onSequence, seqOn }) {
  // In preview the bar collapses to a single floating "edit" pill so the page
  // is shown clean, exactly as a retailer sees it.
  if (preview) {
    return (
      <button className="sb-preview-pill" onClick={onPreview} title="back to editing">
        ✎ editing · <b>preview</b> — click to edit
      </button>
    );
  }
  return (
    <div className="sb-edit-bar">
      <span className="sb-edit-bar-dot" />
      <span className="sb-edit-bar-title">EDIT MODE</span>
      <div className="sb-edit-bar-devices" title="device being edited (image position is saved per-device)">
        <button className={device === "desktop" ? "is-on" : ""} onClick={() => onDevice("desktop")} title="desktop">▭ desktop</button>
        <button className={device === "mobile" ? "is-on" : ""} onClick={() => onDevice("mobile")} title="mobile">▯ mobile</button>
        {onSequence && (
          <button className={seqOn ? "is-on" : ""} onClick={onSequence}
            title="sequence mode — whole book zoomed out; swap photos, reorder sections, pull from the pool">⌗ sequence</button>
        )}
        <button title="edit the passcode gate (cover image + copy, in place)"
          onClick={() => { window.history.pushState({}, "", "/gate"); window.dispatchEvent(new PopStateEvent("popstate")); }}>
          ◫ gate
        </button>
      </div>
      <button className="sb-edit-bar-preview" onClick={onPreview} title="clean preview — hide all editor UI">⦿ preview</button>
      <span className={"sb-edit-bar-status" + (/(failed|error)/i.test(status || "") ? " is-error" : "")}>{status || ""}</span>
      <button className="sb-edit-bar-exit" onClick={onExit}>exit ⟶ admin</button>
    </div>
  );
}
window.EditBar = EditBar;
