<script setup lang="ts">
import type { ChatDocumentAttachment, ChatDocumentLoader } from './types';

import { computed, ref } from 'vue';

type DocumentVisualType =
  | 'generic'
  | 'markdown'
  | 'pdf'
  | 'presentation'
  | 'spreadsheet'
  | 'text'
  | 'word';

const props = withDefaults(
  defineProps<{
    compact?: boolean;
    documentLoader?: ChatDocumentLoader;
    items: ChatDocumentAttachment[];
    removable?: boolean;
    retryable?: boolean;
  }>(),
  {
    compact: false,
    documentLoader: undefined,
    removable: false,
    retryable: false,
  },
);

const emit = defineEmits<{
  remove: [item: ChatDocumentAttachment];
  retry: [item: ChatDocumentAttachment];
}>();

const DOCUMENT_TYPES_BY_EXTENSION: Record<string, DocumentVisualType> = {
  doc: 'word',
  docx: 'word',
  md: 'markdown',
  pdf: 'pdf',
  ppt: 'presentation',
  pptx: 'presentation',
  txt: 'text',
  xls: 'spreadsheet',
  xlsx: 'spreadsheet',
};

const DOCUMENT_TYPE_LABELS: Record<DocumentVisualType, string> = {
  generic: '文档',
  markdown: 'Markdown',
  pdf: 'PDF',
  presentation: 'PowerPoint',
  spreadsheet: 'Excel',
  text: 'TXT',
  word: 'Word',
};

const downloadingKey = ref('');
const downloadErrorKey = ref('');
const downloadErrorMessage = ref('');

const visibleItems = computed(() => props.items.filter(Boolean));

function itemKey(item: ChatDocumentAttachment) {
  return (
    item.uploadId ||
    item.attachmentRef ||
    item.localId ||
    `${item.name}-${item.size || 0}`
  );
}

function documentType(item: ChatDocumentAttachment): DocumentVisualType {
  const extension = item.name.trim().toLowerCase().split('.').pop() || '';
  const extensionType = DOCUMENT_TYPES_BY_EXTENSION[extension];
  if (extensionType) return extensionType;

  const mimeType = String(item.mimeType || '').toLowerCase();
  if (mimeType.includes('pdf')) return 'pdf';
  if (mimeType.includes('word')) return 'word';
  if (mimeType.includes('sheet') || mimeType.includes('excel')) {
    return 'spreadsheet';
  }
  if (mimeType.includes('presentation') || mimeType.includes('powerpoint')) {
    return 'presentation';
  }
  if (mimeType.includes('markdown')) return 'markdown';
  if (mimeType.startsWith('text/')) return 'text';
  return 'generic';
}

function formatSize(value?: number) {
  const bytes = Number(value || 0);
  if (bytes <= 0) return '';
  if (bytes < 1024) return `${bytes} B`;
  if (bytes < 1024 * 1024) return `${Math.ceil(bytes / 1024)} KB`;
  return `${(bytes / 1024 / 1024).toFixed(1)} MB`;
}

function stateText(item: ChatDocumentAttachment) {
  if (item.status === 'uploading') return '上传中';
  if (item.status === 'reading') return '读取中';
  if (item.status === 'error') return item.error || '读取失败';
  return [DOCUMENT_TYPE_LABELS[documentType(item)], formatSize(item.size)]
    .filter(Boolean)
    .join(' · ');
}

function displayedStateText(item: ChatDocumentAttachment) {
  const key = itemKey(item);
  if (downloadingKey.value === key) return '下载中';
  if (downloadErrorKey.value === key) return downloadErrorMessage.value;
  return stateText(item);
}

function errorMessage(error: unknown) {
  if (error instanceof Error && error.message.trim()) {
    return error.message;
  }
  return '下载失败，请重试';
}

async function download(item: ChatDocumentAttachment) {
  if (
    item.status !== 'ready' ||
    !item.downloadUrl ||
    !props.documentLoader ||
    downloadingKey.value
  ) {
    return;
  }
  const key = itemKey(item);
  downloadingKey.value = key;
  downloadErrorKey.value = '';
  downloadErrorMessage.value = '';
  try {
    await props.documentLoader(item);
  } catch (error) {
    downloadErrorKey.value = key;
    downloadErrorMessage.value = errorMessage(error);
  } finally {
    downloadingKey.value = '';
  }
}
</script>

