<template>
  <ElDrawer
    :model-value="visible"
    title="任务日志"
    direction="rtl"
    size="620px"
    append-to-body
    @update:model-value="(v: boolean) => emit('update:visible', v)"
  >
    <template v-if="taskId">
      <!-- 任务简要信息 -->
      <div v-if="task" class="task-summary">
        <ElDescriptions :column="1" size="small" border>
          <ElDescriptionsItem label="任务名称">{{ task.task_name }}</ElDescriptionsItem>
          <ElDescriptionsItem label="任务编号">
            #{{ task.id }}
          </ElDescriptionsItem>
        </ElDescriptions>
      </div>

      <!-- 日志 Tab 切换 -->
      <ElTabs v-model="activeTab" class="log-tabs">
        <!-- ============ 审查日志 ============ -->
        <ElTabPane name="review">
          <template #label>
            <span class="tab-label">
              审查日志
              <span v-if="reviewWaiting" class="tab-status tab-status--running"></span>
              <span v-else-if="reviewFinished" class="tab-status tab-status--done"></span>
            </span>
          </template>
          <div ref="reviewScrollRef" class="tab-scroll" @scroll.passive="onReviewScroll">
            <ElTimeline class="review-timeline">
              <ElTimelineItem
                v-for="(log, idx) in reviewLogs"
                :key="idx"
                :timestamp="log.time"
                placement="top"
                :type="reviewLevelType(log.level)"
                :hollow="log.level === 'info'"
              >
                <div class="review-card" :class="`review-card--${log.level}`">
                  <div class="review-card-header">
                    <ElTag size="small" :type="phaseTagType(log.phase)" effect="light">
                      {{ log.phase }}
                    </ElTag>
                    <span class="review-card-title">{{ log.title }}</span>
                  </div>
                  <div v-if="log.detail" class="review-card-detail">
                    {{ log.detail }}
                  </div>

                  <!-- 子 timeline：分段审查的调用链路 -->
                  <ElTimeline v-if="log.children && log.children.length" class="sub-timeline">
                    <ElTimelineItem
                      v-for="(child, cidx) in log.children"
                      :key="cidx"
                      :timestamp="child.time"
                      placement="top"
                      :type="reviewLevelType(child.level)"
                      :hollow="child.level === 'info'"
                    >
                      <div class="review-sub-card" :class="`review-sub-card--${child.level}`">
                        <div class="review-sub-header">
                          <span class="review-sub-step">{{ child.title }}</span>
                        </div>
                        <div v-if="child.detail" class="review-sub-detail">
                          {{ child.detail }}
                        </div>
                      </div>
                    </ElTimelineItem>
                  </ElTimeline>
                </div>
              </ElTimelineItem>
            </ElTimeline>

            <!-- 无审查日志时的空状态 -->
            <div v-if="reviewLogs.length === 0 && !reviewWaiting && !reviewFinished" class="empty-logs">
              暂无审查日志
            </div>

            <!-- 等待新日志加载动画 -->
            <div v-if="reviewWaiting" class="review-loading">
              <ElIcon class="is-loading" :size="16"><Loading /></ElIcon>
              <span class="loading-text">正在审查，等待日志…</span>
            </div>

            <!-- 审查完成 -->
            <div v-if="reviewFinished" class="review-finished">
              <ElIcon color="#67c23a" :size="16"><CircleCheck /></ElIcon>
              <span>审查完成</span>
              <ElButton link type="primary" :icon="RefreshRight" @click="restartReview">
                重新审查
              </ElButton>
            </div>

            <!-- 滚动锚点 -->
            <div ref="scrollSentinelRef" />
          </div>
        </ElTabPane>

        <!-- ============ 操作日志 ============ -->
        <ElTabPane label="操作日志" name="operation">
          <div class="tab-scroll">
            <ElTimeline v-loading="loading" class="log-timeline">
              <ElTimelineItem
                v-for="log in logList"
                :key="log.id"
                :timestamp="formatDateTime(log.created_time)"
                placement="top"
                :type="logType(log.action_type)"
                :hollow="log.action_type === 'start_review'"
              >
                <div class="log-card">
                  <div class="log-header">
                    <span class="log-action">{{ log.action_name }}</span>
                    <ElTag
                      size="small"
                      :type="logTagType(log.action_type)"
                      effect="light"
                    >
                      {{ logStatusText(log.action_type) }}
                    </ElTag>
                  </div>
                  <div class="log-operator">
                    <ElIcon><User /></ElIcon>
                    {{ log.operator_name || "系统" }}
                  </div>
                  <div v-if="log.description" class="log-desc">
                    {{ log.description }}
                  </div>
                </div>
              </ElTimelineItem>
              <div v-if="!loading && logList.length === 0" class="empty-logs">
                暂无操作记录
              </div>
            </ElTimeline>
          </div>
        </ElTabPane>
      </ElTabs>
    </template>
  </ElDrawer>
