<script setup lang="ts">
import { ElMessage, ElMessageBox } from "element-plus";
import { onMounted, reactive, ref } from "vue";

import { api } from "../services/api";
import KnowledgeContentPreview from "./KnowledgeContentPreview.vue";
import TableRowActions from "./TableRowActions.vue";
import AdminPagination from "./AdminPagination.vue";
import type {
  KnowledgeDetail,
  KnowledgeContentSearchItem,
  KnowledgeBatchSyncResult,
  KnowledgeItem,
  KnowledgeSyncJob,
  MigrationSummary,
} from "../types/api";

const emit = defineEmits<{ preview: [knowledgeId: number] }>();

const rows = ref<KnowledgeItem[]>([]);
const migration = ref<MigrationSummary | null>(null);
const loading = ref(false);
const operatingId = ref<number | null>(null);
const detailOpen = ref(false);
const detail = ref<KnowledgeDetail | null>(null);
const jobs = ref<KnowledgeSyncJob[]>([]);
const previewVersionId = ref<number | null>(null);
const previewTargetSectionId = ref<number | null>(null);
const previewHighlightKeyword = ref("");
const activeTab = ref("overview");
const selectedRows = ref<KnowledgeItem[]>([]);
const editOpen = ref(false);
const editSaving = ref(false);
const batchOpen = ref(false);
const batchSaving = ref(false);
const batchSyncing = ref(false);
const batchSyncResultOpen = ref(false);
const batchSyncResult = ref<KnowledgeBatchSyncResult | null>(null);
const resolvingNode = ref(false);
const nodeResolved = ref(false);
const sourceTitle = ref("");
const filter = reactive({ keyword: "", type: "", open: "", source: "" });
const pager = reactive({ page: 1, pageSize: 20, total: 0 });
const contentSearch = reactive({ keyword: "", includeClosed: true });
const contentSearchRows = ref<KnowledgeContentSearchItem[]>([]);
const contentSearchLoading = ref(false);
const contentSearchPager = reactive({ page: 1, pageSize: 10, total: 0 });
const form = reactive({
  name: "",
  feishuSpaceId: "",
  feishuNodeId: "",
  knowledgeType: "general",
  status: 0,
  remark: "",
});
const editForm = reactive({ id: 0, name: "", knowledgeType: "general" });
const batchForm = reactive({ knowledgeType: "" });
const knowledgeTypeOptions = [
  { label: "课程知识库", value: "course" },
  { label: "答疑知识库", value: "qa" },
  { label: "固定信息类知识库", value: "fixed" },
  { label: "通用知识库", value: "general" },
];

onMounted(load);

async function load(page = pager.page, pageSize = pager.pageSize) {
  loading.value = true;
  try {
    const result = await api.knowledge({
      keyword: filter.keyword, knowledgeType: filter.type, openStatus: filter.open,
      sourceStatus: filter.source,
      page, pageSize,
    });
    rows.value = result.items;
    Object.assign(pager, { page: result.page, pageSize: result.pageSize, total: result.total });
    if (!migration.value) migration.value = await api.migrationSummary();
  } catch (error) {
    ElMessage.error(errorMessage(error, "知识库列表加载失败"));
  } finally {
    loading.value = false;
  }
}

function openEdit() {
  if (!detail.value) return;
  Object.assign(editForm, {
    id: detail.value.knowledge.id,
    name: detail.value.knowledge.name,
    knowledgeType: detail.value.knowledge.knowledgeType,
  });
  editOpen.value = true;
}

async function saveMetadata() {
  if (!editForm.name.trim()) {
    ElMessage.warning("请输入后台知识库名称");
    return;
  }
  editSaving.value = true;
  try {
    await api.updateKnowledgeMetadata(editForm.id, {
      name: editForm.name.trim(), knowledgeType: editForm.knowledgeType,
    });
    ElMessage.success("知识库基本信息已更新");
    editOpen.value = false;
    await Promise.all([load(), refreshDetail(editForm.id)]);
  } catch (error) {
    ElMessage.error(errorMessage(error, "基本信息保存失败"));
  } finally {
    editSaving.value = false;
  }
}

function openBatchEdit() {
  if (!selectedRows.value.length) {
    ElMessage.warning("请先勾选需要修改的知识库");
    return;
  }
  Object.assign(batchForm, { knowledgeType: "" });
  batchOpen.value = true;
}

