/* ============================================================
   Article environment — full-page reader + inline editor.
   Six regions: (1) title+desc, (2) linked/alternative media,
   (3) body, (4) sources, (5) next article, (6) side metadata
   (date, read time, rating, tags, AI-transparency, feedback).
   Plus a multi-sensory media panel (video / voice / AI voice / music).
   ============================================================ */
const { useState: uSa2, useEffect: uEa2, useRef: uRa2, useMemo: uMa2 } = React;

/* ---------- AI transparency badge ---------- */
const AI_LABEL = {
  generated:  { label: "AI-created", note: "Created with substantial AI assistance, reviewed by the author." },
  augmented:  { label: "AI-augmented", note: "Written by the author, with AI used for drafting or editing." },
  translated: { label: "AI-translated", note: "Originally written in another language, translated with AI." },
};
function AiBadge({ mode, compact }) {
  if (!mode || !AI_LABEL[mode]) return null;
  const a = AI_LABEL[mode];
  return (
    <span className={"ai-badge" + (compact ? " compact" : "")} title={a.note}>
      <span className="ai-spark" aria-hidden="true">✦</span>
      {a.label}
    </span>
  );
}

/* ---------- simulated media player (voice / music) ---------- */
function MiniPlayer({ kind, label, onClose }) {
  const [playing, setPlaying] = uSa2(true);
  const [t, setT] = uSa2(0);
  const dur = kind === "music" ? 184 : 326;
  uEa2(() => {
    if (!playing) return;
    const id = setInterval(() => setT(x => (x + 1 >= dur ? (clearInterval(id), dur) : x + 1)), 1000);
    return () => clearInterval(id);
  }, [playing]);
  const fmt = (s) => `${Math.floor(s / 60)}:${String(s % 60).padStart(2, "0")}`;
  const bars = uMa2(() => Array.from({ length: 56 }, () => 0.25 + Math.random() * 0.75), []);
  return (
    <div className="mini-player">
      <button className="mp-play" onClick={() => setPlaying(p => !p)}><Icon name={playing ? "close" : "play"} size={playing ? 16 : 20} /></button>
      <div className="mp-main">
        <div className="row between" style={{ marginBottom: 5 }}>
          <span className="mp-title">{kind === "music" ? "♪ " : ""}{label}</span>
          <span className="mp-time mono">{fmt(t)} / {fmt(dur)}</span>
        </div>
        <div className="mp-wave">
          {bars.map((h, i) => <span key={i} style={{ height: `${h * 100}%`, opacity: i / bars.length <= t / dur ? 1 : .28 }} />)}
        </div>
      </div>
      <span className="mp-demo">demo</span>
      <button className="iconbtn" style={{ flex: "none" }} onClick={onClose}><Icon name="close" size={15} /></button>
    </div>
  );
}

/* ---------- AI read-aloud (real Web Speech API) ---------- */
function useReadAloud() {
  const [speaking, setSpeaking] = uSa2(false);
  const supported = typeof window !== "undefined" && "speechSynthesis" in window;
  const speak = (text) => {
    if (!supported) return;
    window.speechSynthesis.cancel();
    const u = new SpeechSynthesisUtterance(text.slice(0, 4500));
    u.rate = 1; u.pitch = 1;
    u.onend = () => setSpeaking(false);
    u.onerror = () => setSpeaking(false);
    window.speechSynthesis.speak(u);
    setSpeaking(true);
  };
  const stop = () => { if (supported) window.speechSynthesis.cancel(); setSpeaking(false); };
  uEa2(() => () => { if (supported) window.speechSynthesis.cancel(); }, []);
  return { supported, speaking, speak, stop };
}