</template>

<script setup lang="ts">
import { ref, watch, computed, nextTick, onUnmounted } from "vue";
import { useRoute } from "vue-router";
import { User, Loading, CircleCheck, RefreshRight } from "@element-plus/icons-vue";
import { ReviewTaskAPI } from "@/api/module_report/review";
import type { ReviewTaskItem, ReviewLogItem, ReviewLogActionType } from "@/api/module_report/review";
import type { ReportType } from "@/api/module_report/report";

defineOptions({ name: "LogDrawer" });

const props = defineProps<{
  visible: boolean;
  taskId?: number | null;
  task?: ReviewTaskItem | null;
  defaultTab?: "review" | "operation";
  /** 是否在展示审查日志时自动启动审查流程（审查入口为 true，日志入口为 false） */
  startReview?: boolean;
}>();

const route = useRoute();

/** 报告类型：1 能源报告 / 2 光伏报告（由路由参数传入） */
const reportType = computed<ReportType>(() => {
  const t = route.query.report_type;
  return t === "2" ? 2 : 1;
});

const emit = defineEmits<{
  "update:visible": [v: boolean];
}>();

// ========== Tab 状态 ==========
const activeTab = ref<"review" | "operation">(props.defaultTab || "operation");

watch(
  () => props.visible,
  (v) => {
    if (v) {
      activeTab.value = props.defaultTab || "operation";
      isAutoScroll.value = true;
      if (activeTab.value === "review" && props.startReview) {
        restartReview();
      }
      scrollToBottomDelayed();
    } else {
      stopReviewTimer();
    }
  }
);

watch(
  () => props.defaultTab,
  (tab) => {
    if (tab && props.visible) {
      activeTab.value = tab;
    }
  }
);

watch(activeTab, (tab) => {
  if (tab === "review" && props.visible) {
    if (props.startReview) {
      ensureReviewRunning();
    }
    isAutoScroll.value = true;
    scrollToBottomDelayed();
  }
});

// ========== 操作日志 ==========
const loading = ref(false);
const logList = ref<ReviewLogItem[]>([]);

watch(
  () => [props.visible, props.taskId] as const,
  ([v, id]) => {
    if (v && id) {
      loadLogs(id);
    }
  },
  { immediate: true }
);

async function loadLogs(taskId: number) {
  loading.value = true;
  logList.value = [];
  try {
    const resp = await ReviewTaskAPI.listLogs({
      report_review_id: taskId,
      page_no: 1,
      page_size: 100,
    });
    logList.value = resp?.data?.data?.items ?? [];
  } catch (e) {
    logList.value = [];
  } finally {
    loading.value = false;
  }
}

function formatDateTime(value?: string): string {
  if (!value) return "-";
  const d = new Date(value);
  if (Number.isNaN(d.getTime())) return value;
  const pad = (n: number) => String(n).padStart(2, "0");
  return `${d.getFullYear()}-${pad(d.getMonth()+1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`;
}

function logType(t: ReviewLogActionType): "primary" | "success" | "warning" | "danger" {
  switch (t) {
    case "create_task": return "primary";
    case "start_review": return "warning";
    case "review_pass": return "success";
    case "review_reject": return "danger";
    default: return "primary";
  }
}

