<script lang="ts" setup>
/**
 * OA 消息预览组件（独立、纯展示）
 *
 * 用途：在场景表单 Step 2 配置完某平台消息后，实时渲染对应平台的消息气泡或卡片 UI，
 *       让用户所见即所得，复刻企微/钉钉/飞书客户端的展示样式。
 *
 * 设计原则：
 * - 纯展示组件，不调真实接口，无副作用
 * - 输入：platformCode / messageType / payload（+ content / title / url / mediaUrl）
 * - 通过 platformCode 切换容器外壳（仿客户端背景色：企微淡灰 / 钉钉淡绿 / 飞书淡蓝）
 * - 通过 messageType 切换内部渲染（text / markdown / image / news / template_card / link / actionCard / post / interactive ...）
 * - 复用：机器人管理页测试发送也可用本组件预览返回结果
 *
 * 注意：
 * - markdown 用项目已有的 marked 简单渲染（容错：未引入时降级为 <pre> 文本）
 * - 所有外部图片仅用 <img> 标签展示，url 为空时用占位图
 */

import { computed } from 'vue';

/* ------------------------------------------------------------------ *
 * Props / Types
 * ------------------------------------------------------------------ */

export interface MsgPreviewPayload {
  /** 顶层文本（text/markdown 类型用，payload.content 的快捷别名） */
  content?: string;
  /** 标题（部分类型用） */
  title?: string;
  /** 跳转 URL（link/news 等类型用） */
  url?: string;
  /** 媒体 URL（image/file/voice 类型用） */
  media_url?: string;
  /** @ 手机号列表（用于展示「@张三」标签） */
  atMobiles?: string[];
  /** @ userid 列表 */
  atUserIds?: string[];
  /** 完整 payload（结构化数据，按平台官方字段对齐） */
  [key: string]: any;
}

interface Props {
  /** 平台编码：work_wechat / work_wechat_app / dingtalk / feishu */
  platformCode: string;
  /** 消息类型：text / markdown / image / news / file / voice / template_card / link / actionCard / feedCard / post / interactive / image / share_chat */
  messageType: string;
  /** 消息 payload（结构化） */
  payload?: MsgPreviewPayload;
  /** 简单文本内容（覆盖 payload.content，便于父组件直接传表单字段） */
  content?: string;
  /** 标题（覆盖 payload.title） */
  title?: string;
  /** 跳转 URL（覆盖 payload.url） */
  url?: string;
  /** 媒体 URL（覆盖 payload.media_url） */
  mediaUrl?: string;
  /** 是否显示外壳（聊天窗口容器），默认 true */
  showShell?: boolean;
}

const props = withDefaults(defineProps<Props>(), {
  payload: () => ({}),
  content: '',
  title: '',
  url: '',
  mediaUrl: '',
  showShell: true,
});

/* ------------------------------------------------------------------ *
 * 数据合并：把顶层 content/title/url/mediaUrl 与 payload 字段合并
 * 优先级：props.xxx > payload.xxx（顶层字段更具体）
 * ------------------------------------------------------------------ */

const mergedPayload = computed<MsgPreviewPayload>(() => {
  const p = props.payload || {};
  return {
    ...p,
    content: props.content || p.content || '',
    title: props.title || p.title || '',
    url: props.url || p.url || '',
    media_url: props.mediaUrl || p.media_url || '',
  };
});

/* ------------------------------------------------------------------ *
 * 平台分类
 * work_wechat_app 也归到企微样式（同一个客户端外壳）
 * ------------------------------------------------------------------ */

const isWorkWechat = computed(
  () =>
    props.platformCode === 'work_wechat' ||
    props.platformCode === 'work_wechat_app',
);
const isDingTalk = computed(() => props.platformCode === 'dingtalk');
const isFeishu = computed(() => props.platformCode === 'feishu');

/** 容器 className（控制外壳底色 + 文字色） */
const shellClass = computed(() => {
  if (!props.showShell) return 'msg-preview-shell--inline';
  if (isWorkWechat.value) return 'msg-preview-shell msg-preview-shell--ww';
  if (isDingTalk.value) return 'msg-preview-shell msg-preview-shell--dt';
  if (isFeishu.value) return 'msg-preview-shell msg-preview-shell--fs';
  return 'msg-preview-shell msg-preview-shell--default';
});

/** 顶部平台标签文案 */
const platformLabel = computed(() => {
  if (props.platformCode === 'work_wechat') return '企业微信';
  if (props.platformCode === 'work_wechat_app') return '企业微信·应用';
  if (isDingTalk.value) return '钉钉';
  if (isFeishu.value) return '飞书';
  return props.platformCode;
});