/* ---------- multi-sensory media panel ---------- */
const ALT_META = {
  video:   { icon: "play",    label: "Watch the video" },
  read:    { icon: "edit",    label: "Read the article" },
  podcast: { icon: "msg",     label: "Listen to the podcast" },
  page:    { icon: "compass", label: "Open" },
  voice:   { icon: "msg",     label: "Listen — my voice" },
  aivoice: { icon: "bell",    label: "AI read-aloud" },
  music:   { icon: "compass", label: "Reading soundtrack" },
};
function AltMediaPanel({ item, bodyText }) {
  const { db } = useStore();
  const { go } = useNav();
  const alt = item.alt || [];
  const [player, setPlayer] = uSa2(null);   // { kind, label, src }
  const read = useReadAloud();
  if (alt.length === 0) return null;

  const NAV_KINDS = ["video", "read", "podcast", "page"];
  const altSrc = (a) => { if (a.url) return a.url; const m = window.assetById ? window.assetById(db, a.assetId) : null; return m ? (window.assetSrc ? window.assetSrc(m) : m.url) : ""; };
  const targetOf = (a) => a.kind === "page" ? (db.otherPages || []).find(p => p.id === a.ref) : db.content.find(c => c.ref === a.ref);
  const refRoute = (a) => {
    if (a.kind === "page") { const p = (db.otherPages || []).find(x => x.id === a.ref); return p ? p.route : null; }
    const target = db.content.find(c => c.ref === a.ref);
    if (!target) return null;
    const sec = db.sections.find(s => s.kind === target.type);
    return sec ? `${sec.route}/${target.ref}` : null;
  };
  const onChip = (a) => {
    if (NAV_KINDS.includes(a.kind)) { const r = refRoute(a); if (r) go(r); return; }
    if (a.kind === "aivoice") { read.speaking ? read.stop() : read.speak(bodyText); return; }
    setPlayer(p => (p && p.kind === a.kind) ? null : { kind: a.kind, label: a.label || ALT_META[a.kind].label, src: altSrc(a) });
  };

  return (
    <div className="altmedia">
      <div className="am-head">
        <span className="am-kicker"><Icon name="globe" size={13} /> Experience this your way</span>
        <span className="dim mono am-sub">same story · different senses</span>
      </div>
      <div className="am-chips">
        {alt.map((a, i) => {
          const m = ALT_META[a.kind] || { icon: "arrow", label: a.label };
          const tgt = NAV_KINDS.includes(a.kind) ? targetOf(a) : null;
          const label = a.label || (tgt && (tgt.title || tgt.label)) || m.label;
          const active = (a.kind === "aivoice" && read.speaking) || (player && player.kind === a.kind);
          return (
            <button key={i} className={"am-chip" + (active ? " on" : "")} onClick={() => onChip(a)}>
              <span className="am-ic">{tgt && tgt.cover ? <img src={tgt.cover} alt="" style={{ width: "100%", height: "100%", objectFit: "cover", borderRadius: "inherit" }} /> : <Icon name={m.icon} size={18} />}</span>
              <span className="am-l">{label}</span>
              {NAV_KINDS.includes(a.kind) && <Icon name="arrow" size={14} />}
              {a.kind === "aivoice" && <span className="am-state">{read.speaking ? "stop" : read.supported ? "play" : "n/a"}</span>}
            </button>
          );
        })}
      </div>
      {player && (player.src
        ? <div className="am-audio"><span className="am-audio-l">{player.label}</span><audio className="audio-player" controls autoPlay src={player.src} /><button className="iconbtn" onClick={() => setPlayer(null)}><Icon name="close" size={15} /></button></div>
        : <MiniPlayer kind={player.kind} label={player.label} onClose={() => setPlayer(null)} />)}
    </div>
  );
}

/* ============================================================
   Body block model — reader + editor
   ============================================================ */