async function saveBatchMetadata() {
  if (!batchForm.knowledgeType) {
    ElMessage.warning("请选择知识库类型");
    return;
  }
  if (!await confirmAction(
    `将修改已选择的 ${selectedRows.value.length} 个知识库。该操作不影响正式版本和开放状态。`,
    "确认批量修改",
    { confirmButtonText: "确认修改", cancelButtonText: "取消", type: "warning" },
  )) return;
  batchSaving.value = true;
  try {
    const result = await api.batchUpdateKnowledgeMetadata({
      knowledgeIds: selectedRows.value.map((item) => item.id),
      knowledgeType: batchForm.knowledgeType || undefined,
    });
    ElMessage[result.failed ? "warning" : "success"](`批量修改完成：成功 ${result.success} 个，失败 ${result.failed} 个`);
    batchOpen.value = false;
    await load();
  } catch (error) {
    ElMessage.error(errorMessage(error, "批量修改失败"));
  } finally {
    batchSaving.value = false;
  }
}

async function syncSelected() {
  if (!selectedRows.value.length) {
    ElMessage.warning("请先勾选需要同步的知识库");
    return;
  }
  if (!await confirmAction(
    `将依次同步已选择的 ${selectedRows.value.length} 个知识库。同步成功后最新内容立即生效，单个失败不会中断其他知识库。`,
    "确认批量同步",
    { confirmButtonText: "开始同步", cancelButtonText: "取消", type: "warning" },
  )) return;
  batchSyncing.value = true;
  try {
    const result = await api.batchSyncKnowledge(selectedRows.value.map((item) => item.id));
    batchSyncResult.value = result;
    batchSyncResultOpen.value = true;
    ElMessage[result.failed ? "warning" : "success"](
      `批量同步完成：成功 ${result.success} 个，失败 ${result.failed} 个`,
    );
    await load();
  } catch (error) {
    ElMessage.error(errorMessage(error, "批量同步失败"));
  } finally {
    batchSyncing.value = false;
  }
}

async function handleMore(command: string, item: KnowledgeItem) {
  try {
    if (command === "detail") return await openDetail(item);
    if (command === "preview") return emit("preview", item.id);
    if (command === "archive") return await archive(item);
    if (command === "restore") return await restore(item);
    if (command === "delete") return await remove(item);
  } catch (error) {
    if (error !== "cancel" && error !== "close") ElMessage.error(errorMessage(error, "操作失败"));
  }
}

async function searchContent(page = contentSearchPager.page, pageSize = contentSearchPager.pageSize) {
  const keyword = contentSearch.keyword.trim();
  if (!keyword) {
    contentSearchRows.value = [];
    Object.assign(contentSearchPager, { page: 1, pageSize, total: 0 });
    return ElMessage.warning("请输入要搜索的知识库内容关键字");
  }
  contentSearchLoading.value = true;
  try {
    const result = await api.knowledgeContentSearch({
      keyword,
      includeClosed: contentSearch.includeClosed,
      page,
      pageSize,
    });
    contentSearchRows.value = result.items;
    Object.assign(contentSearchPager, { page: result.page, pageSize: result.pageSize, total: result.total });
  } catch (error) {
    ElMessage.error(errorMessage(error, "知识库内容搜索失败"));
  } finally {
    contentSearchLoading.value = false;
  }
}

function clearContentSearch() {
  contentSearch.keyword = "";
  contentSearchRows.value = [];
  Object.assign(contentSearchPager, { page: 1, pageSize: contentSearchPager.pageSize, total: 0 });
}

async function restore(item: KnowledgeItem) {
  if (!await confirmAction("恢复后知识库仍保持关闭，需要单独确认开放。", "恢复知识库", {
    confirmButtonText: "确认恢复", cancelButtonText: "取消", type: "warning",
  })) return;
  await api.setKnowledgeLifecycle(item.id, "active");
  ElMessage.success("知识库已恢复，当前仍为关闭状态");
  await load();
}

async function createKnowledge() {
  if (!nodeResolved.value || !form.name || !form.feishuSpaceId || !form.feishuNodeId) {
    ElMessage.warning("请先通过 Node ID 获取并确认知识库信息");
    return;
  }
  try {
    await api.createKnowledge({ ...form });
    Object.assign(form, { name: "", feishuSpaceId: "", feishuNodeId: "", knowledgeType: "general", status: 0, remark: "" });
    nodeResolved.value = false; sourceTitle.value = "";
    ElMessage.success("知识库已新增，首次同步成功后将自动生效");
    await load();
  } catch (error) {
    ElMessage.error(errorMessage(error, "知识库新增失败"));
  }
}

