<script setup lang="ts">
import type {
  ChatTimelineItem,
  ChatImageLoader,
  ChatTimelineMessageItem,
  ChatTimelineMessagePart,
  ChatTimelineToolApprovalPayload,
} from './types';

import { computed } from 'vue';

import ChatThinkingBlock from '../chat-thinking/ChatThinkingBlock.vue';
import ChatErrorNotice from './ChatErrorNotice.vue';
import ChatKnowledgeCard from './ChatKnowledgeCard.vue';
import ChatImageAttachments from './ChatImageAttachments.vue';
import ChatMessageToolbar from './ChatMessageToolbar.vue';
import ChatTextBlock from './ChatTextBlock.vue';
import ChatTimelineStatusRow from './ChatTimelineStatusRow.vue';
import ChatToolCard from './ChatToolCard.vue';

const props = defineProps<{
  approvalLoading?: boolean;
  copyable?: boolean;
  copyAction?: (item: ChatTimelineMessageItem) => boolean | Promise<boolean>;
  regenerable?: boolean;
  regenerateDisabled?: boolean;
  assistantActionsVisible?: boolean;
  imageLoader?: ChatImageLoader;
  item: ChatTimelineItem;
  variantLoading?: boolean;
}>();

const emit = defineEmits<{
  approve: [payload: ChatTimelineToolApprovalPayload];
  copyMessage: [item: ChatTimelineMessageItem];
  regenerateMessage: [item: ChatTimelineMessageItem];
  reject: [payload: ChatTimelineToolApprovalPayload];
  selectNextVariant: [item: ChatTimelineMessageItem];
  selectPreviousVariant: [item: ChatTimelineMessageItem];
  thinkingToggle: [];
}>();

const messageItem = computed(() =>
  props.item.type === 'message' ? props.item : undefined,
);

const alignmentClass = computed(() => {
  if (props.item.type === 'message' && props.item.role === 'user') {
    return 'is-user';
  }
  return 'is-assistant';
});

const variantCurrent = computed(() => {
  const item = messageItem.value;
  return Number(item?.selectedVariantIndex || item?.variantIndex || 1);
});

const variantTotal = computed(() =>
  Number(messageItem.value?.variantCount || 1),
);

const showVariantNavigator = computed(() => {
  const item = messageItem.value;
  return Boolean(
    item?.role === 'assistant' &&
      item.roundCompleted &&
      item.roundId &&
      item.switchable !== false &&
      variantTotal.value > 1,
  );
});

const disabledVariantPrevious = computed(
  () => props.variantLoading || variantCurrent.value <= 1,
);

const disabledVariantNext = computed(
  () => props.variantLoading || variantCurrent.value >= variantTotal.value,
);

const showToolbar = computed(() => {
  const item = messageItem.value;
  return Boolean(
    item &&
      (item.role === 'assistant'
        ? (props.assistantActionsVisible ?? true) &&
          (props.copyable || props.regenerable || showVariantNavigator.value)
        : props.copyable),
  );
});

function getMessageParts(item: ChatTimelineMessageItem) {
  const textParts = item.parts.filter(
    (part): part is Extract<ChatTimelineMessagePart, { type: 'text' }> =>
      part.type === 'text',
  );
  if (textParts.length <= 1) {
    return item.parts;
  }
  return [
    ...item.parts.filter((part) => part.type !== 'text'),
    {
      content: textParts.map((part) => part.content).join(''),
      id: `${item.id}-merged-text`,
      type: 'text' as const,
    },
  ];
}

function updateThinkingExpanded(partId: string, expanded: boolean) {
  const item = messageItem.value;
  if (!item) {
    return;
  }
  const part = item.parts.find((current) => current.id === partId);
  if (part?.type === 'thinking') {
    emit('thinkingToggle');
    part.expanded = expanded;
  }
}

function handleCopyAction() {
  const item = messageItem.value;
  if (!item || !props.copyAction) {
    return false;
  }
  return props.copyAction(item);
}
</script>

