// render-kb.js — Knowledge base rendering functions extracted from dashboard.html

const KB_CAT_LABELS = {
  architecture: 'Architecture', conventions: 'Conventions',
  'project-notes': 'Project Notes', 'build-reports': 'Build Reports',
  reviews: 'Reviews', learnings: 'Learnings', decisions: 'Decisions',
  incidents: 'Incidents', 'api-notes': 'API Notes',
};
const KB_CAT_ICONS = {
  architecture: '\u{1F3D7}', conventions: '\u{1F4CF}',
  'project-notes': '\u{1F4DD}', 'build-reports': '\u{1F6E0}',
  reviews: '\u{1F50D}', learnings: '\u{1F4A1}', decisions: '\u{2696}',
  incidents: '\u{1F6A8}', 'api-notes': '\u{1F517}',
};
let _kbData = {};
let _kbActiveTab = 'all';
const KB_PER_PAGE = 30;
let _kbPage = 0;

function _kbPrev() { if (_kbPage > 0) { _kbPage--; renderKnowledgeBase({ preserveScroll: false }); } }
function _kbNext() { _kbPage++; renderKnowledgeBase({ preserveScroll: false }); }

function _formatSweepElapsed(startedAt) {
  if (!startedAt) return '';
  const elapsedMs = Date.now() - Number(startedAt);
  if (!isFinite(elapsedMs) || elapsedMs < 0) return '';
  const mins = Math.floor(elapsedMs / 60000);
  if (mins < 1) return Math.max(1, Math.floor(elapsedMs / 1000)) + 's';
  if (mins < 60) return mins + 'm';
  return Math.floor(mins / 60) + 'h ' + (mins % 60) + 'm';
}

async function refreshKnowledgeBase() {
  try {
    // Conditional fetch (browser-OOM fix): /api/knowledge is ~4.5MB and was
    // re-fetched + re-parsed every 4s tick, the single biggest contributor to
    // the heap churn that crashed the tab. On a 304 the payload is unchanged,
    // so _kbData (which cross-slice readers like command-center.js depend on)
    // is already current — skip the parse and the render entirely.
    const resp = await _condFetchJson('/api/knowledge');
    if (resp.notModified) return;
    _kbData = resp.data;
    // Second-level guard: an mtime-only ETag bust (e.g. a sweep touched a file)
    // can arrive with byte-identical content — skip the full kb-list/kb-tabs
    // rewrite when the payload matches last tick. Dedicated cache key
    // 'kbPayload' avoids colliding with any other tracked slice (S1).
    if (typeof _changed !== 'function' || _changed('kbPayload', _kbData)) {
      renderKnowledgeBase();
    }
  } catch (e) { console.error('kb refresh:', e.message); }
}

function kbNewestFirst(a, b) {
  return (b.sortTs || 0) - (a.sortTs || 0) ||
    (b.date || '').localeCompare(a.date || '') ||
    (a.title || '').localeCompare(b.title || '');
}

function kbPinnedNewestFirst(a, b) {
  const aPinned = isPinned(kbPinKey(a.category, a.file));
  const bPinned = isPinned(kbPinKey(b.category, b.file));
  if (aPinned !== bPinned) return aPinned ? -1 : 1;
  return kbNewestFirst(a, b);
}

