<script setup lang="ts">
import { computed, ref, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { nanoid } from 'nanoid'
import { throttle } from 'lodash-es'
import { CloseSmall } from '@icon-park/vue-next'
import CMessage from './c-message'
import { MessageItemInterface, MultiModelDialogueItem, QuestionMessageItem } from '../types'
import { fetchEventStreamSource } from '../utils/fetch-event-stream-source'
import { UploadStatus } from '@/enums/upload-status'
import { ChannelType } from '@/enums/channel'
import { useDialogueFile } from '@/composables/useDialogueFile'
import { useUserStore } from '@/store/modules/user'
import { useUploadImage } from '@/composables/useUploadImage'

const { t } = useI18n()

interface Props {
  agentId: string
  isAnswerResponseWait: boolean
  isEnableDocumentParse: boolean
  isEnableUploadImage: boolean
}

const props = defineProps<Props>()

const emit = defineEmits<{
  addQuestionMessageItem: [messageId: string, messageItem: MessageItemInterface]
  addAnswerMessageItem: [messageId: string, messageItem: MessageItemInterface, index: number]
  updateMessageItem: [messageId: string, messageItem: Partial<MessageItemInterface>, index: number]
  deleteMessageItem: [messageId: string, index: number]
  messageListScrollToBottom: [autoScrollBottom?: boolean]
  clearAllMessage: []
}>()

const userStore = useUserStore()

const { uploadFileList, handleLimitUpload, handleUpload, handleRemoveFile } = useDialogueFile()
const { uploadImageList, handleLimitUploadImage, handleUploadImage, handleRemoveUploadImage } = useUploadImage()

const multiModelDialogueList = defineModel<MultiModelDialogueItem[]>('multiModelDialogueList', { required: true })

const questionContent = ref('')

const isQuestionSubmitDisabled = computed(() => {
  return questionContent.value.trim().length === 0
})

const isAllSelectedModelName = computed(() => {
  return multiModelDialogueList.value.every((modelItem) => modelItem.modelNickName)
})

const isAllowClearAllMessage = computed(() => {
  return multiModelDialogueList.value.some((modelItem) => modelItem.messageList.size > 0)
})

const isInputMessageDisabled = computed(() => {
  return (
    uploadFileList.value.some((fileItem) => fileItem.status !== UploadStatus.FINISHED) ||
    uploadImageList.value.some((imageItem) => imageItem.status !== UploadStatus.FINISHED)
  )
})

const isUploadFileDisabled = computed(() => {
  return uploadFileList.value.length === 1
})

const uploadFileIcon = (type: string) => {
  return `https://gsst-poe-sit.gz.bcebos.com/icon/${type}.svg`
}

const messageListScrollToBottomThrottle = throttle(
  () => {
    emit('messageListScrollToBottom')
  },
  50,
  { leading: true, trailing: true },
)

watch(
  () => uploadImageList.value.length,
  () => {
    emit('messageListScrollToBottom', true)
  },
)

function handleClearAllMessage() {
  isAllowClearAllMessage.value && emit('clearAllMessage')
}

function messageItemFactory() {
  return {
    role: 'user',
    avatar: '',
    nickName: '',
    content: '',
    timestamp: Date.now(),
    isTextContentLoading: false,
    isAnswerResponseLoading: false,
    pluginName: '',
    imageUrl: '',
    reasoningContent: '',
    knowledgeContentResult: [],
  } as MessageItemInterface
}

function handleInputMessageEnter(event: KeyboardEvent) {
  if (event.key === 'Enter' && !event.shiftKey) {
    event.preventDefault()
    handleQuestionSubmit()
  }
}

function handleQuestionSubmit() {
  if (isQuestionSubmitDisabled.value || props.isAnswerResponseWait || isInputMessageDisabled.value) return

  if (!isAllSelectedModelName.value) {
    window.$message.warning(t('multi_model_dialogue_module.please_select_model_first'))
    return
  }

  const questionMessageId = nanoid()

  emit('addQuestionMessageItem', questionMessageId, {
    ...messageItemFactory(),
    content: questionContent.value,
    imageUrl: uploadImageList.value?.[0]?.url || '',
  })

  emit('messageListScrollToBottom', true)

  multiModelDialogueList.value.forEach((modelItem, modelIndex) => {
    const messages: QuestionMessageItem[] = []

    const { topP, temperature, agentSystem, modelNickName, communicationTurn } = modelItem

    modelItem.messageList.forEach((messageItem) => {
      messages.push({
        content: [
          {
            type: 'text',
            text: messageItem.content,
            image_url: {
              url: messageItem?.imageUrl || '',
            },
          },
        ],
        role: messageItem.role,
      })
    })

    modelItem.isAnswerResponseWait = true

    const answerMessageId = nanoid()
    let messageContent = ''
    let reasoningContent = ''
    let isFirstClip = true

    emit(
      'addAnswerMessageItem',
      answerMessageId,
      { ...messageItemFactory(), role: 'assistant', isTextContentLoading: true, isAnswerResponseLoading: true },
      modelIndex,
    )

    setTimeout(() => {
      emit('messageListScrollToBottom', true)
    }, 200)

    modelItem.controller = new AbortController()

    fetchEventStreamSource({
      path: '/api/rest/agentApplicationInfoRest/preview.json',
      payload: {
        agentId: props.agentId,
        fileUrls: uploadFileList.value.map((item) => item.url),
        messages,
        topP,
        temperature,
        agentSystem,
        modelNickName,
        communicationTurn,
        channel: ChannelType.multi_preview,
      },
      controller: modelItem.controller,
      onResponse: (data) => {
        // 推理内容
        if (data.reasoningContent) {
          reasoningContent += data.reasoningContent

          emit('updateMessageItem', answerMessageId, { reasoningContent }, modelIndex)
          messageListScrollToBottomThrottle()
        }

        // 插件
        if (data.function && data.function.name) {
          emit('updateMessageItem', answerMessageId, { pluginName: data.function.name }, modelIndex)
          emit('messageListScrollToBottom')
          return
        }

        // 知识库
        if (data.knowledgeContentResult) {
          emit(
            'updateMessageItem',
            answerMessageId,
            { knowledgeContentResult: data.knowledgeContentResult },
            modelIndex,
          )
          emit('messageListScrollToBottom')
          return
        }

        // 回复内容
        if (data.message) {
          messageContent += data.message

          if (isFirstClip) {
            emit(
              'updateMessageItem',
              answerMessageId,
              { content: messageContent, isTextContentLoading: false },
              modelIndex,
            )
            isFirstClip = false
          } else {
            emit('updateMessageItem', answerMessageId, { content: messageContent }, modelIndex)
          }

          messageListScrollToBottomThrottle()
        }
      },
      onRequestError: () => {
        errorMessageResponse(questionMessageId, answerMessageId, modelIndex)
      },
      onError: (err: any) => {
        CMessage.error(err.message || '', { to: `#${modelItem.id}` })
      },
      onFinally: () => {
        modelItem.controller = null
        modelItem.isAnswerResponseWait = false
        userStore.fetchUpdateEquityInfo()
        emit('updateMessageItem', answerMessageId, { isAnswerResponseLoading: false }, modelIndex)

        setTimeout(() => {
          emit('messageListScrollToBottom')
        }, 100)
      },
    })

    questionContent.value = ''
    uploadImageList.value = []
  })
}

function errorMessageResponse(questionMessageId: string, answerMessageId: string, modelIndex: number) {
  emit('deleteMessageItem', questionMessageId, modelIndex)
  emit('deleteMessageItem', answerMessageId, modelIndex)
}

function handleSelectFile(cb: () => void) {
  if (isUploadFileDisabled.value) {
    window.$message.ctWarning('', t('common_module.dialogue_module.overwrite_file_tip')).then(() => {
      cb()
    })
    return
  }

  cb()
}

function handleSelectUploadImage(cb: () => void) {
  if (uploadImageList.value.length > 0) {
    window.$message.ctWarning('', t('common_module.dialogue_module.overwrite_image_tip')).then(() => {
      cb()
    })
    return
  }

  cb()
}
</script>

<template>
  <footer class="flex flex-col items-center">
    <div class="mb-4 flex items-end gap-[18px]">
      <div class="flex gap-3">
        <n-popover trigger="hover">
          <template #trigger>
            <div
              class="border-inactive-border-color flex h-[54px] w-[54px] shrink-0 items-center justify-center rounded-full border bg-white"
              :class="
                isAllowClearAllMessage
                  ? 'text-font-color hover:text-theme-color cursor-pointer'
                  : 'text-gray-font-color cursor-not-allowed'
              "
              @click="handleClearAllMessage"
            >
              <i class="iconfont icon-clear text-xl leading-none" />
            </div>
          </template>
          <span class="text-xs"> {{ t('common_module.dialogue_module.clear_message_popover_message') }}</span>
        </n-popover>

        <n-upload
          :show-file-list="false"
          accept="image/png, image/jpeg, image/jpg, image/webp"
          abstract
          @before-upload="handleLimitUploadImage"
          @change="handleUploadImage"
        >
          <n-upload-trigger #="{ handleClick }" abstract>
            <n-popover style="width: 210px" trigger="hover">
              <template #trigger>
                <div
                  v-show="isEnableUploadImage"
                  class="border-inactive-border-color text-font-color hover:text-theme-color flex h-[54px] w-[54px] shrink-0 cursor-pointer items-center justify-center rounded-full border bg-white"
                  @click="handleSelectUploadImage(handleClick)"
                >
                  <i class="iconfont icon-upload-image text-xl leading-none" />
                </div>
              </template>

              <span class="text-xs"> {{ t('common_module.dialogue_module.upload_image_limit') }} </span>
            </n-popover>
          </n-upload-trigger>
        </n-upload>

        <n-upload
          :show-file-list="false"
          accept=".doc, .pdf, .docx, .txt, .md"
          abstract
          @before-upload="handleLimitUpload"
          @change="handleUpload"
        >
          <n-upload-trigger #="{ handleClick }" abstract>
            <n-popover style="width: 210px" trigger="hover">
              <template #trigger>
                <div
                  v-show="isEnableDocumentParse"
                  class="border-inactive-border-color text-font-color hover:text-theme-color flex h-[54px] w-[54px] shrink-0 cursor-pointer items-center justify-center rounded-full border bg-white"
                  @click="handleSelectFile(handleClick)"
                >
                  <i class="iconfont icon-upload text-xl leading-none" />
                </div>
              </template>

              <span class="text-xs"> {{ t('common_module.dialogue_module.upload_file_limit') }} </span>
            </n-popover>
          </n-upload-trigger>
        </n-upload>
      </div>

      <div class="flex flex-col gap-1">
        <div class="mb-1.5 flex gap-[14px]">
          <div
            v-for="uploadImageItem in uploadImageList"
            :key="uploadImageItem.id"
            class="border-inactive-border-color relative h-[66px] w-[66px] rounded-[10px] border bg-white"
            :class="{ 'border-[#F25744]!': uploadImageItem.status === UploadStatus.ERROR }"
          >
            <div
              class="absolute right-[-4px] top-[-4px] flex h-4 w-4 cursor-pointer items-center justify-center rounded-full bg-[rgba(0,0,0,0.55)] hover:opacity-80"
              @click="handleRemoveUploadImage(uploadImageItem.id)"
            >
              <CloseSmall theme="outline" size="16" fill="#fff" />
            </div>

            <div class="flex h-full w-full items-center justify-center overflow-hidden rounded-[10px]">
              <n-spin v-show="uploadImageItem.status === UploadStatus.UPLOADING" :size="20" />

              <n-image
                v-show="uploadImageItem.status === UploadStatus.FINISHED"
                width="100"
                object-fit="contain"
                :src="uploadImageItem.url"
                preview-disabled
                class="h-full w-full flex-shrink-0"
              />
            </div>
          </div>
        </div>

        <ul v-show="uploadFileList.length > 0" class="mb-1.5 grid gap-1.5">
          <li
            v-for="uploadFileItem in uploadFileList"
            :key="uploadFileItem.id"
            class="rounded-theme group relative flex h-[42px] w-full items-center overflow-hidden border bg-white/70"
            :class="uploadFileItem.status === 'error' ? 'border-error-font-color' : 'border-transparent'"
          >
            <div class="flex w-full items-center justify-between px-3.5">
              <div class="flex w-full items-center overflow-hidden">
                <img :src="uploadFileIcon(uploadFileItem.type!)" class="h-7 w-7" />

                <div class="mx-2.5 flex flex-1 flex-col overflow-hidden">
                  <n-ellipsis>
                    {{ uploadFileItem.name }}
                  </n-ellipsis>
                </div>
              </div>

              <n-progress
                v-show="!['finished', 'error'].includes(uploadFileItem.status)"
                class="left-13.5 w-[calc(100%-78px)]! absolute bottom-0"
                type="line"
                rail-color="#F3F3F3"
                :height="4"
                :percentage="uploadFileItem.percentage"
                :show-indicator="false"
              />

              <div v-show="['finished', 'error'].includes(uploadFileItem.status)" class="hidden group-hover:block">
                <n-popover trigger="hover" placement="top-end" :show-arrow="false">
                  <template #trigger>
                    <i
                      class="iconfont icon-close cursor-pointer hover:opacity-80"
                      :class="uploadFileItem.status === 'error' ? 'text-error-font-color' : 'text-font-color'"
                      @click="handleRemoveFile(uploadFileItem.id)"
                    />
                  </template>
                  <span>{{ t('common_module.dialogue_module.cancel_associate_file_tip') }}</span>
                </n-popover>
              </div>
            </div>
          </li>
        </ul>

        <div class="relative">
          <n-input
            v-model:value="questionContent"
            type="textarea"
            :autosize="{ minRows: 1, maxRows: 5 }"
            :placeholder="t('common_module.dialogue_module.question_input_placeholder')"
            :disabled="isInputMessageDisabled"
            class="rounded-theme! w-[725px]! border-[#9EA3FF]! border py-[10px] pl-3 pr-[44px]"
            @keydown="handleInputMessageEnter"
          />
          <i
            class="iconfont icon-send-icon absolute bottom-[17px] right-6 text-xl leading-none"
            :class="
              isQuestionSubmitDisabled || isAnswerResponseWait || isInputMessageDisabled
                ? 'text-hover-theme-color cursor-not-allowed'
                : 'text-theme-color cursor-pointer hover:opacity-80'
            "
            @click="handleQuestionSubmit"
          />
        </div>
      </div>
    </div>
    <span class="text-gray-font-color select-none">
      {{ t('common_module.dialogue_module.generate_warning_message') }}
    </span>
  </footer>
</template>