/* ------------------------------------------------------------------ *
 * Markdown 简单渲染（避免依赖外部 marked 库，仅做基础语法）
 * 支持：**粗体** / *斜体* / # 标题 / - 列表 / [text](url) / 换行
 * 复杂语法降级为原始文本展示（保证安全，不执行任何 HTML）
 * ------------------------------------------------------------------ */

function escapeHtml(s: string): string {
  return String(s || '')
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;');
}

function renderMarkdown(md: string): string {
  const lines = String(md || '').split(/\r?\n/);
  const out: string[] = [];
  let inList = false;

  const closeList = () => {
    if (inList) {
      out.push('</ul>');
      inList = false;
    }
  };

  for (const raw of lines) {
    const line = raw.trimEnd();
    if (!line) {
      closeList();
      continue;
    }
    // 标题 # / ## / ###
    const h = line.match(/^(#{1,3})\s+(.+)$/);
    if (h) {
      closeList();
      const level = h[1].length + 1;
      out.push(`<h${level}>${inlineFmt(h[2])}</h${level}>`);
      continue;
    }
    // 无序列表 - / *
    const li = line.match(/^[-*]\s+(.+)$/);
    if (li) {
      if (!inList) {
        out.push('<ul>');
        inList = true;
      }
      out.push(`<li>${inlineFmt(li[1])}</li>`);
      continue;
    }
    closeList();
    out.push(`<p>${inlineFmt(line)}</p>`);
  }
  closeList();
  return out.join('');
}

/** 行内格式化：粗体 / 斜体 / 链接 */
function inlineFmt(s: string): string {
  let out = escapeHtml(s);
  // 链接 [text](url)
  out = out.replace(
    /\[([^\]]+)\]\(([^)\s]+)\)/g,
    '<a href="$2" target="_blank" rel="noopener">$1</a>',
  );
  // 粗体 **text**
  out = out.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>');
  // 斜体 *text*
  out = out.replace(/\*([^*]+)\*/g, '<em>$1</em>');
  // 企微/飞书 @ 标识高亮
  out = out.replace(/@([\w\u4e00-\u9fa5_-]+)/g, '<span class="at-mention">@$1</span>');
  return out;
}

/* ------------------------------------------------------------------ *
 * 按消息类型计算的展示数据
 * ------------------------------------------------------------------ */

/** 文本内容（用于 text / markdown） */
const textContent = computed(() => String(mergedPayload.value.content || ''));

/** @ 人列表（用于在文本后追加展示） */
const atList = computed<string[]>(() => {
  const mobiles = Array.isArray(mergedPayload.value.atMobiles)
    ? mergedPayload.value.atMobiles.map(String)
    : [];
  const userIds = Array.isArray(mergedPayload.value.atUserIds)
    ? mergedPayload.value.atUserIds.map(String)
    : [];
  return [...mobiles, ...userIds].filter(Boolean);
});

/** 图片 URL */
const imageUrl = computed(() => {
  const p = mergedPayload.value;
  // 企微 image 类型 payload 里可能是 mediaId（无 url，展示占位）
  return String(
    p.media_url ||
      p.url ||
      p.image ||
      p.image_url ||
      p.pic_url ||
      p.picUrl ||
      '',
  );
});

/** 是否是 template_card */
const templateCard = computed<Record<string, any>>(() => {
  const p = mergedPayload.value as any;
  return p.template_card || p.card || (p.card_type ? p : {});
});

/** 钉钉 actionCard */
const dingActionCard = computed(() => {
  const p = mergedPayload.value as any;
  return {
    title: p.title || '',
    text: p.text || '',
    btns: Array.isArray(p.btns) ? p.btns : [],
    singleTitle: p.singleTitle || '',
    singleURL: p.singleURL || '',
  };
});

/** 钉钉 link */
const dingLink = computed(() => {
  const p = mergedPayload.value as any;
  return {
    title: p.title || '',
    text: p.text || '',
    picUrl: p.picUrl || '',
    messageUrl: p.messageUrl || '',
  };
});

/** 飞书 post 富文本 */
const feishuPost = computed(() => {
  const p = mergedPayload.value as any;
  // zh_cn 多语言结构：post = { zh_cn: { title, content: [[{tag,text}]] } }
  const post = p.post || p;
  const zh = post.zh_cn || post.en_us || post;
  return {
    title: zh.title || '',
    content: Array.isArray(zh.content) ? zh.content : [],
  };
});

/** 飞书 interactive 卡片 */
const feishuCard = computed(() => {
  const p = mergedPayload.value as any;
  return p.card || p.elements || p;
});

/**
 * 企微 news：优先 articles[]；旧数据仅顶层 title/description 时降级为单条
 */
const newsArticles = computed<
  Array<{ title: string; description: string; url: string; picurl: string }>
