<script setup lang="ts">
import type {
  ChatTimeAssistantSegment,
  ChatTimeAssistantTextSegment,
  ChatTimeTimelineItem,
} from '@easyflow/types';

import {computed, ref} from 'vue';

import {ChatThinkingBlock, ChatTimeMarkdown} from '@easyflow/common-ui';
import {IconifyIcon} from '@easyflow/icons';
import {useUserStore} from '@easyflow/stores';

import {
  ArrowLeft,
  ArrowRight,
  CircleCheck,
  CopyDocument,
  Loading,
  RefreshRight,
} from '@element-plus/icons-vue';
import {ElAvatar, ElIcon, ElMessage} from 'element-plus';

import defaultAssistantAvatar from '#/assets/defaultAssistantAvatar.svg';
import defaultUserAvatar from '#/assets/defaultUserAvatar.png';
import ShowJson from '#/components/json/ShowJson.vue';

interface Props {
  allowRegenerate?: boolean;
  allowVariantSwitch?: boolean;
  bot: any;
  messages: ChatTimeTimelineItem[];
  regenerateDisabled?: boolean;
  switchingRoundIds?: string[];
  variantSwitchDisabled?: boolean;
}
const props = withDefaults(defineProps<Props>(), {
  allowRegenerate: false,
  allowVariantSwitch: false,
  regenerateDisabled: false,
  switchingRoundIds: () => [],
  variantSwitchDisabled: false,
});
const store = useUserStore();
const expandedToolState = ref<Record<string, boolean>>({});
const latestAssistantMessageId = computed(() => {
  return [...props.messages].reverse().find((item) => item.role === 'assistant')?.id || '';
});

const emit = defineEmits<{
  regenerate: [item: ChatTimeTimelineItem];
  selectNextVariant: [item: ChatTimeTimelineItem];
  selectPreviousVariant: [item: ChatTimeTimelineItem];
}>();

function getAssistantAvatar() {
  return props.bot.icon || defaultAssistantAvatar;
}
function getUserAvatar() {
  return store.userInfo?.avatar || defaultUserAvatar;
}

function formatTime(value: number | string) {
  if (typeof value === 'number') {
    return new Date(value).toLocaleString();
  }
  return value || '';
}

function getToolName(item: ChatTimeTimelineItem) {
  return item.role === 'tool' && item.name ? item.name : '工具调用';
}

function hasToolDetails(item: ChatTimeTimelineItem) {
  return item.role === 'tool' && Boolean(item.arguments || item.result);
}

function isToolExpanded(item: ChatTimeTimelineItem) {
  return item.role === 'tool' && Boolean(expandedToolState.value[item.id]);
}

function toggleToolExpanded(item: ChatTimeTimelineItem) {
  if (item.role !== 'tool' || !hasToolDetails(item)) {
    return;
  }
  expandedToolState.value = {
    ...expandedToolState.value,
    [item.id]: !expandedToolState.value[item.id],
  };
}

function getRenderableSegments(item: ChatTimeTimelineItem) {
  if (item.role !== 'assistant') {
    return [] as ChatTimeAssistantSegment[];
  }
  const textSegments = item.segments.filter(
    (segment): segment is ChatTimeAssistantTextSegment =>
      segment.type === 'text',
  );
  if (textSegments.length <= 1) {
    return item.segments;
  }
  return [
    ...item.segments.filter((segment) => segment.type !== 'text'),
    {
      content: textSegments.map((segment) => segment.content).join(''),
      id: `${item.id}-merged-text`,
      type: 'text' as const,
    },
  ];
}

function canCopy(item: ChatTimeTimelineItem) {
  return item.role !== 'tool' && Boolean(String(item.content || '').trim());
}

function canRegenerate(item: ChatTimeTimelineItem) {
  return (
    props.allowRegenerate &&
    item.role === 'assistant' &&
    item.id === latestAssistantMessageId.value
  );
}

function shouldShowVariantNavigator(item: ChatTimeTimelineItem) {
  return (
    props.allowVariantSwitch &&
    item.role === 'assistant' &&
    isFinalAssistantInRound(item) &&
    Number(item.variantCount || 0) > 1 &&
    Boolean(item.roundId)
  );
}

function canSwitchVariant(
  item: ChatTimeTimelineItem,
  direction: 'next' | 'previous',
) {
  if (
    item.role !== 'assistant' ||
    !isFinalAssistantInRound(item) ||
    props.variantSwitchDisabled ||
    isVariantSwitching(item) ||
    !item.switchable
  ) {
    return false;
  }
  const current = Number(item.variantIndex || item.selectedVariantIndex || 1);
  const total = Number(item.variantCount || 1);
  if (direction === 'previous') {
    return current > 1;
  }
  return current < total;
}