async function resolveNode() {
  const nodeId = form.feishuNodeId.trim();
  if (!nodeId) return ElMessage.warning("请输入 Node ID");
  resolvingNode.value = true;
  try {
    const info = await api.resolveKnowledgeNode(nodeId);
    Object.assign(form, { feishuNodeId: info.nodeId, feishuSpaceId: info.spaceId, name: info.name, remark: info.remark || "" });
    sourceTitle.value = info.sourceTitle; nodeResolved.value = true;
    ElMessage.success("已获取飞书知识库信息");
  } catch (error) {
    nodeResolved.value = false; sourceTitle.value = "";
    ElMessage.error(errorMessage(error, "Node ID 信息获取失败"));
  } finally { resolvingNode.value = false; }
}

async function openDetail(
  item: KnowledgeItem | { id: number },
  options: { sectionId?: number | null; keyword?: string; tab?: string } = {},
) {
  detailOpen.value = true;
  detail.value = null;
  activeTab.value = options.tab || "overview";
  previewVersionId.value = null;
  previewTargetSectionId.value = options.sectionId ?? null;
  previewHighlightKeyword.value = options.keyword || "";
  try {
    const refreshedDetail = await refreshDetail(item.id);
    previewVersionId.value = refreshedDetail.currentVersion?.id ?? null;
  } catch (error) {
    detailOpen.value = false;
    ElMessage.error(errorMessage(error, "知识库详情加载失败"));
  }
}

async function openSearchHit(item: KnowledgeContentSearchItem) {
  await openDetail(
    { id: item.knowledgeId },
    { sectionId: item.sectionId, keyword: contentSearch.keyword.trim(), tab: "content" },
  );
}

async function refreshDetail(id: number) {
  const [nextDetail, nextJobs] = await Promise.all([
    api.knowledgeDetail(id), api.knowledgeSyncJobs(id),
  ]);
  detail.value = nextDetail;
  jobs.value = nextJobs;
  return nextDetail;
}

async function sync(item: KnowledgeItem) {
  operatingId.value = item.id;
  try {
    const result = await api.syncKnowledge(item.id);
    const message = result.status === "unchanged"
      ? "飞书内容未变化，未生成新版本"
      : result.status === "published"
        ? "同步完成，最新版本已自动生效"
        : "同步完成";
    ElMessage.success(message);
    await load();
    if (detailOpen.value) await refreshDetail(item.id);
  } catch (error) {
    ElMessage.error(errorMessage(error, "知识库同步失败"));
  } finally {
    operatingId.value = null;
  }
}

async function runMigration() {
  if (!await confirmAction(
    "将依次重新读取全部飞书文章。同步成功后仅保留最新版，旧版本和旧内容会被清理。",
    "开始知识迁移",
    { confirmButtonText: "开始迁移", cancelButtonText: "取消", type: "warning" },
  )) return;
  loading.value = true;
  try {
    const result = await api.runKnowledgeMigration();
    ElMessage.success(`迁移处理完成：${result.success}/${result.total} 成功`);
    await load();
  } catch (error) {
    ElMessage.error(errorMessage(error, "知识迁移失败"));
  } finally {
    loading.value = false;
  }
}

async function toggleOpen(item: KnowledgeItem): Promise<boolean> {
  const next = item.status !== 1;
  if (!await confirmAction(
    next ? "开放后，该知识库将从用户下一条消息开始进入 Agent 动态知识目录。" : "关闭后，当前生成不受影响，从用户下一条消息开始无法检索该知识库。",
    next ? "开放知识库" : "关闭知识库",
    { confirmButtonText: next ? "确认开放" : "确认关闭", cancelButtonText: "取消", type: "warning" },
  )) return false;
  operatingId.value = item.id;
  try {
    await api.setKnowledgeOpen(item.id, next);
    ElMessage.success(next ? "知识库已开放" : "知识库已关闭");
    if (detailOpen.value && detail.value?.knowledge.id === item.id) await refreshDetail(item.id);
    return true;
  } catch (error) {
    ElMessage.error(errorMessage(error, next ? "知识库开放失败" : "知识库关闭失败"));
    return false;
  } finally {
    operatingId.value = null;
  }
}