function BlockReader({ blocks }) {
  return (
    <div className="article-body">
      {(blocks || []).map(b => {
        if (b.type === "h") return b.level === 1
          ? <h2 key={b.id} data-bid={b.id} className="ab-h ab-h1">{b.text}</h2>
          : <h3 key={b.id} data-bid={b.id} className="ab-h">{b.text}</h3>;
        if (b.type === "quote") return (
          <blockquote key={b.id} data-bid={b.id} className={"pullquote" + (b.bar ? " has-bar" : "")}>
            <span className="pq-text"><span className="pq-mark open" aria-hidden="true">“</span>{renderInline(b.text)}<span className="pq-mark close" aria-hidden="true">”</span></span>
            {b.author && <cite className="pq-author">— {b.author}</cite>}
          </blockquote>
        );
        if (b.type === "callout") return (
          <div key={b.id} data-bid={b.id} className={"ab-callout tone-" + (b.tone || "info")}>
            <span className="cal-ic"><Icon name={b.tone === "warn" ? "bell" : b.tone === "tip" ? "star" : "globe"} size={18} /></span>
            <div className="cal-body">{renderInline(b.text)}</div>
          </div>
        );
        if (b.type === "table") {
          const rows = b.rows || [];
          return (
            <div key={b.id} data-bid={b.id} className="ab-table-wrap">
              <table className="ab-table">
                {b.header && rows.length > 0 && <thead><tr>{rows[0].map((c, ci) => <th key={ci}>{renderInline(c)}</th>)}</tr></thead>}
                <tbody>
                  {rows.slice(b.header ? 1 : 0).map((row, ri) => <tr key={ri}>{row.map((c, ci) => <td key={ci}>{renderInline(c)}</td>)}</tr>)}
                </tbody>
              </table>
            </div>
          );
        }
        if (b.type === "list") return (
          <ul key={b.id} data-bid={b.id} className="ab-list">
            {(b.text || "").split("\n").map(s => s.replace(/^[-*]\s*/, "").trim()).filter(Boolean).map((li, i) => <li key={i}>{renderInline(li)}</li>)}
          </ul>
        );
        if (b.type === "hr") return <hr key={b.id} data-bid={b.id} className="ab-sep" />;
        if (b.type === "code") return <pre key={b.id} data-bid={b.id} className="ab-code"><code>{b.code || b.text}</code></pre>;
        if (b.type === "embed") return <div key={b.id} data-bid={b.id} className="ab-embed" dangerouslySetInnerHTML={{ __html: b.html || "" }} />;
        if (b.type === "img") return b.src
          ? <figure key={b.id} data-bid={b.id} className="ab-fig"><img src={b.src} alt={b.caption || ""} /><figcaption>{b.caption}</figcaption></figure>
          : <div key={b.id} data-bid={b.id}><Placeholder label={b.caption || "figure / diagram"} tag="image" style={{ width: "100%", aspectRatio: "16/9", margin: "8px 0" }} /></div>;
        return <div key={b.id} data-bid={b.id}><RichText as="p" text={b.text} /></div>;
      })}
    </div>
  );
}

const BLOCK_KINDS = [
  { type: "h", label: "Heading", icon: "edit" },
  { type: "p", label: "Paragraph", icon: "msg" },
  { type: "list", label: "List", icon: "menu" },
  { type: "quote", label: "Quote", icon: "star" },
  { type: "callout", label: "Callout", icon: "bell" },
  { type: "table", label: "Table", icon: "chart" },
  { type: "img", label: "Image", icon: "compass" },
  { type: "code", label: "Code", icon: "chart" },
  { type: "embed", label: "Embed / HTML", icon: "globe" },
  { type: "hr", label: "Separator", icon: "down" },
];

function blankBlock(type) {
  return { id: uid("b"), type, text: "", code: "", html: "", src: null, caption: "", author: "", tone: "info", header: true, rows: type === "table" ? [["Column A", "Column B"], ["", ""]] : undefined, level: type === "h" ? 2 : undefined };
}