function logTagType(t: ReviewLogActionType): "primary" | "success" | "warning" | "danger" | "info" {
  switch (t) {
    case "create_task": return "primary";
    case "start_review": return "warning";
    case "review_pass": return "success";
    case "review_reject": return "danger";
    default: return "info";
  }
}

function logStatusText(t: ReviewLogActionType): string {
  switch (t) {
    case "create_task": return "已创建";
    case "start_review": return "进行中";
    case "review_pass": return "已完成";
    case "review_reject": return "已驳回";
    default: return t;
  }
}

// ========== 审查日志（Mock 演示数据） ==========

type ReviewLogLevel = "info" | "success" | "warning" | "error";
type ReviewStep = "vectorize" | "search" | "model_load" | "model_output" | "record";

interface MockReviewLog {
  phase: string;
  title: string;
  detail?: string;
  level: ReviewLogLevel;
  /** 步骤类型，用于控制产出间隔 */
  step?: ReviewStep;
  /** 子步骤（分段审查时用子 timeline 展示调用链路） */
  children?: MockReviewLog[];
}

/** 单个分段审查数据，用于构建完整调用链路子日志 */
interface SegData {
  seg: string;
  vectorize: string;
  dify: string;
  local?: string;
  modelInput: string;
  output: string;
  outputLevel: ReviewLogLevel;
}

/** 将分段审查数据构建为一条父日志 + 5条子步骤日志 */
function buildSegLog(d: SegData): MockReviewLog {
  const searchDetail = [
    `Dify知识库：${d.dify}`,
    d.local ? `本地记忆库：${d.local}` : "本地记忆库：无高相似分段",
  ].join("\n");
  return {
    phase: "分段审查",
    title: d.seg,
    level: "info",
    children: [
      { phase: "分段审查", title: "向量化编码", detail: d.vectorize, level: "info", step: "vectorize" },
      { phase: "分段审查", title: "知识库检索", detail: searchDetail, level: "info", step: "search" },
      { phase: "分段审查", title: "大模型加载", detail: d.modelInput, level: "info", step: "model_load" },
      { phase: "分段审查", title: "推理输出", detail: d.output, level: d.outputLevel, step: "model_output" },
      { phase: "分段审查", title: "记录落池", detail: "", level: "success", step: "record" },
    ],
  };
}

