// inline-editor.jsx — In-page edit mode for web_admins.
//
// Surfaces:
//   <EditModeProvider> — wraps the app, owns edit-mode toggle + session
//   <EditableText fieldKey label> — wraps a text node, click-to-edit when on
//   <EditableImage fieldKey label> — image slot with upload + crop + alt-text
//   <EditModeToolbar /> — floating top bar shown when edit mode is on
//   <DraftTray /> — bottom-right pending-changes drawer
//   <ApprovalBadge /> — top-right pending-review pill for admins
//   <ApprovalPanel /> — diff viewer with approve / reject
//   <HistoryPanel /> — audit log with rollback
//   <UserPill /> — top-right "Hi, FrostKaiser" w/ menu (replaces Admin button when logged in)
//
// Auth model in this prototype:
//   - Read sessionStorage via ATS.getSession()
//   - web_admin OR HIGHER can flip Edit mode on (it's a per-tab toggle)
//   - admin / super_admin see the Approval badge

const EditModeCtx = React.createContext(null);

function useEditMode() {
  return React.useContext(EditModeCtx);
}

function EditModeProvider({ children }) {
  const [session, setSession] = React.useState(null);
  const [sessionLoaded, setSessionLoaded] = React.useState(false);
  const [editing, setEditing] = React.useState(false);
  const [activeField, setActiveField] = React.useState(null); // currently focused field_key
  const [showDraftTray, setShowDraftTray] = React.useState(false);
  const [showApproval, setShowApproval] = React.useState(false);
  const [showHistory, setShowHistory] = React.useState(false);
  const [drafts, setDrafts] = React.useState([]);
  const [, forceTick] = React.useReducer((x) => x + 1, 0);

  // Load initial data
  React.useEffect(() => {
    if (window.ContentEdit) ContentEdit.listAllDrafts().then(setDrafts).catch(() => {});
    if (window.ATS) ATS.getSession().then((s) => { setSession(s); setSessionLoaded(true); }).catch(() => setSessionLoaded(true));
    else setSessionLoaded(true);
  }, []);

  // Listen for session changes (login / logout from AdminModal)
  React.useEffect(() => {
    const tick = async () => {
      if (window.ATS) setSession(await ATS.getSession());
    };
    window.addEventListener('storage', tick);
    window.addEventListener('4wos:session', tick);
    return () => {
      window.removeEventListener('storage', tick);
      window.removeEventListener('4wos:session', tick);
    };
  }, []);

  // Listen for content / draft / audit changes — re-render the page
  React.useEffect(() => {
    if (!window.ContentEdit) return;
    const a = ContentEdit.onDraftsChange((d) => { if (Array.isArray(d)) setDrafts(d); else ContentEdit.listAllDrafts().then(setDrafts).catch(() => {}); forceTick(); });
    const b = ContentEdit.onFieldsChange(() => forceTick());
    const c = ContentEdit.onAuditChange(() => forceTick());
    return () => { a(); b(); c(); };
  }, []);

  const canEdit = sessionLoaded && !!session && ATS && ATS.roleAtLeast(session.role, 'web_admin');
  const canApprove = sessionLoaded && !!session && ATS && ATS.roleAtLeast(session.role, 'admin');

  // Auto-disable edit mode if you lose permission
  React.useEffect(() => {
    if (!canEdit && editing) setEditing(false);
  }, [canEdit, editing]);

  const safeDrafts = Array.isArray(drafts) ? drafts : [];
  const value = {
    session, canEdit, canApprove,
    editing, setEditing,
    activeField, setActiveField,
    showDraftTray, setShowDraftTray,
    showApproval, setShowApproval,
    showHistory, setShowHistory,
    drafts: safeDrafts,
    myDraftCount: session ? safeDrafts.filter((d) => d.status === 'pending' && d.author_id === session.id).length : 0,
    pendingReviewCount: safeDrafts.filter((d) => d.status === 'pending' && d.submitted_at).length,
  };
  return <EditModeCtx.Provider value={value}>{children}</EditModeCtx.Provider>;
}