<template>
  <div class="chat-timeline-item" :class="alignmentClass">
    <div
      v-if="messageItem"
      class="chat-timeline-item__message"
      :class="[
        `is-${messageItem.role}`,
        { 'has-variant-navigator': showVariantNavigator },
      ]"
    >
      <ChatImageAttachments
        v-if="messageItem.images?.length"
        :items="messageItem.images"
        :image-loader="imageLoader"
        compact
      />
      <template v-for="part in getMessageParts(messageItem)" :key="part.id">
        <ChatThinkingBlock
          v-if="part.type === 'thinking'"
          :content="part.content"
          :expanded="part.expanded ?? part.status === 'thinking'"
          :status="part.status"
          class="chat-timeline-item__thinking"
          readonly
          @update:expanded="updateThinkingExpanded(part.id, $event)"
        />
        <ChatTextBlock
          v-else
          :content="part.content"
          :streaming="messageItem.status === 'streaming'"
        />
      </template>
      <ChatKnowledgeCard
        v-if="messageItem.knowledgeItems?.length"
        :items="messageItem.knowledgeItems"
      />
      <ChatMessageToolbar
        v-if="showToolbar && messageItem"
        :align="messageItem.role === 'user' ? 'end' : 'start'"
        :allow-copy="copyable"
        :allow-regenerate="regenerable"
        :copy-action="copyAction ? handleCopyAction : undefined"
        :disabled-variant-next="disabledVariantNext"
        :disabled-variant-previous="disabledVariantPrevious"
        :regenerate-disabled="regenerateDisabled"
        :show-variant-navigator="showVariantNavigator"
        :variant-current="variantCurrent"
        :variant-loading="variantLoading"
        :variant-total="variantTotal"
        @copy="emit('copyMessage', messageItem)"
        @regenerate="emit('regenerateMessage', messageItem)"
        @select-next-variant="emit('selectNextVariant', messageItem)"
        @select-previous-variant="emit('selectPreviousVariant', messageItem)"
      />
    </div>
    <ChatToolCard
      v-else-if="item.type === 'tool'"
      :item="item"
      :loading="approvalLoading"
      @approve="emit('approve', $event)"
      @reject="emit('reject', $event)"
    />
    <ChatKnowledgeCard
      v-else-if="item.type === 'knowledge'"
      :items="item.items"
    />
    <ChatTimelineStatusRow v-else-if="item.type === 'status'" :item="item" />
    <ChatErrorNotice
      v-else-if="item.type === 'error'"
      :message="item.message"
    />
  </div>
</template>

<style scoped>
.chat-timeline-item {
  display: flex;
  width: 100%;
}

.chat-timeline-item.is-user {
  justify-content: flex-end;
}

.chat-timeline-item.is-assistant {
  justify-content: flex-start;
}

.chat-timeline-item__bubble {
  display: flex;
  flex-direction: column;
  gap: 8px;
  max-width: min(78%, 680px);
  min-width: 0;
}

.chat-timeline-item__message {
  display: flex;
  flex-direction: column;
  gap: 8px;
  max-width: min(78%, 680px);
  min-width: 0;
}

.chat-timeline-item__message :deep(.chat-text-block),
.chat-timeline-item__message :deep(.chat-thinking-block) {
  padding: 0;
  color: var(--el-text-color-primary);
  background: transparent;
  border: 0;
}

.chat-timeline-item__message :deep(.chat-text-block) {
  font-size: 14px;
  line-height: 22px;
}

.chat-timeline-item__message.is-user {
  color: var(--el-text-color-primary);
}

.chat-timeline-item__message.is-assistant.has-variant-navigator {
  width: min(78%, 680px);
}

.chat-timeline-item__bubble.is-assistant,
.chat-timeline-item__bubble.is-system {
  color: var(--el-text-color-primary);
}

.chat-timeline-item__thinking {
  margin-bottom: 0;
}
</style>
