// ═══════════════════════════════════════════════════════════════════════
// Editable — inline-editable wrappers that visitors never see edit chrome for.
//
// <EText path={["hero", "firstName"]} data={data} update={update} />
//   Renders the value. When admin mode is on, it becomes a contentEditable
//   span with a subtle highlight on hover.
//
// <ETextarea ...> — multiline equivalent.
// <EImage ...>    — placeholder + URL editor in admin mode.
// <EList ...>     — generic add/remove/reorder for an array of items.
// ═══════════════════════════════════════════════════════════════════════

const AdminCtx = React.createContext({ isAdmin: false, data: {}, update: () => {} });

function EditProvider({ isAdmin, data, update, children }) {
  return <AdminCtx.Provider value={{ isAdmin, data, update }}>{children}</AdminCtx.Provider>;
}

// ─── Helpers ────────────────────────────────────────────────────────────

// Set a value at a section + sub-path. Section is the top-level key
// (e.g. "hero"); subpath is "firstName" or ["paragraphs", 0].
function setIn(obj, subpath, value) {
  if (!subpath || (Array.isArray(subpath) && subpath.length === 0)) return value;
  const [k, ...rest] = Array.isArray(subpath) ? subpath : [subpath];
  if (Array.isArray(obj)) {
    const next = obj.slice();
    next[k] = setIn(obj[k] ?? (typeof rest[0] === "number" ? [] : {}), rest, value);
    return next;
  }
  return { ...obj, [k]: setIn((obj && obj[k]) ?? (typeof rest[0] === "number" ? [] : {}), rest, value) };
}

function getIn(obj, subpath) {
  if (!subpath || (Array.isArray(subpath) && subpath.length === 0)) return obj;
  const path = Array.isArray(subpath) ? subpath : [subpath];
  let cur = obj;
  for (const k of path) {
    if (cur == null) return undefined;
    cur = cur[k];
  }
  return cur;
}

// ─── Editable text (single line or multi-line) ──────────────────────────

function EText({ section, sub, multiline = false, placeholder = "Click to edit", as = "span", className, style }) {
  const { isAdmin, data, update } = React.useContext(AdminCtx);
  const subPath = sub === undefined ? [] : Array.isArray(sub) ? sub : [sub];
  const value = getIn(data[section], subPath) ?? "";
  const ref = React.useRef(null);

  const commit = () => {
    const text = (multiline
      ? ref.current.innerText
      : ref.current.innerText.replace(/\s+/g, " ")
    ).trim();
    if (text !== value) {
      const sectionData = data[section];
      const nextSection = subPath.length === 0 ? text : setIn(sectionData, subPath, text);
      update(section, nextSection);
    }
  };

  // Keep DOM in sync when value changes from outside (e.g. Firestore push)
  React.useEffect(() => {
    if (ref.current && !ref.current.isContentEditable) {
      // Read-only branch: just set text.
      ref.current.innerText = value || "";
    } else if (ref.current && document.activeElement !== ref.current) {
      ref.current.innerText = value || "";
    }
  }, [value, isAdmin]);

  const Tag = as;
  const baseProps = {
    ref,
    className: className,
    style: style,
  };

  if (!isAdmin) {
    return <Tag {...baseProps}>{value || ""}</Tag>;
  }

  return (
    <Tag
      {...baseProps}
      contentEditable
      suppressContentEditableWarning
      spellCheck={false}
      onBlur={commit}
      onKeyDown={(e) => {
        if (!multiline && e.key === "Enter") {
          e.preventDefault();
          ref.current.blur();
        }
        if (e.key === "Escape") {
          ref.current.innerText = value || "";
          ref.current.blur();
        }
      }}
      data-editable
      data-empty={!value}
      data-placeholder={placeholder}
    />
  );
}

// ─── Editable image (URL paste OR file upload) ──────────────────────────