function isVariantSwitching(item: ChatTimeTimelineItem) {
  return Boolean(
    item.roundId && props.switchingRoundIds.includes(String(item.roundId)),
  );
}

function isFinalAssistantInRound(item: ChatTimeTimelineItem) {
  if (item.role !== 'assistant') {
    return false;
  }
  if (!item.roundId) {
    return true;
  }
  for (let index = props.messages.length - 1; index >= 0; index -= 1) {
    const candidate = props.messages[index];
    if (
      candidate?.role === 'assistant' &&
      candidate.roundId === item.roundId &&
      String(candidate.variantIndex || '') === String(item.variantIndex || '')
    ) {
      return candidate.id === item.id;
    }
  }
  return true;
}

async function handleCopy(item: ChatTimeTimelineItem) {
  if (!canCopy(item)) {
    return;
  }
  try {
    await navigator.clipboard.writeText(String(item.content || ''));
    ElMessage.success('已复制');
  } catch {
    ElMessage.error('复制失败');
  }
}
</script>

<template>
  <ElBubbleList :list="messages" max-height="calc(100vh - 345px)">
    <!-- 自定义头像 -->
    <template #avatar="{ item }">
      <ElAvatar
        :src="item.role === 'user' ? getUserAvatar() : getAssistantAvatar()"
        :size="40"
      />
    </template>

    <!-- 自定义头部 -->
    <template #header="{ item }">
      <div class="flex flex-col">
        <span class="text-foreground/50 text-xs">
          {{ formatTime(item.created) }}
        </span>
      </div>
    </template>

    <!-- 自定义气泡内容 -->
    <template #content="{ item }">
      <template v-if="item.role === 'assistant'">
        <div class="flex flex-col gap-2">
          <template v-for="segment in getRenderableSegments(item)" :key="segment.id">
            <ChatThinkingBlock
              v-if="segment.type === 'thinking'"
              v-model:expanded="segment.expanded"
              :content="segment.content"
              :status="segment.status"
              class="chat-thinking-block-item"
            />
            <ChatTimeMarkdown
              v-else
              :content="segment.content"
              :streaming="item.typing"
            />
          </template>
        </div>
      </template>
      <template v-else-if="item.role === 'tool'">
        <div class="chat-tool-panel" :class="{ 'is-expanded': isToolExpanded(item) }">
          <button
            type="button"
            class="chat-tool-header"
            :class="{ 'is-clickable': hasToolDetails(item) }"
            @click="toggleToolExpanded(item)"
          >
            <div class="chat-tool-title">
              <ElIcon class="chat-tool-title-icon" size="14" aria-hidden="true">
                <IconifyIcon icon="mdi:hammer" />
              </ElIcon>
              <span class="chat-tool-title-text">{{ getToolName(item) }}</span>
            </div>
            <div class="chat-tool-meta">
              <span
                class="chat-tool-status"
                :class="item.status === 'TOOL_CALL' ? 'is-calling' : 'is-done'"
              >
                <ElIcon size="13">
                  <IconifyIcon
                    v-if="item.status === 'TOOL_CALL'"
                    icon="mdi:clock-time-five-outline"
                  />
                  <CircleCheck v-else />
                </ElIcon>
                <span>{{ item.status === 'TOOL_CALL' ? '调用中' : '已完成' }}</span>
              </span>
              <ElIcon
                v-if="hasToolDetails(item)"
                size="14"
                class="chat-tool-arrow"
                :class="{ 'is-expanded': isToolExpanded(item) }"
              >
                <IconifyIcon icon="ep:arrow-right" />
              </ElIcon>
            </div>
          </button>
          <div v-if="isToolExpanded(item) && hasToolDetails(item)" class="chat-tool-body">
            <ShowJson
              v-if="item.arguments"
              :value="item.arguments"
              plain
            />
            <ShowJson
              v-if="item.result"
              :value="item.result"
              plain
            />
          </div>
        </div>
      </template>
      <ChatTimeMarkdown
        v-else
        :content="item.content"
        :streaming="item.typing"
      />
    </template>

    <template #footer="{ item }">
      <div
        v-if="canCopy(item) || canRegenerate(item) || shouldShowVariantNavigator(item)"
        class="chat-message-actions"
        :class="{ 'is-user': item.role === 'user' }"
      >
        <div
          v-if="shouldShowVariantNavigator(item)"
          class="chat-message-actions__variant"
        >
          <button
            type="button"
            class="chat-message-actions__button"
            :disabled="!canSwitchVariant(item, 'previous')"
            aria-label="查看上一版答案"
            @click="emit('selectPreviousVariant', item)"
          >
            <Loading v-if="isVariantSwitching(item)" class="is-loading" />
            <ArrowLeft v-else />
          </button>
          <span class="chat-message-actions__variant-label">
            {{ Number(item.variantIndex || item.selectedVariantIndex || 1) }}/{{
              Number(item.variantCount || 1)
            }}
          </span>
          <button
            type="button"
            class="chat-message-actions__button"
            :disabled="!canSwitchVariant(item, 'next')"
            aria-label="查看下一版答案"
            @click="emit('selectNextVariant', item)"
          >
            <Loading v-if="isVariantSwitching(item)" class="is-loading" />
            <ArrowRight v-else />
          </button>
        </div>

        <button
          v-if="canRegenerate(item)"
          type="button"
          class="chat-message-actions__button"
          :disabled="regenerateDisabled"
          aria-label="重新生成"
          @click="emit('regenerate', item)"
        >
          <RefreshRight />
        </button>

        <button
          v-if="canCopy(item)"
          type="button"
          class="chat-message-actions__button"
          aria-label="复制消息"
          @click="handleCopy(item)"
        >
          <CopyDocument />
        </button>
      </div>
    </template>
  </ElBubbleList>