// ── EditableText ─────────────────────────────────────────────
// A wrapper that, in edit mode, makes its text content click-to-edit.
// `as` controls the wrapping element (span / h1 / p / etc).
// `multiline` switches to a textarea-style editor (still inline).
function EditableText({ fieldKey, label, as: Tag = 'span', multiline, className, style, children }) {
  const ctx = useEditMode();
  const ref = React.useRef(null);
  const [editing, setEditing] = React.useState(false);

  // Compute the text to show: published / draft / default (children)
  const fallback = React.useMemo(() => {
    if (typeof children === 'string') return children;
    if (Array.isArray(children) && children.every((c) => typeof c === 'string' || typeof c === 'number')) {
      return children.join('');
    }
    return ref.current ? ref.current.textContent : '';
  }, [children]);

  const effective = window.ContentEdit
    ? ContentEdit.getEffectiveValue(fieldKey, fallback, {
        preview: !!ctx?.editing,
        viewerId: ctx?.session?.id,
      })
    : fallback;

  const hasDraft = !!(ctx?.session && window.ContentEdit && ContentEdit.getMyPendingDraft(fieldKey, ctx.session.id));
  const isActive = ctx?.activeField === fieldKey;

  // Click to enter edit; outside-click to exit
  const commit = () => {
    if (!ref.current) return;
    const next = ref.current.innerText.replace(/\u00A0/g, ' ').trim();
    if (next !== effective && ctx?.session) {
      ContentEdit.saveDraft(fieldKey, multiline ? 'textarea' : 'text', next, label || fieldKey, ctx.session.id);
    }
    setEditing(false);
    ctx?.setActiveField(null);
  };

  React.useEffect(() => {
    if (!editing) return;
    const onKey = (e) => {
      if (e.key === 'Escape') { setEditing(false); ctx?.setActiveField(null); }
      if (e.key === 'Enter' && !multiline) { e.preventDefault(); commit(); }
    };
    const onClickAway = (e) => {
      if (ref.current && !ref.current.contains(e.target)) commit();
    };
    window.addEventListener('keydown', onKey);
    setTimeout(() => window.addEventListener('mousedown', onClickAway), 0);
    return () => {
      window.removeEventListener('keydown', onKey);
      window.removeEventListener('mousedown', onClickAway);
    };
  }, [editing, multiline, effective, ctx?.session?.id]);

  if (!ctx?.editing) {
    // Read-only — just render the effective value (or original children if no override)
    if (effective === fallback) return <Tag className={className} style={style}>{children}</Tag>;
    return <Tag className={className} style={style}>{effective}</Tag>;
  }

  const wrapClass =
    'iedit ' +
    (editing ? 'iedit-on ' : '') +
    (isActive ? 'iedit-active ' : '') +
    (hasDraft ? 'iedit-draft ' : '') +
    (className || '');

  return (
    <Tag
      ref={ref}
      className={wrapClass}
      style={style}
      contentEditable={editing}
      suppressContentEditableWarning
      data-edit-key={fieldKey}
      onClick={(e) => {
        if (editing) return;
        e.stopPropagation();
        setEditing(true);
        ctx.setActiveField(fieldKey);
        setTimeout(() => {
          ref.current?.focus();
          // place cursor at end
          const r = document.createRange();
          r.selectNodeContents(ref.current);
          r.collapse(false);
          const sel = window.getSelection();
          sel.removeAllRanges();
          sel.addRange(r);
        }, 0);
      }}
    >
      {editing ? effective : effective}
      {!editing && (
        <span className="iedit-pin" title={label || fieldKey}>
          {hasDraft ? <span className="iedit-pin-dot" /> : null}
          <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round"><path d="M12 20h9M16.5 3.5a2.12 2.12 0 0 1 3 3L7 19l-4 1 1-4 12.5-12.5z" /></svg>
        </span>
      )}
    </Tag>
  );
}

