// ── Session History Browser ────────────────────────────────────────────
// Provides the history drawer: list past sessions, browse archived
// transcripts, favourite/delete/load. Exactly one row is ever CURRENT;
// all others are loadable past sessions.
// ─────────────────────────────────────────────────────────────────────────────

"use strict";

(function () {
  const drawer = document.getElementById("history-drawer");
  const overlay = document.getElementById("history-overlay");
  const drawerBody = document.getElementById("history-drawer-body");
  const closeBtn = document.getElementById("history-close-btn");
  const historyBtn = document.getElementById("history-btn");
  const archiveToast = document.getElementById("session-archive-toast");
  const archiveToastLink = document.getElementById("session-archive-link");
  const searchInput = document.getElementById("history-search-input");
  const searchClear = document.getElementById("history-search-clear");

  let isOpen = false;
  let toastTimer = null;
  let __viewingFile = null;
  const DELETE_CONFIRM_MS = 5000;
  const SEARCH_DEBOUNCE_MS = 250;
  let searchDebounceTimer = null;
  let activeSearchToken = 0;

  // ── Open / close ──────────────────────────────────────────────────────
  function openDrawer() {
    isOpen = true;
    drawer.classList.add("open");
    overlay.classList.add("open");
    drawer.setAttribute("aria-hidden", "false");
    void loadSessionList();
  }

  function closeDrawer() {
    isOpen = false;
    drawer.classList.remove("open");
    overlay.classList.remove("open");
    drawer.setAttribute("aria-hidden", "true");
    __viewingFile = null;
    showToolCalls = false;
    if (searchInput && searchInput.value) {
      searchInput.value = "";
      if (searchClear) {
        searchClear.hidden = true;
      }
    }
  }

  historyBtn.addEventListener("click", () => (isOpen ? closeDrawer() : openDrawer()));
  closeBtn.addEventListener("click", closeDrawer);
  overlay.addEventListener("click", closeDrawer);
  document.addEventListener("keydown", (e) => {
    if (e.key === "Escape" && isOpen) {
      closeDrawer();
    }
  });

  // ── Cross-session search ─────────────────────────────────────────────
  if (searchInput) {
    searchInput.addEventListener("input", () => {
      const value = searchInput.value;
      if (searchClear) {
        searchClear.hidden = !value;
      }
      if (searchDebounceTimer) {
        clearTimeout(searchDebounceTimer);
      }
      searchDebounceTimer = setTimeout(() => {
        const trimmed = value.trim();
        if (!trimmed) {
          void loadSessionList();
        } else {
          void runSessionSearch(trimmed);
        }
      }, SEARCH_DEBOUNCE_MS);
    });
    searchInput.addEventListener("keydown", (e) => {
      if (e.key === "Escape" && searchInput.value) {
        e.stopPropagation();
        searchInput.value = "";
        if (searchClear) {
          searchClear.hidden = true;
        }
        void loadSessionList();
      }
    });
  }
  if (searchClear) {
    searchClear.addEventListener("click", () => {
      if (!searchInput) {
        return;
      }
      searchInput.value = "";
      searchClear.hidden = true;
      searchInput.focus();
      void loadSessionList();
    });
  }

  // ── Session list ──────────────────────────────────────────────────────
  async function loadSessionList() {
    drawerBody.innerHTML = '<div class="history-loading">Loading sessions…</div>';
    __viewingFile = null;

    const currentKey = window.SESSION_KEY ?? "agent:main:main";
    const url = "/api/sessions?currentKey=" + encodeURIComponent(currentKey);

    let sessions;
    try {
      const res = await fetch(url);
      const payload = await res.json();
      // Response shape: { sessions: [...] } (2.7.1+)
      sessions = Array.isArray(payload?.sessions) ? payload.sessions : [];
    } catch {
      drawerBody.innerHTML = '<div class="history-empty">Failed to load sessions.</div>';
      return;
    }

    if (!sessions.length) {
      drawerBody.innerHTML = '<div class="history-empty">No session history found.</div>';
      return;
    }

    // Group rows: CURRENT / FAVOURITES / RECENT
    const current = sessions.filter((s) => s.kind === "current");
    const favourites = sessions.filter((s) => s.kind !== "current" && s.favorited);
    const recent = sessions.filter((s) => s.kind !== "current" && !s.favorited);

    // Newest `.reset.*` archive with content — this becomes the target of
    // the Load Session button on the CURRENT row, giving the user a
    // one-click path back to the session they just left.
    const returnCandidate = findReturnCandidate(sessions);

    drawerBody.innerHTML = "";

    if (current.length > 0) {
      drawerBody.appendChild(renderSection("CURRENT", current, returnCandidate));
    }
    if (favourites.length > 0) {
      drawerBody.appendChild(renderSection("★ FAVOURITES", favourites, null));
    }
    if (recent.length > 0) {
      drawerBody.appendChild(renderSection("RECENT", recent, null));
    }
  }

  function findReturnCandidate(sessions) {
    const now = Date.now();
    const windowMs = 24 * 60 * 60 * 1000; // 24 hours
    const candidates = sessions.filter((s) => {
      if (s.kind !== "archived") {
        return false;
      }
      if (s.archivedReason !== "reset") {
        return false;
      }
      if (!s.msgCount || s.msgCount <= 0) {
        return false;
      }
      if (!s.archivedAt) {
        return false;
      }
      const archivedMs = new Date(s.archivedAt).getTime();
      if (Number.isNaN(archivedMs)) {
        return false;
      }
      return now - archivedMs < windowMs;
    });
    if (!candidates.length) {
      return null;
    }
    // Newest first
    candidates.sort((a, b) => (b.archivedAt ?? "").localeCompare(a.archivedAt ?? ""));
    return candidates[0];
  }

  function renderSection(title, rows, returnCandidate) {
    const section = document.createElement("section");
    section.className = "history-section";
    const heading = document.createElement("div");
    heading.className = "history-section-heading";
    heading.textContent = title;
    section.appendChild(heading);
    for (const row of rows) {
      // Pass the return candidate through only for the CURRENT row — it
      // drives the row's Load Session button (which returns the user to
      // their previous session). Other rows use their own row data.
      section.appendChild(renderRow(row, row.kind === "current" ? returnCandidate : null));
    }
    return section;
  }

  function renderRow(row, returnCandidate) {
    const item = document.createElement("div");
    item.className = "history-session-item history-session-" + row.kind;

    const badge = BADGE[row.kind] ?? BADGE.past;
    const when = formatTimestamp(row.lastActivity);
    const preview = row.preview
      ? escHtml(row.preview) + (row.preview.length >= 80 ? "…" : "")
      : '<em style="opacity:0.5">No messages</em>';

    // CURRENT row's Load Session button targets the most-recent .reset.*
    // archive (returnCandidate). Non-current rows use their own file.
    let footerHtml;
    if (row.kind === "current") {
      footerHtml = returnCandidate
        ? `<footer class="history-session-actions">
             <button class="history-load-btn" data-return="1">Load Session</button>
           </footer>`
        : "";
    } else {
      footerHtml = `<footer class="history-session-actions">
          ${row.canLoad ? `<button class="history-load-btn">Load Session</button>` : ""}
          ${row.canDelete ? `<button class="history-delete-btn">Delete</button>` : ""}
        </footer>`;
    }

    item.innerHTML = `
      <header class="history-session-header">
        <button class="history-fav-btn ${row.favorited ? "active" : ""}"
                aria-pressed="${row.favorited ? "true" : "false"}"
                aria-label="${row.favorited ? "Unfavourite" : "Favourite"} session"
                title="${row.favorited ? "Unfavourite" : "Favourite"}">★</button>
        <span class="history-session-badge ${badge.className}">${badge.label}</span>
        <span class="history-session-date">${escHtml(when)}</span>
      </header>
      <div class="history-session-meta">
        <span class="history-session-count">${row.msgCount} msgs · ${fmtSize(row.size)}</span>
      </div>
      <div class="history-session-preview">${preview}</div>
      ${footerHtml}
    `;

    // Body-click behaviour varies by kind:
    // - CURRENT: close the drawer (user is already viewing this session
    //   in the main feed — no point opening a transcript preview).
    // - PAST / ARCHIVED: open the transcript viewer.
    item.addEventListener("click", (e) => {
      const target = e.target;
      if (target instanceof Element && target.closest("button")) {
        return; // button click handled below
      }
      if (row.kind === "current") {
        closeDrawer();
        return;
      }
      void loadTranscript(row.file, when);
    });

    const favBtn = item.querySelector(".history-fav-btn");
    favBtn?.addEventListener("click", async (e) => {
      e.stopPropagation();
      await toggleFavorite(row, favBtn);
    });

    const loadBtn = item.querySelector(".history-load-btn");
    loadBtn?.addEventListener("click", async (e) => {
      e.stopPropagation();
      // On the CURRENT row, Load Session loads the most-recent archive
      // (returnCandidate). On PAST/ARCHIVED rows, it loads that row.
      const target = loadBtn.getAttribute("data-return") === "1" ? returnCandidate : row;
      if (target) {
        await loadRow(target);
      }
    });

    const deleteBtn = item.querySelector(".history-delete-btn");
    if (deleteBtn instanceof HTMLButtonElement) {
      wireTwoStageDelete(deleteBtn, () => deleteRow(row));
    }

    return item;
  }

  const BADGE = {
    current: { label: "CURRENT", className: "badge-current" },
    past: { label: "PAST", className: "badge-past" },
    archived: { label: "ARCHIVED", className: "badge-archived" },
  };

  // ── Cross-session search ─────────────────────────────────────────────
  async function runSessionSearch(query) {
    if (!window.gateway?.connected) {
      drawerBody.innerHTML = '<div class="history-empty">Gateway not connected.</div>';
      return;
    }
    const token = ++activeSearchToken;
    drawerBody.innerHTML = '<div class="history-loading">Searching…</div>';

    let result;
    try {
      result = await window.gateway.rpc("sessions.search", { query, limit: 24 });
    } catch (err) {
      if (token !== activeSearchToken) {
        return;
      }
      drawerBody.innerHTML =
        '<div class="history-empty">Search failed: ' +
        escHtml(err?.message ?? String(err)) +
        "</div>";
      return;
    }

    // Drop stale responses if a newer search was kicked off mid-flight.
    if (token !== activeSearchToken) {
      return;
    }

    const matches = Array.isArray(result?.matches) ? result.matches : [];
    if (!matches.length) {
      drawerBody.innerHTML = `<div class="history-search-status">No matches for ${escHtml(
        query,
      )}.</div>`;
      return;
    }

    drawerBody.innerHTML = "";
    const status = document.createElement("div");
    status.className = "history-search-status";
    status.textContent = `${matches.length} match${matches.length === 1 ? "" : "es"} for "${query}"`;
    drawerBody.appendChild(status);

    const section = document.createElement("section");
    section.className = "history-section";
    for (const match of matches) {
      section.appendChild(renderSearchMatch(match, query));
    }
    drawerBody.appendChild(section);
  }

  function renderSearchMatch(match, query) {
    const item = document.createElement("div");
    item.className = "history-session-item history-session-search-match";

    const title =
      match.label ||
      match.displayName ||
      match.sessionKey ||
      `session ${match.sessionId.slice(0, 8)}`;
    const when = match.updatedAt ? formatTimestamp(new Date(match.updatedAt).toISOString()) : "—";
    const lineRange =
      match.lineStart === match.lineEnd
        ? `line ${match.lineStart}`
        : `lines ${match.lineStart}–${match.lineEnd}`;
    const score = typeof match.score === "number" ? match.score.toFixed(2) : "—";
    const snippetHtml = highlightSnippet(match.snippet ?? "", query);

    const canLoad = Boolean(match.sessionKey);
    const footerHtml = canLoad
      ? `<footer class="history-session-actions">
           <button class="history-load-btn">Load Session</button>
         </footer>`
      : "";

    item.innerHTML = `
      <header class="history-session-header">
        <span class="history-session-badge badge-past">MATCH</span>
        <span class="history-session-date">${escHtml(title)}</span>
      </header>
      <div class="history-match-snippet">${snippetHtml}</div>
      <div class="history-match-meta">
        <span>${escHtml(when)}</span>
        <span>${escHtml(lineRange)}</span>
        <span>score ${escHtml(score)}</span>
      </div>
      ${footerHtml}
    `;

    const loadBtn = item.querySelector(".history-load-btn");
    loadBtn?.addEventListener("click", async (e) => {
      e.stopPropagation();
      if (!match.sessionKey) {
        return;
      }
      // Adopt-by-file using the session's current transcript file (basename).
      await loadRow({ file: match.sessionId + ".jsonl" });
    });

    item.addEventListener("click", (e) => {
      const target = e.target;
      if (target instanceof Element && target.closest("button")) {
        return;
      }
      void loadTranscript(match.sessionId + ".jsonl", title);
    });

    return item;
  }

  function highlightSnippet(text, query) {
    if (!text) {
      return "";
    }
    if (!query) {
      return escHtml(text);
    }
    const safeText = escHtml(text);
    const escapedQuery = query.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
    try {
      const re = new RegExp(escapedQuery, "gi");
      return safeText.replace(re, (m) => `<mark>${m}</mark>`);
    } catch {
      return safeText;
    }
  }

  // ── Actions ───────────────────────────────────────────────────────────

  async function toggleFavorite(row, favBtn) {
    if (!window.gateway?.connected) {
      return;
    }
    const next = !row.favorited;
    try {
      await window.gateway.rpc("sessions.favoriteFile", {
        file: row.file,
        favorited: next,
      });
      row.favorited = next;
      favBtn.classList.toggle("active", next);
      favBtn.setAttribute("aria-pressed", next ? "true" : "false");
      // Re-render to move the row between FAVOURITES and RECENT sections.
      void loadSessionList();
    } catch (err) {
      console.warn("[history] favorite toggle failed", err);
    }
  }

  async function loadRow(row) {
    if (!window.gateway?.connected) {
      alert("Gateway not connected.");
      return;
    }
    drawerBody.innerHTML = '<div class="history-loading">Loading session…</div>';
    try {
      await window.gateway.rpc("sessions.adoptFile", {
        key: window.SESSION_KEY,
        targetFile: row.file,
      });
      closeDrawer();
      if (typeof window.reloadSession === "function") {
        window.reloadSession();
      }
    } catch (err) {
      drawerBody.innerHTML =
        '<div class="history-empty">Load failed: ' +
        escHtml(err?.message ?? String(err)) +
        "</div>";
    }
  }

  async function deleteRow(row) {
    if (!window.gateway?.connected) {
      return;
    }
    try {
      if (row.kind === "past" && row.sessionKey) {
        // Archives the transcript via `sessions.delete` — the file becomes
        // an ARCHIVED row in the next list refresh. Reversible.
        await window.gateway.rpc("sessions.delete", {
          key: row.sessionKey,
          deleteTranscript: true,
        });
      } else {
        // Archived files (or orphan live files): permanent deletion.
        await window.gateway.rpc("sessions.deleteFile", { file: row.file });
      }
      await loadSessionList();
    } catch (err) {
      console.warn("[history] delete failed", err);
      alert("Delete failed: " + (err?.message ?? String(err)));
    }
  }

  /**
   * Wire a Delete button as a two-stage confirm. First click arms it with
   * "Really delete?" for DELETE_CONFIRM_MS; second click within the window
   * runs the action. Auto-reverts if the user walks away.
   */
  function wireTwoStageDelete(btn, run) {
    let armed = false;
    let armTimer = null;
    const originalLabel = btn.textContent;
    btn.addEventListener("click", (e) => {
      e.stopPropagation();
      if (!armed) {
        armed = true;
        btn.textContent = "Really delete?";
        btn.classList.add("armed");
        if (armTimer) {
          clearTimeout(armTimer);
        }
        armTimer = setTimeout(() => {
          armed = false;
          btn.textContent = originalLabel;
          btn.classList.remove("armed");
        }, DELETE_CONFIRM_MS);
        return;
      }
      clearTimeout(armTimer);
      armed = false;
      btn.disabled = true;
      btn.textContent = "Deleting…";
      btn.classList.remove("armed");
      void run();
    });
  }

  // ── Transcript viewer ─────────────────────────────────────────────────
  // Per-drawer-session preference: whether the transcript modal includes
  // tool_use / tool_result content blocks alongside chat text. Defaults
  // off (matches the historical view); the toggle button in the banner
  // flips it for the current viewing session, then resets on close.
  let showToolCalls = false;

  async function loadTranscript(file, label) {
    __viewingFile = file;
    drawerBody.innerHTML = '<div class="history-loading">Loading transcript…</div>';

    let messages;
    try {
      const res = await fetch("/api/transcript?" + new URLSearchParams({ file }));
      const data = await res.json();
      messages = data.messages ?? [];
    } catch {
      drawerBody.innerHTML = '<div class="history-empty">Failed to load transcript.</div>';
      return;
    }

    renderTranscript(messages, label);
  }

  function renderTranscript(messages, label) {
    drawerBody.innerHTML = "";

    const banner = document.createElement("div");
    banner.className = "archive-banner";
    banner.innerHTML = `
      <span class="archive-banner-label">◈ ${escHtml(label)}</span>
      <div class="archive-banner-actions">
        <button class="archive-banner-toggle ${showToolCalls ? "active" : ""}"
                id="archive-tools-toggle"
                aria-pressed="${showToolCalls ? "true" : "false"}">
          ${showToolCalls ? "Hide tool calls" : "Show tool calls"}
        </button>
        <button class="archive-banner-back" id="archive-back-btn">← All Sessions</button>
      </div>
    `;
    drawerBody.appendChild(banner);
    drawerBody.querySelector("#archive-back-btn").addEventListener("click", loadSessionList);
    drawerBody.querySelector("#archive-tools-toggle").addEventListener("click", () => {
      showToolCalls = !showToolCalls;
      renderTranscript(messages, label);
    });

    const container = document.createElement("div");
    container.className = "history-transcript";

    const relevant = messages.filter((m) => m.role === "user" || m.role === "assistant");
    if (!relevant.length) {
      container.innerHTML = '<div class="history-empty">No chat messages in this session.</div>';
    } else {
      let renderedAny = false;
      for (const msg of relevant) {
        const text = extractMsgText(msg);
        const tools = showToolCalls ? extractToolBlocks(msg) : [];
        if (!text.trim() && tools.length === 0) {
          continue;
        }
        renderedAny = true;
        const el = document.createElement("div");
        el.className = `history-msg ${msg.role}`;
        const roleLabel = msg.role === "user" ? "YOU" : "SYMI";

        let html = `<div class="history-msg-role ${msg.role}">${roleLabel}</div>`;
        if (text.trim()) {
          html += `<div class="history-msg-text">${escHtml(text)}</div>`;
        }
        for (const block of tools) {
          html += renderToolBlock(block);
        }
        el.innerHTML = html;
        container.appendChild(el);
      }
      if (!renderedAny) {
        container.innerHTML = '<div class="history-empty">No chat messages in this session.</div>';
      }
    }

    drawerBody.appendChild(container);
  }

  function extractToolBlocks(msg) {
    if (!Array.isArray(msg.content)) {
      return [];
    }
    const out = [];
    for (const block of msg.content) {
      if (!block || typeof block !== "object") {
        continue;
      }
      if (block.type === "tool_use") {
        out.push({
          kind: "use",
          name: typeof block.name === "string" ? block.name : "tool",
          input: block.input ?? null,
        });
      } else if (block.type === "tool_result") {
        let resultText = "";
        if (typeof block.content === "string") {
          resultText = block.content;
        } else if (Array.isArray(block.content)) {
          resultText = block.content
            .filter((c) => c && (c.type === "text" || typeof c === "string"))
            .map((c) => (typeof c === "string" ? c : (c.text ?? "")))
            .join("\n");
        }
        out.push({
          kind: "result",
          isError: block.is_error === true,
          text: resultText,
        });
      }
    }
    return out;
  }

  function renderToolBlock(block) {
    const MAX_INPUT_CHARS = 400;
    const MAX_OUTPUT_CHARS = 600;
    if (block.kind === "use") {
      let body = "";
      try {
        body = block.input == null ? "" : JSON.stringify(block.input, null, 2);
      } catch {
        body = String(block.input);
      }
      if (body.length > MAX_INPUT_CHARS) {
        body = body.slice(0, MAX_INPUT_CHARS) + "\n… [truncated]";
      }
      return `
        <div class="history-tool-block history-tool-use">
          <div class="history-tool-head">⚙ ${escHtml(block.name)}</div>
          ${body ? `<pre class="history-tool-body">${escHtml(body)}</pre>` : ""}
        </div>
      `;
    }
    let text = block.text ?? "";
    if (text.length > MAX_OUTPUT_CHARS) {
      text = text.slice(0, MAX_OUTPUT_CHARS) + "\n… [truncated]";
    }
    const cls = block.isError ? "history-tool-result-error" : "history-tool-result";
    return `
      <div class="history-tool-block ${cls}">
        <div class="history-tool-head">${block.isError ? "✗ tool error" : "✓ tool result"}</div>
        ${text ? `<pre class="history-tool-body">${escHtml(text)}</pre>` : ""}
      </div>
    `;
  }

  // ── Archive toast (shown after /new) ──────────────────────────────────
  window.showArchiveToast = function () {
    if (toastTimer) {
      clearTimeout(toastTimer);
    }
    archiveToast.classList.add("show");
    toastTimer = setTimeout(() => archiveToast.classList.remove("show"), 8000);
  };

  archiveToastLink.addEventListener("click", () => {
    archiveToast.classList.remove("show");
    openDrawer();
  });

  // ── Helpers ───────────────────────────────────────────────────────────
  function extractMsgText(msg) {
    if (typeof msg.content === "string") {
      return msg.content;
    }
    if (!Array.isArray(msg.content)) {
      return "";
    }
    return msg.content
      .filter((b) => b.type === "text" || b.type === "thinking")
      .map((b) => b.text ?? b.thinking ?? "")
      .join("\n")
      .trim();
  }

  function escHtml(str) {
    return String(str)
      .replace(/&/g, "&amp;")
      .replace(/</g, "&lt;")
      .replace(/>/g, "&gt;")
      .replace(/"/g, "&quot;");
  }

  function formatTimestamp(iso) {
    if (!iso) {
      return "Unknown";
    }
    const d = new Date(iso);
    if (Number.isNaN(d.getTime())) {
      return "Unknown";
    }
    return d.toLocaleString("en-US", {
      month: "short",
      day: "numeric",
      year: "numeric",
      hour: "2-digit",
      minute: "2-digit",
      hour12: true,
    });
  }

  function fmtSize(bytes) {
    if (typeof bytes !== "number" || !Number.isFinite(bytes) || bytes < 0) {
      return "0B";
    }
    if (bytes < 1024) {
      return `${bytes}B`;
    }
    if (bytes < 1024 * 1024) {
      return `${(bytes / 1024).toFixed(0)}KB`;
    }
    return `${(bytes / 1024 / 1024).toFixed(1)}MB`;
  }
})();