</template>

<style lang="css" scoped>
:deep(.el-bubble-header) {
  width: 100%;
}

:deep(.el-bubble-end .el-bubble-header) {
  width: fit-content;
}

:deep(.el-bubble-content-wrapper .el-bubble-content) {
  --bubble-content-max-width: 100%;
}

.chat-thinking-block-item {
  margin-bottom: 0;
}

.chat-tool-panel {
  min-width: 0;
  background: hsl(var(--surface-panel) / 0.98);
  border: 1px solid hsl(var(--divider-faint) / 0.16);
  border-radius: 10px;
  box-shadow: none;
}

.chat-tool-header {
  display: flex;
  gap: 8px;
  align-items: center;
  justify-content: space-between;
  width: 100%;
  min-height: 34px;
  padding: 0 10px;
  text-align: left;
  background: transparent;
  border: none;
}

.chat-tool-header.is-clickable {
  cursor: pointer;
}

.chat-tool-title {
  display: inline-flex;
  gap: 6px;
  align-items: center;
  min-width: 0;
  color: hsl(var(--text-strong));
}

.chat-tool-title-icon {
  flex-shrink: 0;
  color: hsl(var(--primary));
}

.chat-tool-title-text {
  overflow: hidden;
  font-size: 12px;
  font-weight: 600;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.chat-tool-meta {
  display: inline-flex;
  flex-shrink: 0;
  gap: 6px;
  align-items: center;
}

.chat-tool-status {
  display: inline-flex;
  flex-shrink: 0;
  gap: 4px;
  align-items: center;
  padding: 1px 8px;
  font-size: 11px;
  line-height: 1.4;
  border: 1px solid transparent;
  border-radius: 999px;
}

.chat-tool-status.is-calling {
  color: hsl(var(--primary));
  background: hsl(var(--primary) / 0.08);
  border-color: hsl(var(--primary) / 0.12);
}

.chat-tool-status.is-done {
  color: hsl(var(--success));
  background: hsl(var(--success) / 0.08);
  border-color: hsl(var(--success) / 0.12);
}

.chat-tool-arrow {
  color: hsl(var(--text-muted));
  transition: transform 0.15s ease;
}

.chat-tool-arrow.is-expanded {
  transform: rotate(90deg);
}

.chat-tool-body {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 0 10px 10px;
}

.chat-message-actions {
  display: inline-flex;
  gap: 4px;
  align-items: center;
  margin-top: 6px;
}

.chat-message-actions.is-user {
  justify-content: flex-end;
}

.chat-message-actions__variant {
  display: inline-flex;
  gap: 2px;
  align-items: center;
}

.chat-message-actions__variant-label {
  min-width: 42px;
  font-size: 11px;
  font-weight: 600;
  text-align: center;
  color: hsl(var(--text-muted));
}

.chat-message-actions__button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  padding: 0;
  color: hsl(var(--text-muted));
  cursor: pointer;
  border: none;
  border-radius: 999px;
  background: transparent;
  transition:
    color 0.18s ease,
    background-color 0.18s ease;
}

.chat-message-actions__button:hover:not(:disabled) {
  color: hsl(var(--text-strong));
  background: hsl(var(--foreground) / 0.05);
}

.chat-message-actions__button:focus-visible {
  outline: 2px solid hsl(var(--primary) / 0.32);
  outline-offset: 2px;
}

.chat-message-actions__button:disabled {
  opacity: 0.42;
  cursor: not-allowed;
}

.chat-message-actions__button :deep(svg) {
  width: 15px;
  height: 15px;
}

.chat-message-actions__button .is-loading {
  animation: chat-action-spin 0.8s linear infinite;
}

@keyframes chat-action-spin {
  to {
    transform: rotate(360deg);
  }
}
</style>