// ── EditableImage ────────────────────────────────────────────
function EditableImage({ fieldKey, label, defaultSrc, alt, className, style, aspect = 16 / 9 }) {
  const ctx = useEditMode();
  const [picker, setPicker] = React.useState(false);

  const stored = window.ContentEdit
    ? ContentEdit.getEffectiveValue(fieldKey, null, {
        preview: !!ctx?.editing, viewerId: ctx?.session?.id,
      })
    : null;
  // stored is a JSON string: { mediaId, src, alt, crop }
  let v = null;
  try { v = stored ? JSON.parse(stored) : null; } catch { v = null; }

  const src = v?.src || defaultSrc;
  const altText = v?.alt || alt || '';
  const hasDraft = !!(ctx?.session && window.ContentEdit && ContentEdit.getMyPendingDraft(fieldKey, ctx.session.id));

  if (!ctx?.editing) {
    if (!src) {
      return (
        <div className={'edit-img-empty ' + (className || '')} style={style} role="img" aria-label={altText}>
          <div className="edit-img-empty-inner">
            <span className="mono">{alt || label || 'image'}</span>
          </div>
        </div>
      );
    }
    return <img src={src} alt={altText} className={className} style={style} />;
  }

  return (
    <>
      <div
        className={'iedit-img ' + (hasDraft ? 'iedit-draft ' : '') + (className || '')}
        style={style}
        data-edit-key={fieldKey}
        onClick={(e) => { e.stopPropagation(); setPicker(true); ctx.setActiveField(fieldKey); }}
        role="button"
        tabIndex={0}
      >
        {src ? (
          <img src={src} alt={altText} />
        ) : (
          <div className="edit-img-empty-inner"><span className="mono">{alt || label || 'image'}</span></div>
        )}
        <span className="iedit-pin">
          {hasDraft ? <span className="iedit-pin-dot" /> : null}
          <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round"><path d="M12 20h9M16.5 3.5a2.12 2.12 0 0 1 3 3L7 19l-4 1 1-4 12.5-12.5z" /></svg>
        </span>
      </div>
      {picker && (
        <ImagePickerModal
          fieldKey={fieldKey}
          label={label}
          initial={v}
          aspect={aspect}
          onClose={() => { setPicker(false); ctx.setActiveField(null); }}
          onSave={(payload) => {
            if (!ctx?.session) return;
            const m = ContentEdit.addMedia({
              data_url: payload.src,
              alt: payload.alt,
              crop: payload.crop,
              uploader_id: ctx.session.id,
            });
            ContentEdit.saveDraft(fieldKey, 'image', JSON.stringify({
              mediaId: m.id, src: payload.src, alt: payload.alt, crop: payload.crop,
            }), label || fieldKey, ctx.session.id);
            setPicker(false);
            ctx.setActiveField(null);
          }}
        />
      )}
    </>
  );
}

