<script setup lang="ts">
//! 单条「用户」消息渲染 — 从 MessageList.vue 提取,降低 God 文件复杂度
//!
//! 职责(单一,仅 user 角色):渲染用户消息气泡,
//! 含 Mention chip + 图片附件。
//!
//! 设计说明:assistant / system / error 三角色由 MessageList 内联渲染
//! (含流式分块、工具卡片、token 用量、错误重试、压缩摘要置顶等聚合逻辑),
//! 本组件专注 user,不做 AI 消息渲染,避免两套渲染入口并存导致的死代码与职责混乱。

import { useI18n } from 'vue-i18n'
import type { AiMessage, ContentPart, MentionSpan } from '../../api/types'

const { t } = useI18n()

defineProps<{
  msg: AiMessage
}>()

const emit = defineEmits<{
  (e: 'copy', msg: AiMessage): void
}>()

// ── Input Augmentation: 按 mention 区间切段 ──
type UserContentSegment =
  | { type: 'text'; text: string }
  | { type: 'chip'; span: MentionSpan }

function segmentUserContent(msg: AiMessage): UserContentSegment[] {
  const content = msg.content
  const spans = msg.mentionSpans
  if (!spans || spans.length === 0) {
    return [{ type: 'text', text: content }]
  }
  const sorted = [...spans].sort((a, b) => a.start - b.start)
  const segments: UserContentSegment[] = []
  let cursor = 0
  for (const span of sorted) {
    const start = span.start
    const end = start + span.length
    if (start < 0 || end > content.length) continue
    if (start < cursor) continue
    if (content.slice(start, end) !== span.label) continue
    if (start > cursor) {
      segments.push({ type: 'text', text: content.slice(cursor, start) })
    }
    segments.push({ type: 'chip', span })
    cursor = end
  }
  if (cursor < content.length) {
    segments.push({ type: 'text', text: content.slice(cursor) })
  }
  if (segments.length === 0 && content.length > 0) {
    return [{ type: 'text', text: content }]
  }
  return segments
}

// ── ContentPart 图片处理 ──
type ImageContentPart = Extract<ContentPart, { type: 'image' }>
function msgImageParts(msg: AiMessage): ImageContentPart[] {
  if (!msg.parts) return []
  return msg.parts.filter((p): p is ImageContentPart => p.type === 'image')
}
function imageSrc(img: ImageContentPart): string {
  if (img.base64) {
    const mt = img.media_type || 'image/png'
    return `data:${mt};base64,${img.base64}`
  }
  return img.url || ''
}
function imgAlt(img: ImageContentPart): string {
  return img.alt || t('aiChat.imagePreviewAlt')
}
</script>

<template>
  <div
    class="ai-msg"
    :class="'ai-msg--' + msg.role"
    :data-msg-id="msg.id"
  >
    <!-- 用户消息(本组件唯一渲染角色;assistant/system/error 由 MessageList 内联渲染) -->
    <div class="ai-msg-user">
      <div class="ai-msg-avatar ai-msg-avatar--user">
        <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M20 21v-2a4 4 0 00-4-4H8a4 4 0 00-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>
      </div>
      <div class="ai-msg-bubble ai-msg-bubble--user">
        <template v-for="(seg, idx) in segmentUserContent(msg)" :key="msg.id + '-seg-' + idx">
          <span v-if="seg.type === 'text'">{{ seg.text }}</span>
          <span v-else class="ai-msg-chip" :class="'ai-msg-chip--' + seg.span.kind">{{ seg.span.label }}</span>
        </template>
        <template v-if="msgImageParts(msg).length">
          <div class="ai-msg-images">
            <img v-for="(img, idx) in msgImageParts(msg)" :key="msg.id + '-img-' + idx" :src="imageSrc(img)" :alt="imgAlt(img)" class="ai-msg-image" loading="lazy" />
          </div>
        </template>
        <button class="ai-copy-btn" :title="t('aiChat.copyMsg')" @click.stop="emit('copy', msg)">
          <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/></svg>
        </button>
      </div>
    </div>
  </div>
</template>

<style scoped>
/* 用户消息:头像在右(整条靠右) */
.ai-msg-user {
  display: flex;
  gap: 8px;
  align-items: flex-start;
  flex-direction: row-reverse;
}
/* 头像通用 */
.ai-msg-avatar {
  width: 22px;
  height: 22px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}
.ai-msg-avatar--user {
  background: var(--df-info-bg, rgba(100,181,246,0.15));
  color: var(--df-info, #64b5f6);
}
/* 气泡通用 */
.ai-msg-bubble {
  border-radius: 10px;
  padding: 8px 12px;
  word-break: break-word;
  max-width: 80%;
}
.ai-msg-bubble--user {
  background: var(--df-info-bg, rgba(100,181,246,0.1));
  color: var(--df-text);
}
/* chip */
.ai-msg-chip {
  display: inline-block;
  padding: 1px 6px;
  border-radius: 4px;
  font-size: 11px;
  margin: 0 2px;
  background: var(--df-accent-bg, rgba(108,99,255,0.1));
  color: var(--df-accent, #6c63ff);
}
/* 图片 */
.ai-msg-images {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  margin-top: 4px;
}
.ai-msg-image {
  max-width: 200px;
  max-height: 200px;
  border-radius: 6px;
}
/* 复制按钮(hover 显示) */
.ai-copy-btn {
  opacity: 0;
  transition: opacity 0.15s;
  background: none;
  border: none;
  cursor: pointer;
  padding: 2px;
  color: var(--df-text-dim);
}
.ai-msg-user:hover .ai-copy-btn {
  opacity: 1;
}
</style>