function EImage({ section, sub, alt = "", className, style, placeholderText = "Image" }) {
  const { isAdmin, data, update } = React.useContext(AdminCtx);
  const subPath = sub === undefined ? [] : Array.isArray(sub) ? sub : [sub];
  const value = getIn(data[section], subPath) ?? "";
  const [editing, setEditing] = React.useState(false);
  const [input, setInput] = React.useState(value);
  const [uploading, setUploading] = React.useState(false);
  const [progress, setProgress] = React.useState(0);
  const [error, setError] = React.useState("");
  const fileRef = React.useRef(null);

  React.useEffect(() => setInput(value), [value]);

  const commit = (url) => {
    const sectionData = data[section];
    const nextSection = subPath.length === 0 ? url : setIn(sectionData, subPath, url);
    update(section, nextSection);
  };

  const saveUrl = () => {
    commit(input);
    setEditing(false);
  };

  const handleFile = async (file) => {
    if (!file) return;
    setError("");
    setUploading(true);
    setProgress(0);
    try {
      const url = await window.uploadFile(file, { folder: "images", onProgress: setProgress });
      commit(url);
      setEditing(false);
    } catch (e) {
      setError(e.message || "Upload failed.");
    } finally {
      setUploading(false);
    }
  };

  const onDrop = (e) => {
    e.preventDefault();
    e.stopPropagation();
    const f = e.dataTransfer?.files?.[0];
    if (f) handleFile(f);
  };

  if (!isAdmin && !value) {
    return <div className={`placeholder ${className || ""}`} style={style}>{placeholderText}</div>;
  }

  if (isAdmin && editing) {
    const storageReady = window.isCloudStorageAvailable && window.isCloudStorageAvailable();
    return (
      <div
        className={`editable-image ${className || ""}`}
        style={style}
        data-editing
        onDragOver={(e) => { e.preventDefault(); e.stopPropagation(); }}
        onDrop={onDrop}
      >
        <div className="ei-form">
          <div className="ei-form-tabs">
            <span className="ei-form-tab on">Upload or paste URL</span>
          </div>

          {!uploading && (
            <>
              <button
                type="button"
                className="ei-upload-zone"
                onClick={() => fileRef.current?.click()}
              >
                <svg viewBox="0 0 24 24" width="22" height="22" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round">
                  <path d="M12 17V3" />
                  <path d="m6 9 6-6 6 6" />
                  <path d="M5 21h14" />
                </svg>
                <span>Click or drop an image here</span>
                <span className="ei-upload-sub">
                  {storageReady ? "→ Cloudflare R2" : "→ Saved as data URL (small images only)"}
                </span>
              </button>
              <input
                ref={fileRef}
                type="file"
                accept="image/*"
                style={{ display: "none" }}
                onChange={(e) => handleFile(e.target.files?.[0])}
              />
              <div className="ei-form-or">— or —</div>
              <label>Paste image URL</label>
              <input
                type="url"
                value={input}
                placeholder="https://…"
                onChange={(e) => setInput(e.target.value)}
              />
            </>
          )}

          {uploading && (
            <div className="ei-progress">
              <div className="ei-progress-label">// Uploading… {Math.round(progress * 100)}%</div>
              <div className="ei-progress-bar"><div style={{ width: `${progress * 100}%` }} /></div>
            </div>
          )}

          {error && <div className="ei-error">// {error}</div>}

          {!uploading && (
            <div className="ei-form-actions">
              <button type="button" className="btn-mini primary" onClick={saveUrl}>Save URL</button>
              <button type="button" className="btn-mini" onClick={() => { setInput(value); setEditing(false); setError(""); }}>Cancel</button>
            </div>
          )}
        </div>
      </div>
    );
  }

  return (
    <div
      className={`editable-image ${className || ""}`}
      style={style}
      onDragOver={isAdmin ? (e) => { e.preventDefault(); e.stopPropagation(); } : undefined}
      onDrop={isAdmin ? onDrop : undefined}
    >
      {value
        ? <img src={value} alt={alt} />
        : <div className={`placeholder ${className || ""}`}>{placeholderText}</div>
      }
      {isAdmin && (
        <button className="ei-edit-btn" onClick={() => setEditing(true)} title="Change image">
          <svg viewBox="0 0 16 16" width="14" height="14" fill="currentColor">
            <path d="M11.4 1.6a1.5 1.5 0 0 1 2.1 0l.9.9a1.5 1.5 0 0 1 0 2.1L5.5 13.5 2 14l.5-3.5 8.9-8.9z" />
          </svg>
          Edit
        </button>
      )}
      {isAdmin && uploading && (
        <div className="ei-overlay-progress">
          <div className="ei-progress-label">Uploading… {Math.round(progress * 100)}%</div>
          <div className="ei-progress-bar"><div style={{ width: `${progress * 100}%` }} /></div>
        </div>
      )}
    </div>
  );
}

// ─── File uploader — drag/drop input that sets a single URL field ───────
// Used for videos, PDFs, etc. Doesn't render the file itself, just the form.

function EFileField({ section, sub, label, hint, accept, folder = "uploads" }) {
  const { isAdmin, data, update } = React.useContext(AdminCtx);
  if (!isAdmin) return null;

  const subPath = sub === undefined ? [] : Array.isArray(sub) ? sub : [sub];
  const value = getIn(data[section], subPath) ?? "";
  const [input, setInput] = React.useState(value);
  const [uploading, setUploading] = React.useState(false);
  const [progress, setProgress] = React.useState(0);
  const [error, setError] = React.useState("");
  const fileRef = React.useRef(null);
  const storageReady = window.isCloudStorageAvailable && window.isCloudStorageAvailable();

  React.useEffect(() => setInput(value), [value]);

  const commit = (url) => {
    const sectionData = data[section];
    const nextSection = subPath.length === 0 ? url : setIn(sectionData, subPath, url);
    update(section, nextSection);
    setInput(url);
  };

  const handleFile = async (file) => {
    if (!file) return;
    setError("");
    setUploading(true);
    setProgress(0);
    try {
      const url = await window.uploadFile(file, { folder, onProgress: setProgress });
      commit(url);
    } catch (e) {
      setError(e.message || "Upload failed.");
    } finally {
      setUploading(false);
    }
  };

  const onDrop = (e) => {
    e.preventDefault();
    e.stopPropagation();
    const f = e.dataTransfer?.files?.[0];
    if (f) handleFile(f);
  };

  return (
    <div className="efile">
      <label className="efile-label">{label}</label>

      <button
        type="button"
        className="ei-upload-zone compact"
        onClick={() => fileRef.current?.click()}
        onDragOver={(e) => { e.preventDefault(); e.stopPropagation(); }}
        onDrop={onDrop}
        disabled={uploading}
      >
        {!uploading ? (
          <>
            <svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round">
              <path d="M12 17V3" />
              <path d="m6 9 6-6 6 6" />
              <path d="M5 21h14" />
            </svg>
            <span>Click or drop file</span>
          </>
        ) : (
          <span>// {Math.round(progress * 100)}% uploading…</span>
        )}
      </button>
      <input
        ref={fileRef}
        type="file"
        accept={accept}
        style={{ display: "none" }}
        onChange={(e) => handleFile(e.target.files?.[0])}
      />

      {uploading && (
        <div className="ei-progress-bar"><div style={{ width: `${progress * 100}%` }} /></div>
      )}

      {error && <div className="ei-error">// {error}</div>}

      <div className="efile-or">— or paste a URL —</div>
      <input
        type="url"
        className="efile-url"
        value={input}
        placeholder="https://…"
        onChange={(e) => setInput(e.target.value)}
        onBlur={() => { if (input !== value) commit(input); }}
      />

      {hint && <p className="ei-hint">{hint}</p>}
      {!storageReady && (
        <p className="ei-hint warn">// Not deployed to Cloudflare yet — uploads fall back to inline data (small files only).</p>
      )}
    </div>
  );
}

// ─── Editable list — array of objects ───────────────────────────────────

function EList({ section, sub, template, renderItem, label = "item", itemClassName }) {
  const { isAdmin, data, update } = React.useContext(AdminCtx);
  const subPath = sub === undefined ? [] : Array.isArray(sub) ? sub : [sub];
  const list = getIn(data[section], subPath) ?? [];

  const change = (next) => {
    const sectionData = data[section];
    const nextSection = subPath.length === 0 ? next : setIn(sectionData, subPath, next);
    update(section, nextSection);
  };

  const add = () => {
    const t = typeof template === "function" ? template() : template;
    change([...list, JSON.parse(JSON.stringify(t))]);
  };

  const remove = (i) => change(list.filter((_, j) => j !== i));

  const move = (i, dir) => {
    const j = i + dir;
    if (j < 0 || j >= list.length) return;
    const next = list.slice();
    [next[i], next[j]] = [next[j], next[i]];
    change(next);
  };

  return (
    <>
      {list.map((item, i) => (
        <div
          key={i}
          className={`${isAdmin ? "elist-item" : "elist-item-plain"}${itemClassName ? ` ${itemClassName(item, i)}` : ""}`}
          data-elist-item
        >
          {renderItem(item, i, list)}
          {isAdmin && (
            <div className="elist-controls" data-noncommentable>
              <button onClick={() => move(i, -1)} title="Move up" disabled={i === 0}>↑</button>
              <button onClick={() => move(i, 1)} title="Move down" disabled={i === list.length - 1}>↓</button>
              <button onClick={() => remove(i)} title={`Remove ${label}`} className="danger">✕</button>
            </div>
          )}
        </div>
      ))}
      {isAdmin && (
        <button className="elist-add" onClick={add} data-noncommentable>+ Add {label}</button>
      )}
    </>
  );
}

// ─── Styles for editable controls (admin only) ──────────────────────────

const EDIT_STYLES = `
  [data-editable] {
    outline: none;
    position: relative;
    transition: background 0.15s ease, box-shadow 0.15s ease;
    padding: 0 3px;
    margin: 0 -3px;
    cursor: text;
  }
  [data-editable]:hover { background: color-mix(in oklab, var(--accent) 14%, transparent); }
  [data-editable]:focus {
    background: color-mix(in oklab, var(--accent) 22%, transparent);
    box-shadow: 0 0 0 1.5px var(--accent);
  }
  [data-editable][data-empty="true"]:empty::before {
    content: attr(data-placeholder);
    color: color-mix(in oklab, var(--accent) 70%, var(--mute));
    font-style: italic;
    opacity: 0.7;
  }

  .editable-image { position: relative; }
  .editable-image .ei-edit-btn {
    position: absolute;
    top: 8px; left: 8px;
    background: var(--bg);
    color: var(--accent);
    border: 1px solid var(--accent);
    padding: 6px 10px;
    font-family: var(--mono);
    font-size: 10.5px;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    display: inline-flex;
    align-items: center;
    gap: 6px;
    cursor: pointer;
    opacity: 0;
    transition: opacity 0.15s ease, transform 0.15s ease;
    z-index: 5;
  }
  .editable-image:hover .ei-edit-btn { opacity: 1; }
  .editable-image .ei-edit-btn:hover { background: var(--accent); color: var(--bg); transform: translateY(-1px); }

  .editable-image[data-editing] {
    background: var(--bg-2);
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 24px;
    border: 1px solid var(--accent);
  }
  .ei-form {
    background: var(--bg);
    padding: 20px;
    border: 1px solid var(--line);
    max-width: 420px;
    width: 100%;
    font-family: var(--sans);
    color: var(--ink);
  }
  .ei-form label {
    display: block;
    font-family: var(--mono);
    font-size: 10.5px;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--mute);
    margin-bottom: 6px;
  }
  .ei-form input {
    width: 100%;
    padding: 10px 12px;
    border: 1px solid var(--line);
    background: var(--bg-2);
    color: var(--ink);
    font-family: var(--mono);
    font-size: 12px;
    outline: none;
  }
  .ei-form input:focus { border-color: var(--accent); }
  .ei-form-actions { display: flex; gap: 8px; margin-top: 12px; }
  .ei-hint { font-family: var(--mono); font-size: 11px; color: var(--mute); margin: 10px 0 0; line-height: 1.5; letter-spacing: 0.02em; }
  .ei-hint.warn { color: var(--warn); }

  .ei-form-tabs {
    display: flex;
    margin: 0 0 14px;
    padding-bottom: 8px;
    border-bottom: 1px solid var(--line);
  }
  .ei-form-tab {
    font-family: var(--mono);
    font-size: 10.5px;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--mute);
  }
  .ei-form-tab.on { color: var(--accent); }
  .ei-form-or {
    text-align: center;
    margin: 14px 0 10px;
    font-family: var(--mono);
    font-size: 10.5px;
    letter-spacing: 0.08em;
    color: var(--mute);
  }

  .ei-upload-zone {
    width: 100%;
    padding: 24px 16px;
    background: var(--bg-2);
    border: 1.5px dashed var(--line);
    color: var(--ink-2);
    font-family: var(--mono);
    font-size: 12px;
    letter-spacing: 0.04em;
    cursor: pointer;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 8px;
    transition: all 0.15s ease;
  }
  .ei-upload-zone:hover {
    border-color: var(--accent);
    color: var(--accent);
    background: color-mix(in oklab, var(--accent) 6%, var(--bg-2));
  }
  .ei-upload-zone:disabled { opacity: 0.6; cursor: wait; }
  .ei-upload-zone.compact {
    padding: 14px;
    flex-direction: row;
    justify-content: center;
    gap: 10px;
  }
  .ei-upload-sub {
    font-size: 10.5px;
    color: var(--mute);
    text-transform: uppercase;
  }

  .ei-progress {
    padding: 18px 0;
  }
  .ei-progress-label {
    font-family: var(--mono);
    font-size: 11px;
    color: var(--accent);
    letter-spacing: 0.04em;
    margin-bottom: 8px;
  }
  .ei-progress-bar {
    width: 100%;
    height: 6px;
    background: var(--bg);
    border: 1px solid var(--line);
    overflow: hidden;
  }
  .ei-progress-bar > div {
    height: 100%;
    background: var(--accent);
    box-shadow: 0 0 8px var(--accent);
    transition: width 0.2s ease;
  }

  .ei-overlay-progress {
    position: absolute;
    inset: 0;
    background: color-mix(in oklab, var(--bg) 85%, transparent);
    display: flex;
    flex-direction: column;
    justify-content: center;
    padding: 24px;
    gap: 8px;
    z-index: 6;
  }

  .ei-error {
    margin-top: 10px;
    padding: 8px 10px;
    background: color-mix(in oklab, var(--red) 12%, transparent);
    border-left: 2px solid var(--red);
    font-family: var(--mono);
    font-size: 11px;
    color: var(--red);
    letter-spacing: 0.02em;
  }

  /* EFileField — drop zone reused inside admin panels */
  .efile { display: flex; flex-direction: column; gap: 8px; }
  .efile-label {
    font-family: var(--mono);
    font-size: 10.5px;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--mute);
  }
  .efile-or {
    font-family: var(--mono);
    font-size: 10px;
    color: var(--mute);
    text-align: center;
    letter-spacing: 0.06em;
    margin: 2px 0;
  }
  .efile-url {
    width: 100%;
    padding: 8px 10px;
    background: var(--bg);
    border: 1px solid var(--line);
    color: var(--accent);
    font-family: var(--mono);
    font-size: 11px;
    outline: none;
  }
  .efile-url:focus { border-color: var(--accent); }

  .elist-item {
    position: relative;
    padding-right: 80px;
  }
  /* The view-mode wrapper (no drag/reorder controls) has no styling of its
     own — it exists only for React's key/list bookkeeping. Left as a real
     box, it becomes the actual CSS grid item in any grid parent (e.g. the
     Reel grid), which silently defeats per-card span classes like
     .video.span-12. display:contents removes it from the box tree so its
     rendered child participates in the parent grid directly. */
  .elist-item-plain {
    display: contents;
  }
  .elist-controls {
    position: absolute;
    top: 0; right: 0;
    display: flex;
    gap: 4px;
    opacity: 0;
    transition: opacity 0.15s ease;
    z-index: 4;
  }
  .elist-item:hover .elist-controls,
  .elist-item:focus-within .elist-controls { opacity: 1; }
  .elist-controls button {
    width: 26px; height: 26px;
    border: 1px solid var(--line);
    background: var(--bg-2);
    color: var(--ink-2);
    font-family: var(--mono);
    font-size: 12px;
    cursor: pointer;
    transition: all 0.15s ease;
    display: inline-flex;
    align-items: center;
    justify-content: center;
  }
  .elist-controls button:hover:not(:disabled) { background: var(--accent); color: var(--bg); border-color: var(--accent); }
  .elist-controls button:disabled { opacity: 0.3; cursor: not-allowed; }
  .elist-controls button.danger { color: var(--red); }
  .elist-controls button.danger:hover { background: var(--red); color: var(--bg); border-color: var(--red); }

  .elist-add {
    margin-top: 16px;
    padding: 10px 18px;
    border: 1px dashed var(--accent);
    background: transparent;
    color: var(--accent);
    font-family: var(--mono);
    font-size: 11.5px;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    cursor: pointer;
    transition: all 0.15s ease;
  }
  .elist-add:hover { background: color-mix(in oklab, var(--accent) 12%, transparent); }
`;

function EditStyles() {
  return <style>{EDIT_STYLES}</style>;
}

window.EditProvider = EditProvider;
window.EditStyles = EditStyles;
window.EText = EText;
window.EImage = EImage;
window.EFileField = EFileField;
window.EList = EList;
window.AdminCtx = AdminCtx;