async function archive(item: KnowledgeItem) {
  if (!await confirmAction("归档后知识库会立即关闭，当前最新版和检索记录仍会保留。", "归档知识库", {
    confirmButtonText: "确认归档", cancelButtonText: "取消", type: "warning",
  })) return;
  await api.setKnowledgeLifecycle(item.id, "archived");
  ElMessage.success("知识库已归档");
  await load();
}

async function remove(item: KnowledgeItem) {
  if (!await confirmAction(
    `将永久删除“${item.name}”的知识正文、版本、章节、切片和同步记录，并立即停止 Agent 使用。审计日志仍会保留，该操作不可恢复。`,
    "永久删除知识库",
    { confirmButtonText: "永久删除", cancelButtonText: "取消", type: "error" },
  )) return;
  await api.deleteKnowledge(item.id);
  ElMessage.success("知识库已删除");
  await load();
}

function statusType(status: string) {
  if (["published", "passed", "normal", "completed"].includes(status)) return "success";
  if (["failed", "blocked", "error", "rejected"].includes(status)) return "danger";
  if (["pending_review", "processing", "pending"].includes(status)) return "warning";
  return "info";
}

function typeLabel(value: string) {
  return knowledgeTypeOptions.find((item) => item.value === value)?.label || value;
}

function typeDescription(value: string) {
  return ({
    fixed: "固定信息类用于上课时间、带练安排、回放音频、课程作业、会议链接、服务权益、助理联系方式等当前安排；与其他库冲突时优先采用固定信息类。",
    course: "课程知识库用于课程概念、功课步骤、老师观点和课程原文解释；优先级高于答疑和通用知识。",
    qa: "答疑知识库用于参考过往问答和常见问题，但不能覆盖课程原文或固定信息类的当前安排。",
    general: "通用知识库只做背景补充，不能覆盖课程知识、答疑知识或固定信息类。",
  } as Record<string, string>)[value] || "用于补充背景信息。";
}

function contentItemTypeLabel(value: string) {
  return ({ section: "章节", card: "知识卡", chunk: "切片" } as Record<string, string>)[value] || value;
}

function matchedLabel(values: string[]) {
  const labels = values.map((value) => ({ title: "标题", content: "正文" } as Record<string, string>)[value] || value);
  return labels.join("、");
}

function highlightSnippet(text: string, terms: string[] = []) {
  const keywords = Array.from(new Set([...terms, contentSearch.keyword.trim()].filter(Boolean)))
    .sort((left, right) => right.length - left.length);
  if (!keywords.length) return [{ text, match: false }];
  const parts: Array<{ text: string; match: boolean }> = [];
  const source = text || "";
  const lowerSource = source.toLowerCase();
  let cursor = 0;
  while (cursor < source.length) {
    const next = keywords
      .map((keyword) => ({ keyword, index: lowerSource.indexOf(keyword.toLowerCase(), cursor) }))
      .filter((item) => item.index >= 0)
      .sort((left, right) => left.index - right.index || right.keyword.length - left.keyword.length)[0];
    if (!next) {
      parts.push({ text: source.slice(cursor), match: false });
      break;
    }
    const { index, keyword } = next;
    if (index > cursor) parts.push({ text: source.slice(cursor, index), match: false });
    parts.push({ text: source.slice(index, index + keyword.length), match: true });
    cursor = index + keyword.length;
  }
  return parts.length ? parts : [{ text: source, match: false }];
}

function errorMessage(error: unknown, fallback: string) {
  return error instanceof Error ? error.message : fallback;
}

async function confirmAction(
  message: string,
  title: string,
  options: Parameters<typeof ElMessageBox.confirm>[2],
) {
  try {
    await ElMessageBox.confirm(message, title, options);
    return true;
  } catch {
    return false;
  }
}
</script>