/* a small free-form syntax hint shown under text inputs */
function InlineHint() {
  return (
    <div className="dim mono inline-hint" style={{ fontSize: 11, marginTop: 5, lineHeight: 1.6 }}>
      <b>**bold**</b> · <b>__underline__</b> · <span style={{ color: "var(--accent-ink)" }}>[link](https://…)</span> · internal <span style={{ color: "var(--accent-ink)" }}>[Blog](/blog)</span> · <span style={{ borderBottom: "2px dotted var(--accent)" }}>[[term|definition]]</span> bubble · new line = line break
    </div>
  );
}

/* table editor — add / remove rows & columns, toggle header */
function TableEditor({ block, onChange }) {
  const rows = block.rows && block.rows.length ? block.rows : [["", ""], ["", ""]];
  const cols = rows[0] ? rows[0].length : 2;
  const setCell = (r, c, v) => { const n = rows.map(row => row.slice()); n[r][c] = v; onChange({ rows: n }); };
  const addRow = () => onChange({ rows: [...rows.map(r => r.slice()), Array.from({ length: cols }, () => "")] });
  const delRow = (r) => onChange({ rows: rows.length > 1 ? rows.filter((_, i) => i !== r) : rows });
  const addCol = () => onChange({ rows: rows.map(r => [...r, ""]) });
  const delCol = (c) => onChange({ rows: cols > 1 ? rows.map(r => r.filter((_, i) => i !== c)) : rows });
  return (
    <div className="table-editor">
      <div className="row gap-8" style={{ marginBottom: 8, flexWrap: "wrap" }}>
        <button type="button" className={"chip" + (block.header ? " on" : "")} onClick={() => onChange({ header: !block.header })}><Icon name="check" size={12} style={{ verticalAlign: "-2px", marginRight: 3 }} />Header row</button>
        <span className="grow" />
        <button type="button" className="btn ghost sm" onClick={addRow}><Icon name="plus" size={13} /> Row</button>
        <button type="button" className="btn ghost sm" onClick={addCol}><Icon name="plus" size={13} /> Column</button>
      </div>
      <div className="te-grid" style={{ overflowX: "auto" }}>
        <table className="te-table">
          <tbody>
            {rows.map((row, r) => (
              <tr key={r}>
                {row.map((cell, c) => (
                  <td key={c} className={block.header && r === 0 ? "te-head" : ""}>
                    <input value={cell} onChange={e => setCell(r, c, e.target.value)} placeholder={block.header && r === 0 ? "Heading" : "…"} />
                  </td>
                ))}
                <td className="te-rowtool"><button type="button" className="iconbtn sm danger" onClick={() => delRow(r)} title="delete row"><Icon name="trash" size={13} /></button></td>
              </tr>
            ))}
            <tr>
              {Array.from({ length: cols }).map((_, c) => (
                <td key={c} className="te-coltool"><button type="button" className="iconbtn sm danger" onClick={() => delCol(c)} title="delete column"><Icon name="trash" size={12} /></button></td>
              ))}
              <td />
            </tr>
          </tbody>
        </table>
      </div>
    </div>
  );
}

function BlockEditor({ blocks, onChange }) {
  const list = blocks || [];
  const setBlock = (i, patch) => onChange(list.map((b, j) => j === i ? { ...b, ...patch } : b));
  const move = (i, dir) => {
    const j = i + dir; if (j < 0 || j >= list.length) return;
    const n = list.slice(); const [m] = n.splice(i, 1); n.splice(j, 0, m); onChange(n);
  };
  const remove = (i) => onChange(list.filter((_, j) => j !== i));
  const add = (type) => onChange([...list, blankBlock(type)]);
  const addBelow = (i) => { const n = list.slice(); n.splice(i + 1, 0, blankBlock("p")); onChange(n); };
  const changeType = (i, type) => setBlock(i, { type, level: type === "h" ? (list[i].level || 2) : undefined });
  return (
    <div className="block-editor">
      {list.map((b, i) => (
        <div key={b.id} data-bid={b.id} className="be-block">
          <div className="be-bar">
            <select className="be-type-sel" value={b.type} onChange={e => changeType(i, e.target.value)} title="block type">
              {BLOCK_KINDS.map(k => <option key={k.type} value={k.type}>{k.label}</option>)}
            </select>
            {b.type === "h" && (
              <div className="row gap-8" style={{ marginLeft: 2 }}>
                {[1, 2].map(L => <button key={L} type="button" className={"chip" + ((b.level || 2) === L ? " on" : "")} onClick={() => setBlock(i, { level: L })}>H{L}</button>)}
              </div>
            )}
            <span className="grow" />
            <button className="iconbtn sm" onClick={() => move(i, -1)} disabled={i === 0}><Icon name="up" size={14} /></button>
            <button className="iconbtn sm" onClick={() => move(i, 1)} disabled={i === list.length - 1}><Icon name="down" size={14} /></button>
            <button className="iconbtn sm danger" onClick={() => remove(i)}><Icon name="trash" size={14} /></button>
          </div>
          {b.type === "img" ? (
            <div className="col gap-8">
              <ImageUpload value={b.src} onChange={v => setBlock(i, { src: v })} label="upload figure / diagram" aspect="16/9" />
              <input value={b.caption || ""} onChange={e => setBlock(i, { caption: e.target.value })} placeholder="Caption (optional)" />
            </div>
          ) : b.type === "code" ? (
            <textarea rows={4} className="mono" value={b.code || ""} onChange={e => setBlock(i, { code: e.target.value })} placeholder="paste code…" style={{ fontSize: 13 }} />
          ) : b.type === "embed" ? (
            <div className="col gap-8">
              <textarea rows={3} className="mono" value={b.html || ""} onChange={e => setBlock(i, { html: e.target.value })} placeholder="<iframe …>, any embed HTML, or a link to another media on the site" style={{ fontSize: 12 }} />
              <div className="dim mono" style={{ fontSize: 11 }}>Tip: paste an <b>&lt;iframe&gt;</b>, or an <b>&lt;a href=\"#/blog/…\"&gt;</b> link to another piece on the site.</div>
            </div>
          ) : b.type === "table" ? (
            <TableEditor block={b} onChange={patch => setBlock(i, patch)} />
          ) : b.type === "callout" ? (
            <div className="col gap-8">
              <div className="row gap-8">
                {[{ id: "info", l: "Info" }, { id: "tip", l: "Tip" }, { id: "warn", l: "Warning" }].map(o => (
                  <button key={o.id} type="button" className={"chip" + ((b.tone || "info") === o.id ? " on" : "")} onClick={() => setBlock(i, { tone: o.id })}>{o.l}</button>
                ))}
              </div>
              <textarea rows={3} value={b.text || ""} onChange={e => setBlock(i, { text: e.target.value })} placeholder="Highlight a tip, a warning or an aside…" />
              <InlineHint />
            </div>
          ) : b.type === "hr" ? (
            <div className="be-hr-note">— horizontal separator —</div>
          ) : b.type === "list" ? (
            <textarea rows={4} value={b.text || ""} onChange={e => setBlock(i, { text: e.target.value })} placeholder={"One item per line…\n- first point\n- second point"} />
          ) : b.type === "h" ? (
            <input value={b.text || ""} onChange={e => setBlock(i, { text: e.target.value })} placeholder="Heading…" style={{ fontFamily: "var(--font-display)", fontWeight: 800, fontSize: b.level === 1 ? 22 : 18 }} />
          ) : b.type === "quote" ? (
            <div className="col gap-8">
              <textarea rows={2} value={b.text || ""} onChange={e => setBlock(i, { text: e.target.value })} placeholder="“ A quote… ”" />
              <input value={b.author || ""} onChange={e => setBlock(i, { author: e.target.value })} placeholder="Author / source (optional)" />
              <div className="row gap-8" style={{ alignItems: "center" }}>
                <button type="button" className={"chip" + (b.bar ? " on" : "")} onClick={() => setBlock(i, { bar: !b.bar })}><Icon name="check" size={12} style={{ verticalAlign: "-2px", marginRight: 3 }} />Left accent bar</button>
                <span className="dim mono" style={{ fontSize: 11 }}>off = quote marks only</span>
              </div>
            </div>
          ) : (
            <div className="col gap-8">
              <textarea rows={4} value={b.text || ""} onChange={e => setBlock(i, { text: e.target.value })} placeholder="Write… (**bold**, __underline__, [link](url), [[term|definition]] supported)" />
              <InlineHint />
            </div>
          )}
          <button className="be-addbelow" onClick={() => addBelow(i)} title="add a block below"><Icon name="plus" size={12} /> add block below</button>
        </div>
      ))}
      <div className="be-add">
        <span className="dim mono" style={{ fontSize: 11, alignSelf: "center", marginRight: 2 }}>add block:</span>
        {BLOCK_KINDS.map(k => <button key={k.type} className="chip" onClick={() => add(k.type)}><Icon name={k.icon} size={12} style={{ verticalAlign: "-2px", marginRight: 3 }} />{k.label}</button>)}
      </div>
    </div>
  );
}

/* ---------- body <-> markup (export / import for API publishing) ---------- */
function bodyToMarkup(blocks) {
  return (blocks || []).map(b => {
    if (b.type === "h") return (b.level === 1 ? "# " : "## ") + (b.text || "");
    if (b.type === "quote") return "> " + (b.text || "") + (b.author ? `\n> — ${b.author}` : "");
    if (b.type === "callout") return ":::" + (b.tone || "info") + "\n" + (b.text || "") + "\n:::";
    if (b.type === "table") return (b.rows || []).map(r => "| " + r.join(" | ") + " |").join("\n");
    if (b.type === "list") return (b.text || "").split("\n").map(s => s.replace(/^[-*]\s*/, "").trim()).filter(Boolean).map(s => "- " + s).join("\n");
    if (b.type === "hr") return "---";
    if (b.type === "code") return "```\n" + (b.code || "") + "\n```";
    if (b.type === "embed") return "<embed>\n" + (b.html || "") + "\n</embed>";
    if (b.type === "img") return `![${b.caption || ""}](${b.src || ""})`;
    return b.text || "";
  }).join("\n\n");
}
function markupToBody(text) {
  const out = [];
  const chunks = (text || "").split(/\n{2,}/);
  chunks.forEach(raw => {
    const block = raw.replace(/\r/g, "");
    const t = block.trim();
    if (!t) return;
    if (/^```/.test(t)) { out.push({ ...blankBlock("code"), code: t.replace(/^```[^\n]*\n?/, "").replace(/```$/, "").trim() }); return; }
    if (/^:::/.test(t)) { const tn = t.match(/^:::(\w+)/); out.push({ ...blankBlock("callout"), tone: tn ? tn[1] : "info", text: t.replace(/^:::\w*\s*/, "").replace(/\n?:::\s*$/, "").trim() }); return; }
    if (/^\|.*\|/.test(t)) {
      const rows = t.split("\n").map(l => l.trim()).filter(Boolean)
        .map(l => l.replace(/^\|/, "").replace(/\|$/, "").split("|").map(c => c.trim()))
        .filter(r => !r.every(c => /^:?-+:?$/.test(c)));
      out.push({ ...blankBlock("table"), header: true, rows: rows.length ? rows : [["", ""]] }); return;
    }
    if (/^<embed>/i.test(t)) { out.push({ ...blankBlock("embed"), html: t.replace(/^<embed>\s*/i, "").replace(/<\/embed>\s*$/i, "").trim() }); return; }
    if (/^---+$/.test(t)) { out.push(blankBlock("hr")); return; }
    if (/^!\[/.test(t)) { const m = t.match(/^!\[(.*?)\]\((.*?)\)/); out.push({ ...blankBlock("img"), caption: m ? m[1] : "", src: m && m[2] ? m[2] : null }); return; }
    if (/^#\s/.test(t)) { out.push({ ...blankBlock("h"), level: 1, text: t.replace(/^#\s+/, "") }); return; }
    if (/^##\s/.test(t)) { out.push({ ...blankBlock("h"), level: 2, text: t.replace(/^##\s+/, "") }); return; }
    if (/^>\s/.test(t)) {
      const lines = t.split("\n").map(l => l.replace(/^>\s?/, ""));
      const authorLine = lines.find(l => /^—|^-\s/.test(l));
      const body = lines.filter(l => l !== authorLine).join(" ").trim();
      out.push({ ...blankBlock("quote"), text: body, author: authorLine ? authorLine.replace(/^[—-]\s*/, "") : "" });
      return;
    }
    if (/^[-*]\s/.test(t)) { out.push({ ...blankBlock("list"), text: t.split("\n").map(l => l.replace(/^[-*]\s*/, "")).join("\n") }); return; }
    out.push({ ...blankBlock("p"), text: t });
  });
  return out.length ? out : [blankBlock("p")];
}

function ExportImportModal({ blocks, onClose, onImport }) {
  const [text, setText] = uSa2(() => bodyToMarkup(blocks));
  const [copied, setCopied] = uSa2(false);
  const copy = () => { try { navigator.clipboard.writeText(text); setCopied(true); setTimeout(() => setCopied(false), 1400); } catch (e) {} };
  return (
    <Modal title="Export / import body" onClose={onClose}
      footer={<>
        <button className="btn ghost" style={{ marginRight: "auto" }} onClick={copy}><Icon name="save" size={15} /> {copied ? "Copied!" : "Copy markup"}</button>
        <button className="btn ghost" onClick={onClose}>Close</button>
        <button className="btn accent" onClick={() => { onImport(markupToBody(text)); onClose(); }}><Icon name="check" size={16} /> Apply to article</button>
      </>}>
      <p className="dim" style={{ fontSize: 13, marginTop: -4 }}>A simple markup an API or another tool can read &amp; write. <b># / ##</b> headings, <b>-</b> lists, <b>&gt;</b> quotes, <b>---</b> separators, <code>```</code> code, <b>![caption](url)</b> images, <b>&lt;embed&gt;…&lt;/embed&gt;</b> HTML.</p>
      <textarea rows={14} className="mono" style={{ fontSize: 12.5, lineHeight: 1.5 }} value={text} onChange={e => setText(e.target.value)} />
    </Modal>
  );
}

Object.assign(window, { ExportImportModal, bodyToMarkup, markupToBody, TableEditor, InlineHint });

/* ---------- sources editor ---------- */
function SourcesEditor({ sources, onChange }) {
  const list = sources || [];
  const set = (i, k, v) => onChange(list.map((s, j) => j === i ? { ...s, [k]: v } : s));
  return (
    <div className="col gap-8">
      {list.map((s, i) => (
        <div className="field-row" key={i}>
          <input placeholder="Label" value={s.label} onChange={e => set(i, "label", e.target.value)} />
          <input placeholder="https://…" value={s.url} onChange={e => set(i, "url", e.target.value)} />
          <button className="iconbtn danger" style={{ flex: "none" }} onClick={() => onChange(list.filter((_, j) => j !== i))}><Icon name="trash" size={15} /></button>
        </div>
      ))}
      <button className="btn ghost sm" style={{ alignSelf: "flex-start" }} onClick={() => onChange([...list, { label: "", url: "" }])}><Icon name="plus" size={14} /> Add source</button>
    </div>
  );
}

/* ---------- alternative-media editor ---------- */
const ALT_KINDS = [
  { kind: "video", label: "Video" }, { kind: "read", label: "Read (article)" },
  { kind: "podcast", label: "Podcast" }, { kind: "page", label: "Other page (game, app…)" },
  { kind: "voice", label: "My voice" }, { kind: "aivoice", label: "AI read-aloud" }, { kind: "music", label: "Music" },
];
const ALT_NEEDS_REF = ["video", "read", "podcast", "page"];
const ALT_NEEDS_ASSET = ["voice", "music"];
const ALT_DEFAULT_FOLDER = { voice: "readloud-blog", music: "soundtrack" };
function AltEditor({ alt, content, pages = [], item, onChange }) {
  const { db } = useStore();
  const list = alt || [];
  const [pick, setPick] = uSa2(null);   // index of the row awaiting an audio asset
  const set = (i, patch) => onChange(list.map((a, j) => j === i ? { ...a, ...patch } : a));
  const refsFor = (kind) => {
    if (kind === "video")   return content.filter(c => c.type === "video" && c.id !== item.id).map(c => ({ id: c.ref, title: c.title, cover: c.cover }));
    if (kind === "read")    return content.filter(c => c.type === "blog" && c.id !== item.id).map(c => ({ id: c.ref, title: c.title, cover: c.cover }));
    if (kind === "podcast") return content.filter(c => c.type === "podcast" && c.id !== item.id).map(c => ({ id: c.ref, title: c.title, cover: c.cover }));
    if (kind === "page")    return (pages || []).filter(p => p.kind !== "secret").map(p => ({ id: p.id, title: p.label, cover: null }));
    return [];
  };
  const targetOf = (a) => {
    if (a.kind === "page") return (pages || []).find(p => p.id === a.ref);
    return content.find(c => c.ref === a.ref);
  };
  return (
    <div className="col gap-8">
      {list.map((a, i) => {
        const opts = refsFor(a.kind);
        const tgt = ALT_NEEDS_REF.includes(a.kind) ? targetOf(a) : null;
        const m = ALT_META[a.kind] || { icon: "arrow" };
        return (
          <div key={i} className="alt-edit-row2">
            <span className="aer-thumb">
              {tgt && tgt.cover ? <img src={tgt.cover} alt="" /> : <Icon name={m.icon} size={16} />}
            </span>
            <div className="col gap-8" style={{ flex: 1, minWidth: 0 }}>
              <div className="row gap-8">
                <select value={a.kind} onChange={e => set(i, { kind: e.target.value, ref: "" })} style={{ flex: ".9" }}>
                  {ALT_KINDS.map(k => <option key={k.kind} value={k.kind}>{k.label}</option>)}
                </select>
                {ALT_NEEDS_REF.includes(a.kind) && (
                  <select value={a.ref || ""} onChange={e => set(i, { ref: e.target.value })} style={{ flex: 1 }}>
                    <option value="">{opts.length ? "— choose —" : "none available"}</option>
                    {opts.map(o => <option key={o.id} value={o.id}>{(o.title || "").slice(0, 32)}</option>)}
                  </select>
                )}
                <button className="iconbtn danger" style={{ flex: "none" }} onClick={() => onChange(list.filter((_, j) => j !== i))}><Icon name="trash" size={15} /></button>
              </div>
              <input placeholder={tgt ? `Label — optional (defaults to “${(tgt.title || "").slice(0, 24)}”)` : "Label shown on the reader's chip — optional"} value={a.label || ""} onChange={e => set(i, { label: e.target.value })} />
              {ALT_NEEDS_ASSET.includes(a.kind) && (
                <div className="row gap-8 wrap" style={{ alignItems: "center" }}>
                  <button type="button" className="chip" onClick={() => setPick(i)}><Icon name="compass" size={12} style={{ verticalAlign: "-2px", marginRight: 3 }} />{a.assetId ? "Change media" : "Search media"}</button>
                  {a.assetId && window.AssetRef && <window.AssetRef assetId={a.assetId} onClear={() => set(i, { assetId: undefined })} />}
                  {!a.assetId && <span className="dim mono" style={{ fontSize: 11 }}>no file linked — plays a demo</span>}
                </div>
              )}
            </div>
          </div>
        );
      })}
      <button className="btn ghost sm" style={{ alignSelf: "flex-start" }} onClick={() => onChange([...list, { kind: "voice", label: "" }])}><Icon name="plus" size={14} /> Link an alternative</button>
      {pick != null && window.MediaPicker && <window.MediaPicker type="audio" defaultFolder={ALT_DEFAULT_FOLDER[(list[pick] || {}).kind] || "all"} title="Search media" onPick={(id) => set(pick, { assetId: id })} onClose={() => setPick(null)} />}
    </div>
  );
}

Object.assign(window, { AiBadge, AltMediaPanel, MiniPlayer, BlockReader, BlockEditor, SourcesEditor, AltEditor, useReadAloud, AI_LABEL, ALT_META });
