// 个人知识库 modal:左栏列库,右栏库详情(已入库条目 + 待入库 + 入库进度)/ 单篇正文。
// 照 memory.js master-detail 范式;删除走 dialogConfirm(crons.js 同款);上传用 XHR
// FormData(files.js 同款,带 Bearer);入库进行中每 3s 轮询库详情(procs.js 自适应启停)。
// 后端:/v1/kb*(core/kb.py 纯文件,真实文件为准);检索侧 agent 走 fs 工具,前端只当"眼睛+搬运工"。
import { $ } from "./dom.js";
import { api } from "./api.js";
import { state } from "./state.js";
import { escapeHtml } from "./format.js";
import { renderMd, highlightIn } from "./markdown.js";
import { dialogConfirm, dialogPrompt, message } from "./dialog.js";
import { downloadFile } from "./media.js";
import { openFilePreview } from "./preview.js";

const PLACEHOLDER = '<div class="sk-empty">← 选一个库查看,或「+ 新建库」</div>';
const POLL_MS = 3000;
let _current = null;   // 当前选中库名
let _pollTimer = null;
let _docSources = {};  // doc 文件名 → source 原件名(renderDetail 时填,单篇页「下载原件」用)

// .kb 就在 user_root 下,下载直接复用 /v1/files/download(media.js downloadFile)
const kbRel = (sub) => ".kb/" + _current + "/" + sub;

function openKbModal() {
  $("kb-modal").classList.add("show");
  $("kb-detail").innerHTML = PLACEHOLDER;
  _current = null;
  renderList();
}
export function closeKbModal() {
  $("kb-modal").classList.remove("show");
  stopPolling();
}

function startPolling() {
  if (!_pollTimer) _pollTimer = setInterval(() => { if (_current) showKb(_current, null, true); }, POLL_MS);
}
function stopPolling() {
  if (_pollTimer) { clearInterval(_pollTimer); _pollTimer = null; }
}

// ───── 左栏:库列表 ─────
async function renderList() {
  const list = $("kb-list");
  let data;
  try {
    data = await api("GET", "/v1/kb");
  } catch (e) {
    list.innerHTML = `<div class="err" style="padding:8px;">加载失败: ${escapeHtml(e.message)}</div>`;
    return;
  }
  const kbs = data.results || [];
  let html = '<div class="sk-group-title">我的库</div>';
  html += kbs.length
    ? kbs.map((k) => `<div class="sk-item${k.name === _current ? " active" : ""}" data-kb="${escapeHtml(k.name)}">
        <div class="sk-name">${escapeHtml(k.name)}${k.pending_count ? ` <span class="sk-badge">待入库 ${k.pending_count}</span>` : ""}</div>
        <div class="sk-desc">${k.doc_count} 篇文档</div>
      </div>`).join("")
    : '<div class="muted" style="padding:4px 8px;font-size:12px;">还没有库。建一个,把常用资料(规范 / 报告 / 标准…)传进来,以后对话我能直接查。</div>';
  html += '<div style="padding:8px;"><button id="kb-new" class="small" style="width:100%;">+ 新建库</button></div>';
  list.innerHTML = html;
}

async function createKb() {
  const name = await dialogPrompt({
    title: "新建知识库",
    message: "库名(中文 / 字母 / 数字,如「行业标准」「项目资料」):",
    okText: "创建",
  });
  if (!name || !name.trim()) return;
  try {
    await api("POST", "/v1/kb", { name: name.trim() });
  } catch (e) { message("创建失败: " + e.message, "error"); return; }
  await renderList();
  showKb(name.trim(), null);
}

// ───── 右栏:库详情 ─────
function highlightSel(name) {
  $("kb-list").querySelectorAll(".sk-item").forEach((el) => {
    el.classList.toggle("active", el.getAttribute("data-kb") === name);
  });
}

async function showKb(name, itemEl, isPoll = false) {
  _current = name;
  if (!isPoll) {
    highlightSel(name);
    $("kb-detail").innerHTML = '<div class="muted" style="padding:8px;">加载中…</div>';
  }
  let d;
  try {
    d = await api("GET", "/v1/kb/" + encodeURIComponent(name));
  } catch (e) {
    if (!isPoll) $("kb-detail").innerHTML = `<div class="err" style="padding:8px;">加载失败: ${escapeHtml(e.message)}</div>`;
    stopPolling();
    return;
  }
  if (_current !== name) return; // 轮询回来时已切走
  renderDetail(d);
  if (d.ingest && d.ingest.running) startPolling(); else stopPolling();
}