>(() => {
  const p = mergedPayload.value as any;
  const raw = Array.isArray(p.articles) ? p.articles : [];
  if (raw.length > 0) {
    return raw.slice(0, 8).map((a: any) => ({
      title: String(a?.title || ''),
      description: String(a?.description || ''),
      url: String(a?.url || ''),
      picurl: String(a?.picurl || a?.picUrl || ''),
    }));
  }
  // 兼容旧扁平结构
  if (p.title || p.description || p.url || p.picurl) {
    return [
      {
        title: String(p.title || ''),
        description: String(p.description || ''),
        url: String(p.url || ''),
        picurl: String(p.picurl || ''),
      },
    ];
  }
  return [];
});

/** 企微 textcard 预览字段 */
const textCardPreview = computed(() => {
  const p = mergedPayload.value as any;
  return {
    title: String(p.title || ''),
    description: String(p.description || ''),
    url: String(p.url || ''),
    btntxt: String(p.btntxt || '详情'),
  };
});

/** 是否展示「@ 提示」 */
const showAtFooter = computed(
  () => atList.value.length > 0 && ['text', 'markdown'].includes(props.messageType),
);

/* ------------------------------------------------------------------ *
 * 资源占位
 * ------------------------------------------------------------------ */

const PLACEHOLDER_IMG =
  'data:image/svg+xml;utf8,' +
  encodeURIComponent(
    '<svg xmlns="http://www.w3.org/2000/svg" width="240" height="160"><rect width="100%" height="100%" fill="#f0f2f5"/><text x="50%" y="50%" font-size="14" fill="#86909c" text-anchor="middle" dominant-baseline="middle">图片占位</text></svg>',
  );

function safeImg(url: string): string {
  return url && /^https?:\/\//.test(url) ? url : PLACEHOLDER_IMG;
}

/** 链接域名（用于卡片底部展示「域名前缀」提示） */
function urlHost(url: string): string {
  try {
    return new URL(url).host || url;
  } catch {
    return url;
  }
}
</script>