function renderKnowledgeBase(opts) {
  opts = opts || {};
  _syncPinsFromServer();
  const tabsEl = document.getElementById('kb-tabs');
  const listEl = document.getElementById('kb-list');
  const countEl = document.getElementById('kb-count');
  const scrollState = opts.preserveScroll === false ? null : captureDashboardScrollState(listEl, [':self']);

  // Single pass: flatten all KB items and count pinned
  const allItems = [];
  let pinnedCount = 0;
  for (const [cat, catItems] of Object.entries(_kbData)) {
    if (!Array.isArray(catItems)) continue;
    for (const item of catItems) {
      const entry = { ...item, category: cat };
      allItems.push(entry);
      if (isPinned(kbPinKey(cat, item.file))) pinnedCount++;
    }
  }
  countEl.textContent = allItems.length;

  const sweptEl = document.getElementById('kb-swept-time');
  if (sweptEl) {
    let sweptText = _kbData.lastSwept ? 'swept ' + timeSinceStr(new Date(_kbData.lastSwept)) : '';
    if (_kbData.sweepInFlight) {
      const dur = _formatSweepElapsed(_kbData.sweepStartedAt);
      sweptText = sweptText
        ? sweptText + ' \u00b7 now sweeping' + (dur ? ' (' + dur + ')' : '')
        : 'now sweeping' + (dur ? ' (' + dur + ')' : '');
    }
    sweptEl.textContent = sweptText;
  }

  if (allItems.length === 0) {
    tabsEl.innerHTML = '';
    listEl.innerHTML = '<p class="empty">No knowledge entries yet. Notes are classified here after consolidation.</p>';
    restoreDashboardScrollState(listEl, scrollState);
    return;
  }

  if (_kbActiveTab === 'pinned' && pinnedCount === 0) _kbActiveTab = 'all';

  // Render tabs
  let tabsHtml = '';
  if (pinnedCount > 0) {
    tabsHtml += '<button class="kb-tab ' + (_kbActiveTab === 'pinned' ? 'active' : '') + '" style="color:var(--yellow)" onclick="kbSetTab(\'pinned\')">Pinned <span class="badge">' + pinnedCount + '</span></button>';
  }
  tabsHtml += '<button class="kb-tab ' + (_kbActiveTab === 'all' ? 'active' : '') + '" onclick="kbSetTab(\'all\')">All <span class="badge">' + allItems.length + '</span></button>';
  for (const [cat, catArr] of Object.entries(_kbData)) {
    if (!Array.isArray(catArr) || catArr.length === 0) continue;
    const label = KB_CAT_LABELS[cat] || cat;
    tabsHtml += '<button class="kb-tab ' + (_kbActiveTab === cat ? 'active' : '') + '" onclick="kbSetTab(\'' + cat + '\')">' + label + ' <span class="badge">' + catArr.length + '</span></button>';
  }
  // eslint-disable-next-line no-unsanitized/property -- reason: composed from known KB category constants and item counts (no user data flows in)
  tabsEl.innerHTML = tabsHtml;

  // Filter items for active tab
  let items;
  if (_kbActiveTab === 'pinned') {
    items = allItems.filter(i => isPinned(kbPinKey(i.category, i.file)));
    items.sort(kbNewestFirst);
  } else if (_kbActiveTab === 'all') {
    items = allItems.slice();
    items.sort(kbPinnedNewestFirst);
  } else {
    items = allItems.filter(i => i.category === _kbActiveTab);
    items.sort(kbPinnedNewestFirst);
  }

  if (items.length === 0) {
    listEl.innerHTML = '<p class="empty">No entries in this category.</p>';
    restoreDashboardScrollState(listEl, scrollState);
    return;
  }

  const totalKbPages = Math.ceil(items.length / KB_PER_PAGE);
  if (_kbPage >= totalKbPages) _kbPage = totalKbPages - 1;
  if (_kbPage < 0) _kbPage = 0;
  const kbStart = _kbPage * KB_PER_PAGE;
  const pageItems = items.slice(kbStart, kbStart + KB_PER_PAGE);

  // eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escapeHtml() (fields: category, file, title, agent, preview)
  listEl.innerHTML = pageItems.map(item => {
    const icon = KB_CAT_ICONS[item.category] || '\u{1F4C4}';
    const label = KB_CAT_LABELS[item.category] || item.category;
    var pinKey = kbPinKey(item.category, item.file);
    var pinned = isPinned(pinKey);
    return '<div class="kb-item' + (pinned ? ' item-pinned' : '') + '" data-file="knowledge/' + escapeHtml(item.category) + '/' + escapeHtml(item.file) + '" onclick="kbOpenItem(\'' + escapeHtml(item.category) + '\', \'' + escapeHtml(item.file) + '\')">' +
      '<div class="kb-item-body">' +
        '<div class="kb-item-title">' + icon + ' ' + escapeHtml(item.title) +
          ' ' + pinButton(pinKey, pinned, 'kb', { extraStyle: 'padding:1px 6px;margin-left:6px;vertical-align:middle' }) +
        '</div>' +
        '<div class="kb-item-meta">' +
          '<span>' + label + '</span>' +
          (item.agent ? '<span>' + escapeHtml(item.agent) + '</span>' : '') +
          '<span>' + (item.date || '') + '</span>' +
          '<span>' + Math.round(item.size / 1024) + 'KB</span>' +
        '</div>' +
        (item.preview ? '<div class="kb-item-preview">' + escapeHtml(item.preview) + '</div>' : '') +
      '</div>' +
    '</div>';
  }).join('');
  const kbPager = renderPager({
    start: kbStart, perPage: KB_PER_PAGE, total: items.length,
    page: _kbPage, totalPages: totalKbPages,
    onPrev: '_kbPrev()', onNext: '_kbNext()',
  });
  // eslint-disable-next-line no-unsanitized/method -- reason: composed from internal numeric page bounds and fixed renderPager() callback names (no user data flows in)
  if (kbPager) listEl.insertAdjacentHTML('beforeend', kbPager);
  restoreDashboardScrollState(listEl, scrollState);
  restoreNotifBadges();
}

function kbSetTab(tab) {
  _kbActiveTab = tab;
  _kbPage = 0;
  renderKnowledgeBase({ preserveScroll: false });
}