function renderDetail(d) {
  const ing = d.ingest || {};
  const entries = d.entries || [];
  const pending = d.pending || [];
  let html = `<div class="sk-d-head">
    <span class="sk-d-name">${escapeHtml(d.name)}</span>
    <span class="sk-badge">${entries.length} 篇</span>
    <span class="spacer"></span>
    <button id="kb-upload-btn" class="small primary">上传文件</button>
    <button id="kb-del" class="small danger" data-kb="${escapeHtml(d.name)}">删除库</button>
  </div>`;

  if (ing.running) {
    html += `<div class="kb-ingest-bar">入库中… ${ing.done}/${ing.total}${ing.current ? " · " + escapeHtml(ing.current) : ""}</div>`;
  }
  if (ing.errors && ing.errors.length) {
    html += `<div class="kb-ingest-err">${ing.errors.map((x) =>
      `<div>⚠ ${escapeHtml(x.source)}:${escapeHtml(x.error)}</div>`).join("")}</div>`;
  }
  if (pending.length) {
    html += `<div class="sk-group-title" style="margin-top:10px;">待入库 (${pending.length})</div>`;
    html += pending.map((n) => `<div class="kb-pending">${escapeHtml(n)}
      <button class="small kb-src-view" data-src="${escapeHtml(n)}" title="查看原件">查看</button>
      <button class="small kb-src-dl" data-src="${escapeHtml(n)}" title="下载原件">下载</button></div>`).join("");
    if (!ing.running) {
      html += `<div style="padding:6px 0;"><button id="kb-reingest" class="small">重试入库</button></div>`;
    }
  }
  html += '<div class="sk-group-title" style="margin-top:10px;">已入库</div>';
  _docSources = {};
  html += entries.length
    ? entries.map((e) => {
        const doc = e.doc.replace(/^docs\//, "");
        const src = (e.source || "").replace(/^sources\//, "");
        if (src) _docSources[doc] = src;
        // 行内只留标题(+原件名小字)+ 单行摘要(CSS 省略);完整摘要/关键词进 title 悬停提示,不占行高
        const tip = e.summary + (e.keywords ? "\n关键词: " + e.keywords : "") + (src ? "\n原件: " + src : "");
        return `<div class="kb-entry">
        <div class="kb-entry-main" data-doc="${escapeHtml(doc)}" title="${escapeHtml(tip)}">
          <div class="sk-name">${escapeHtml(e.title)}${src ? `<span class="kb-src-name">${escapeHtml(src)}</span>` : ""}</div>
          <div class="sk-desc">${escapeHtml(e.summary)}</div>
        </div>
        ${src ? `<button class="small kb-src-view" data-src="${escapeHtml(src)}" title="查看原件">👁</button>
        <button class="small kb-src-dl" data-src="${escapeHtml(src)}" title="下载原件">⬇</button>` : ""}
        <button class="small danger kb-doc-del" data-doc="${escapeHtml(doc)}" title="删除该篇(含原件)">✕</button>
      </div>`;
      }).join("")
    : '<div class="muted" style="padding:4px 0;font-size:12px;">空库。点「上传文件」把资料传进来,自动转 markdown 并写摘要。</div>';
  $("kb-detail").innerHTML = html;
}

// ───── 单篇查看 ─────
async function showDoc(filename) {
  const detail = $("kb-detail");
  detail.innerHTML = '<div class="muted" style="padding:8px;">加载中…</div>';
  let data;
  try {
    data = await api("GET", "/v1/kb/" + encodeURIComponent(_current) + "/docs/" + encodeURIComponent(filename));
  } catch (e) {
    detail.innerHTML = `<div class="err" style="padding:8px;">加载失败: ${escapeHtml(e.message)}</div>`;
    return;
  }
  const src = _docSources[filename];
  detail.innerHTML =
    `<div class="sk-d-head"><button id="kb-back" class="small">← 返回</button>
     <span class="sk-d-name">${escapeHtml(filename)}</span>
     ${src ? `<span class="kb-src-name">原件: ${escapeHtml(src)}</span>` : ""}
     <span class="spacer"></span>
     ${src ? `<button id="kb-doc-src-view" class="small" data-src="${escapeHtml(src)}">查看原件</button>
     <button id="kb-doc-src-dl" class="small" data-src="${escapeHtml(src)}">下载原件</button>` : ""}
     <button id="kb-doc-md-dl" class="small" data-doc="${escapeHtml(filename)}">下载 Markdown</button></div>` +
    `<div class="sk-detail-md">${renderMd(data.content)}</div>`;
  highlightIn(detail);
}

// ───── 上传(files.js 同款 XHR,带进度)─────
function uploadTo(name, fileList) {
  const files = Array.from(fileList || []);
  if (!files.length) return;
  const fd = new FormData();
  for (const f of files) fd.append("files", f);
  const xhr = new XMLHttpRequest();
  xhr.open("POST", "/v1/kb/" + encodeURIComponent(name) + "/upload");
  xhr.setRequestHeader("Authorization", "Bearer " + state.token);
  xhr.onload = () => {
    let payload = null;
    try { payload = JSON.parse(xhr.responseText); } catch (e) {}
    if (xhr.status >= 200 && xhr.status < 300) {
      message(`已上传 ${payload && payload.count || files.length} 个文件,开始入库…`);
      showKb(name, null);   // 立即刷详情;running 会自动开轮询
    } else {
      const msg = (payload && payload.detail) || (xhr.status + " " + xhr.statusText);
      message("上传失败: " + msg, "error");
    }
  };
  xhr.onerror = () => message("上传失败: 网络错误", "error");
  xhr.send(fd);
  message("上传中…");
}

// ───── 顶层绑定 ─────
$("hd-kb").onclick = openKbModal;
$("kb-close").onclick = closeKbModal;
$("kb-modal").addEventListener("click", (e) => {
  if (e.target.id === "kb-modal") closeKbModal(); // 点遮罩关闭
});
$("kb-list").addEventListener("click", (e) => {
  if (e.target.id === "kb-new" || e.target.closest("#kb-new")) { createKb(); return; }
  const item = e.target.closest(".sk-item");
  if (item) showKb(item.getAttribute("data-kb"), item);
});
$("kb-upload-input").addEventListener("change", () => {
  const inp = $("kb-upload-input");
  if (_current) uploadTo(_current, inp.files);
  inp.value = "";
});
$("kb-detail").addEventListener("click", async (e) => {
  if (e.target.id === "kb-upload-btn") { $("kb-upload-input").click(); return; }
  if (e.target.id === "kb-back") { showKb(_current, null); return; }
  if (e.target.id === "kb-doc-md-dl") { downloadFile(kbRel("docs/" + e.target.getAttribute("data-doc"))); return; }
  if (e.target.id === "kb-doc-src-dl") { downloadFile(kbRel("sources/" + e.target.getAttribute("data-src"))); return; }
  // 查看原件:复用主预览弹框(preview.js,z 114 盖在 kb modal 之上),路径同下载走 .kb 相对路径
  if (e.target.id === "kb-doc-src-view") { openFilePreview(kbRel("sources/" + e.target.getAttribute("data-src"))); return; }
  const srcView = e.target.closest(".kb-src-view");
  if (srcView) { openFilePreview(kbRel("sources/" + srcView.getAttribute("data-src"))); return; }
  const srcDl = e.target.closest(".kb-src-dl");
  if (srcDl) { downloadFile(kbRel("sources/" + srcDl.getAttribute("data-src"))); return; }
  if (e.target.id === "kb-reingest") {
    try { await api("POST", "/v1/kb/" + encodeURIComponent(_current) + "/ingest"); } catch (err) {
      message("触发失败: " + err.message, "error"); return;
    }
    showKb(_current, null);
    return;
  }
  const del = e.target.closest("#kb-del");
  if (del) {
    const name = del.getAttribute("data-kb");
    if (!await dialogConfirm({
      title: "删除知识库",
      message: `删除库「${name}」?其中所有文档与原件将一并删除,不可恢复。`,
      okText: "删除", danger: true,
    })) return;
    try { await api("DELETE", "/v1/kb/" + encodeURIComponent(name)); } catch (err) {
      message("删除失败: " + err.message, "error"); return;
    }
    _current = null;
    stopPolling();
    $("kb-detail").innerHTML = PLACEHOLDER;
    await renderList();
    return;
  }
  const docDel = e.target.closest(".kb-doc-del");
  if (docDel) {
    const doc = docDel.getAttribute("data-doc");
    if (!await dialogConfirm({
      title: "删除文档",
      message: `删除「${doc}」?对应原件与索引条目一并删除,不可恢复。`,
      okText: "删除", danger: true,
    })) return;
    try {
      await api("DELETE", "/v1/kb/" + encodeURIComponent(_current) + "/docs/" + encodeURIComponent(doc));
    } catch (err) { message("删除失败: " + err.message, "error"); return; }
    showKb(_current, null);
    renderList();  // 左栏计数同步
    return;
  }
  const main = e.target.closest(".kb-entry-main");
  if (main) showDoc(main.getAttribute("data-doc"));
});