// ── ImagePickerModal ─────────────────────────────────────────
function ImagePickerModal({ fieldKey, label, initial, aspect, onSave, onClose }) {
  const [src, setSrc] = React.useState(initial?.src || '');
  const [altText, setAltText] = React.useState(initial?.alt || '');
  const [crop, setCrop] = React.useState(initial?.crop || { x: 0, y: 0, w: 100, h: 100 });
  const fileRef = React.useRef(null);

  const onFile = (e) => {
    const f = e.target.files?.[0];
    if (!f) return;
    const r = new FileReader();
    r.onload = () => setSrc(r.result);
    r.readAsDataURL(f);
  };

  // Drag the crop rectangle
  const onCropDrag = (e) => {
    const box = e.currentTarget.getBoundingClientRect();
    const startX = e.clientX, startY = e.clientY;
    const start = { ...crop };
    const move = (ev) => {
      const dx = ((ev.clientX - startX) / box.width) * 100;
      const dy = ((ev.clientY - startY) / box.height) * 100;
      setCrop((c) => ({
        ...c,
        x: Math.max(0, Math.min(100 - start.w, start.x + dx)),
        y: Math.max(0, Math.min(100 - start.h, start.y + dy)),
      }));
    };
    const up = () => { window.removeEventListener('mousemove', move); window.removeEventListener('mouseup', up); };
    window.addEventListener('mousemove', move);
    window.addEventListener('mouseup', up);
  };

  return (
    <div className="confirm-bg" onMouseDown={(e) => { if (e.target === e.currentTarget) onClose(); }}>
      <div className="confirm-box" style={{ maxWidth: 560, width: '100%' }}>
        <h3>Replace image · <span style={{ color: 'var(--text-faint)', fontWeight: 500, fontSize: '0.9em' }}>{label || fieldKey}</span></h3>

        <div style={{ display: 'flex', flexDirection: 'column', gap: 14, marginTop: 16 }}>
          <div className="img-picker-canvas" style={{ aspectRatio: aspect }}>
            {src ? (
              <>
                <img src={src} alt="" />
                <div className="img-picker-crop"
                     style={{ left: crop.x + '%', top: crop.y + '%', width: crop.w + '%', height: crop.h + '%' }}
                     onMouseDown={onCropDrag}>
                  <span className="img-picker-crop-grip" />
                </div>
                <div className="img-picker-hint">Drag the highlighted box to reframe</div>
              </>
            ) : (
              <div className="img-picker-empty">
                <span className="mono">drop an image or click upload</span>
              </div>
            )}
          </div>

          <div style={{ display: 'flex', gap: 10, flexWrap: 'wrap' }}>
            <input ref={fileRef} type="file" accept="image/*" onChange={onFile} style={{ display: 'none' }} />
            <button className="btn btn-ghost btn-sm" type="button" onClick={() => fileRef.current?.click()}>
              {src ? 'Replace file' : 'Upload file'}
            </button>
            {src && (
              <>
                <div className="picker-aspect">
                  <span>Crop size</span>
                  <input type="range" min="20" max="100" value={crop.w}
                         onChange={(e) => {
                           const w = Number(e.target.value);
                           const h = w / aspect * (100 / (100 / aspect));
                           setCrop((c) => ({ ...c, w, h: Math.min(100, w) }));
                         }} />
                </div>
                <button className="btn btn-ghost btn-sm" type="button" onClick={() => setSrc('')}>Remove</button>
              </>
            )}
          </div>

          <div className="field">
            <label>Alt text <span style={{ color: 'var(--text-faint)', fontWeight: 400 }}>· accessibility & SEO</span></label>
            <input value={altText} onChange={(e) => setAltText(e.target.value)}
                   placeholder="e.g. State 3802 capital banner at sunset" />
          </div>
        </div>

        <div className="row" style={{ marginTop: 18 }}>
          <button className="btn btn-ghost btn-sm" type="button" onClick={onClose}>Cancel</button>
          <button className="btn btn-primary btn-sm" type="button"
                  disabled={!src}
                  onClick={() => onSave({ src, alt: altText, crop })}>
            Save as draft
          </button>
        </div>
      </div>
    </div>
  );
}