<template>
  <div
    v-if="visibleItems.length > 0"
    class="chat-document-attachments"
    :class="{ 'is-compact': compact }"
  >
    <div
      v-for="item in visibleItems"
      :key="itemKey(item)"
      class="chat-document-attachments__item"
      :class="[
        `is-${item.status || 'ready'}`,
        { 'has-download-error': downloadErrorKey === itemKey(item) },
      ]"
      :data-document-type="documentType(item)"
    >
      <button
        type="button"
        class="chat-document-attachments__main"
        :disabled="
          item.status !== 'ready' ||
          !item.downloadUrl ||
          !documentLoader ||
          Boolean(downloadingKey)
        "
        :title="item.status === 'ready' ? `下载 ${item.name}` : stateText(item)"
        @click="download(item)"
      >
        <span
          class="chat-document-attachments__icon-box"
          :class="`is-${documentType(item)}`"
          aria-hidden="true"
        >
          <span
            v-if="
              item.status === 'uploading' ||
              item.status === 'reading' ||
              downloadingKey === itemKey(item)
            "
            class="chat-document-attachments__spinner"
          ></span>
          <svg
            v-else
            class="chat-document-attachments__icon"
            viewBox="0 0 24 24"
          >
            <g v-if="documentType(item) === 'pdf'">
              <path d="M6.5 3.5h7l4 4v13h-11v-17Zm7 0v4h4" />
              <path d="m9 17 3-7 3 7M10.1 14.5h3.8" />
            </g>
            <g v-else-if="documentType(item) === 'word'">
              <rect x="4.5" y="4.5" width="15" height="15" rx="2.5" />
              <path d="m7.5 8 2 8 2.5-5 2.5 5 2-8" />
            </g>
            <g v-else-if="documentType(item) === 'spreadsheet'">
              <rect x="4.5" y="4.5" width="15" height="15" rx="2.5" />
              <path d="M4.5 9.5h15M10 4.5v15M10 14.5h9.5" />
            </g>
            <g v-else-if="documentType(item) === 'presentation'">
              <rect x="4" y="4.5" width="16" height="13" rx="2.5" />
              <path d="M8 20h8M12 17.5V20M8 13v-3M12 13V8M16 13v-1.5" />
            </g>
            <g v-else-if="documentType(item) === 'markdown'">
              <path d="M5 16V8l3.5 3.5L12 8v8M17 8v8M14.5 13.5 17 16l2.5-2.5" />
            </g>
            <g v-else-if="documentType(item) === 'text'">
              <path d="M6 6.5h12M6 10.5h12M6 14.5h9M6 18.5h6" />
            </g>
            <g v-else>
              <path d="M6.5 3.5h7l4 4v13h-11v-17Zm7 0v4h4" />
              <path d="M9.5 12h5M9.5 15.5h5" />
            </g>
          </svg>
        </span>
        <span class="chat-document-attachments__meta">
          <span class="chat-document-attachments__name" :title="item.name">
            {{ item.name }}
          </span>
          <span class="chat-document-attachments__state">
            {{ displayedStateText(item) }}
          </span>
        </span>
      </button>
      <div
        v-if="(retryable && item.status === 'error') || removable"
        class="chat-document-attachments__actions"
      >
        <button
          v-if="retryable && item.status === 'error'"
          type="button"
          class="chat-document-attachments__action"
          aria-label="重新读取文档"
          title="重新读取"
          @click.stop="emit('retry', item)"
        >
          <svg viewBox="0 0 24 24" aria-hidden="true">
            <path d="M20 11a8 8 0 1 0-2.35 5.65M20 5v6h-6" />
          </svg>
        </button>
        <button
          v-if="removable"
          type="button"
          class="chat-document-attachments__action"
          aria-label="移除文档"
          title="移除文档"
          @click.stop="emit('remove', item)"
        >
          <svg viewBox="0 0 24 24" aria-hidden="true">
            <path d="m7 7 10 10M17 7 7 17" />
          </svg>
        </button>
      </div>
    </div>
  </div>
</template>

<style scoped>
.chat-document-attachments {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2, 8px);
  align-items: stretch;
  width: 100%;
}

