// 學習教材 HTML 渲染：白話化、互動路徑、案例→通用原則
(function (global) {
  const KIND_LABEL = {
    overview: '課綱總覽', principleMap: '原則地圖', quiz: '練習題庫',
    category: '學習分類', case: '案例講解', principle: '投資原則',
    term: '名詞', company: '公司', episode: '單集',
  };

  const LEARN_PATHS = {
    market: {
      title: '我想知道：現在大環境適不適合加碼？',
      lead: '先別猜明天漲跌。用利率、通膨、就業、信用四條線，決定你該積極還是保守。',
      steps: [
        { title: '① 讀懂大環境在說什麼', body: '利率往哪走、通膨高不高、就業強不強——這三個決定「順風還是逆風」。', read: { kind: 'category', id: '總經與利率', label: '總經與利率（分類）' }, practice: { view: 'macro', label: '打開總經儀表板' } },
        { title: '② 用一套流程決定倉位', body: '不是滿倉或空手，而是「這週該偏進攻還是偏防守」。', read: { kind: 'case', id: '總經數據怎麼看', label: '案例：總經數據怎麼看' }, practice: { view: 'macro', label: '對照 CPI、非農卡片' } },
        { title: '③ 把原則記下來', body: '每次調倉都寫「為什麼」，避免事後用結果論合理化。', read: { kind: 'category', id: '交易與資金管理', label: '交易與資金管理' }, practice: { view: 'journal', label: '寫一筆復盤' } },
      ],
    },
    stock: {
      title: '我想知道：這家公司值不值得研究？',
      lead: '把財報、生意模式、估值、趨勢拆成檢查清單，重複用在新公司上。',
      steps: [
        { title: '① 財報在說什麼', body: '營收、毛利、EPS、財測——先會讀，再談貴不貴。', read: { kind: 'category', id: '財報基本功', label: '財報基本功' }, read2: { kind: 'case', id: 'NVIDIA財報怎麼看', label: '案例：財報怎麼看' }, practice: { view: 'stock', label: '個股工具 · 財報健檢' } },
        { title: '② 生意好不好、有沒有護城河', body: '數字背後是定價權與產業位置，別只看熱門題材。', read: { kind: 'category', id: '護城河與商業模式', label: '護城河與商業模式' }, practice: { view: 'stock', label: '個股工具 · 投資地圖' } },
        { title: '③ 用案例練「可重複的判斷」', body: 'NVDA、台積電只是例子；重點是抽出你下次也能用的問題。', read: { kind: 'case', id: 'NVIDIA決策複盤', label: '案例：決策複盤（框架）' }, practice: { view: 'stock', label: '查一檔你關心的股票' } },
      ],
    },
    trade: {
      title: '我想知道：這筆交易哪裡做對、哪裡做錯？',
      lead: '賺賠是結果；真正要檢查的是當初的理由、風險與紀律有沒有成立。',
      steps: [
        { title: '① 避開結果論', body: '賺了不代表判斷對，賠了也不代表一定錯——先分清楚。', read: { kind: 'principle', id: '原則九十六：結果論陷阱（Outcome Bias）', label: '原則：結果論陷阱' }, practice: { view: 'journal', label: '打開交易復盤' } },
        { title: '② 進出場要有依據', body: '進場理由、停損/減倉規則寫清楚，復盤才有東西可改。', read: { kind: 'category', id: '交易與資金管理', label: '交易與資金管理' }, practice: { view: 'journal', label: '新增一筆交易' } },
        { title: '③ 定期回頭對照原則', body: '把常犯的錯連回具體原則，下次遇到類似情境才改得動。', read: { kind: 'principleMap', id: '心法地圖', label: '原則地圖（分群索引）' }, practice: { view: 'learn', section: 'quiz', label: '做練習題' } },
      ],
    },
  };

  const PRINCIPLE_GROUPS = [
    { id: 'macro', label: '大局與倉位', re: /降息|升息|通膨|總經|倉位|現金|信用|利率|PMI|非農|CPI|風險偏好/ },
    { id: 'research', label: '研究與選股', re: /Capex|毛利率|財報|估值|定價權|護城河|供給|營收|EPS|財測|產業/ },
    { id: 'trade', label: '交易與紀律', re: /減倉|停損|結果論|賣|買|趨勢|紀律|倉位|觸發|弱|強|復盤/ },
    { id: 'mind', label: '心態與認知', re: /新聞|情緒|認知|耐心|時間|概率|陷阱|偏誤/ },
  ];

  function deEmmy(text) {
    return String(text || '')
      .replace(/Emmy 投資心法/g, '投資原則庫')
      .replace(/Emmy 投資台/g, '投資學習台')
      .replace(/\bEmmy\b/g, '講者')
      .replace(/110 條原則/g, '完整原則庫')
      .replace(/心法地圖/g, '原則地圖')
      .replace(/心法/g, '原則');
  }

  function cleanPrincipleTitle(title) {
    return deEmmy(String(title || ''))
      .replace(/^原則[^：:]+[：:]\s*/, '')
      .trim();
  }

  function extractWikiLinks(body) {
    const links = [];
    const re = /\[\[([^\]]+)\]\]/g;
    let m;
    while ((m = re.exec(String(body || '')))) links.push(m[1].trim());
    return links;
  }

  function resolveLinkTarget(raw, linkMap) {
    if (!raw || !linkMap) return null;
    const key = raw.trim();
    return linkMap[key] || linkMap[key.split('#').pop()] || linkMap[key.split('/').pop()] || null;
  }

  function extractPrinciples(note, linkMap, principles) {
    const out = [];
    const seen = new Set();
    const add = (p) => {
      if (!p || seen.has(p.id)) return;
      seen.add(p.id);
      out.push(p);
    };
    for (const raw of extractWikiLinks(note.body)) {
      const hit = resolveLinkTarget(raw, linkMap);
      if (hit && hit.kind === 'principle') {
        const p = (principles || []).find(x => x.id === hit.id);
        add(p || { id: hit.id, title: raw.split('#').pop() || raw });
      }
      if (/投資心法#|投資原則#|原則/.test(raw)) {
        const id = raw.includes('#') ? raw.split('#').slice(1).join('#') : raw;
        const p = (principles || []).find(x => x.id === id || x.title === id);
        add(p || { id, title: id });
      }
    }
    return out.slice(0, 12);
  }

  function leadFromNote(note) {
    if (note.summary) return deEmmy(note.summary);
    const body = deEmmy(note.body || '');
    for (const line of body.split('\n')) {
      let l = line.trim();
      if (!l || /^#/.test(l)) continue;
      if (l.startsWith('>')) l = l.replace(/^>\s?/, '');
      l = l.replace(/\[\[([^\]|]+)(\|[^\]]+)?\]\]/g, '$1').replace(/[*`]/g, '').trim();
      if (l.length > 12) return l.slice(0, 160);
    }
    return '';
  }

  function buildTocFromMarkdown(md) {
    const items = [];
    for (const line of String(md || '').split('\n')) {
      const m = line.match(/^(#{2,3})\s+(.+)$/);
      if (!m) continue;
      const level = m[1].length;
      const text = deEmmy(m[2].replace(/\[\[([^\]|]+)(\|([^\]]+))?\]\]/g, (_, a, _b, c) => c || a)).trim();
      const id = 'sec-' + items.length;
      items.push({ level, text, id });
    }
    return items.slice(0, 14);
  }

  function stripMd(text) {
    return deEmmy(String(text || ''))
      .replace(/```[\s\S]*?```/g, ' ')
      .replace(/\[\[([^\]|]+)(\|([^\]]+))?\]\]/g, (_, a, _b, c) => c || a)
      .replace(/\[([^\]]+)\]\([^)]+\)/g, '$1')
      .replace(/[#>*_`|]/g, '')
      .replace(/\s+/g, ' ')
      .trim();
  }

  function splitSections(md) {
    const sections = [];
    let cur = null;
    for (const line of String(md || '').split('\n')) {
      const h = line.match(/^(#{2,3})\s+(.+)$/);
      if (h) {
        if (cur && cur.lines.length) sections.push(cur);
        cur = { level: h[1].length, title: stripMd(h[2]), lines: [] };
      } else if (cur) {
        cur.lines.push(line);
      }
    }
    if (cur && cur.lines.length) sections.push(cur);
    return sections
      .map(s => ({ ...s, text: stripMd(s.lines.join('\n')) }))
      .filter(s => s.title && s.text.length > 20)
      .slice(0, 8);
  }

  function extractInsights(note) {
    const body = deEmmy(note.body || '');
    const lines = body.split('\n');
    const out = [];
    const seen = new Set();
    const push = (raw) => {
      let text = stripMd(raw)
        .replace(/^[-\d.)、\s]+/, '')
        .replace(/^一句話精華[:：]\s*/, '');
      if (text.length < 14 || seen.has(text)) return;
      seen.add(text);
      out.push(text.slice(0, 118));
    };
    for (const line of lines) {
      const t = line.trim();
      if (/^>\s*\S/.test(t) || /^[-*]\s+\S/.test(t) || /一句話精華/.test(t) || /^\*\*.+\*\*/.test(t)) push(t);
      if (out.length >= 5) break;
    }
    if (out.length < 3) {
      for (const s of splitSections(body)) {
        push(s.text);
        if (out.length >= 5) break;
      }
    }
    return out;
  }

  function buildRecallCards(note, sections, principles) {
    const title = cleanPrincipleTitle(note.title || note.id || '這篇筆記');
    const cards = [
      {
        q: `先不看內容，你會怎麼用一句話說明「${title}」？`,
        a: leadFromNote(note) || '抓出核心判斷，再用自己的話重說一次。',
      },
    ];
    sections.slice(0, 3).forEach(s => cards.push({
      q: `「${s.title}」真正要判斷的是什麼？`,
      a: s.text.slice(0, 150),
    }));
    principles.slice(0, 2).forEach(p => cards.push({
      q: `這個案例可以連到哪一條可重複使用的原則？`,
      a: cleanPrincipleTitle(p.title),
    }));
    return cards.slice(0, 5);
  }

  function addHeadingAnchors(bodyHtml, toc) {
    let idx = 0;
    return bodyHtml.replace(/<h([23])>([\s\S]*?)<\/h\1>/g, (m, level, inner) => {
      const t = toc[idx];
      idx += 1;
      if (!t || String(t.level) !== String(level)) return m;
      return `<h${level} id="${t.id}">${inner}</h${level}>`;
    });
  }

  function renderPathSteps(pathId, path) {
    return path.steps.map((step, i) => {
      const links = [
        step.read ? `<button type="button" class="la-link" data-note-kind="${step.read.kind}" data-note-id="${step.read.id}">${step.read.label}</button>` : '',
        step.read2 ? `<button type="button" class="la-link" data-note-kind="${step.read2.kind}" data-note-id="${step.read2.id}">${step.read2.label}</button>` : '',
      ].filter(Boolean).join('');
      const practice = step.practice
        ? `<button type="button" class="la-practice" data-view="${step.practice.view || ''}" data-section="${step.practice.section || ''}">${step.practice.label}</button>`
        : '';
      return `<details class="path-step" ${i === 0 ? 'open' : ''}>
        <summary><span class="path-n">${i + 1}</span><strong>${deEmmy(step.title)}</strong></summary>
        <p>${deEmmy(step.body)}</p>
        <div class="path-actions">${links}${practice}</div>
      </details>`;
    }).join('');
  }

  function renderHome(opts) {
    const esc = opts.escapeHtml;
    const cards = [
      { id: 'market', icon: '🌐', ...LEARN_PATHS.market },
      { id: 'stock', icon: '📊', ...LEARN_PATHS.stock },
      { id: 'trade', icon: '📝', ...LEARN_PATHS.trade },
    ];
    return `
    <div class="learning-method">
      <div>
        <div class="eyebrow">Learning UX</div>
        <h2>先回想，再展開，最後拿去判斷</h2>
        <p>這裡把長筆記拆成短任務：主動回想、章節重點、案例原則、工具應用。你不用把全部內容一次吞完，而是每次完成一個判斷。</p>
      </div>
      <div class="method-rail">
        <span>回想</span><span>重點</span><span>連結</span><span>應用</span>
      </div>
    </div>
    <div class="learning-board">
      <div class="board-copy">
        <div class="eyebrow">從問題開始</div>
        <h2>選一個你現在真的想回答的問題</h2>
        <p>不用從頭讀完。展開下面步驟，依序「讀一篇 → 連到工具 → 做一次判斷」。同一套問題可以反覆用在不同股票與不同月份。</p>
      </div>
      <div class="learning-cards">
        ${cards.map(c => `<button type="button" class="learning-card" data-path="${c.id}">
          <span class="lc-step">${esc(c.icon)}</span>
          <strong>${esc(c.title)}</strong>
          <span>${esc(c.lead)}</span>
          <em>展開學習步驟</em>
        </button>`).join('')}
      </div>
    </div>
    <div id="learnPathHost" class="learn-path-host" hidden>
      <div class="learn-path-head">
        <button type="button" class="back-link" id="learnPathBack">← 換一個問題</button>
        <h2 id="learnPathTitle"></h2>
        <p id="learnPathLead"></p>
      </div>
      <div id="learnPathSteps" class="learn-path-steps"></div>
    </div>
    <div class="practice-strip">
      <div><b>讀</b><span>先懂概念，不用背完。</span></div>
      <div><b>連</b><span>紫色按鈕跳到相關原則、名詞、案例。</span></div>
      <div><b>做</b><span>到總經、個股或復盤頁實際操作一次。</span></div>
    </div>
    <div class="module-grid learn-shortcuts">
      <div class="module-card" data-section-jump="principleMap"><div class="mod-name">原則地圖</div><div class="mod-meta">110+ 條原則的分群索引，可當查詢表。</div></div>
      <div class="module-card" data-section-jump="cases"><div class="mod-name">案例講解</div><div class="mod-meta">從單一公司/events 抽出可重複用的判斷。</div></div>
      <div class="module-card" data-section-jump="quiz"><div class="mod-name">練習題庫</div><div class="mod-meta">用問題檢查自己是不是真的懂。</div></div>
    </div>`;
  }

  function bindHome(container, handlers) {
    const host = container.querySelector('#learnPathHost');
    const stepsEl = container.querySelector('#learnPathSteps');
    const titleEl = container.querySelector('#learnPathTitle');
    const leadEl = container.querySelector('#learnPathLead');
    const showPath = (id) => {
      const path = LEARN_PATHS[id];
      if (!path) return;
      container.querySelector('.learning-board').hidden = true;
      container.querySelector('.practice-strip').hidden = true;
      container.querySelector('.learn-shortcuts').hidden = true;
      host.hidden = false;
      titleEl.textContent = path.title;
      leadEl.textContent = path.lead;
      stepsEl.innerHTML = renderPathSteps(id, path);
      bindPathSteps(stepsEl, handlers);
      host.scrollIntoView({ behavior: 'smooth', block: 'start' });
    };
    container.querySelector('#learnPathBack')?.addEventListener('click', () => {
      host.hidden = true;
      container.querySelector('.learning-board').hidden = false;
      container.querySelector('.practice-strip').hidden = false;
      container.querySelector('.learn-shortcuts').hidden = false;
    });
    container.querySelectorAll('.learning-card[data-path]').forEach(btn => {
      btn.addEventListener('click', () => showPath(btn.dataset.path));
    });
    container.querySelectorAll('[data-section-jump]').forEach(el => {
      el.addEventListener('click', () => handlers.showSection(el.dataset.sectionJump));
    });
  }

  function bindPathSteps(root, handlers) {
    root.querySelectorAll('.la-link[data-note-kind]').forEach(btn => {
      btn.addEventListener('click', () => handlers.openNote(btn.dataset.noteKind, btn.dataset.noteId));
    });
    root.querySelectorAll('.la-practice[data-view]').forEach(btn => {
      btn.addEventListener('click', () => {
        const v = btn.dataset.view;
        if (v === 'learn' && btn.dataset.section) handlers.showSection(btn.dataset.section);
        else if (v && v !== 'learn') handlers.goView(v);
      });
    });
  }

  function renderArticle(note, opts) {
    const esc = opts.escapeHtml;
    const kind = note.kind || '';
    const kindLabel = KIND_LABEL[kind] || '筆記';
    const title = deEmmy(note.title || note.id || '');
    const lead = leadFromNote(note);
    const toc = buildTocFromMarkdown(note.body);
    const sections = splitSections(note.body);
    const insights = extractInsights(note);
    const principles = (kind === 'case') ? extractPrinciples(note, opts.linkMap, opts.principles) : [];
    const recallCards = buildRecallCards(note, sections, principles);
    const fm = note.frontmatter || {};
    const noteKey = (kind && note.id) ? `learn_note:${kind}:${note.id}` : '';
    let savedNote = '';
    if (noteKey) {
      try { savedNote = JSON.parse(localStorage.getItem(noteKey) || '{}').text || ''; } catch (_) {}
    }
    let tags = '';
    if (fm.ticker) tags += `<span class="fm-tag">代號 ${esc([].concat(fm.ticker).join(' / '))}</span>`;
    if (fm.sector) tags += `<span class="fm-tag">${esc(fm.sector)}</span>`;
    if (fm.category) tags += `<span class="fm-tag">${esc(fm.category)}</span>`;

    const principlePanel = principles.length ? `
      <section class="la-principles">
        <div class="la-principles-head">
          <h2>可抽出的通用原則</h2>
          <p>這些原則不只適用這一檔公司——下次遇到類似情境，可以直接拿來問自己。</p>
        </div>
        <div class="la-principle-chips">
          ${principles.map(p => `<button type="button" class="principle-chip" data-note-kind="principle" data-note-id="${esc(p.id)}">${esc(cleanPrincipleTitle(p.title))}</button>`).join('')}
        </div>
      </section>` : '';

    const tocHtml = toc.length > 2 ? `
      <nav class="la-toc" aria-label="本篇目錄">
        <div class="la-toc-title">章節導航</div>
        <div class="la-toc-links">${toc.map(t => `<a href="#${esc(t.id)}" class="lv${t.level}">${esc(t.text)}</a>`).join('')}</div>
      </nav>` : '';

    let bodyHtml = opts.renderMarkdown(deEmmy(note.body || ''));
    bodyHtml = addHeadingAnchors(bodyHtml, toc);

    const insightPanel = insights.length ? `
      <section class="learning-panel insight-panel">
        <div class="panel-head"><span>01</span><h2>先抓重點</h2></div>
        <div class="insight-grid">
          ${insights.map((x, i) => `<div class="insight-card"><b>${String(i + 1).padStart(2, '0')}</b><p>${esc(x)}</p></div>`).join('')}
        </div>
      </section>` : '';

    const recallPanel = recallCards.length ? `
      <section class="learning-panel recall-panel">
        <div class="panel-head"><span>02</span><h2>主動回想</h2></div>
        <div class="recall-grid">
          ${recallCards.map((c, i) => `<div class="recall-card">
            <div class="recall-q">${esc(c.q)}</div>
            <button type="button" class="recall-toggle" aria-expanded="false">看參考答案</button>
            <div class="recall-a" hidden>${esc(c.a)}</div>
          </div>`).join('')}
        </div>
      </section>` : '';

    const sectionPanel = sections.length ? `
      <section class="learning-panel section-panel">
        <div class="panel-head"><span>03</span><h2>章節速讀</h2></div>
        <div class="section-ladder">
          ${sections.slice(0, 6).map(s => `<details class="section-chip">
            <summary>${esc(s.title)}</summary>
            <p>${esc(s.text.slice(0, 180))}</p>
          </details>`).join('')}
        </div>
      </section>` : '';

    const reviewPanel = `
      <section class="learning-panel review-panel">
        <div class="panel-head"><span>04</span><h2>間隔複習</h2></div>
        <div class="review-track">
          <button type="button" class="review-step on">今天</button>
          <button type="button" class="review-step">3 天後</button>
          <button type="button" class="review-step">7 天後</button>
          <button type="button" class="review-step">14 天後</button>
        </div>
      </section>`;

    const notePanel = noteKey ? `
      <section class="learning-panel personal-note-panel">
        <div class="panel-head"><span>✎</span><h2>我的筆記</h2></div>
        <textarea class="personal-note-input" data-note-key="${esc(noteKey)}" placeholder="用自己的話寫：這篇對我下次判斷股票、倉位或復盤有什麼用？">${esc(savedNote)}</textarea>
        <div class="personal-note-actions"><span class="personal-note-status">尚未儲存</span><button type="button" class="btn sm save-personal-note">儲存筆記</button></div>
      </section>` : '';

    const toolActions = [
      { view: 'macro', label: '總經儀表板', sub: '對照利率、通膨' },
      { view: 'stock', label: '個股工具', sub: '查財報、投資地圖' },
      { view: 'journal', label: '交易復盤', sub: '記錄判斷與檢討' },
    ];

    return `
    <div class="note-toolbar">
      <span class="back-link" id="noteBack">← 返回</span>
      ${note.kind && note.id ? '<button type="button" class="btn ghost sm" id="noteGraphBtn">🔗 周邊圖譜</button>' : ''}
    </div>
    ${tags ? `<div class="note-frontmatter">${tags}</div>` : ''}
    <article class="learn-article">
      <header class="la-head">
        <div class="la-kind">${esc(kindLabel)}</div>
        <h1 class="la-title">${esc(title)}</h1>
        ${lead ? `<p class="la-lead">${esc(lead)}</p>` : ''}
      </header>
      <div class="learn-tabs" role="tablist" aria-label="學習模式">
        <button type="button" class="learn-tab on" data-tab="study">學習模式</button>
        <button type="button" class="learn-tab" data-tab="note">完整筆記</button>
        <button type="button" class="learn-tab" data-tab="apply">應用</button>
      </div>
      <div class="learn-tab-panel" data-panel="study">
        ${insightPanel}
        ${recallPanel}
        ${sectionPanel}
        ${reviewPanel}
        ${notePanel}
        ${principlePanel}
      </div>
      <div class="learn-tab-panel" data-panel="note" hidden>
        ${tocHtml}
        <div class="learn-body md">${bodyHtml}</div>
      </div>
      <div class="learn-tab-panel" data-panel="apply" hidden>
        ${notePanel}
        ${principlePanel}
        ${reviewPanel}
      </div>
      <footer class="la-footer">
        <div class="la-footer-title">把這篇用出去</div>
        <div class="la-tool-grid">
          ${toolActions.map(a => `<button type="button" class="la-tool-card" data-view="${a.view}">
            <strong>${a.label}</strong><span>${a.sub}</span>
          </button>`).join('')}
        </div>
      </footer>
    </article>`;
  }

  function bindArticle(container, handlers) {
    container.querySelector('#noteBack')?.addEventListener('click', handlers.onBack);
    container.querySelector('#noteGraphBtn')?.addEventListener('click', handlers.onGraph);
    container.querySelectorAll('.principle-chip[data-note-id], .la-link[data-note-id]').forEach(btn => {
      btn.addEventListener('click', () => handlers.openNote(btn.dataset.noteKind, btn.dataset.noteId));
    });
    container.querySelectorAll('.la-tool-card[data-view]').forEach(btn => {
      btn.addEventListener('click', () => handlers.goView(btn.dataset.view));
    });
    container.querySelectorAll('.learn-tab[data-tab]').forEach(btn => {
      btn.addEventListener('click', () => {
        const tab = btn.dataset.tab;
        container.querySelectorAll('.learn-tab').forEach(x => x.classList.toggle('on', x === btn));
        container.querySelectorAll('.learn-tab-panel').forEach(p => { p.hidden = p.dataset.panel !== tab; });
      });
    });
    container.querySelectorAll('.recall-toggle').forEach(btn => {
      btn.addEventListener('click', () => {
        const ans = btn.parentElement.querySelector('.recall-a');
        const open = ans.hidden;
        ans.hidden = !open;
        btn.setAttribute('aria-expanded', open ? 'true' : 'false');
        btn.textContent = open ? '收起答案' : '看參考答案';
      });
    });
    container.querySelectorAll('.review-step').forEach(btn => {
      btn.addEventListener('click', () => {
        btn.parentElement.querySelectorAll('.review-step').forEach(x => x.classList.toggle('on', x === btn));
      });
    });
    container.querySelectorAll('.save-personal-note').forEach(btn => {
      btn.addEventListener('click', () => {
        const panel = btn.closest('.personal-note-panel');
        const input = panel?.querySelector('.personal-note-input');
        const key = input?.dataset.noteKey;
        if (!key) return;
        const title = container.querySelector('.la-title')?.textContent?.trim() || '';
        const parts = key.split(':');
        const payload = {
          key, title, kind: parts[1] || '', id: parts.slice(2).join(':'),
          text: input.value.trim(), updatedAt: new Date().toISOString(),
        };
        if (payload.text) localStorage.setItem(key, JSON.stringify(payload));
        else localStorage.removeItem(key);
        const status = panel.querySelector('.personal-note-status');
        if (status) status.textContent = payload.text ? '已儲存' : '已清空';
      });
    });
    container.querySelectorAll('.la-toc a').forEach(a => {
      a.addEventListener('click', e => {
        e.preventDefault();
        const el = container.querySelector(a.getAttribute('href'));
        el?.scrollIntoView({ behavior: 'smooth', block: 'start' });
      });
    });
  }

  function groupPrinciples(principles) {
    const groups = PRINCIPLE_GROUPS.map(g => ({ ...g, items: [] }));
    const other = { id: 'other', label: '其他', items: [] };
    for (const p of principles || []) {
      const title = cleanPrincipleTitle(p.title);
      const hit = groups.find(g => g.re.test(title));
      (hit || other).items.push({ ...p, cleanTitle: title });
    }
    const out = groups.filter(g => g.items.length);
    if (other.items.length) out.push(other);
    return out;
  }

  function renderPrincipleGroups(principles, esc) {
    const groups = groupPrinciples(principles);
    return groups.map(g => `
      <details class="principle-group" open>
        <summary>${esc(g.label)} <span class="pg-count">${g.items.length}</span></summary>
        <div class="module-grid pg-grid">
          ${g.items.map(p => `<div class="module-card pg-card" data-id="${esc(p.id)}">
            <div class="mod-name">${esc(p.cleanTitle)}</div>
          </div>`).join('')}
        </div>
      </details>`).join('');
  }

  function renderCaseCards(items, esc, opts) {
    opts = opts || {};
    return (items || []).map(it => {
      const principles = extractPrinciples(it, opts.linkMap, opts.principles);
      const badge = principles.length ? `<span class="case-badge">${principles.length} 條可重用原則</span>` : '';
      return `<div class="module-card case-card" data-id="${esc(it.id)}">
        <div class="mod-name">${esc(deEmmy(it.title))}${badge}</div>
        ${it.summary ? `<div class="mod-meta">${esc(deEmmy(it.summary))}</div>` : ''}
      </div>`;
    }).join('');
  }

  global.LearnUI = {
    deEmmy, cleanPrincipleTitle, renderHome, bindHome, renderArticle, bindArticle,
    renderPrincipleGroups, renderCaseCards, extractPrinciples, LEARN_PATHS,
  };
})(window);