// ── EditModeToolbar ──────────────────────────────────────────
function EditModeToolbar() {
  const ctx = useEditMode();
  if (!ctx?.editing) return null;
  return (
    <div className="edit-toolbar">
      <div className="edit-toolbar-inner">
        <span className="admin-tag" style={{ background: 'rgba(125,211,252,0.15)', borderColor: 'rgba(125,211,252,0.35)' }}>
          <span className="iedit-pin-dot" style={{ background: 'var(--ice)' }} />
          Edit mode · viewing your drafts
        </span>
        <div className="edit-toolbar-hint">
          Click any highlighted element to edit. Images open a crop dialog.
        </div>
        <div className="edit-toolbar-actions">
          <button className="btn btn-ghost btn-sm" onClick={() => ctx.setShowDraftTray(!ctx.showDraftTray)}>
            <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round"><path d="M22 12h-6l-2 3h-4l-2-3H2M5 4l-3 8v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3-8z" /></svg>
            Drafts {ctx.myDraftCount > 0 && <span className="count">{ctx.myDraftCount}</span>}
          </button>
          <button className="btn btn-ghost btn-sm" onClick={() => ctx.setShowHistory(true)}>History</button>
          <button className="btn btn-primary btn-sm" onClick={() => ctx.setEditing(false)}>
            Done editing
          </button>
        </div>
      </div>
    </div>
  );
}

// ── DraftTray ───────────────────────────────────────────────
function DraftTray() {
  const ctx = useEditMode();
  if (!ctx?.editing || !ctx.showDraftTray || !ctx.session) return null;
  const mine = ctx.drafts.filter((d) => d.status === 'pending' && d.author_id === ctx.session.id);
  const submitted = mine.filter((d) => d.submitted_at);
  const unsubmitted = mine.filter((d) => !d.submitted_at);

  const onSubmitAll = () => {
    ContentEdit.submitDraftsForReview(ctx.session.id);
  };
  const onWithdraw = (id) => {
    if (confirm('Withdraw this draft? You can re-edit the field afterwards.')) {
      ContentEdit.withdrawDraft(id, ctx.session.id);
    }
  };

  return (
    <div className="draft-tray glass-strong">
      <div className="draft-tray-head">
        <div>
          <h4 style={{ fontSize: '0.95rem', marginBottom: 2 }}>Your drafts</h4>
          <div className="draft-tray-sub">
            {mine.length === 0 ? 'No pending changes yet.' :
              unsubmitted.length > 0
                ? `${unsubmitted.length} draft${unsubmitted.length === 1 ? '' : 's'} not yet submitted for review.`
                : `${submitted.length} draft${submitted.length === 1 ? '' : 's'} awaiting review.`}
          </div>
        </div>
        <button className="modal-x" onClick={() => ctx.setShowDraftTray(false)} aria-label="Close">
          <svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round"><path d="M18 6L6 18M6 6l12 12" /></svg>
        </button>
      </div>
      <div className="draft-tray-list">
        {mine.length === 0 && (
          <div className="draft-empty">
            <div className="mono" style={{ color: 'var(--text-faint)' }}>// edit anything on the page</div>
          </div>
        )}
        {mine.map((d) => (
          <div key={d.id} className={'draft-item' + (d.submitted_at ? ' submitted' : '')}>
            <div className="draft-item-head">
              <span className="draft-item-label">{d.label}</span>
              <span className="draft-item-status">
                {d.submitted_at ? 'In review' : 'Draft'}
              </span>
            </div>
            <DraftDiff prior={d.prior_value} next={d.value} fieldType={d.field_type} />
            <div className="draft-item-actions">
              <button className="link-btn" onClick={() => onWithdraw(d.id)}>Withdraw</button>
              <button className="link-btn" onClick={() => {
                // scroll to the field
                const el = document.querySelector('[data-edit-key="' + d.field_key + '"]');
                if (el) el.scrollIntoView({ behavior: 'smooth', block: 'center' });
              }}>Jump to field</button>
            </div>
          </div>
        ))}
      </div>
      {unsubmitted.length > 0 && (
        <div className="draft-tray-foot">
          <button className="btn btn-primary btn-sm" onClick={onSubmitAll}>
            Submit {unsubmitted.length} draft{unsubmitted.length === 1 ? '' : 's'} for review
          </button>
        </div>
      )}
    </div>
  );
}

