/* ui.jsx — shared primitives: icons, buttons, editable fields, modal, flourishes. */

// ── Icon set (stroke-based, 24×24, currentColor) ────────────────────────────
const ICON_PATHS = {
  users: '<path d="M16 18v-1a4 4 0 0 0-4-4H7a4 4 0 0 0-4 4v1"/><circle cx="9.5" cy="7" r="3.2"/><path d="M17 13a4 4 0 0 1 4 4v1"/><path d="M16 4a3.2 3.2 0 0 1 0 6"/>',
  spark: '<path d="M12 2l1.6 6.4L20 10l-6.4 1.6L12 18l-1.6-6.4L4 10l6.4-1.6z"/><path d="M19 15l.7 2.3L22 18l-2.3.7L19 21l-.7-2.3L16 18l2.3-.7z"/>',
  flag: '<path d="M4 15s1-1 4-1 5 2 8 2 4-1 4-1V3s-1 1-4 1-5-2-8-2-4 1-4 1z"/><line x1="4" y1="22" x2="4" y2="15"/>',
  scroll: '<path d="M6 4h11a2 2 0 0 1 2 2v11"/><path d="M6 4a2 2 0 0 0-2 2v1a2 2 0 0 0 2 2h2"/><path d="M8 9v9a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2v-1H10"/>',
  key: '<circle cx="7.5" cy="14.5" r="3.5"/><path d="M10 12l8.5-8.5"/><path d="M16 6l2.5 2.5"/><path d="M19 3l2 2"/>',
  map: '<path d="M9 4 3 6v14l6-2 6 2 6-2V4l-6 2-6-2z"/><path d="M9 4v14"/><path d="M15 6v14"/>',
  mask: '<path d="M4 5s2-1 8-1 8 1 8 1v6a8 8 0 0 1-16 0V5z"/><path d="M8.5 10c.8.8 2 .8 2.8 0"/><path d="M12.7 10c.8.8 2 .8 2.8 0"/><path d="M9 15c1.5 1.2 4.5 1.2 6 0"/>',
  sword: '<polyline points="14.5 17.5 3 6 3 3 6 3 17.5 14.5"/><line x1="13" y1="19" x2="19" y2="13"/><line x1="16" y1="16" x2="20" y2="20"/><line x1="19" y1="21" x2="21" y2="19"/>',
  gem: '<path d="M6 3h12l3 6-9 12L3 9z"/><path d="M3 9h18"/><path d="M9 3 7.5 9 12 21l4.5-12L15 3"/>',
  plus: '<path d="M12 5v14M5 12h14"/>',
  trash: '<path d="M4 7h16"/><path d="M9 7V5a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2"/><path d="M6 7l1 13a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1l1-13"/>',
  play: '<path d="M7 4.5v15l13-7.5z"/>',
  pen: '<path d="M14 4 20 10 9 21H3v-6z"/><path d="m13 5 6 6"/>',
  book: '<path d="M4 5a2 2 0 0 1 2-2h13v16H6a2 2 0 0 0-2 2z"/><path d="M19 19a2 2 0 0 0-2-2H6"/>',
  sparkles: '<path d="M12 3l1.8 5.2L19 10l-5.2 1.8L12 17l-1.8-5.2L5 10l5.2-1.8z"/><path d="M5 16l.8 2.2L8 19l-2.2.8L5 22l-.8-2.2L2 19l2.2-.8z"/>',
  eye: '<path d="M2 12s4-7 10-7 10 7 10 7-4 7-10 7S2 12 2 12z"/><circle cx="12" cy="12" r="3"/>',
  eyeoff: '<path d="M9.9 5.2A9.5 9.5 0 0 1 12 5c6 0 10 7 10 7a16 16 0 0 1-3.2 3.8"/><path d="M6 7.4A15.7 15.7 0 0 0 2 12s4 7 10 7a9.4 9.4 0 0 0 4-.9"/><path d="m3 3 18 18"/><path d="M9.5 9.6a3 3 0 0 0 4.2 4.2"/>',
  back: '<path d="M19 12H5"/><path d="m12 19-7-7 7-7"/>',
  cal: '<rect x="3" y="5" width="18" height="16" rx="2"/><path d="M3 9h18M8 3v4M16 3v4"/>',
  check: '<path d="M5 12.5 10 17 19 6"/>',
  x: '<path d="M6 6l12 12M18 6 6 18"/>',
  dots: '<circle cx="5" cy="12" r="1.4"/><circle cx="12" cy="12" r="1.4"/><circle cx="19" cy="12" r="1.4"/>',
  grip: '<circle cx="9" cy="6" r="1.3"/><circle cx="15" cy="6" r="1.3"/><circle cx="9" cy="12" r="1.3"/><circle cx="15" cy="12" r="1.3"/><circle cx="9" cy="18" r="1.3"/><circle cx="15" cy="18" r="1.3"/>',
  copy: '<rect x="8" y="8" width="13" height="13" rx="2"/><path d="M5 16a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2"/>',
  chevron: '<path d="m9 6 6 6-6 6"/>',
  flame: '<path d="M12 3s5 4 5 9a5 5 0 0 1-10 0c0-2 1-3 1-3s0 2 1.5 2S12 7 12 3z"/>',
  clock: '<circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 2"/>',
  feather: '<path d="M20 4C10 4 4 11 4 20l4-4h7a5 5 0 0 0 5-5z"/><path d="M16 8 8 16"/>',
  settings: '<circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/>',
  import: '<path d="M12 4v12M8 12l4 4 4-4M4 19h16"/>',
  export: '<path d="M12 16V4M8 8l4-4 4 4M4 19h16"/>',
  expand: '<path d="M15 3h6v6M9 21H3v-6M21 3l-7 7M3 21l7-7"/>',
  image: '<rect x="3" y="5" width="18" height="14" rx="2"/><path d="m3 15 5-5 4 4 3-3 6 6"/><circle cx="8.5" cy="9.5" r="1.5"/>',
  skull: '<path d="M12 4a7 7 0 0 0-7 7c0 2.8 1.5 5.2 3.8 6.5V20h6.4v-2.5C17.5 16.2 19 13.8 19 11a7 7 0 0 0-7-7z"/><circle cx="9.5" cy="11" r="1.5"/><circle cx="14.5" cy="11" r="1.5"/><path d="M10 20v2M12 20v2M14 20v2"/>',
  person: '<circle cx="12" cy="7" r="4"/><path d="M4 21v-1a8 8 0 0 1 16 0v1"/>',
};