async function kbSweep() {
  // Optimistic: instant feedback before any network round-trip.
  showToast('kb-sweep-toast', 'KB sweep queued — runs in the background, this can take many minutes for a large KB', true, 8000);
  try {
    const res = await fetch('/api/knowledge/sweep', {
      method: 'POST', headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ pinnedKeys: getPinnedItems().filter(function(k) { return k.startsWith('knowledge/'); }) })
    });
    const data = await res.json();
    if (!data.ok) {
      showToast('kb-sweep-toast', 'Sweep failed: ' + (data.error || 'unknown'), false);
      return;
    }
    if (data.alreadyRunning) {
      const dur = _formatSweepElapsed(data.startedAt);
      showToast('kb-sweep-toast', 'KB sweep already running' + (dur ? ' (' + dur + ' elapsed)' : '') + ' — let it finish first', true, 8000);
    }
  } catch (e) {
    showToast('kb-sweep-toast', 'Sweep error: ' + e.message, false);
  }
}

function openCreateKbModal() {
  document.getElementById('modal-title').textContent = 'New Knowledge Base Entry';
  document.getElementById('modal-body').innerHTML =
    '<div style="display:flex;flex-direction:column;gap:12px">' +
      '<label style="color:var(--text);font-size:var(--text-md)">Category' +
        '<select id="kb-new-category" style="display:block;width:100%;margin-top:4px;padding:6px 8px;background:var(--bg);border:1px solid var(--border);border-radius:var(--radius-sm);color:var(--text)">' +
          '<option value="architecture">Architecture</option>' +
          '<option value="conventions">Conventions</option>' +
          '<option value="project-notes">Project Notes</option>' +
          '<option value="build-reports">Build Reports</option>' +
          '<option value="reviews">Reviews</option>' +
        '</select></label>' +
      '<label style="color:var(--text);font-size:var(--text-md)">Title' +
        '<input id="kb-new-title" style="display:block;width:100%;margin-top:4px;padding:6px 8px;background:var(--bg);border:1px solid var(--border);border-radius:var(--radius-sm);color:var(--text)" placeholder="Entry title"></label>' +
      '<label style="color:var(--text);font-size:var(--text-md)">Content' +
        '<textarea id="kb-new-content" rows="8" style="display:block;width:100%;margin-top:4px;padding:6px 8px;background:var(--bg);border:1px solid var(--border);border-radius:var(--radius-sm);color:var(--text);resize:vertical;font-family:inherit" placeholder="Write your knowledge entry..."></textarea></label>' +
      '<div style="display:flex;justify-content:flex-end;gap:8px">' +
        '<button onclick="closeModal()" class="pr-pager-btn">Cancel</button>' +
        '<button onclick="submitKbEntry(event)" class="btn-primary-lg">Save</button>' +
      '</div>' +
    '</div>';
  document.getElementById('modal').classList.add('open');
}

async function submitKbEntry(e) {
  var btn = (e || window.event)?.target; if (btn) { btn.disabled = true; btn.textContent = 'Saving...'; }
  const category = document.getElementById('kb-new-category').value;
  const title = document.getElementById('kb-new-title').value;
  const content = document.getElementById('kb-new-content').value;
  if (!title || !content) { if (btn) { btn.disabled = false; btn.textContent = 'Save'; } alert('Title and content are required'); return; }
  try {
    showToast('kb-sweep-toast', 'KB entry created', true);
    const res = await fetch('/api/knowledge', {
      method: 'POST', headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ category, title, content })
    });
    if (res.ok) { closeModal(); refreshKnowledgeBase(); }
    else { if (btn) { btn.disabled = false; btn.textContent = 'Save'; } const d = await res.json().catch(() => ({})); showToast('kb-sweep-toast', 'KB create failed: ' + (d.error || 'unknown'), false); }
  } catch (err) { if (btn) { btn.disabled = false; btn.textContent = 'Save'; } showToast('kb-sweep-toast', 'Error: ' + err.message, false); }
}

async function kbOpenItem(category, file) {
  try {
    const content = await fetch('/api/knowledge/' + category + '/' + encodeURIComponent(file)).then(r => r.text());
    const display = content.replace(/^---[\s\S]*?---\n*/m, '');
    document.getElementById('modal-title').textContent = file;
    const modalBody = document.getElementById('modal-body');
    // eslint-disable-next-line no-unsanitized/property -- reason: renderMd() escapes all user-controlled fields before assembling HTML (see dashboard/js/utils.js)
    modalBody.innerHTML = renderMd(display);
    _modalDocContext = { title: file, content: display, selection: '' };
    _modalFilePath = 'knowledge/' + category + '/' + file; showModalQa();
    // Clear notification badge when opening this document
    const card = findCardForFile(_modalFilePath);
    if (card) clearNotifBadge(card);
    document.getElementById('modal').classList.add('open');
  } catch (e) {
    console.error('Failed to load KB item:', e);
  }
}

window.MinionsKb = { refreshKnowledgeBase, renderKnowledgeBase, kbSetTab, kbSweep, openCreateKbModal, submitKbEntry, kbOpenItem };