// ── Draft diff renderer ─────────────────────────────────────
function DraftDiff({ prior, next, fieldType }) {
  if (fieldType === 'image') {
    let p = null, n = null;
    try { p = prior ? JSON.parse(prior) : null; } catch {}
    try { n = next ? JSON.parse(next) : null; } catch {}
    return (
      <div className="draft-diff img">
        <div className="draft-diff-side">
          <div className="draft-diff-tag">before</div>
          {p?.src ? <img src={p.src} alt="" /> : <div className="draft-diff-empty">no image</div>}
        </div>
        <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" style={{ opacity: 0.5, alignSelf: 'center' }}><path d="M5 12h14M13 5l7 7-7 7" /></svg>
        <div className="draft-diff-side">
          <div className="draft-diff-tag">after</div>
          {n?.src ? <img src={n.src} alt={n.alt || ''} /> : <div className="draft-diff-empty">no image</div>}
        </div>
      </div>
    );
  }
  return (
    <div className="draft-diff text">
      <div className="draft-diff-prior">{prior || <span style={{ color: 'var(--text-faint)' }}>(no published value)</span>}</div>
      <div className="draft-diff-next">{next}</div>
    </div>
  );
}

// ── ApprovalBadge — top-right pill for admin / super_admin ──
function ApprovalBadge() {
  const ctx = useEditMode();
  if (!ctx?.canApprove || ctx.pendingReviewCount === 0) return null;
  return (
    <button className="approval-badge" onClick={() => ctx.setShowApproval(true)}>
      <span className="approval-badge-dot" />
      <span><b>{ctx.pendingReviewCount}</b> pending review</span>
    </button>
  );
}

// ── ApprovalPanel — diff viewer for admins ──────────────────
function ApprovalPanel() {
  const ctx = useEditMode();
  const [filter, setFilter] = React.useState('pending'); // pending | all
  const [reason, setReason] = React.useState({});
  if (!ctx?.showApproval) return null;
  const all = ctx.drafts;
  const list = filter === 'pending'
    ? all.filter((d) => d.status === 'pending' && d.submitted_at)
    : all;

  const onApprove = (id) => {
    ContentEdit.approveDraft(id, ctx.session.id, reason[id] || null);
    setReason((r) => ({ ...r, [id]: '' }));
  };
  const onReject = (id) => {
    if (!reason[id]) {
      alert('Please add a short note so the web admin knows why.');
      return;
    }
    ContentEdit.rejectDraft(id, ctx.session.id, reason[id]);
    setReason((r) => ({ ...r, [id]: '' }));
  };

  return (
    <div className="scrim open" onMouseDown={(e) => { if (e.target === e.currentTarget) ctx.setShowApproval(false); }}>
      <div className="modal glass-strong sched-editor" style={{ maxWidth: 760 }}>
        <button className="modal-x" onClick={() => ctx.setShowApproval(false)} aria-label="Close">
          <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round"><path d="M18 6L6 18M6 6l12 12" /></svg>
        </button>
        <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 18, gap: 16, flexWrap: 'wrap' }}>
          <div>
            <span className="eyebrow" style={{ fontSize: '0.7rem' }}>Review queue</span>
            <h3 style={{ marginTop: 4 }}>Content changes pending review</h3>
          </div>
          <div className="seg-toggle">
            <button className={filter === 'pending' ? 'on' : ''} onClick={() => setFilter('pending')}>
              Pending ({all.filter((d) => d.status === 'pending' && d.submitted_at).length})
            </button>
            <button className={filter === 'all' ? 'on' : ''} onClick={() => setFilter('all')}>All</button>
          </div>
        </div>

        {list.length === 0 && (
          <div className="approval-empty">
            <div className="mono" style={{ color: 'var(--text-faint)' }}>// nothing waiting</div>
            <p style={{ marginTop: 8, color: 'var(--text-dim)', fontSize: '0.92rem' }}>
              When a web admin submits content drafts, they'll appear here for your approval.
            </p>
          </div>
        )}

        <div className="approval-list">
          {list.map((d) => (
            <div key={d.id} className={'approval-item ' + d.status}>
              <div className="approval-item-head">
                <div>
                  <div className="approval-item-label">{d.label}</div>
                  <div className="approval-item-meta">
                    by <b>{d.author_name}</b> · {ATS.fmtAgo(d.submitted_at || d.created_at)}
                    {d.field_key && <span className="approval-item-key">· {d.field_key}</span>}
                  </div>
                </div>
                <span className={'approval-status ' + d.status}>{d.status}</span>
              </div>
              <DraftDiff prior={d.prior_value} next={d.value} fieldType={d.field_type} />
              {d.status === 'pending' && (
                <div className="approval-item-actions">
                  <input
                    placeholder="Optional note for the web admin…"
                    value={reason[d.id] || ''}
                    onChange={(e) => setReason((r) => ({ ...r, [d.id]: e.target.value }))}
                  />
                  <button className="btn btn-ghost btn-sm" onClick={() => onReject(d.id)}>Reject</button>
                  <button className="btn btn-primary btn-sm" onClick={() => onApprove(d.id)}>Approve & publish</button>
                </div>
              )}
              {d.status === 'rejected' && d.review_note && (
                <div className="approval-note">Rejected by <b>{d.reviewer_name}</b>: {d.review_note}</div>
              )}
              {d.status === 'approved' && (
                <div className="approval-note approved">Approved by <b>{d.reviewer_name}</b> · live now</div>
              )}
            </div>
          ))}
        </div>
      </div>
    </div>
  );
}