function Icon({ name, size = 20, sw = 1.7, style, className }) {
  return (
    <svg width={size} height={size} viewBox="0 0 24 24" fill="none"
    stroke="currentColor" strokeWidth={sw} strokeLinecap="round" strokeLinejoin="round"
    className={className} style={style} aria-hidden="true"
    dangerouslySetInnerHTML={{ __html: ICON_PATHS[name] || "" }} />
  );
}

// ── Decorative flourish — a thin filigree divider ───────────────────────────
function Flourish({ w = 120 }) {
  return (
    <svg className="flourish" width={w} height="14" viewBox="0 0 120 14" fill="none"
    stroke="currentColor" strokeWidth="1" aria-hidden="true">
      <path d="M2 7h36" /><path d="M118 7H82" />
      <path d="M44 7c0-3 3-3 3 0s-3 3-3 0" /><path d="M76 7c0 3-3 3-3 0s3-3 3 0" />
      <path d="M50 7h20" /><circle cx="60" cy="7" r="2.4" />
    </svg>
  );
}

// ── Buttons ──────────────────────────────────────────────────────────────────
function Button({ children, onClick, variant = "ghost", icon, size = "md", title, type = "button", style }) {
  return (
    <button type={type} className={`btn btn-${variant} btn-${size}`} onClick={onClick} title={title} style={style}>
      {icon && <Icon name={icon} size={size === "sm" ? 15 : 17} sw={1.9} />}
      {children && <span>{children}</span>}
    </button>
  );
}

function IconButton({ name, onClick, title, danger, size = 16, active }) {
  return (
    <button type="button" className={`icon-btn${danger ? " danger" : ""}${active ? " active" : ""}`} onClick={onClick} title={title} aria-label={title}>
      <Icon name={name} size={size} sw={1.8} />
    </button>
  );
}