<template>
  <div class="page-head inline">
    <div><h2>知识库管理</h2><p>一篇飞书文章对应一个知识库；同步成功后自动生效，并且只保留最新版。</p></div>
    <el-button type="primary" :loading="loading" @click="runMigration">迁移现有知识库</el-button>
  </div>

  <section class="migration-overview">
    <div><span>知识库总数</span><strong>{{ migration?.total ?? 0 }}</strong></div>
    <div><span>待迁移</span><strong>{{ migration?.counts.pending ?? 0 }}</strong></div>
    <div><span>迁移完成</span><strong>{{ migration?.counts.completed ?? 0 }}</strong></div>
    <div><span>失败</span><strong>{{ migration?.counts.failed ?? 0 }}</strong></div>
  </section>

  <section class="knowledge-work-panel">
    <h3>通过 Node ID 快速新增</h3>
    <div class="node-resolve-row">
      <el-input v-model="form.feishuNodeId" placeholder="请输入飞书 Node ID" :disabled="resolvingNode" @input="nodeResolved = false" />
      <el-button type="primary" :loading="resolvingNode" @click="resolveNode">获取知识库信息</el-button>
    </div>
    <el-alert v-if="nodeResolved" :title="`已获取：${sourceTitle}`" type="success" :closable="false" show-icon />
    <div v-if="nodeResolved" class="knowledge-create-grid">
      <el-input v-model="form.feishuSpaceId" placeholder="Space ID" disabled />
      <el-input :model-value="sourceTitle" placeholder="飞书原标题" disabled />
      <el-input v-model="form.name" placeholder="后台显示名称" />
      <el-select v-model="form.knowledgeType">
        <el-option v-for="item in knowledgeTypeOptions" :key="item.value" :label="item.label" :value="item.value" />
      </el-select>
      <el-input v-model="form.remark" placeholder="备注（可修改）" />
      <el-button type="primary" @click="createKnowledge">保存知识库</el-button>
    </div>
  </section>

  <section class="knowledge-content-search-panel">
    <div class="content-search-head">
      <div>
        <h3>搜索知识库内容</h3>
        <p>跨全部当前最新版搜索章节、知识卡和切片，用于确认内容是否已同步进检索链路。</p>
      </div>
      <el-switch v-model="contentSearch.includeClosed" active-text="包含已关闭" inactive-text="仅开放" />
    </div>
    <div class="content-search-row">
      <el-input
        v-model="contentSearch.keyword"
        placeholder="输入内容关键字，例如：静水流深静心、合一作业、会议链接"
        clearable
        @clear="clearContentSearch"
        @keydown.enter.exact.prevent="searchContent(1)"
      />
      <el-button type="primary" :loading="contentSearchLoading" @click="searchContent(1)">搜索内容</el-button>
    </div>
    <div v-if="contentSearchPager.total || contentSearchRows.length" v-loading="contentSearchLoading" class="content-search-results">
      <div class="content-search-result-summary">
        找到 {{ contentSearchPager.total }} 条命中
        <span>结果只来自当前最新版；已关闭知识库会标记为不可被用户端默认召回。</span>
      </div>
      <article v-for="item in contentSearchRows" :key="`${item.itemType}-${item.itemId}`" class="content-search-hit">
        <div class="content-search-hit-main">
          <div class="content-search-hit-title">
            <strong>{{ item.title }}</strong>
            <el-tag v-for="type in item.itemTypes || [item.itemType]" :key="type" size="small" effect="plain">{{ contentItemTypeLabel(type) }}</el-tag>
            <el-tag size="small" :type="item.canAgentUse ? 'success' : 'warning'">{{ item.canAgentUse ? '可参与召回' : '不可默认召回' }}</el-tag>
          </div>
          <p class="content-search-hit-meta">
            {{ item.knowledgeName }} · V{{ item.versionNo }} · {{ item.sectionKey }} · 命中{{ matchedLabel(item.matchedIn) }}
          </p>
          <p class="content-search-hit-snippet">
            <template v-for="(part, index) in highlightSnippet(item.snippet, item.highlightTerms)" :key="index">
              <mark v-if="part.match">{{ part.text }}</mark><span v-else>{{ part.text }}</span>
            </template>
          </p>
          <div class="content-search-diagnostics">
            <span>{{ item.knowledgeStatus === 1 ? '已开放' : '已关闭' }}</span>
            <span>{{ item.sourceStatus === 'normal' ? '源正常' : '源异常' }}</span>
            <span>{{ item.hasChunks ? '已生成切片' : '未生成切片' }}</span>
            <span>{{ item.isCurrentVersion ? '当前版本' : '非当前版本' }}</span>
          </div>
        </div>
        <el-button type="primary" plain @click="openSearchHit(item)">查看内容</el-button>
      </article>
      <AdminPagination :page="contentSearchPager.page" :page-size="contentSearchPager.pageSize" :total="contentSearchPager.total" @change="searchContent" />
    </div>
    <el-empty v-else-if="contentSearch.keyword && !contentSearchLoading" description="暂无内容搜索结果" :image-size="64" />
  </section>

  <div class="knowledge-filters">
    <el-input v-model="filter.keyword" placeholder="搜索后台名称或飞书标题" clearable @change="load(1)" />
    <el-select v-model="filter.type" placeholder="知识库类型" clearable @change="load(1)"><el-option v-for="item in knowledgeTypeOptions" :key="item.value" :label="item.label" :value="item.value" /></el-select>
    <el-select v-model="filter.open" placeholder="开放状态" clearable @change="load(1)"><el-option label="已开放" value="1" /><el-option label="已关闭" value="0" /></el-select>
    <el-select v-model="filter.source" placeholder="源状态" clearable @change="load(1)"><el-option label="正常" value="normal" /><el-option label="源异常" value="error" /></el-select>
  </div>

  <div class="knowledge-batch-bar">
    <span>已选择 {{ selectedRows.length }} 个知识库</span>
    <div class="knowledge-batch-actions">
      <el-button type="primary" plain :disabled="selectedRows.length === 0 || batchSaving" :loading="batchSyncing" @click="syncSelected">批量同步</el-button>
      <el-button :disabled="selectedRows.length === 0 || batchSyncing" @click="openBatchEdit">批量修改类型</el-button>
    </div>
  </div>

  <el-table v-loading="loading" :data="rows" stripe @selection-change="selectedRows = $event">
    <el-table-column type="selection" width="48" />
    <el-table-column label="知识库" min-width="240"><template #default="{ row }"><strong>{{ row.name }}</strong><small class="table-subline">{{ row.sourceTitle || "尚未同步源标题" }}</small></template></el-table-column>
    <el-table-column label="类型" width="140"><template #default="{ row }">{{ typeLabel(row.knowledgeType) }}</template></el-table-column>
    <el-table-column label="当前版本" width="130"><template #default="{ row }"><span>{{ row.currentVersionId ? `#${row.currentVersionId}` : '尚未同步' }}</span></template></el-table-column>
    <el-table-column label="状态" width="210"><template #default="{ row }"><div class="knowledge-status-cell"><el-switch v-model="row.status" :active-value="1" :inactive-value="0" active-text="开放" inactive-text="关闭" inline-prompt :loading="operatingId === row.id" :disabled="row.lifecycleStatus === 'archived'" :before-change="() => toggleOpen(row)" /><el-tag :type="statusType(row.sourceStatus)">{{ row.sourceStatus === 'normal' ? '源正常' : '源异常' }}</el-tag></div></template></el-table-column>
    <el-table-column prop="lastSyncedAt" label="最近同步" width="180" />
    <el-table-column label="操作" width="220" fixed="right"><template #default="{ row }"><TableRowActions><el-button link type="primary" @click="openDetail(row)">详情</el-button><el-button link type="primary" :disabled="row.lifecycleStatus === 'archived'" :loading="operatingId === row.id" @click="sync(row)">同步最新版</el-button><el-dropdown trigger="click" :teleported="true" @command="handleMore($event, row)"><el-button link type="primary">更多</el-button><template #dropdown><el-dropdown-menu><el-dropdown-item command="detail">查看详情</el-dropdown-item><el-dropdown-item command="preview">进入 Agent 预览</el-dropdown-item><el-dropdown-item v-if="row.lifecycleStatus === 'active'" command="archive" divided>归档</el-dropdown-item><el-dropdown-item v-else command="restore" divided>恢复</el-dropdown-item><el-dropdown-item command="delete" divided>永久删除</el-dropdown-item></el-dropdown-menu></template></el-dropdown></TableRowActions></template></el-table-column>
  </el-table>
  <AdminPagination :page="pager.page" :page-size="pager.pageSize" :total="pager.total" @change="load" />

  <el-drawer v-model="detailOpen" size="76%" title="知识库详情" destroy-on-close>
    <template v-if="detail">
      <div class="knowledge-detail-head">
        <div><h3>{{ detail.knowledge.name }}</h3><p>{{ typeLabel(detail.knowledge.knowledgeType) }} · 同步后自动生效</p></div>
        <div class="knowledge-detail-head-actions"><el-button type="primary" plain @click="activeTab = 'content'">预览内容</el-button><el-tag :type="detail.knowledge.status === 1 ? 'success' : 'info'">{{ detail.knowledge.status === 1 ? '已开放' : '已关闭' }}</el-tag><el-tag :type="statusType(detail.knowledge.sourceStatus)">{{ detail.knowledge.sourceStatus === 'normal' ? '源文章正常' : '源文章异常' }}</el-tag></div>
      </div>
      <el-alert v-if="detail.knowledge.sourceError" :title="detail.knowledge.sourceError" type="error" :closable="false" />
      <el-tabs v-model="activeTab">
        <el-tab-pane label="基本信息" name="overview">
          <div class="knowledge-section-head"><h4>基本信息</h4><el-button type="primary" plain @click="openEdit">编辑基本信息</el-button></div>
          <dl class="knowledge-meta"><dt>后台名称</dt><dd>{{ detail.knowledge.name }}</dd><dt>知识库类型</dt><dd>{{ typeLabel(detail.knowledge.knowledgeType) }}</dd><dt>版本更新</dt><dd>同步成功后自动替换</dd><dt>SpaceID</dt><dd>{{ detail.knowledge.feishuSpaceId }}</dd><dt>NodeID</dt><dd>{{ detail.knowledge.feishuNodeId }}</dd><dt>当前版本</dt><dd>{{ detail.currentVersion ? `V${detail.currentVersion.versionNo}` : '暂无' }}</dd></dl>
          <el-alert :title="typeDescription(detail.knowledge.knowledgeType)" type="info" :closable="false" show-icon />
          <section v-if="detail.manifest" class="manifest-panel"><h4>Knowledge Manifest</h4><p><strong>用途：</strong>{{ detail.manifest.purpose }}</p><p><strong>适用：</strong>{{ detail.manifest.applicableQuestions }}</p><p><strong>不适用：</strong>{{ detail.manifest.inapplicableQuestions }}</p><p><strong>核心主题：</strong>{{ detail.manifest.coreTopics }}</p><p><strong>边界：</strong>{{ detail.manifest.boundaries }}</p></section>
        </el-tab-pane>
        <el-tab-pane label="内容预览" name="content" lazy>
          <KnowledgeContentPreview
            :version-id="previewVersionId"
            :target-section-id="previewTargetSectionId"
            :highlight-keyword="previewHighlightKeyword"
          />
        </el-tab-pane>
        <el-tab-pane label="同步记录" name="jobs"><el-table :data="jobs"><el-table-column prop="id" label="任务" width="80" /><el-table-column prop="status" label="状态" width="130" /><el-table-column prop="stage" label="阶段" width="130" /><el-table-column label="进度" width="180"><template #default="{ row }"><el-progress :percentage="row.progress" /></template></el-table-column><el-table-column prop="sourceError" label="失败原因" min-width="260" /></el-table></el-tab-pane>
      </el-tabs>
    </template>
  </el-drawer>

  <el-dialog v-model="editOpen" title="编辑知识库基本信息" width="min(520px, 92vw)" append-to-body destroy-on-close>
    <el-form label-position="top">
      <el-form-item label="后台知识库名称" required><el-input v-model="editForm.name" maxlength="100" show-word-limit /></el-form-item>
      <el-form-item label="知识库类型" required><el-select v-model="editForm.knowledgeType"><el-option v-for="item in knowledgeTypeOptions" :key="item.value" :label="item.label" :value="item.value" /></el-select></el-form-item>
      <el-alert title="本次修改不会重新同步飞书，也不会改变当前正式版本和开放状态。" type="info" :closable="false" />
    </el-form>
    <template #footer><el-button @click="editOpen = false">取消</el-button><el-button type="primary" :loading="editSaving" @click="saveMetadata">保存</el-button></template>
  </el-dialog>

  <el-dialog v-model="batchOpen" title="批量修改知识库" width="min(520px, 92vw)" append-to-body destroy-on-close>
    <el-alert :title="`已选择 ${selectedRows.length} 个知识库，未选择的字段将保持原值。`" type="info" :closable="false" />
    <el-form label-position="top" class="batch-edit-form">
      <el-form-item label="知识库类型"><el-select v-model="batchForm.knowledgeType" clearable placeholder="不修改"><el-option v-for="item in knowledgeTypeOptions" :key="item.value" :label="item.label" :value="item.value" /></el-select></el-form-item>
    </el-form>
    <template #footer><el-button @click="batchOpen = false">取消</el-button><el-button type="primary" :loading="batchSaving" @click="saveBatchMetadata">下一步</el-button></template>
  </el-dialog>

  <el-dialog v-model="batchSyncResultOpen" title="批量同步结果" width="min(760px, 94vw)" append-to-body destroy-on-close>
    <el-alert
      v-if="batchSyncResult"
      :title="`共处理 ${batchSyncResult.total} 个：成功 ${batchSyncResult.success} 个，失败 ${batchSyncResult.failed} 个`"
      :type="batchSyncResult.failed ? 'warning' : 'success'"
      :closable="false"
      show-icon
    />
    <el-table v-if="batchSyncResult" :data="batchSyncResult.results" max-height="440" class="batch-sync-result-table">
      <el-table-column label="知识库" min-width="220">
        <template #default="{ row }">{{ row.name || `知识库 #${row.knowledgeId}` }}</template>
      </el-table-column>
      <el-table-column label="结果" width="100">
        <template #default="{ row }"><el-tag :type="row.ok ? 'success' : 'danger'">{{ row.ok ? '成功' : '失败' }}</el-tag></template>
      </el-table-column>
      <el-table-column label="状态 / 原因" min-width="300">
        <template #default="{ row }">{{ row.ok ? (row.status === 'unchanged' ? '内容无变化' : '最新内容已生效') : row.error }}</template>
      </el-table-column>
    </el-table>
    <template #footer><el-button type="primary" @click="batchSyncResultOpen = false">完成</el-button></template>
  </el-dialog>