// ── HistoryPanel — audit log + rollback ─────────────────────
function HistoryPanel() {
  const ctx = useEditMode();
  const [entries, setEntries] = React.useState([]);
  React.useEffect(() => {
    if (window.ContentEdit) ContentEdit.loadAudit().then(setEntries).catch(() => {});
  }, [ctx?.showHistory]);
  if (!ctx?.showHistory) return null;

  const onRollback = async (e) => {
    if (!confirm(`Roll back "${e.label}" to its previous value? This creates a new audit entry; the current value will be restorable too.`)) return;
    await ContentEdit.rollbackTo(e.id, ctx.session.id);
  };

  return (
    <div className="scrim open" onMouseDown={(ev) => { if (ev.target === ev.currentTarget) ctx.setShowHistory(false); }}>
      <div className="modal glass-strong sched-editor" style={{ maxWidth: 720 }}>
        <button className="modal-x" onClick={() => ctx.setShowHistory(false)} aria-label="Close">
          <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round"><path d="M18 6L6 18M6 6l12 12" /></svg>
        </button>
        <span className="eyebrow" style={{ fontSize: '0.7rem' }}>Audit log</span>
        <h3 style={{ marginTop: 4, marginBottom: 18 }}>Content change history</h3>

        {entries.length === 0 && (
          <div className="approval-empty">
            <div className="mono" style={{ color: 'var(--text-faint)' }}>// no changes yet</div>
          </div>
        )}

        <div className="audit-list">
          {entries.map((e) => (
            <div key={e.id} className={'audit-item ' + e.action}>
              <div className="audit-item-head">
                <div>
                  <span className={'audit-action ' + e.action}>{e.action}</span>
                  <span className="audit-label"> {e.label}</span>
                  <span className="audit-key">· {e.field_key}</span>
                </div>
                <span className="audit-when">{ATS.fmtDate(e.created_at)}</span>
              </div>
              <div className="audit-meta">
                {e.action === 'approve' && <span>by <b>{e.author_name}</b> · approved by <b>{e.reviewer_name}</b></span>}
                {e.action === 'reject' && <span>by <b>{e.author_name}</b> · rejected by <b>{e.reviewer_name}</b></span>}
                {e.action === 'rollback' && <span>rolled back by <b>{e.reviewer_name}</b></span>}
              </div>
              <DraftDiff prior={e.from_value} next={e.to_value} fieldType={e.field_type} />
              {e.review_note && <div className="audit-note">"{e.review_note}"</div>}
              {e.can_restore && ctx.canApprove && (
                <div className="audit-actions">
                  <button className="link-btn" onClick={() => onRollback(e)}>Restore "before" version</button>
                </div>
              )}
            </div>
          ))}
        </div>
      </div>
    </div>
  );
}