// ── Editable fields (contentEditable-free; auto-growing textarea / input) ────
function EditableLine({ value, placeholder, onChange, className, big, wrap, ...rest }) {
  const fitRef = React.useCallback((el) => {
    if (!el) return;
    el.style.height = "auto";
    el.style.height = el.scrollHeight + "px";
  }, []);
  if (wrap) {
    return (
      <textarea
        ref={fitRef}
        className={`ed-line${big ? " ed-line-big" : ""} ${className || ""}`}
        value={value || ""}
        placeholder={placeholder}
        rows={1}
        style={{ resize: "none", overflow: "hidden" }}
        onChange={(e) => { onChange(e.target.value); fitRef(e.currentTarget); }}
        onInput={(e) => fitRef(e.currentTarget)}
        onKeyDown={(e) => { if (e.key === "Enter") e.preventDefault(); }}
        spellCheck={false}
        {...rest} />
    );
  }
  return (
    <input
      className={`ed-line${big ? " ed-line-big" : ""} ${className || ""}`}
      value={value || ""}
      placeholder={placeholder}
      onChange={(e) => onChange(e.target.value)}
      spellCheck={false}
      {...rest} />
  );
}

const BLOCK_START = /^(#{1,6} |[-*+] |\d+\. |>|```|~~~)/;

function renderableMarkdown(text) {
  // Pre-process [[Card Name]] wiki-links → inline HTML span before marked runs
  text = text.replace(/\[\[([^\]]+)\]\]/g, (_, name) => {
    const esc = name.replace(/&/g, '&amp;').replace(/"/g, '&quot;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
    return `<span class="card-link" data-card-title="${esc}">${esc}</span>`;
  });
  const lines = text.split('\n');
  const withHardBreaks = lines.map((line, i) => {
    const next = lines[i + 1] ?? '';
    // Blank line, or next line is blank — no hard break (paragraph boundary)
    if (!line.trim() || !next.trim()) return line;
    // This line or the next is a block-level element — don't interfere
    if (BLOCK_START.test(line.trimStart()) || BLOCK_START.test(next.trimStart())) return line;
    return line.trimEnd() + '  '; // double trailing space = hard line break
  }).join('\n');

  // Preserve blank lines as visible spacers. End with \n\n so the <br> HTML block is
  // properly terminated and the following text starts a new paragraph (otherwise marked
  // absorbs the text into the HTML block and double-spaces are never processed).
  return withHardBreaks.replace(/\n{2,}/g, m => {
    const brs = Math.max(1, m.length - 2);
    return '\n\n' + '<br>\n'.repeat(brs - 1) + '<br>\n\n';
  });
}

function EditableArea({ value, placeholder, onChange, className, minRows = 2, highlight, onCardLinkClick, cardIndex, ...rest }) {
  const [focused, setFocused] = React.useState(false);
  const [suggest, setSuggest] = React.useState(null);
  const focusPending = React.useRef(false);
  const taRef = React.useRef(null);

  const textareaRef = React.useCallback((el) => {
    taRef.current = el;
    if (el && focusPending.current) {
      focusPending.current = false;
      el.focus();
      const len = (el.value || "").length;
      el.selectionStart = el.selectionEnd = len;
      el.style.height = "auto";
      el.style.height = el.scrollHeight + "px";
    }
  }, []);

  const fit = React.useCallback((el) => {
    if (!el) return;
    el.style.height = "auto";
    el.style.height = el.scrollHeight + "px";
  }, []);

  const checkSuggest = (ta) => {
    if (!cardIndex || !cardIndex.size) { setSuggest(null); return; }
    const cursor = ta.selectionStart;
    const text = ta.value;
    const textBefore = text.slice(0, cursor);
    // Don't suggest inside [[...]]
    const lastOpen = textBefore.lastIndexOf('[[');
    const lastClose = textBefore.lastIndexOf(']]');
    if (lastOpen !== -1 && lastOpen > lastClose) { setSuggest(null); return; }
    const lo = textBefore.toLowerCase();
    for (const [key, entry] of cardIndex) {
      if (lo.endsWith(key)) {
        const start = cursor - key.length;
        setSuggest((prev) => {
          if (prev && prev.title === entry.title && prev.start === start) return prev;
          return { title: entry.title, start, end: cursor };
        });
        return;
      }
    }
    setSuggest(null);
  };

  const applyLink = () => {
    if (!suggest) return;
    const { title, start, end } = suggest;
    const before = (value || '').slice(0, start);
    const after = (value || '').slice(end);
    const newCursor = start + title.length + 4; // past `[[title]]`
    onChange(before + `[[${title}]]` + after);
    setSuggest(null);
    requestAnimationFrame(() => {
      if (taRef.current) {
        taRef.current.selectionStart = taRef.current.selectionEnd = newCursor;
      }
    });
  };

  const handleKeyDown = (e) => {
    if (e.key !== "Enter") return;
    const ta = e.currentTarget;
    const pos = ta.selectionStart;
    const text = ta.value;
    const lineStart = text.lastIndexOf("\n", pos - 1) + 1;
    const line = text.slice(lineStart, pos);
    if (line === "- ") {
      e.preventDefault();
      onChange(text.slice(0, lineStart) + text.slice(pos));
      requestAnimationFrame(() => {
        ta.selectionStart = ta.selectionEnd = lineStart;
        fit(ta);
      });
    } else if (line.startsWith("- ")) {
      e.preventDefault();
      const next = text.slice(0, pos) + "\n- " + text.slice(pos);
      onChange(next);
      requestAnimationFrame(() => {
        ta.selectionStart = ta.selectionEnd = pos + 3;
        fit(ta);
      });
    }
  };

  if (!focused && value && value.trim()) {
    let html = window.marked.parse(renderableMarkdown(value));
    if (highlight) {
      const esc = highlight.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
      html = html.replace(
        new RegExp(`(<[^>]*>)|(${esc})`, 'gi'),
        (m, tag, word) => tag ? tag : `<mark class="run-hl">${word}</mark>`
      );
    }
    return (
      <div
        className={`ed-area-display ${className || ""}`}
        tabIndex={0}
        onMouseDown={(e) => {
          if (onCardLinkClick) {
            const link = e.target.closest('.card-link');
            if (link) e.preventDefault(); // prevent focus change so edit mode doesn't open
          }
        }}
        onClick={(e) => {
          if (onCardLinkClick) {
            const link = e.target.closest('.card-link');
            if (link) { onCardLinkClick(link.dataset.cardTitle); return; }
          }
          focusPending.current = true; setFocused(true);
        }}
        onFocus={() => { focusPending.current = true; setFocused(true); }}
        dangerouslySetInnerHTML={{ __html: html }} />
    );
  }

  return (
    <div style={{ position: "relative" }}>
      <textarea
        ref={textareaRef}
        className={`ed-area ${className || ""}`}
        value={value || ""}
        placeholder={placeholder}
        rows={minRows}
        onChange={(e) => { onChange(e.target.value); fit(e.currentTarget); if (cardIndex) checkSuggest(e.currentTarget); }}
        onKeyDown={handleKeyDown}
        onInput={(e) => fit(e.currentTarget)}
        onFocus={() => setFocused(true)}
        onBlur={() => { setFocused(false); setSuggest(null); }}
        {...rest} />
      {suggest && (
        <div className="card-link-suggest" onMouseDown={(e) => e.preventDefault()}>
          <button type="button" className="card-link-suggest-btn" onClick={applyLink}>
            Link to &#8220;{suggest.title}&#8221;
          </button>
          <button type="button" className="card-link-suggest-dismiss" onClick={() => setSuggest(null)}>✕</button>
        </div>
      )}
    </div>
  );
}

// ── Aspects editor — chips of evocative descriptors ─────────────────────────
function AspectsEditor({ value, onChange, placeholder }) {
  const list = Array.isArray(value) ? value : [];
  const [draft, setDraft] = React.useState("");
  const add = () => {
    const v = draft.trim();
    if (!v) return;
    onChange([...list, v]);
    setDraft("");
  };
  return (
    <div className="aspects">
      {list.map((a, i) =>
        <span className="aspect-chip" key={i}>
          {a}
          <button type="button"
            style={{ display:"flex", alignItems:"center", justifyContent:"center", width:18, height:18, minWidth:18, minHeight:18, flexShrink:0, background:"none", border:0, padding:0, opacity:0.55, borderRadius:"50%", cursor:"pointer", color:"inherit" }}
            onClick={() => onChange(list.filter((_, j) => j !== i))} aria-label="Remove aspect">
            <Icon name="x" size={11} sw={2.4} />
          </button>
        </span>
      )}
      <input
        className="aspect-input"
        value={draft}
        placeholder={list.length ? "add aspect…" : placeholder || "add aspect…"}
        onChange={(e) => setDraft(e.target.value)}
        onKeyDown={(e) => { if (e.key === "Enter" || e.key === ",") { e.preventDefault(); add(); } }}
        onBlur={add} />
    </div>
  );
}

// ── Modal shell ──────────────────────────────────────────────────────────────
function Modal({ title, onClose, children, width = 460 }) {
  React.useEffect(() => {
    const onKey = (e) => { if (e.key === "Escape") onClose(); };
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, [onClose]);
  return ReactDOM.createPortal(
    <div className="modal-scrim" onMouseDown={onClose}>
      <div className="modal" style={{ maxWidth: width }} onMouseDown={(e) => e.stopPropagation()}>
        <div className="modal-hd">
          <h3>{title}</h3>
          <IconButton name="x" onClick={onClose} title="Close" size={18} />
        </div>
        <div className="modal-body">{children}</div>
      </div>
    </div>,
    document.querySelector('.app') || document.body
  );
}

// ── ConfirmDelete: trash button → modal confirmation ──────────────────────────
function ConfirmDelete({ onConfirm, label = "Delete" }) {
  const [open, setOpen] = React.useState(false);
  return (
    <>
      <button type="button" className="icon-btn danger" title={label}
        onClick={(e) => { e.stopPropagation(); setOpen(true); }}>
        <Icon name="trash" size={16} sw={1.8} />
      </button>
      {open && (
        <Modal title="Delete?" onClose={() => setOpen(false)} width={320}>
          <div className="confirm-modal-body">
            <p className="confirm-modal-msg">This can't be undone.</p>
            <div className="confirm-modal-btns">
              <button type="button" className="btn btn-sm btn-ghost" onClick={() => setOpen(false)}>Cancel</button>
              <button type="button" className="btn btn-sm btn-danger"
                onClick={() => { setOpen(false); onConfirm(); }}>Delete</button>
            </div>
          </div>
        </Modal>
      )}
    </>
  );
}

function NotesSectionImg({ src, size = "thumb", onRemove, onSizeChange }) {
  const [open, setOpen] = React.useState(false);
  const [blobUrl, setBlobUrl] = React.useState(null);

  React.useEffect(() => {
    if (!open || !src) return;
    const isData = src.startsWith("data:");
    const url = isData ? window.dataUriToBlobUrl(src) : src;
    setBlobUrl(url);
    return () => { if (isData) URL.revokeObjectURL(url); setBlobUrl(null); };
  }, [open, src]);

  React.useEffect(() => {
    if (!open) return;
    const onKey = (e) => { if (e.key === "Escape") setOpen(false); };
    document.addEventListener("keydown", onKey);
    return () => document.removeEventListener("keydown", onKey);
  }, [open]);

  return (
    <>
      <div className="notes-section-img" onClick={() => setOpen(true)}>
        <img src={src} alt="" />
        <div className="notes-section-img-controls" onClick={(e) => e.stopPropagation()}>
          <div className="notes-section-img-sizes">
            {[["thumb", "S", "Thumbnail"], ["medium", "M", "Medium"], ["full", "L", "Full image"]].map(([s, label, title]) => (
              <button key={s} className={`notes-img-size-btn${size === s ? " active" : ""}`}
                onClick={() => onSizeChange(s)} title={title}>
                {label}
              </button>
            ))}
          </div>
          <button className="notes-section-img-remove" title="Remove image" onClick={onRemove}>
            <Icon name="trash" size={11} />
          </button>
        </div>
      </div>
      {open && (blobUrl || !src.startsWith("data:")) && (
        <div className="img-lightbox" onClick={() => setOpen(false)}>
          <img src={open && !src.startsWith("data:") ? src : blobUrl} alt=""
            className="img-lightbox-img" onClick={(e) => e.stopPropagation()} />
        </div>
      )}
    </>
  );
}

function NotesSections({ sections, onChange, bodyPlaceholder, ctx = {}, highlight }) {
  const [imageModalId, setImageModalId] = React.useState(null);
  const imageModalSection = (sections || []).find((s) => s.id === imageModalId) || null;

  // onChange is called with an updater function so every edit derives from the
  // freshest section list. Passing a pre-computed array here would let a second
  // edit that fires before React commits clobber the first (lost/scrambled text,
  // sections vanishing). The functional form threads through to App's setState.
  const add = () =>
    onChange((prev) => [...(prev || []), { id: window.RPG.uid(), title: "", body: "", image: null, imageSize: "thumb" }]);

  // A focused EditableArea body collapses from <textarea> to a rendered-markdown
  // <div> on blur, which shifts everything below it. If a click on one of these
  // controls were allowed to blur the body first, that reflow would move the
  // button out from under the pointer mid-click — the click misses (add does
  // nothing) or lands on a neighbouring control (e.g. delete, wiping the section).
  // preventDefault on mousedown keeps focus where it is, so no reflow happens and
  // the click lands on the intended button. The click handler still fires.
  const keepFocus = (e) => e.preventDefault();
  const update = (id, patch) =>
    onChange((prev) => (prev || []).map((s) => (s.id === id ? { ...s, ...patch } : s)));
  const remove = (id) => {
    const sec = (sections || []).find((s) => s.id === id);
    if (sec?.image && window.RPGStorage) window.RPGStorage.remove(sec.image);
    onChange((prev) => (prev || []).filter((s) => s.id !== id));
  };

  return (
    <div className="notes-sections">
      {(sections || []).map((sec) => (
        <div key={sec.id} className={`notes-section-card${sec.image ? ` nsc--${sec.imageSize || "thumb"}` : ""}`}>
          <div className="notes-section-tools" onMouseDown={keepFocus}>
            <IconButton name="image" size={14} title="Add / change image" active={!!sec.image}
              onClick={() => setImageModalId(sec.id)} />
            <IconButton name="trash" size={14} danger title="Delete section"
              onClick={() => remove(sec.id)} />
          </div>
          <div className="nsc-text">
            <EditableLine value={sec.title} placeholder="Section title…" wrap
              className="notes-section-title"
              onChange={(v) => update(sec.id, { title: v })} />
            <EditableArea value={sec.body} placeholder={bodyPlaceholder || "Notes…"}
              className="notes-section-body"
              highlight={highlight}
              onChange={(v) => update(sec.id, { body: v })} />
          </div>
          {sec.image && (
            <NotesSectionImg src={sec.image} size={sec.imageSize || "thumb"}
              onSizeChange={(sz) => update(sec.id, { imageSize: sz })}
              onRemove={() => {
                if (window.RPGStorage) window.RPGStorage.remove(sec.image);
                update(sec.id, { image: null });
              }} />
          )}
        </div>
      ))}
      <button type="button" className="notes-add-btn" onMouseDown={keepFocus} onClick={add}>
        <Icon name="plus" size={14} /> Add section
      </button>
      {imageModalSection && (
        <window.ImageModal
          item={imageModalSection}
          step={{ name: "notes", itemNoun: "section" }}
          ctx={ctx}
          onUse={async (url) => {
            const stored = window.RPGStorage ? await window.RPGStorage.ensureUploaded(url) : url;
            if (imageModalSection.image && imageModalSection.image !== stored && window.RPGStorage)
              window.RPGStorage.remove(imageModalSection.image);
            update(imageModalSection.id, { image: stored });
            setImageModalId(null);
          }}
          onClose={() => setImageModalId(null)} />
      )}
    </div>
  );
}

Object.assign(window, {
  Icon, Flourish, Button, IconButton,
  EditableLine, EditableArea, AspectsEditor, Modal, ConfirmDelete,
  NotesSections,
  ICON_PATHS,
  renderableMarkdown,
});