<template>
  <div :class="shellClass">
    <!-- 平台标签（仅在外壳模式下显示） -->
    <div v-if="showShell" class="msg-preview-header">
      <span class="platform-badge" :class="{
        'platform-badge--ww': isWorkWechat,
        'platform-badge--dt': isDingTalk,
        'platform-badge--fs': isFeishu,
      }">{{ platformLabel }}</span>
      <span class="msg-type-badge">{{ messageType }}</span>
    </div>

    <!-- 消息主体气泡容器 -->
    <div class="msg-preview-body" :class="{
      'msg-preview-body--ww': isWorkWechat,
      'msg-preview-body--dt': isDingTalk,
      'msg-preview-body--fs': isFeishu,
    }">
      <!-- ================= 企微 / 钉钉 / 飞书：text ================= -->
      <div v-if="messageType === 'text'" class="bubble bubble--text">
        <span v-if="textContent" class="text-content">{{ textContent }}</span>
        <span v-else class="empty-tip">（请填写文本内容）</span>
        <div v-if="showAtFooter" class="at-footer">
          <span v-for="(at, i) in atList" :key="i" class="at-pill">@{{ at }}</span>
        </div>
      </div>

      <!-- ================= markdown ================= -->
      <div v-else-if="messageType === 'markdown' || messageType === 'markdown_v2'" class="bubble bubble--markdown">
        <div v-if="textContent" class="md-content" v-html="renderMarkdown(textContent)"></div>
        <div v-else class="empty-tip">（请填写 Markdown 内容）</div>
        <div v-if="showAtFooter" class="at-footer">
          <span v-for="(at, i) in atList" :key="i" class="at-pill">@{{ at }}</span>
        </div>
      </div>

      <!-- ================= image ================= -->
      <div v-else-if="messageType === 'image'" class="bubble bubble--image">
        <img :src="safeImg(imageUrl)" alt="image" class="bubble-img" />
      </div>

      <!-- ================= news（企微图文：articles[]，首条大图 + 后续列表） ================= -->
      <div v-else-if="messageType === 'news'" class="bubble bubble--card news-list">
        <div v-if="newsArticles.length === 0" class="empty-tip">请添加图文</div>
        <template v-else>
          <div
            v-for="(art, i) in newsArticles"
            :key="i"
            :class="['news-item', i === 0 ? 'news-item--hero' : 'news-item--row']"
          >
            <img
              v-if="art.picurl || (i === 0 && imageUrl)"
              :src="safeImg(art.picurl || imageUrl)"
              alt="news"
              :class="i === 0 ? 'card-cover' : 'news-thumb'"
            />
            <div class="news-body">
              <div class="card-title">{{ art.title || '（无标题）' }}</div>
              <div v-if="art.description" class="card-desc">{{ art.description }}</div>
              <a
                v-if="art.url"
                :href="art.url"
                target="_blank"
                rel="noopener"
                class="card-link"
              >
                {{ urlHost(art.url) }} ›
              </a>
            </div>
          </div>
        </template>
      </div>

      <!-- ================= textcard（企微文本卡片） ================= -->
      <div v-else-if="messageType === 'textcard'" class="bubble bubble--card">
        <div class="card-title">{{ textCardPreview.title || '（无标题）' }}</div>
        <div v-if="textCardPreview.description" class="card-desc">
          {{ textCardPreview.description }}
        </div>
        <a
          v-if="textCardPreview.url"
          :href="textCardPreview.url"
          target="_blank"
          rel="noopener"
          class="card-link textcard-btn"
        >
          {{ textCardPreview.btntxt }} ›
        </a>
        <div v-else class="card-link textcard-btn">{{ textCardPreview.btntxt }} ›</div>
      </div>

      <!-- ================= template_card（企微模板卡片） ================= -->
      <div v-else-if="messageType === 'template_card'" class="bubble bubble--card ww-template-card">
        <div v-if="templateCard.source?.icon_url || templateCard.source?.desc" class="tc-source">
          <img v-if="templateCard.source?.icon_url" :src="safeImg(templateCard.source.icon_url)" class="tc-source-icon" />
          <span>{{ templateCard.source?.desc || '' }}</span>
        </div>
        <div v-if="templateCard.main_title?.title || templateCard.main_title?.desc" class="tc-main-title">
          <div v-if="templateCard.main_title?.title" class="tc-title">{{ templateCard.main_title.title }}</div>
          <div v-if="templateCard.main_title?.desc" class="tc-desc">{{ templateCard.main_title.desc }}</div>
        </div>
        <div v-if="templateCard.card_image?.url" class="tc-image">
          <img :src="safeImg(templateCard.card_image.url)" />
        </div>
        <div v-if="templateCard.emphasis_content?.title || templateCard.emphasis_content?.desc" class="tc-emphasis">
          <span class="tc-emphasis-title">{{ templateCard.emphasis_content?.title }}</span>
          <span class="tc-emphasis-desc">{{ templateCard.emphasis_content?.desc }}</span>
        </div>
        <div v-if="templateCard.sub_title_text" class="tc-sub">{{ templateCard.sub_title_text }}</div>
        <!-- 二级标题+文本列表 -->
        <div v-if="Array.isArray(templateCard.horizontal_content_list) && templateCard.horizontal_content_list.length" class="tc-list">
          <div v-for="(item, i) in templateCard.horizontal_content_list" :key="i" class="tc-list-row">
            <span class="tc-list-key">{{ item.keyname }}</span>
            <span class="tc-list-val">{{ item.value }}</span>
          </div>
        </div>
        <!-- 跳转链接列表 -->
        <div v-if="Array.isArray(templateCard.jump_list) && templateCard.jump_list.length" class="tc-jump">
          <a v-for="(item, i) in templateCard.jump_list" :key="i" :href="item.url || '#'" target="_blank" rel="noopener" class="tc-jump-row">
            <span>{{ item.title }}</span><span class="arrow">›</span>
          </a>
        </div>
      </div>

      <!-- ================= 钉钉 link ================= -->
      <div v-else-if="messageType === 'link'" class="bubble bubble--card">
        <div class="card-title">{{ dingLink.title || '（无标题）' }}</div>
        <div class="card-flex">
          <div v-if="dingLink.text" class="card-desc">{{ dingLink.text }}</div>
          <img v-if="dingLink.picUrl" :src="safeImg(dingLink.picUrl)" class="card-thumb" />
        </div>
        <a v-if="dingLink.messageUrl" :href="dingLink.messageUrl" target="_blank" rel="noopener" class="card-link">
          {{ urlHost(dingLink.messageUrl) }} ›
        </a>
      </div>

      <!-- ================= 钉钉 actionCard ================= -->
      <div v-else-if="messageType === 'actionCard'" class="bubble bubble--card">
        <div v-if="dingActionCard.title" class="card-title">{{ dingActionCard.title }}</div>
        <div v-if="dingActionCard.text" class="card-desc md-content" v-html="renderMarkdown(dingActionCard.text)"></div>
        <a v-if="dingActionCard.singleTitle && dingActionCard.singleURL" :href="dingActionCard.singleURL" target="_blank" rel="noopener" class="btn-primary">
          {{ dingActionCard.singleTitle }}
        </a>
        <div v-else-if="dingActionCard.btns.length" class="btn-grid">
          <a v-for="(b, i) in dingActionCard.btns" :key="i" :href="b.actionURL" target="_blank" rel="noopener" class="btn-default">
            {{ b.title }}
          </a>
        </div>
      </div>

      <!-- ================= 飞书 post 富文本 ================= -->
      <div v-else-if="messageType === 'post'" class="bubble bubble--post">
        <div v-if="feishuPost.title" class="post-title">{{ feishuPost.title }}</div>
        <div class="post-content">
          <template v-for="(line, i) in feishuPost.content" :key="i">
            <div class="post-line">
              <template v-for="(node, j) in line" :key="j">
                <a v-if="node.tag === 'a'" :href="node.href" target="_blank" rel="noopener">{{ node.text }}</a>
                <img v-else-if="node.tag === 'img'" :src="safeImg(node.image_key)" class="post-img" />
                <strong v-else-if="node.tag === 'b'">{{ node.text }}</strong>
                <span v-else>{{ node.text }}</span>
              </template>
            </div>
          </template>
          <div v-if="!feishuPost.content.length" class="empty-tip">（请填写 post 内容）</div>
        </div>
      </div>

      <!-- ================= 飞书 interactive 卡片 ================= -->
      <div v-else-if="messageType === 'interactive'" class="bubble bubble--card fs-interactive">
        <div v-if="feishuCard?.header?.title?.content" class="fs-card-header" :style="{ background: feishuCard?.header?.template ? '' : '#3370ff' }">
          {{ feishuCard.header.title.content }}
        </div>
        <div v-if="Array.isArray(feishuCard?.elements)" class="fs-elements">
          <template v-for="(el, i) in feishuCard.elements" :key="i">
            <div v-if="el.tag === 'div' && el.text" class="fs-el-text" v-html="renderMarkdown(el.text.content || '')"></div>
            <div v-else-if="el.tag === 'div' && el.column_set" class="fs-el-cols">
              <div v-for="(col, ci) in el.column_set.columns || []" :key="ci" class="fs-el-col">
                <template v-for="(cel, cci) in col.elements || []" :key="cci">
                  <div v-if="cel.tag === 'markdown'" v-html="renderMarkdown(cel.content || '')"></div>
                </template>
              </div>
            </div>
            <a v-else-if="el.tag === 'action' && Array.isArray(el.actions)" class="fs-btn-row">
              <span v-for="(btn, bi) in el.actions" :key="bi" class="fs-btn">{{ btn.text?.text || btn.text || '' }}</span>
            </a>
            <img v-else-if="el.tag === 'img'" :src="safeImg(el.img_key)" class="fs-img" />
            <div v-else-if="el.tag === 'hr'" class="fs-hr"></div>
            <div v-else-if="el.tag === 'note'" class="fs-note">
              <template v-for="(nel, ni) in el.elements || []" :key="ni">
                <img v-if="nel.tag === 'img'" :src="safeImg(nel.img_key)" class="fs-note-img" />
                <span v-else>{{ nel.content || nel.text || '' }}</span>
              </template>
            </div>
          </template>
        </div>
      </div>

      <!-- ================= 钉钉 feedCard ================= -->
      <div v-else-if="messageType === 'feedCard'" class="bubble bubble--card">
        <div v-for="(link, i) in mergedPayload.links || []" :key="i" class="feed-row">
          <div class="feed-flex">
            <div>
              <div class="card-title">{{ link.title }}</div>
              <div class="card-link">{{ urlHost(link.messageURL) }}</div>
            </div>
            <img v-if="link.picURL" :src="safeImg(link.picURL)" class="card-thumb" />
          </div>
        </div>
      </div>

      <!-- ================= file / voice / share_chat ================= -->
      <div v-else-if="['file', 'voice', 'share_chat'].includes(messageType)" class="bubble bubble--file">
        <div class="file-icon">📁</div>
        <div class="file-info">
          <div class="file-name">{{ mergedPayload.name || `${messageType} 文件` }}</div>
          <div class="file-meta">{{ messageType }} · 仅展示文件名，实际发送需 media_id</div>
        </div>
      </div>

      <!-- ================= 未识别类型 ================= -->
      <div v-else class="bubble bubble--unknown">
        <div class="empty-tip">暂未实现「{{ messageType }}」类型的预览样式</div>
        <pre v-if="Object.keys(mergedPayload).length" class="raw-json">{{ mergedPayload }}</pre>
      </div>
    </div>
  </div>