.chat-document-attachments__item {
  display: flex;
  flex: 0 0 280px;
  width: 280px;
  min-width: 0;
  max-width: 100%;
  height: 56px;
  overflow: hidden;
  background: hsl(var(--surface-elevated));
  border: 0;
  border-radius: var(--radius-toolbar, 12px);
  box-shadow: inset 0 0 0 1px hsl(var(--line-subtle));
  transition:
    background-color var(--motion-duration-fast, 120ms)
      var(--motion-ease-standard, ease),
    box-shadow var(--motion-duration-fast, 120ms)
      var(--motion-ease-standard, ease);
}

.chat-document-attachments__item.is-ready:hover {
  background: hsl(var(--surface-subtle));
  box-shadow: inset 0 0 0 1px hsl(var(--border));
}

.chat-document-attachments__item.is-error,
.chat-document-attachments__item.has-download-error {
  box-shadow: inset 0 0 0 1px hsl(var(--destructive) / 46%);
}

.chat-document-attachments__main {
  display: flex;
  flex: 1;
  gap: var(--space-2, 8px);
  align-items: center;
  min-width: 0;
  padding: var(--space-2, 8px);
  color: inherit;
  text-align: left;
  cursor: pointer;
  background: transparent;
  border: 0;
}

.chat-document-attachments__main:disabled {
  cursor: default;
}

.chat-document-attachments__main:active:not(:disabled) {
  background: hsl(var(--surface-contrast-soft));
}

.chat-document-attachments__main:focus-visible,
.chat-document-attachments__action:focus-visible {
  outline: 2px solid hsl(var(--primary));
  outline-offset: -2px;
}

.chat-document-attachments__icon-box {
  display: inline-flex;
  flex: 0 0 auto;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  color: hsl(var(--document-icon-foreground));
  background: hsl(var(--document-icon-generic));
  border-radius: var(--radius-control, 10px);
}

.chat-document-attachments__icon-box.is-pdf {
  background: hsl(var(--document-icon-pdf));
}

.chat-document-attachments__icon-box.is-word {
  background: hsl(var(--document-icon-word));
}

.chat-document-attachments__icon-box.is-spreadsheet {
  background: hsl(var(--document-icon-spreadsheet));
}

.chat-document-attachments__icon-box.is-presentation {
  background: hsl(var(--document-icon-presentation));
}

.chat-document-attachments__icon-box.is-text {
  background: hsl(var(--document-icon-text));
}

.chat-document-attachments__icon-box.is-markdown {
  background: hsl(var(--document-icon-markdown));
}

.chat-document-attachments__icon {
  width: 22px;
  height: 22px;
  fill: none;
  stroke: currentcolor;
  stroke-width: 1.9;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.chat-document-attachments__spinner {
  width: 18px;
  height: 18px;
  border: 2px solid hsl(var(--document-icon-foreground) / 38%);
  border-top-color: hsl(var(--document-icon-foreground));
  border-radius: 50%;
  animation: chat-document-spin 0.9s linear infinite;
}

.chat-document-attachments__meta {
  display: flex;
  flex: 1;
  flex-direction: column;
  justify-content: center;
  min-width: 0;
}

.chat-document-attachments__name {
  overflow: hidden;
  text-overflow: ellipsis;
  font-family: var(--font-family);
  font-size: 14px;
  font-weight: 600;
  line-height: 20px;
  color: hsl(var(--text-strong));
  white-space: nowrap;
}

.chat-document-attachments__state {
  overflow: hidden;
  text-overflow: ellipsis;
  font-family: var(--font-family);
  font-size: 12px;
  font-weight: 400;
  line-height: 16px;
  color: hsl(var(--text-muted));
  white-space: nowrap;
}

.is-error .chat-document-attachments__state,
.has-download-error .chat-document-attachments__state {
  color: hsl(var(--destructive));
}

.chat-document-attachments__actions {
  display: flex;
  flex: 0 0 auto;
  gap: var(--space-1, 4px);
  align-items: center;
  padding-right: var(--space-2, 8px);
}

.chat-document-attachments__action {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  padding: 0;
  color: hsl(var(--text-muted));
  cursor: pointer;
  background: transparent;
  border: 0;
  border-radius: var(--radius-control, 10px);
}

.chat-document-attachments__action svg {
  width: 16px;
  height: 16px;
  fill: none;
  stroke: currentcolor;
  stroke-width: 1.8;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.chat-document-attachments__action:hover {
  color: hsl(var(--text-strong));
  background: hsl(var(--surface-contrast-soft));
}

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