/** 9个分段审查数据（严格依据审查日志7.23.md） */
const segDataList: SegData[] = [
  { seg: "Seg01｜第一章 项目概况", vectorize: "分段文本向量化编码，向量写入本地记忆RAG库生成唯一索引", dify: "检索标签「项目概况、公共机构能源托管」，召回《JS/T 301—2024》5.1条款、医院可研编制范本", local: "召回附件Att01规划批复，向量相似度0.88", modelInput: "Seg01原文 + 第一章审查规则（地址精确到门牌号）+ Dify规范条文 + Att01附件文本", output: "章节框架完整；仅标注街道无精确门牌号，违反业务规则；院区面积描述合规；可研地址与规划附件文字存在偏差", outputLevel: "warning" },
  { seg: "Seg02｜第二章 商业合作模式", vectorize: "分段向量化，写入本地记忆RAG库", dify: "检索能源托管合作、计费结算行业规范", local: "检索Seg01面积相关段落，相似度0.77", modelInput: "大模型加载本章规则、规范、跨段面积数据执行校验", output: "15年托管周期符合标准；缺少能源单价涨跌调整约定、保底用能条款", outputLevel: "warning" },
  { seg: "Seg03｜第三章 项目建设方案", vectorize: "向量编码存入本地记忆库", dify: "高效冷站、空气源热泵、光伏设计国标", local: "召回Att01规划图纸分段，相似度0.82", modelInput: "大模型校验负荷、装机、分期建设面积与规划文件一致性", output: "冷站EER、热泵参数满足一级能效；屋面光伏缺少荷载安全鉴定说明", outputLevel: "warning" },
  { seg: "Seg04｜第四章 项目运行和保障措施", vectorize: "分段向量化入库本地记忆库", dify: "检索运维、维保相关企业标准", modelInput: "大模型校验人员配置、维保周期、维保成本测算逻辑", output: "人工、维保测算逻辑合规；未明确空调末端故障责任免责条款", outputLevel: "warning" },
  { seg: "Seg05｜第五章 投资估算", vectorize: "向量写入本地记忆RAG库", dify: "高效机房、光伏造价企业标准", local: "召回Seg03建设面积分段，相似度0.78", modelInput: "大模型交叉核对单位造价、总投资与建设规模匹配度", output: "冷站造价与公司标准基本持平；管路、电缆主材材质明细缺失", outputLevel: "warning" },
  { seg: "Seg06｜第六章 经济可行性分析", vectorize: "分段向量化入库本地记忆库", dify: "检索能源项目经济评价规范", local: "召回Seg04运维、Seg05投资分段", modelInput: "大模型联动多段校验收入、成本、IRR、回收期测算", output: "全投资、资本金指标计算逻辑合规；缺少完整25年逐年明细表格", outputLevel: "warning" },
  { seg: "Seg07｜第七章 不确定性分析", vectorize: "向量存入本地记忆库", dify: "项目敏感性分析规范检索", local: "召回Seg05投资、Seg04成本分段", modelInput: "大模型校验三类敏感因素分析完整性", output: "初投资、维保、能效敏感性完整；缺失电价单独敏感性测算", outputLevel: "warning" },
  { seg: "Seg08｜第八章 风险分析与防控措施", vectorize: "分段向量化入库本地记忆库", dify: "能源项目风险编制规范检索", modelInput: "大模型逐条校验回款、技术、实施、运维、续签风险条款", output: "现有风险防控完整；未单独设置能源价格波动风险专项分析", outputLevel: "warning" },
  { seg: "Seg09｜第九章 结论与建议", vectorize: "向量写入本地记忆RAG库", dify: "可研结论编制规范检索", local: "召回前8段问题记录向量", modelInput: "大模型结合全章节问题校验结论合理性", output: "投资可行结论表述标准；未针对识别缺陷补充专项整改建议", outputLevel: "warning" },
];

/** 审查日志 Mock 数据（结构化：独立日志 + 分段父日志含子步骤） */
const mockReviewData: MockReviewLog[] = [
  // —— 基础资源加载 ——
  { phase: "基础资源加载", title: "系统初始化", detail: "加载Dify能源专业RAG知识库，分类：国标库/暖通行标/综合能源企业范本，存量规范92份，向量模型bge-large-zh，语义检索阈值0.7", level: "info" },
  { phase: "基础资源加载", title: "规则引擎加载", detail: "载入人工预设9章多级大纲模板、全章节业务审查规则47条，每章节绑定专属知识库检索过滤标签", level: "info" },
  { phase: "基础资源加载", title: "本地记忆库初始化", detail: "创建本项目独立临时RAG向量库，清空历史向量数据，生成分段索引存储表", level: "info" },
  // —— 多模态文件解析 & 分段拆分 ——
  { phase: "文件解析", title: "MinerUR引擎启动", detail: "并行解析主DOCX可研、2份PDF附件，执行版面分层、文字/表格/图像/公式识别、关键数值提取", level: "info" },
  { phase: "文件解析", title: "多模态解析完成", detail: "提取面积、托管单价、总投资、负荷、营收等核心数据，识别造价、财务、工期表格11张，过滤页眉空白冗余内容", level: "success" },
  { phase: "文件解析", title: "长文档拆分", detail: "按9大章边界切割，生成Seg01~Seg09共9个独立分段（一章对应一个分段）；附件拆分为Att01、Att02", level: "info" },
  { phase: "文件解析", title: "分段预处理完毕", detail: "9个主分段完成文本清洗，进入循环审查队列", level: "success" },
  // —— 分段循环审查 Seg01~Seg09（父日志 + 子timeline展示调用链路） ——
  ...segDataList.map(d => buildSegLog(d)),
  // —— 全章节汇总 & 生成最终审查报告 ——
  { phase: "数据汇总", title: "汇总推理任务启动", detail: "本地大模型读取9个分段全部初审记录、全部Dify知识库检索原文、本地记忆跨段比对数据", level: "info" },
  { phase: "数据汇总", title: "综合归集分类", detail: "大模型统一归类所有问题：章节内容缺失、规范条文遗漏、跨段数据不一致、合同条款缺失四大类，每条绑定对应Seg分段ID、知识库来源、附件溯源", level: "info" },
  { phase: "数据汇总", title: "报告输出落地", detail: "导出标准化审查报告，每条问题附带检索来源、分段编号、校验时间戳", level: "success" },
  // —— 全流程技术调用统计 ——
  { phase: "流程统计", title: "Dify专业知识库调用", detail: "9次（每章1次检索）", level: "info" },
  { phase: "流程统计", title: "本地记忆临时RAG库检索", detail: "7次（存在跨章/附件比对章节触发）", level: "info" },
  { phase: "流程统计", title: "本地大模型推理", detail: "分段推理43次", level: "info" },
  { phase: "流程统计", title: "MinerUR多模态解析总耗时", detail: "3分37秒", level: "success" },
  { phase: "流程统计", title: "全流程运行状态", detail: "全流程运行稳定，无算力不足、上下文溢出报错", level: "success" },
];