</template>

<style scoped>
.knowledge-batch-bar,
.knowledge-section-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
}

.knowledge-batch-bar {
  min-height: 48px;
  padding: 8px 0;
  color: #66756f;
}

.knowledge-batch-actions {
  display: flex;
  align-items: center;
  gap: 8px;
}

.knowledge-section-head h4 {
  margin: 0;
}

.knowledge-status-cell {
  display: flex;
  align-items: center;
  gap: 10px;
}

.knowledge-detail-head-actions {
  display: flex;
  align-items: center;
  gap: 8px;
}

.batch-edit-form {
  margin-top: 20px;
}

.batch-sync-result-table {
  margin-top: 16px;
}

.knowledge-content-search-panel {
  margin: 18px 0;
  padding: 18px;
  border: 1px solid #dfe8e5;
  border-radius: 10px;
  background: #fbfdfc;
}

.content-search-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 20px;
  margin-bottom: 14px;
}

.content-search-head h3 {
  margin: 0;
  color: #203b32;
}

.content-search-head p {
  margin: 6px 0 0;
  color: #71817b;
  font-size: 13px;
}

.content-search-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 12px;
}

.content-search-results {
  display: grid;
  gap: 10px;
  margin-top: 14px;
}

.content-search-result-summary {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  color: #53645e;
  font-size: 13px;
}

