// render-inbox.js — Inbox and notes rendering functions extracted from dashboard.html

const INBOX_PER_PAGE = 15;
let _inboxPage = 0;

// Quick relative-time formatter — mirrors engine/queries.js timeSince so the
// inbox-item "age" chip reads the same as it did via /api/status.
function _timeSinceMs(mtimeMs) {
  if (!mtimeMs) return '';
  const ageMs = Date.now() - Number(mtimeMs);
  if (ageMs < 0) return 'just now';
  const sec = Math.floor(ageMs / 1000);
  if (sec < 60) return sec + 's ago';
  const min = Math.floor(sec / 60);
  if (min < 60) return min + 'm ago';
  const hr = Math.floor(min / 60);
  if (hr < 24) return hr + 'h ago';
  const days = Math.floor(hr / 24);
  return days + 'd ago';
}

// Fetch the inbox via the /state/notes/inbox directory listing + per-file
// content fetch. The listing call returns {entries:[{name, mtimeMs, size,
// isDir}]} and is cheap (one statSync per entry). The per-file fetches run
// in parallel with their own mtime+size ETag so unchanged files 304 with
// no body. Issue #2949 — the staleness fix applies here too: the inbox
// directory mtime advances on every new note, and per-file ETags surface
// content edits within one 4 s poll.
async function fetchInboxFromDisk() {
  try {
    const listResp = await fetch('/state/notes/inbox');
    if (!listResp.ok) return null;
    const listing = await listResp.json();
    const entries = Array.isArray(listing && listing.entries) ? listing.entries : [];
    const mdFiles = entries
      .filter((e) => e && !e.isDir && typeof e.name === 'string' && e.name.endsWith('.md'))
      .sort((a, b) => (b.mtimeMs || 0) - (a.mtimeMs || 0));
    const items = await Promise.all(mdFiles.map(async (e) => {
      try {
        const r = await fetch('/state/notes/inbox/' + encodeURIComponent(e.name));
        if (!r.ok) return null;
        const content = await r.text();
        return {
          name: e.name,
          mtime: e.mtimeMs,
          age: _timeSinceMs(e.mtimeMs),
          content,
        };
      } catch {
        return null;
      }
    }));
    return items.filter(Boolean);
  } catch {
    return null;
  }
}