/** 播放队列：将结构化数据展开为扁平的播放序列 */
interface PlaybackItem {
  isChild: boolean;
  log: MockReviewLog;
}

const playbackQueue: PlaybackItem[] = [];
for (const item of mockReviewData) {
  if (item.children && item.children.length > 0) {
    playbackQueue.push({ isChild: false, log: { ...item, children: [] } });
    for (const child of item.children) {
      playbackQueue.push({ isChild: true, log: child });
    }
  } else {
    playbackQueue.push({ isChild: false, log: item });
  }
}

interface ReviewLogEntry extends MockReviewLog {
  time: string;
  children?: ReviewLogEntry[];
}

const reviewLogs = ref<ReviewLogEntry[]>([]);
const reviewWaiting = ref(false);
const reviewFinished = ref(false);
const reviewIndex = ref(0);
let reviewTimer: ReturnType<typeof setTimeout> | null = null;
const reviewScrollRef = ref<HTMLElement | null>(null);
const scrollSentinelRef = ref<HTMLElement | null>(null);

/** 智能自动滚动：用户手动上滚时暂停，回到底部时恢复 */
const isAutoScroll = ref(true);

/** 任务切换时重置审查日志 */
watch(
  () => props.taskId,
  () => {
    stopReviewTimer();
    reviewLogs.value = [];
    reviewIndex.value = 0;
    reviewFinished.value = false;
    reviewWaiting.value = false;
    isAutoScroll.value = true;
  }
);