.content-search-result-summary span {
  color: #81918b;
}

.content-search-hit {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  gap: 16px;
  padding: 14px;
  border: 1px solid #e1ebe7;
  border-radius: 8px;
  background: #ffffff;
}

.content-search-hit-main {
  min-width: 0;
}

.content-search-hit-title {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
}

.content-search-hit-title strong {
  overflow: hidden;
  color: #203b32;
  font-size: 14px;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.content-search-hit-meta {
  margin: 6px 0 0;
  color: #708179;
  font-size: 12px;
}

.content-search-hit-snippet {
  margin: 8px 0 0;
  color: #354840;
  font-size: 13px;
  line-height: 1.7;
}

.content-search-hit-snippet mark {
  padding: 0 2px;
  border-radius: 3px;
  background: #fff1a8;
  color: inherit;
}

.content-search-diagnostics {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: 8px;
}

.content-search-diagnostics span {
  padding: 3px 7px;
  border-radius: 999px;
  background: #eef5f2;
  color: #536b61;
  font-size: 11px;
}

@media (max-width: 760px) {
  .content-search-head,
  .content-search-result-summary,
  .content-search-hit {
    align-items: stretch;
    grid-template-columns: 1fr;
    flex-direction: column;
  }

  .content-search-row {
    grid-template-columns: 1fr;
  }
}
</style>