// Fetch notes.md via /state/notes.md and return the renderer-shaped
// { content, updatedAt } pair. updatedAt comes from a HEAD-style poll of
// the ETag (mtime+size); we don't have direct mtime exposure, so a parallel
// listing of the parent dir would be needed. Cheaper path: just include the
// ETag value (which is "<mtimeMs>-<size>") and parse it out.
async function fetchNotesFromDisk() {
  try {
    const r = await fetch('/state/notes.md');
    if (!r.ok) return null;
    const content = await r.text();
    const etag = r.headers.get('ETag') || '';
    const m = etag.match(/^"(\d+)-/);
    const updatedAt = m ? Number(m[1]) : null;
    return { content, updatedAt };
  } catch {
    return null;
  }
}

function _inboxPrev() { if (_inboxPage > 0) { _inboxPage--; renderInbox(inboxData, { preserveScroll: false }); } }
function _inboxNext() { _inboxPage++; renderInbox(inboxData, { preserveScroll: false }); }

function renderInbox(inbox, opts) {
  opts = opts || {};
  invalidatePinsCache();
  inbox = inbox.filter(function(item) { return !isDeleted('inbox:' + item.name); });
  // Stable sort — pinned items float to top
  inbox.sort(function(a, b) {
    return (isPinned(inboxPinKey(a.name)) ? 0 : 1) - (isPinned(inboxPinKey(b.name)) ? 0 : 1);
  });
  inboxData = inbox;
  const list = document.getElementById('inbox-list');
  const count = document.getElementById('inbox-count');
  count.textContent = inbox.length;
  const scrollState = opts.preserveScroll === false ? null : captureDashboardScrollState(list, [':self']);
  if (!inbox.length) { list.innerHTML = '<p class="empty">No messages yet.</p>'; restoreDashboardScrollState(list, scrollState); return; }

  const totalInboxPages = Math.ceil(inbox.length / INBOX_PER_PAGE);
  if (_inboxPage >= totalInboxPages) _inboxPage = totalInboxPages - 1;
  if (_inboxPage < 0) _inboxPage = 0;
  const inboxStart = _inboxPage * INBOX_PER_PAGE;
  const pageInbox = inbox.slice(inboxStart, inboxStart + INBOX_PER_PAGE);

  // eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escapeHtml() (fields: inbox name, age, content, pin key)
  list.innerHTML = pageInbox.map((item, i) => {
    const idx = inboxStart + i;
    const pk = inboxPinKey(item.name);
    const pinned = isPinned(pk);
    return `<div class="inbox-item${pinned ? ' item-pinned' : ''}" data-file="notes/inbox/${escapeHtml(item.name)}">
      <div class="inbox-name" onclick="openModal(${idx})" style="cursor:pointer">
        <span>${escapeHtml(item.name)}</span><span>${escapeHtml(item.age || '')}</span>
      </div>
      <div class="inbox-preview" onclick="openModal(${idx})" style="cursor:pointer">${escapeHtml(item.content.slice(0,200))}</div>
      <div style="display:flex;gap:6px;margin-top:6px;align-items:center">
        ${pinButton(pk, pinned, 'inbox')}
        <button class="pr-pager-btn" style="font-size:var(--text-xs);padding:2px 8px" data-inbox-name="${escapeHtml(item.name)}" onclick="event.stopPropagation();promoteToKB(this.dataset.inboxName)">Add to Knowledge Base</button>
        <button class="pr-pager-btn" style="font-size:var(--text-xs);padding:2px 8px" data-inbox-name="${escapeHtml(item.name)}" onclick="event.stopPropagation();openInboxInExplorer(this.dataset.inboxName)">Open in Explorer</button>
        <button class="pr-pager-btn" style="font-size:var(--text-xs);padding:2px 8px;color:var(--red)" data-inbox-name="${escapeHtml(item.name)}" onclick="event.stopPropagation();deleteInboxItem(this.dataset.inboxName)">Delete</button>
      </div>
    </div>`;
  }).join('');
  const inboxPager = renderPager({
    start: inboxStart, perPage: INBOX_PER_PAGE, total: inbox.length,
    page: _inboxPage, totalPages: totalInboxPages,
    onPrev: '_inboxPrev()', onNext: '_inboxNext()',
  });
  // 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 (inboxPager) list.insertAdjacentHTML('beforeend', inboxPager);
  restoreDashboardScrollState(list, scrollState);
  restoreNotifBadges();
}

function promoteToKB(name) {
  const categories = [
    { id: 'architecture', label: 'Architecture' },
    { id: 'conventions', label: 'Conventions' },
    { id: 'project-notes', label: 'Project Notes' },
    { id: 'build-reports', label: 'Build Reports' },
    { id: 'reviews', label: 'Reviews' },
  ];
  const picker = '<div style="padding:16px 20px">' +
    '<p style="font-size:var(--text-lg);color:var(--text);margin-bottom:12px">Choose a category for <strong>' + escapeHtml(name) + '</strong>:</p>' +
    '<div style="display:flex;flex-direction:column;gap:8px">' +
    categories.map(c =>
      '<button class="pr-pager-btn" style="font-size:var(--text-md);padding:8px 16px;text-align:left" onclick="doPromoteToKB(\'' + escapeHtml(name) + '\',\'' + c.id + '\')">' + c.label + '</button>'
    ).join('') +
    '</div></div>';
  document.getElementById('modal-title').textContent = 'Add to Knowledge Base';
  // eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escapeHtml() (fields: inbox item name)
  document.getElementById('modal-body').innerHTML = picker;
  document.getElementById('modal').classList.add('open');
}

function renderNotes(notes) {
  const el = document.getElementById('notes-list');
  const content = typeof notes === 'object' ? notes.content : notes;
  const updatedAt = typeof notes === 'object' ? notes.updatedAt : null;
  const scrollState = captureDashboardScrollState(el, ['.notes-preview']);

  // Show last updated timestamp
  const updatedEl = document.getElementById('notes-updated');
  if (updatedEl && updatedAt) {
    updatedEl.textContent = 'updated ' + formatLocalDateTime(updatedAt);
  }

  if (!content || !content.trim()) { el.innerHTML = '<p class="empty">No team notes yet.</p>'; restoreDashboardScrollState(el, scrollState); return; }
  // eslint-disable-next-line no-unsanitized/property -- reason: renderMd() escapes all user-controlled fields before assembling HTML (see dashboard/js/utils.js)
  el.innerHTML = '<div class="notes-preview" data-file="notes.md" onclick="openNotesModal()" title="Click to expand">' + renderMd(content) + '</div>';
  restoreDashboardScrollState(el, scrollState);
  el.querySelector('.notes-preview')._rawContent = content;
  restoreNotifBadges();
}

function openNotesModal() {
  const preview = document.querySelector('.notes-preview');
  if (!preview) return;
  const content = preview._rawContent || preview.textContent;
  document.getElementById('modal-title').textContent = 'Team Notes';
  // eslint-disable-next-line no-unsanitized/property -- reason: renderMd() escapes all user-controlled fields before assembling HTML (see dashboard/js/utils.js)
  document.getElementById('modal-body').innerHTML = renderMd(content);
  document.getElementById('modal-body').style.fontFamily = "'Segoe UI', system-ui, sans-serif";
  document.getElementById('modal-body').style.whiteSpace = 'normal';
  _modalDocContext = { title: 'Team Notes', content, selection: '' };
  _modalEditable = 'notes.md';
  _modalFilePath = 'notes.md'; showModalQa();
  document.getElementById('modal-edit-btn').style.display = '';
  document.getElementById('modal').classList.add('open');
}

function modalToggleEdit() {
  const body = document.getElementById('modal-body');
  body.contentEditable = 'true';
  body.style.border = '1px solid var(--blue)';
  body.style.borderRadius = '4px';
  body.style.padding = '12px';
  body.style.outline = 'none';
  body.focus();
  document.getElementById('modal-edit-btn').style.display = 'none';
  document.getElementById('modal-save-btn').style.display = '';
  document.getElementById('modal-cancel-edit-btn').style.display = '';
}

async function modalSaveEdit() {
  if (!_modalEditable) return;
  const body = document.getElementById('modal-body');
  const content = body.innerText;

  body.contentEditable = 'false';
  body.style.border = '';
  body.style.padding = '';
  document.getElementById('modal-edit-btn').style.display = '';
  document.getElementById('modal-save-btn').style.display = 'none';
  document.getElementById('modal-cancel-edit-btn').style.display = 'none';
  _modalDocContext.content = content;
  showToast('cmd-toast', 'Team Notes saved', true);
  try {
    const res = await fetch('/api/notes-save', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ file: _modalEditable, content }),
    });
    const data = await res.json();
    if (!res.ok) showToast('cmd-toast', 'Save failed: ' + (data.error || 'unknown'), false);
  } catch (e) {
    showToast('cmd-toast', 'Save error: ' + e.message, false);
  }
}