function nowTime(): string {
  const d = new Date();
  const pad = (n: number) => String(n).padStart(2, "0");
  return `${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
}

/** 根据步骤类型返回不同的日志产出间隔（ms），模拟真实处理耗时 */
function getIntervalForLog(log: MockReviewLog): number {
  switch (log.step) {
    case "vectorize": return 1500 + Math.random() * 1500;
    case "search": return 2500 + Math.random() * 2000;
    case "model_load": return 2000 + Math.random() * 1500;
    case "model_output": return 3500 + Math.random() * 3000;
    case "record": return 1000 + Math.random() * 1000;
    default:
      switch (log.phase) {
        case "基础资源加载": return 3000 + Math.random() * 2000;
        case "文件解析": return 4000 + Math.random() * 3000;
        case "数据汇总": return 6000 + Math.random() * 4000;
        case "流程统计": return 3000 + Math.random() * 2000;
        default: return 3000 + Math.random() * 2000;
      }
  }
}

function ensureReviewRunning() {
  if (reviewFinished.value) return;
  if (reviewTimer !== null) return;
  scheduleNextReviewLog();
}

function scheduleNextReviewLog() {
  if (reviewIndex.value >= playbackQueue.length) {
    reviewWaiting.value = false;
    reviewFinished.value = true;
    return;
  }
  reviewWaiting.value = true;
  const item = playbackQueue[reviewIndex.value]!;
  const interval = getIntervalForLog(item.log);
  reviewTimer = setTimeout(() => {
    reviewTimer = null;
    if (item.isChild) {
      // 追加到最后一条父日志的 children 中
      const parent = reviewLogs.value[reviewLogs.value.length - 1];
      if (parent && parent.children) {
        const { children: _c, ...childRest } = item.log;
        parent.children.push({ ...childRest, time: nowTime() });
      }
    } else {
      const { children: _children, ...rest } = item.log;
      const entry: ReviewLogEntry = { ...rest, time: nowTime() };
      if (item.log.children) {
        entry.children = [];
      }
      reviewLogs.value.push(entry);
    }
    reviewIndex.value++;
    scheduleNextReviewLog();
  }, interval);
}

function stopReviewTimer() {
  if (reviewTimer !== null) {
    clearTimeout(reviewTimer);
    reviewTimer = null;
  }
}

function restartReview() {
  stopReviewTimer();
  reviewLogs.value = [];
  reviewIndex.value = 0;
  reviewFinished.value = false;
  reviewWaiting.value = false;
  isAutoScroll.value = true;
  ensureReviewRunning();
}

/** 用户滚动时判断是否在底部附近 */
function onReviewScroll() {
  const el = reviewScrollRef.value;
  if (!el) return;
  const threshold = 50;
  const isAtBottom = el.scrollHeight - el.scrollTop - el.clientHeight < threshold;
  isAutoScroll.value = isAtBottom;
}

function scrollToBottom() {
  const sentinel = scrollSentinelRef.value;
  if (sentinel) {
    sentinel.scrollIntoView({ behavior: 'instant', block: 'nearest' });
  }
}

function scrollToBottomDelayed() {
  if (!isAutoScroll.value) return;
  nextTick(() => {
    if (!isAutoScroll.value) return;
    scrollToBottom();
    setTimeout(() => {
      if (isAutoScroll.value) scrollToBottom();
    }, 400);
  });
}

/** 监听日志总数变化（父日志 + 子日志）触发自动滚动 */
const totalLogCount = computed(() => {
  return reviewLogs.value.reduce((sum, log) => sum + 1 + (log.children?.length ?? 0), 0);
});

watch(
  () => totalLogCount.value,
  () => {
    scrollToBottomDelayed();
  }
);

onUnmounted(() => {
  stopReviewTimer();
});

function reviewLevelType(level: ReviewLogLevel): "primary" | "success" | "warning" | "danger" {
  switch (level) {
    case "info": return "primary";
    case "success": return "success";
    case "warning": return "warning";
    case "error": return "danger";
    default: return "primary";
  }
}

function phaseTagType(phase: string): "primary" | "success" | "warning" | "danger" | "info" {
  switch (phase) {
    case "基础资源加载": return "primary";
    case "文件解析": return "success";
    case "分段审查": return "warning";
    case "数据汇总": return "info";
    case "流程统计": return "info";
    default: return "info";
  }
}
</script>

<style lang="scss" scoped>
/* 抽屉 body 改为 flex 布局，使 Tab 内容区可独立滚动 */
:deep(.el-drawer__body) {
  display: flex;
  flex-direction: column;
  overflow: hidden;
  padding: 0;
}

.task-summary {
  flex-shrink: 0;
  padding: 16px 20px 0;
}

/* Tab 区域 */
.log-tabs {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  padding: 0 20px 16px;

  :deep(.el-tabs__header) {
    margin-bottom: 0;
    flex-shrink: 0;
  }

  :deep(.el-tabs__content) {
    flex: 1;
    min-height: 0;
    overflow: hidden;
  }

  :deep(.el-tab-pane) {
    height: 100%;
  }
}

.tab-label {
  display: inline-flex;
  align-items: center;
  gap: 4px;
}

.tab-status {
  display: inline-block;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  margin-left: 2px;

  &--running {
    background: #e6a23c;
    animation: tab-pulse 1.5s infinite;
  }

  &--done {
    background: #67c23a;
  }
}

@keyframes tab-pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.3; }
}

.tab-scroll {
  height: 100%;
  overflow-y: auto;
  padding: 12px 4px 0 0;
}

/* ===== 审查日志卡片 ===== */
.review-timeline {
  padding-left: 4px;
}

.review-card {
  background: #f9fafb;
  border-radius: 6px;
  padding: 10px 12px;
  border: 1px solid #f0f0f0;
  animation: review-card-in 0.35s ease;

  &--success {
    background: #f0fdf4;
    border-color: #dcfce7;
  }
  &--warning {
    background: #fffbeb;
    border-color: #fef3c7;
  }
  &--error {
    background: #fef2f2;
    border-color: #fee2e2;
  }
}

@keyframes review-card-in {
  from {
    opacity: 0;
    transform: translateY(8px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.review-card-header {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 6px;
}

.review-card-title {
  font-weight: 600;
  font-size: 13px;
  color: #303133;
}

.review-card-detail {
  font-size: 12px;
  color: #606266;
  line-height: 1.6;
  background: #fff;
  padding: 6px 8px;
  border-radius: 4px;
  white-space: pre-line;
}

/* ===== 子 timeline：分段审查调用链路 ===== */
.sub-timeline {
  margin-top: 8px;
  padding-left: 8px;

  :deep(.el-timeline-item__timestamp) {
    font-size: 11px;
    color: #c0c4cc;
  }

  :deep(.el-timeline-item__node--normal) {
    left: -1px;
    width: 8px;
    height: 8px;
  }

  :deep(.el-timeline-item__tail) {
    left: 3px;
  }

  :deep(.el-timeline-item:last-child .el-timeline-item__tail) {
    display: none;
  }
}

.review-sub-card {
  background: #fff;
  border-radius: 4px;
  padding: 6px 10px;
  border: 1px solid #f0f0f0;
  animation: review-card-in 0.3s ease;

  &--success {
    border-color: #dcfce7;
    background: #f0fdf4;
  }
  &--warning {
    border-color: #fef3c7;
    background: #fffbeb;
  }
  &--error {
    border-color: #fee2e2;
    background: #fef2f2;
  }
}

.review-sub-header {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-bottom: 4px;
}

.review-sub-step {
  font-weight: 600;
  font-size: 12px;
  color: #606266;
}

.review-sub-detail {
  font-size: 11px;
  color: #909399;
  line-height: 1.5;
  white-space: pre-line;
}

/* 等待新日志加载动画 */
.review-loading {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 14px 0 8px 28px;
  color: #409eff;

  .loading-text {
    font-size: 12px;
    color: #909399;
  }
}

/* 审查完成 */
.review-finished {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 12px 0 8px 28px;
  font-size: 13px;
  color: #67c23a;
  font-weight: 500;
}

/* ===== 操作日志卡片（保持原有样式） ===== */
.log-card {
  background: #f9fafb;
  border-radius: 6px;
  padding: 10px 12px;
  border: 1px solid #f0f0f0;
}

.log-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 6px;
}

.log-action {
  font-weight: 600;
  font-size: 14px;
  color: #303133;
}

.log-operator {
  display: flex;
  align-items: center;
  gap: 4px;
  font-size: 12px;
  color: #909399;
  margin-bottom: 4px;
}

.log-desc {
  font-size: 12px;
  color: #606266;
  line-height: 1.6;
  background: #fff;
  padding: 6px 8px;
  border-radius: 4px;
}

.empty-logs {
  text-align: center;
  color: #909399;
  font-size: 13px;
  padding: 24px 0;
}

:deep(.el-timeline-item__timestamp) {
  color: #909399;
  font-size: 12px;
}
</style>
