<script setup lang="ts">
import type {
  ChatDocumentAttachment,
  ChatDocumentLoader,
  ChatImageAttachment,
  ChatImageLoader,
} from '@easyflow/common-ui';

import { computed, ref } from 'vue';

import {
  ChatDocumentAttachments,
  ChatImageAttachments,
} from '@easyflow/common-ui';

import { Paperclip, Promotion } from '@element-plus/icons-vue';
import { ElButton, ElInput, ElMessage } from 'element-plus';

const props = withDefaults(
  defineProps<{
    documentLoader?: ChatDocumentLoader;
    documents?: ChatDocumentAttachment[];
    imageEnabled?: boolean;
    imageLoader?: ChatImageLoader;
    images?: ChatImageAttachment[];
    loading?: boolean;
    modelValue?: string;
    placeholder?: string;
  }>(),
  {
    documentLoader: undefined,
    documents: () => [],
    imageEnabled: true,
    imageLoader: undefined,
    images: () => [],
    modelValue: '',
    placeholder: '',
  },
);

const emit = defineEmits<{
  addDocumentFiles: [files: File[]];
  addFiles: [files: File[]];
  removeDocument: [item: ChatDocumentAttachment];
  removeImage: [item: ChatImageAttachment];
  retryDocument: [item: ChatDocumentAttachment];
  retryImage: [item: ChatImageAttachment];
  send: [text: string];
  stop: [];
  'update:modelValue': [value: string];
}>();

const text = computed({
  get: () => props.modelValue || '',
  set: (value: string) => emit('update:modelValue', value),
});
const fileInput = ref<HTMLInputElement>();
const dragActive = ref(false);
const hasReadyImage = computed(() =>
  (props.images || []).some((item) => item.status === 'ready'),
);
const hasPendingImage = computed(() =>
  (props.images || []).some((item) => item.status !== 'ready'),
);
const hasReadyDocument = computed(() =>
  (props.documents || []).some((item) => item.status === 'ready'),
);
const hasPendingDocument = computed(() =>
  (props.documents || []).some((item) => item.status !== 'ready'),
);
const canSend = computed(
  () =>
    (text.value.trim().length > 0 ||
      hasReadyImage.value ||
      hasReadyDocument.value) &&
    (!hasReadyImage.value || props.imageEnabled !== false) &&
    !hasPendingImage.value &&
    !hasPendingDocument.value &&
    !props.loading,
);
const fileAccept = computed(() => {
  const documents =
    '.pdf,.doc,.docx,.ppt,.pptx,.xls,.xlsx,.txt,.md,application/pdf,text/plain,text/markdown';
  if (props.imageEnabled === false) return documents;
  return `${documents},.png,.jpg,.jpeg,.webp,.gif,.bmp,image/png,image/jpeg,image/webp,image/gif,image/bmp`;
});

function send() {
  const value = text.value.trim();
  if (
    (!value && !hasReadyImage.value && !hasReadyDocument.value) ||
    props.loading ||
    hasPendingImage.value ||
    hasPendingDocument.value
  )
    return;
  emit('send', value);
}

function chooseFiles() {
  fileInput.value?.click();
}

function handleFiles(event: Event) {
  const target = event.target as HTMLInputElement;
  const files = [...(target.files || [])];
  const imageFiles = files.filter((file) => file.type.startsWith('image/'));
  const documentFiles = files.filter((file) => !file.type.startsWith('image/'));
  if (imageFiles.length > 0 && props.imageEnabled !== false) {
    emit('addFiles', imageFiles);
  }
  if (documentFiles.length > 0) {
    emit('addDocumentFiles', documentFiles);
  }
  target.value = '';
}

function handlePaste(event: ClipboardEvent) {
  if (props.imageEnabled === false) return;
  const files = [...(event.clipboardData?.files || [])].filter((file) =>
    file.type.startsWith('image/'),
  );
  if (files.length === 0) return;
  event.preventDefault();
  emit('addFiles', files);
  ElMessage.success(
    files.length === 1 ? '已粘贴图片' : `已粘贴 ${files.length} 张图片`,
  );
}

function handleDragEnter(event: DragEvent) {
  if (
    !props.loading &&
    ((props.images?.length || 0) < 5 || (props.documents?.length || 0) < 3) &&
    [...(event.dataTransfer?.types || [])].includes('Files')
  ) {
    dragActive.value = true;
  }
}

function handleDragLeave(event: DragEvent) {
  const container = event.currentTarget as HTMLElement;
  if (
    !(event.relatedTarget instanceof Node) ||
    !container.contains(event.relatedTarget)
  ) {
    dragActive.value = false;
  }
}