function modalCancelEdit() {
  const body = document.getElementById('modal-body');
  body.contentEditable = 'false';
  // eslint-disable-next-line no-unsanitized/property -- reason: renderMd() escapes all user-controlled fields before assembling HTML (see dashboard/js/utils.js)
  body.innerHTML = renderMd(_modalDocContext.content); // revert (render Markdown, not raw text)
  body.style.border = '';
  body.style.padding = '';
  document.getElementById('modal-edit-btn').style.display = '';
  document.getElementById('modal-save-btn').style.display = 'none';
  document.getElementById('modal-cancel-edit-btn').style.display = 'none';
}

async function deleteInboxItem(name) {
  if (!confirm('Delete "' + name + '" from inbox?')) return;
  showToast('cmd-toast', 'Inbox item deleted', true);
  markDeleted('inbox:' + name);
  const card = document.querySelector('.inbox-item[data-file="notes/inbox/' + CSS.escape(name) + '"]');
  if (card) card.remove();
  try {
    const res = await fetch('/api/inbox/delete', {
      method: 'POST', headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ name })
    });
    if (!res.ok) { const d = await res.json().catch(() => ({})); clearDeleted('inbox:' + name); showToast('cmd-toast', 'Delete failed: ' + (d.error || 'unknown'), false); refresh(); }
  } catch (e) { clearDeleted('inbox:' + name); showToast('cmd-toast', 'Delete error: ' + e.message, false); refresh(); }
}

