/* Brain of Reese — document viewer (phase 10). * * Reads `source`/`path` query params, fetches the stateless content * endpoint (GET /api/documents/content — database only, no filesystem), * and renders: * * • md / markdown → the shared escape-first renderer (markdown.js) in a * ≤46rem centered column; * • any other → the raw content as a text node inside *
 (mono, horizontal scroll).
 *
 * XSS-safe by construction: markdown is escaped before transform, raw
 * formats are set via textContent, and every document-derived string
 * (title, badges, path) is written with textContent — never innerHTML.
 *
 * A missing document (unknown pair, missing params, network error) shows
 * the designed not-found card with a link back to the Sources page.
 *
 * Phase 19: the viewer joins the shared header (assets/header.js) — the
 * whoami fetch is the module's cached promise (one request per page,
 * shared with initSharedHeader's toggling), and the bar gains the New
 * chat button: on a non-chat page "new chat" means going to the chat,
 * fresh (clear the phase-14 conversation key, then navigate to "/").
 */

import { clearChatStorage, fetchIsAdmin, initSharedHeader } from "./header.js";

const params = new URLSearchParams(window.location.search);
const source = params.get("source") || "";
const path = params.get("path") || "";

const titleEl = document.querySelector("#doc-title");
const metaEl = document.querySelector("#doc-meta");
const contentEl = document.querySelector("#doc-content");
const notFoundEl = document.querySelector("#doc-not-found");
const mainEl = document.querySelector("#main");
const backLink = document.querySelector("#doc-back");

/* Back button (phase 13): the return target comes from the `back` query
 * param, not the browser history — both entry points (chat source chips
 * and the Sources table) open the viewer in a NEW tab, where there is no
 * history to go back to. The param is honored only for same-origin
 * relative URLs (starts with "/" but not "//"), so absolute (https://…),
 * protocol-relative (//…), and pseudo-protocol (javascript:…) values are
 * rejected; anything else falls back to the Sources page. The static
 * href="/sources.html" in document.html remains the no-JS fallback, and
 * with the href set the anchor's default click behavior IS the
 * deterministic navigation (no browser-history heuristics). */
const backParam = params.get("back") || "";
const backTarget =
  backParam.startsWith("/") && !backParam.startsWith("//")
    ? backParam
    : "/sources.html";
backLink.href = backTarget;
const backLabel = backLink.querySelector("span");
if (backLabel) {
  backLabel.textContent =
    backTarget === "/"
      ? "Chat"
      : backTarget === "/sources.html"
        ? "Sources"
        : "Back";
}

function fmtDate(iso) {
  try {
    return new Date(iso).toLocaleString();
  } catch {
    return iso;
  }
}

function contentUrl(s, p) {
  return "/api/documents/content?source=" + encodeURIComponent(s) + "&path=" + encodeURIComponent(p);
}

function metaBadge(cls, text) {
  const el = document.createElement("span");
  el.className = cls;
  el.textContent = text;
  return el;
}

function render(doc) {
  titleEl.textContent = doc.title;
  document.title = `${doc.title} · Brain of Reese`;

  const pathCode = document.createElement("code");
  pathCode.className = "doc-path";
  pathCode.textContent = doc.path;
  metaEl.replaceChildren(
    metaBadge("doc-source-badge", doc.source),
    metaBadge("format-badge", doc.format),
    pathCode,
    metaBadge("doc-indexed", `Indexed ${fmtDate(doc.indexed_at)}`),
    metaBadge("doc-chunks", `${doc.chunks} chunk${doc.chunks === 1 ? "" : "s"}`),
  );

  notFoundEl.hidden = true;
  contentEl.replaceChildren();
  if (doc.format === "md" || doc.format === "markdown") {
    const wrap = document.createElement("div");
    wrap.className = "doc-md";
    wrap.innerHTML = renderMarkdown(doc.content); // escape-first: XSS-safe
    contentEl.appendChild(wrap);
  } else {
    const pre = document.createElement("pre");
    pre.className = "doc-raw";
    pre.textContent = doc.content; // text node: never parsed as HTML
    contentEl.appendChild(pre);
  }
}

function showNotFound() {
  titleEl.textContent = "Document not found";
  document.title = "Document not found · Brain of Reese";
  metaEl.replaceChildren();
  contentEl.replaceChildren();
  notFoundEl.hidden = false;
}

/* Phase 19: the shared header controls (Sign in / Sign out — exactly one
 * visible) are toggled here; the viewer has no nav, so there is no
 * #nav-sources for the module to touch. Independent of the doc fetch
 * (its own IIFE — load() below never waits on it).
 * (fetchIsAdmin is imported for parity with the other header consumers —
 * the module's cached promise is the single whoami per page either way.) */
(async () => {
  await initSharedHeader();
})();

/* Phase 19: New chat on a non-chat page means "go to the chat, fresh":
 * clear the phase-14 conversation key, then land on the chat page — its
 * empty state, since the conversation is gone from storage. */
const newChatBtn = document.querySelector("#new-chat-btn");
if (newChatBtn) {
  newChatBtn.addEventListener("click", () => {
    clearChatStorage();
    window.location.href = "/";
  });
}

async function load() {
  try {
    if (!source || !path) {
      showNotFound();
      return;
    }
    const r = await fetch(contentUrl(source, path));
    if (!r.ok) {
      showNotFound();
      return;
    }
    render(await r.json());
  } catch {
    showNotFound();
  } finally {
    mainEl.focus(); // move focus to main on load (a11y)
  }
}

load();