// ── UserPill — replaces the "Admin" button when logged in ────
function UserPill({ onOpenAdmin }) {
  const ctx = useEditMode();
  const [open, setOpen] = React.useState(false);
  const ref = React.useRef(null);
  React.useEffect(() => {
    const onDoc = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); };
    document.addEventListener('mousedown', onDoc);
    return () => document.removeEventListener('mousedown', onDoc);
  }, []);
  if (!ctx?.session) return null;
  const s = ctx.session;
  const initials = (s.name || s.email || '?').slice(0, 2).toUpperCase();

  return (
    <div className="user-pill-wrap" ref={ref}>
      <button className="user-pill" onClick={() => setOpen(!open)}>
        <span className="user-pill-avatar">{initials}</span>
        <span className="user-pill-meta">
          <span className="user-pill-name">{s.name}</span>
          <span className={'role-pill ' + s.role}>{ATS.ROLE_LABEL[s.role]}</span>
        </span>
        <svg width="10" height="6" viewBox="0 0 10 6" style={{ opacity: 0.5 }}><path d="M0 0h10L5 6z" fill="currentColor" /></svg>
      </button>
      {open && (
        <div className="user-menu glass-strong">
          {ctx.canEdit && (
            <button className="user-menu-item" onClick={() => { ctx.setEditing(!ctx.editing); setOpen(false); }}>
              <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><path d="M12 20h9M16.5 3.5a2.12 2.12 0 0 1 3 3L7 19l-4 1 1-4 12.5-12.5z" /></svg>
              {ctx.editing ? 'Exit edit mode' : 'Edit this page'}
              {ctx.myDraftCount > 0 && <span className="count">{ctx.myDraftCount}</span>}
            </button>
          )}
          {ctx.canApprove && (
            <button className="user-menu-item" onClick={() => { ctx.setShowApproval(true); setOpen(false); }}>
              <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><path d="M9 12l2 2 4-4M12 22c5.523 0 10-4.477 10-10S17.523 2 12 2 2 6.477 2 12s4.477 10 10 10z" /></svg>
              Review pending changes
              {ctx.pendingReviewCount > 0 && <span className="count">{ctx.pendingReviewCount}</span>}
            </button>
          )}
          {ctx.canEdit && (
            <button className="user-menu-item" onClick={() => { ctx.setShowHistory(true); setOpen(false); }}>
              <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="10" /><path d="M12 6v6l4 2" /></svg>
              View change history
            </button>
          )}
          <a className="user-menu-item" href="admin.html">
            <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><rect x="3" y="3" width="7" height="9" rx="1" /><rect x="14" y="3" width="7" height="5" rx="1" /><rect x="14" y="12" width="7" height="9" rx="1" /><rect x="3" y="16" width="7" height="5" rx="1" /></svg>
            Open admin dashboard
          </a>
          <div className="user-menu-sep" />
          <button className="user-menu-item" onClick={() => {
            ATS.clearSession();
            window.dispatchEvent(new CustomEvent('4wos:session'));
            setOpen(false);
          }}>
            <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4M16 17l5-5-5-5M21 12H9" /></svg>
            Log out
          </button>
        </div>
      )}
    </div>
  );
}

Object.assign(window, {
  EditModeProvider, EditModeCtx, useEditMode,
  EditableText, EditableImage,
  EditModeToolbar, DraftTray, ApprovalBadge, ApprovalPanel, HistoryPanel,
  UserPill,
});