async function openInboxInExplorer(name) {
  try {
    await fetch('/api/inbox/open', {
      method: 'POST', headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ name })
    });
  } catch (e) { console.error('inbox open:', e.message); }
}

function openQuickNoteModal() {
  document.getElementById('modal-title').textContent = 'Quick Note';
  document.getElementById('modal-body').innerHTML =
    '<div style="display:flex;flex-direction:column;gap:12px">' +
      '<label style="color:var(--text);font-size:var(--text-md)">Title' +
        '<input id="note-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="Decision, observation, or context..."></label>' +
      '<label style="color:var(--text);font-size:var(--text-md)">Content' +
        '<textarea id="note-content" rows="6" 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 note... Agents will see this after consolidation."></textarea></label>' +
      '<div style="display:flex;justify-content:flex-end;gap:8px">' +
        '<button onclick="closeModal()" class="pr-pager-btn">Cancel</button>' +
        '<button onclick="submitQuickNote()" class="btn-primary-lg">Save Note</button>' +
      '</div>' +
    '</div>';
  document.getElementById('modal').classList.add('open');
}

async function submitQuickNote() {
  const titleEl = document.getElementById('note-title');
  const contentEl = document.getElementById('note-content');
  if (!titleEl || !contentEl) { alert('Form elements not found'); return; }
  const title = titleEl.value;
  const content = contentEl.value;
  if (!title && !content) { alert('Title or content required'); return; }
  try { closeModal(); } catch { /* expected */ }
  showToast('cmd-toast', 'Note saved to inbox', true);
  try {
    const res = await fetch('/api/notes', {
      method: 'POST', headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ title: title || 'Quick note', what: content || title })
    });
    if (res.ok) {
      refresh();
      const currentPage = document.querySelector('.sidebar-link.active')?.getAttribute('data-page');
      if (currentPage !== 'inbox') {
        const link = document.querySelector('.sidebar-link[data-page="inbox"]');
        if (link && !link.querySelector('.notif-badge')) showNotifBadge(link);
      }
    }
    else { const d = await res.json().catch(() => ({})); showToast('cmd-toast', 'Note failed: ' + (d.error || 'unknown'), false); openQuickNoteModal(); }
  } catch (e) { showToast('cmd-toast', 'Error saving note: ' + e.message, false); openQuickNoteModal(); }
}

async function doPromoteToKB(name, category) {
  showToast('cmd-toast', 'Promoted to Knowledge Base', true);
  try { closeModal(); } catch { /* expected */ }
  markDeleted('inbox:' + name);
  const card = document.querySelector('.inbox-item[data-file="notes/inbox/' + CSS.escape(name) + '"]');
  if (card) card.remove();
  try {
    const res = await fetch('/api/inbox/promote-kb', {
      method: 'POST', headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ name, category })
    });
    const data = await res.json();
    if (res.ok) { refreshKnowledgeBase(); }
    else { clearDeleted('inbox:' + name); showToast('cmd-toast', 'Promote failed: ' + (data.error || 'unknown'), false); refresh(); }
  } catch (e) { clearDeleted('inbox:' + name); showToast('cmd-toast', 'Promote error: ' + e.message, false); refresh(); }
}

window.MinionsInbox = { renderInbox, promoteToKB, renderNotes, openNotesModal, modalToggleEdit, modalSaveEdit, modalCancelEdit, deleteInboxItem, openInboxInExplorer, openQuickNoteModal, submitQuickNote, doPromoteToKB };