</template>

<style scoped lang="scss">
/* ========== 外壳（仿客户端聊天窗口） ========== */
/* 浅色：保留品牌色调淡版；暗色由下方 html.dark 块覆盖 */
.msg-preview-shell {
  width: 100%;
  padding: 12px;
  border-radius: 12px;
  background: var(--mp-shell-default, #ededed);
  min-height: 200px;
}

.msg-preview-shell--ww {
  background: var(--mp-shell-ww, #ededed);
}

.msg-preview-shell--dt {
  background: var(--mp-shell-dt, #e8f5e9);
}

.msg-preview-shell--fs {
  background: var(--mp-shell-fs, #eef4ff);
}

.msg-preview-shell--default {
  background: var(--mp-shell-default, #f5f5f5);
}

.msg-preview-shell--inline {
  background: transparent;
  padding: 0;
}

.msg-preview-header {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 8px;
}

.platform-badge {
  padding: 2px 8px;
  border-radius: 4px;
  font-size: 12px;
  color: #fff;
  background: var(--ant-color-text-tertiary, #999);
}

/* 品牌色保留：企微绿 / 钉钉蓝 / 飞书蓝 */
.platform-badge--ww {
  background: #07c160;
}

.platform-badge--dt {
  background: #0089ff;
}

.platform-badge--fs {
  background: #3370ff;
}

.msg-type-badge {
  padding: 2px 6px;
  border-radius: 3px;
  font-size: 11px;
  background: var(--ant-color-fill-tertiary, rgba(0, 0, 0, 0.06));
  color: var(--ant-color-text-secondary, #555);
}

/* ========== 消息主体气泡 ========== */
.msg-preview-body {
  display: flex;
  flex-direction: column;
  gap: 6px;
  max-width: 80%;
}

.bubble {
  padding: 10px 12px;
  border-radius: 8px;
  font-size: 14px;
  line-height: 1.5;
  word-break: break-word;
}

/* 企微气泡：白底圆角 */
.msg-preview-body--ww .bubble {
  background: var(--ant-color-bg-container, #fff);
  color: var(--ant-color-text, #1a1a1a);
}

/* 钉钉气泡：白底，markdown 绿字（暗色由 html.dark 块调亮） */
.msg-preview-body--dt .bubble {
  background: var(--ant-color-bg-container, #fff);
  color: var(--ant-color-text, #1a1a1a);
}

.msg-preview-body--dt .bubble--markdown {
  color: #2e8b57;
}

/* 飞书气泡：白底带边框 */
.msg-preview-body--fs .bubble {
  background: var(--ant-color-bg-container, #fff);
  color: var(--ant-color-text, #1f2329);
  border: 1px solid var(--ant-color-border-secondary, #e5e6eb);
}

/* ========== 文本/markdown 内容样式 ========== */
.text-content {
  white-space: pre-wrap;
}

.md-content {
  :deep(h2) {
    font-size: 16px;
    font-weight: 600;
    margin: 6px 0 4px;
  }

  :deep(h3) {
    font-size: 14px;
    font-weight: 600;
    margin: 4px 0;
  }

  :deep(p) {
    margin: 2px 0;
  }

  :deep(ul) {
    padding-left: 20px;
    margin: 4px 0;
  }

  /* 品牌色链接 */
  :deep(a) {
    color: #3370ff;
    text-decoration: none;
  }

  :deep(strong) {
    font-weight: 600;
  }
}

.at-footer {
  margin-top: 6px;
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
}

.at-pill {
  padding: 1px 6px;
  border-radius: 3px;
  background: rgba(51, 112, 255, 0.1);
  /* 品牌色 */
  color: #3370ff;
  font-size: 12px;
}

:deep(.at-mention) {
  /* 品牌色 */
  color: #3370ff;
}

/* ========== 图片气泡 ========== */
.bubble-img {
  max-width: 240px;
  max-height: 240px;
  border-radius: 6px;
  display: block;
}

/* ========== 卡片通用样式（news / link / actionCard / template_card / interactive） ========== */
.bubble--card {
  background: var(--ant-color-bg-container, #fff);
  padding: 0;
  overflow: hidden;
  border-radius: 6px;
  max-width: 320px;
}

.card-title {
  font-size: 14px;
  font-weight: 600;
  padding: 10px 12px 4px;
  color: var(--ant-color-text, #1a1a1a);
}

.card-desc {
  font-size: 13px;
  color: var(--ant-color-text-secondary, #555);
  padding: 0 12px 6px;
}

.card-cover {
  width: 100%;
  max-height: 160px;
  object-fit: cover;
  display: block;
}

/* news 多图文列表：首条大图，后续缩略行 */
.news-list .news-item + .news-item {
  border-top: 1px solid var(--ant-color-border-secondary, #f0f0f0);
}

.news-item--row {
  display: flex;
  flex-direction: row-reverse;
  align-items: center;
  gap: 8px;
  padding: 8px 12px;
}

.news-item--row .news-body {
  flex: 1;
  min-width: 0;
}

.news-item--row .card-title {
  padding: 0;
  font-size: 13px;
}

.news-item--row .card-desc {
  padding: 2px 0 0;
  font-size: 12px;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.news-item--row .card-link {
  padding: 2px 0 0;
  font-size: 12px;
}

.news-thumb {
  width: 48px;
  height: 48px;
  object-fit: cover;
  border-radius: 4px;
  flex-shrink: 0;
}

.textcard-btn {
  display: block;
  padding: 8px 12px 10px;
  font-weight: 500;
}

.card-flex {
  display: flex;
  gap: 8px;
  padding: 0 12px 6px;
}

.card-thumb {
  width: 64px;
  height: 64px;
  border-radius: 4px;
  object-fit: cover;
}

.card-link {
  display: block;
  padding: 4px 12px 10px;
  font-size: 12px;
  /* 品牌色 */
  color: #3370ff;
  text-decoration: none;
}

.btn-primary,
.btn-default {
  display: block;
  text-align: center;
  padding: 8px;
  font-size: 14px;
  text-decoration: none;
  /* 品牌色 */
  color: #3370ff;
  border-top: 1px solid var(--ant-color-split, #f0f0f0);
}

.btn-default:nth-child(odd) {
  border-right: 1px solid var(--ant-color-split, #f0f0f0);
}

.btn-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
}

/* ========== 企微 template_card ========== */
.ww-template-card {
  max-width: 300px;
}

.tc-source {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 8px 12px 0;
  font-size: 12px;
  color: var(--ant-color-text-tertiary, #888);
}

.tc-source-icon {
  width: 16px;
  height: 16px;
  border-radius: 2px;
}

.tc-main-title {
  padding: 4px 12px 0;
}

.tc-title {
  font-size: 16px;
  font-weight: 600;
}

.tc-desc {
  font-size: 12px;
  color: var(--ant-color-text-tertiary, #888);
  margin-top: 2px;
}

.tc-image img {
  width: 100%;
  max-height: 140px;
  object-fit: cover;
  margin: 6px 0;
}

.tc-emphasis {
  display: flex;
  align-items: baseline;
  gap: 4px;
  padding: 4px 12px;
}

.tc-emphasis-title {
  font-size: 24px;
  font-weight: 700;
  color: var(--ant-color-text, #222);
}

.tc-emphasis-desc {
  font-size: 12px;
  color: var(--ant-color-text-tertiary, #888);
}

.tc-sub {
  font-size: 13px;
  color: var(--ant-color-text-secondary, #444);
  padding: 2px 12px 6px;
}

.tc-list {
  padding: 4px 12px;
}

.tc-list-row {
  display: flex;
  justify-content: space-between;
  font-size: 13px;
  padding: 2px 0;
}

.tc-list-key {
  color: var(--ant-color-text-tertiary, #888);
}

.tc-list-val {
  color: var(--ant-color-text, #1a1a1a);
}

.tc-jump {
  margin-top: 6px;
  border-top: 1px solid var(--ant-color-split, #f0f0f0);
}

.tc-jump-row {
  display: flex;
  justify-content: space-between;
  padding: 8px 12px;
  font-size: 13px;
  /* 品牌色 */
  color: #3370ff;
  text-decoration: none;
  border-bottom: 1px solid var(--ant-color-split, #f7f7f7);

  .arrow {
    color: var(--ant-color-text-quaternary, #ccc);
  }
}

/* ========== 飞书 post 富文本 ========== */
.bubble--post {
  max-width: 320px;
}

.post-title {
  font-size: 14px;
  font-weight: 600;
  margin-bottom: 4px;
}

.post-line {
  margin: 2px 0;
}

.post-img {
  max-width: 200px;
  border-radius: 4px;
}

/* ========== 飞书 interactive 卡片 ========== */
.fs-interactive {
  max-width: 320px;
}

.fs-card-header {
  padding: 10px 12px;
  color: #fff;
  font-weight: 600;
  font-size: 14px;
}

.fs-elements {
  padding: 4px 0;
}

.fs-el-text {
  padding: 4px 12px;
  font-size: 13px;
}

.fs-el-cols {
  display: flex;
  gap: 6px;
  padding: 4px 12px;
}

.fs-el-col {
  flex: 1;
}

.fs-btn-row {
  display: flex;
  gap: 6px;
  padding: 6px 12px;
}

.fs-btn {
  flex: 1;
  text-align: center;
  padding: 6px;
  /* 品牌色 */
  background: #3370ff;
  color: #fff;
  border-radius: 4px;
  font-size: 13px;
}

.fs-img {
  width: calc(100% - 24px);
  margin: 4px 12px;
  border-radius: 4px;
}

.fs-hr {
  height: 1px;
  background: var(--ant-color-border-secondary, #e5e6eb);
  margin: 6px 12px;
}

.fs-note {
  padding: 6px 12px;
  font-size: 12px;
  color: var(--ant-color-text-tertiary, #888);
  display: flex;
  align-items: center;
  gap: 4px;
}

.fs-note-img {
  width: 14px;
  height: 14px;
}

/* ========== 钉钉 feedCard ========== */
.feed-row {
  padding: 8px 12px;
  border-bottom: 1px solid var(--ant-color-split, #f5f5f5);
}

.feed-flex {
  display: flex;
  justify-content: space-between;
  gap: 8px;
  align-items: flex-start;
}

/* ========== 文件类型（file/voice/share_chat） ========== */
.bubble--file {
  display: flex;
  align-items: center;
  gap: 8px;
  background: var(--ant-color-bg-container, #fff);
}

.file-icon {
  font-size: 28px;
}

.file-info {
  flex: 1;
}

.file-name {
  font-size: 14px;
  color: var(--ant-color-text, #1a1a1a);
}

.file-meta {
  font-size: 12px;
  color: var(--ant-color-text-tertiary, #888);
}

/* ========== 占位提示 ========== */
.empty-tip {
  color: var(--ant-color-text-quaternary, #aaa);
  font-size: 13px;
  font-style: italic;
}

.raw-json {
  margin-top: 6px;
  font-size: 11px;
  background: var(--ant-color-fill-quaternary, #f7f7f7);
  padding: 6px;
  border-radius: 4px;
  color: var(--ant-color-text-secondary, #555);
  white-space: pre-wrap;
  word-break: break-all;
  max-height: 200px;
  overflow: auto;
}

/* ========== 暗色覆盖（html.dark / .dark）==========
 * 用户选项 full-dark-adapt：浅色外壳和气泡也跟随暗色，仅保留品牌色
 * 注：scoped 下 html.dark 作为祖先选择器不会改写，能直接命中
 */
html.dark .msg-preview-shell,
.dark .msg-preview-shell,
html.dark .msg-preview-shell--ww,
.dark .msg-preview-shell--ww,
html.dark .msg-preview-shell--default,
.dark .msg-preview-shell--default {
  background: #1f1f1f;
}

html.dark .msg-preview-shell--dt,
.dark .msg-preview-shell--dt {
  background: #16201a;
}

html.dark .msg-preview-shell--fs,
.dark .msg-preview-shell--fs {
  background: #161d2e;
}

html.dark .bubble,
.dark .bubble {
  background: #2a2a2a !important;
  color: rgba(255, 255, 255, 0.88) !important;
  border-color: #3a3a3a !important;
}

html.dark .bubble--card,
.dark .bubble--card,
html.dark .bubble--file,
.dark .bubble--file {
  background: #2a2a2a !important;
}

html.dark .card-title,
.dark .card-title,
html.dark .tc-emphasis-title,
.dark .tc-emphasis-title,
html.dark .tc-list-val,
.dark .tc-list-val,
html.dark .file-name,
.dark .file-name {
  color: rgba(255, 255, 255, 0.92);
}

html.dark .card-desc,
.dark .card-desc,
html.dark .tc-desc,
.dark .tc-desc,
html.dark .file-meta,
.dark .file-meta {
  color: rgba(255, 255, 255, 0.55);
}

html.dark .tc-list-key,
.dark .tc-list-key,
html.dark .fs-note,
.dark .fs-note,
html.dark .tc-source,
.dark .tc-source,
html.dark .tc-emphasis-desc,
.dark .tc-emphasis-desc {
  color: rgba(255, 255, 255, 0.45);
}

html.dark .tc-sub,
.dark .tc-sub {
  color: rgba(255, 255, 255, 0.75);
}

html.dark .msg-type-badge,
.dark .msg-type-badge {
  background: rgba(255, 255, 255, 0.1);
  color: rgba(255, 255, 255, 0.65);
}

html.dark .fs-hr,
.dark .fs-hr {
  background: #3a3a3a;
}

html.dark .tc-jump,
.dark .tc-jump,
html.dark .btn-primary,
.dark .btn-primary,
html.dark .btn-default,
.dark .btn-default {
  border-color: #3a3a3a !important;
}

html.dark .tc-jump-row,
.dark .tc-jump-row {
  border-bottom-color: #3a3a3a;
}

html.dark .raw-json,
.dark .raw-json {
  background: #1f1f1f;
  color: rgba(255, 255, 255, 0.65);
}

html.dark .empty-tip,
.dark .empty-tip {
  color: rgba(255, 255, 255, 0.35);
}

html.dark .feed-row,
.dark .feed-row {
  border-bottom-color: #3a3a3a;
}

/* 钉钉 markdown 在暗色下用浅绿，与暗底对比 */
html.dark .msg-preview-body--dt .bubble--markdown,
.dark .msg-preview-body--dt .bubble--markdown {
  color: #69db94;
}
</style>