function handleDrop(event: DragEvent) {
  dragActive.value = false;
  if (
    props.loading ||
    ((props.images?.length || 0) >= 5 && (props.documents?.length || 0) >= 3)
  )
    return;
  const files = [...(event.dataTransfer?.files || [])];
  const imageFiles = files.filter((file) => file.type.startsWith('image/'));
  const documentFiles = files.filter((file) => !file.type.startsWith('image/'));
  if (imageFiles.length > 0 && props.imageEnabled !== false) {
    emit('addFiles', imageFiles);
  }
  if (documentFiles.length > 0) {
    emit('addDocumentFiles', documentFiles);
  }
}

function stop() {
  if (!props.loading) return;
  emit('stop');
}

function handleKeydown(event: Event | KeyboardEvent) {
  if (!(event instanceof KeyboardEvent)) return;
  if (event.key !== 'Enter' || event.shiftKey) return;
  event.preventDefault();
  send();
}
</script>

<template>
  <div
    class="ai-prompt-input"
    :class="{ 'is-dragging': dragActive }"
    @dragenter.prevent="handleDragEnter"
    @dragover.prevent
    @dragleave.prevent="handleDragLeave"
    @drop.prevent="handleDrop"
  >
    <ChatImageAttachments
      v-if="images?.length"
      class="ai-prompt-input__images"
      :items="images"
      :image-loader="imageLoader"
      removable
      retryable
      @remove="emit('removeImage', $event)"
      @retry="emit('retryImage', $event)"
    />
    <ChatDocumentAttachments
      v-if="documents?.length"
      class="ai-prompt-input__documents"
      :items="documents"
      :document-loader="documentLoader"
      removable
      retryable
      @remove="emit('removeDocument', $event)"
      @retry="emit('retryDocument', $event)"
    />
    <ElInput
      v-model="text"
      class="ai-prompt-input__textarea"
      type="textarea"
      resize="none"
      :autosize="{ minRows: 1, maxRows: 5 }"
      :disabled="loading"
      :placeholder="placeholder || '输入消息'"
      @paste="handlePaste"
      @keydown="handleKeydown"
    />
    <input
      ref="fileInput"
      class="ai-prompt-input__file"
      type="file"
      :accept="fileAccept"
      multiple
      @change="handleFiles"
    />
    <ElButton
      :icon="Paperclip"
      circle
      text
      :disabled="
        loading || ((images?.length || 0) >= 5 && (documents?.length || 0) >= 3)
      "
      aria-label="添加附件"
      title="添加附件"
      class="ai-prompt-input__attach"
      @click="chooseFiles"
    />
    <ElButton
      v-if="loading"
      type="primary"
      circle
      aria-label="中止"
      title="中止"
      class="ai-prompt-input__action is-stop"
      @click="stop"
    />
    <ElButton
      v-else
      type="primary"
      circle
      :icon="Promotion"
      :disabled="!canSend"
      aria-label="发送"
      class="ai-prompt-input__action"
      @click="send"
    />
  </div>
</template>

<style scoped>
.ai-prompt-input {
  position: relative;
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  align-items: flex-end;
  padding: 8px;
  margin: 0 16px 16px;
  background: var(--el-bg-color);
  background: color-mix(in srgb, var(--el-bg-color) 92%, transparent);
  border: 1px solid var(--el-border-color-lighter);
  border-radius: 8px;
  box-shadow: var(--el-box-shadow-lighter);
}

.ai-prompt-input.is-dragging {
  background: var(--el-color-primary-light-9);
  border-color: var(--el-color-primary-light-5);
}

.ai-prompt-input__images {
  flex: 0 0 100%;
}

.ai-prompt-input__documents {
  flex: 0 0 100%;
}

.ai-prompt-input__file {
  display: none;
}

.ai-prompt-input__attach {
  width: 36px;
  height: 36px;
}

.ai-prompt-input__textarea {
  flex: 1;
}

.ai-prompt-input__textarea :deep(.el-textarea__inner) {
  min-height: 36px !important;
  padding: 8px 10px;
  line-height: 20px;
  background: transparent;
  border: none;
  box-shadow: none;
}

.ai-prompt-input__action {
  width: 36px;
  height: 36px;
}

.ai-prompt-input__action.is-stop::before {
  display: block;
  width: 12px;
  height: 12px;
  content: '';
  background: var(--el-color-white);
  border-radius: 2px;
}
</style>
