<script setup lang="ts">
import { computed, h, nextTick, ref, shallowRef, useTemplateRef, watch, toValue } from 'vue'
import type { AgentApplicationRecordItem, MessageItemInterface } from '../types'
import { fetchAgentApplicationSelectList, fetchFileUpload } from '@/apis/home-agent'
import { nanoid } from 'nanoid'
import { CloseSmall } from '@icon-park/vue-next'
import fetchEventStreamSource from '../utils/fetch-event-stream-source'
import { throttle } from 'lodash-es'
import { useI18n } from 'vue-i18n'
import { useUserStore } from '@/store/modules/user'
import { ChannelType } from '@/enums/channel'
import { UploadStatus } from '@/enums/upload-status'
import { useUploadImage } from '@/composables/useUploadImage'
import { useSystemLanguageStore } from '@/store/modules/system-language'
import { PluginType } from '@/enums/plugin'
import { NAvatar, NEllipsis } from 'naive-ui'
import type { SelectRenderLabel, SelectRenderTag } from 'naive-ui'

interface Props {
  currentSessionId: string
  messageListLength: number
  isNotShowBackBottomBtn: boolean | undefined
}

const props = defineProps<Props>()
const emit = defineEmits<{
  messageListScrollToBottom: []
  addMessageItem: [messageId: string, messageItem: MessageItemInterface]
  updateSpecifyMessageItem: [messageId: string, newMessageItem: Partial<MessageItemInterface>]
  deleteMessageItem: [messageId: string]
  createNewSession: []
  historyRecordListUpdate: []
}>()

const questionContent = defineModel<string>('questionContent', { required: true })
const currentAgentApplication = defineModel<AgentApplicationRecordItem>('currentAgentApplication', { required: true })
const isAgentResponding = defineModel<boolean>('isAgentResponding', { required: true })
const currentFetchEventSourceController = defineModel<AbortController | null>('currentFetchEventSourceController', {
  required: true,
})

const { t } = useI18n()

const inputFileRef = useTemplateRef<HTMLInputElement | null>('inputFileRef')
const userStore = useUserStore()

const systemLanguageStore = useSystemLanguageStore()

const { uploadImageList, handleLimitUploadImage, handleUploadImage, handleRemoveUploadImage } = useUploadImage()

let fileUploadController = shallowRef<AbortController | null>(null)

const agentApplicationSelectList = ref<AgentApplicationRecordItem[]>([])

const currentLatestMessageItemKeyMap = ref(new Map<'assistant' | 'user', string>())

const currentInputFileInfo = ref({
  url: '',
  fileName: '',
  percentage: 5,
  uploading: false,
})

const isQuestionSubmitBtnDisabled = computed(() => {
  return (
    questionContent.value.trim().length === 0 ||
    isAgentResponding.value ||
    currentInputFileInfo.value.uploading ||
    uploadImageList.value.some((imageItem) => imageItem.status !== UploadStatus.FINISHED)
  )
})

const isHasUploadImage = computed(() => {
  return uploadImageList.value.length > 0
})

const isEnglishLanguage = computed(() => systemLanguageStore.currentLanguageInfo.key === 'en')

const isEnableUploadImage = computed(() => {
  return currentAgentApplication.value.unitIds?.includes(PluginType.IMAGE_OCR) || false
})

;(function () {
  getAgentApplicationSelectList()
})()

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

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

function getAgentApplicationSelectList() {
  fetchAgentApplicationSelectList<AgentApplicationRecordItem[]>().then((res) => {
    if (res.data && Array.isArray(res.data)) {
      agentApplicationSelectList.value = res.data.map((item) => ({ ...item, class: 'application-menu-item' }))
    }

    currentAgentApplication.value = res.data[0]
  })
}

function handleCreateNewSession() {
  if (isAgentResponding.value) {
    window.$message.ctWarning(t('home_module.interrupt_dialogue_prompt')).then(() => {
      currentFetchEventSourceController.value && currentFetchEventSourceController.value.abort()

      isAgentResponding.value = false

      emit('createNewSession')
      handleFileUploadCancel()

      /* 延时，避免页面刷新后，历史记录列表未刷新 */
      setTimeout(() => {
        emit('historyRecordListUpdate')
      }, 300)
    })
  } else {
    if (props.messageListLength === 0) {
      window.$message.warning(t('home_module.currently_in_the_latest_session'))

      return
    }

    emit('createNewSession')
    handleFileUploadCancel()
  }
}

function questionSubmit() {
  const latestUserMessageKey = nanoid()
  const latestAssistantMessageKey = nanoid()

  currentLatestMessageItemKeyMap.value.set('user', latestUserMessageKey)
  currentLatestMessageItemKeyMap.value.set('assistant', latestAssistantMessageKey)

  emit('addMessageItem', latestUserMessageKey, {
    role: 'user',
    agentId: '',
    content: questionContent.value.trim(),
    timestamp: Date.now(),
    isAnswerLoading: false,
    avatar: '',
    name: '',
    imageUrl: uploadImageList.value?.[0]?.url || '',
    reasoningContent: '',
  })

  const agentAvatar = currentAgentApplication.value.agentAvatar
  const agentName = currentAgentApplication.value.agentTitle

  nextTick(() => {
    emit('addMessageItem', latestAssistantMessageKey, {
      role: 'assistant',
      agentId: '',
      content: '',
      timestamp: Date.now(),
      isAnswerLoading: true,
      avatar: agentAvatar,
      name: agentName,
      pluginName: '',
      reasoningContent: '',
    })

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

  isAgentResponding.value = true

  // let isFirstClip = true
  let messageContent = ''
  let reasoningContent = ''

  currentFetchEventSourceController.value = fetchEventStreamSource(
    '/agentApplicationRest/callAgentApplication.json',
    {
      dialogsId: props.currentSessionId, //会话ID
      agentId: currentAgentApplication.value.agentId, //应用ID
      input: questionContent.value.trim(), //提问文本
      fileUrls: currentInputFileInfo.value.url ? [currentInputFileInfo.value.url] : [],
      channel: ChannelType.index,
      imageUrl: uploadImageList.value?.[0]?.url || '', // 图片链接
    },
    {
      onResponse: (data) => {
        // 推理内容
        if (data.reasoningContent) {
          reasoningContent += data.reasoningContent

          emit('updateSpecifyMessageItem', currentLatestMessageItemKeyMap.value.get('assistant')!, {
            reasoningContent: reasoningContent,
          })

          props.isNotShowBackBottomBtn && messageListScrollToBottomThrottle()
        }

        // 回复消息
        if (data.message) {
          messageContent += data.message

          emit('updateSpecifyMessageItem', currentLatestMessageItemKeyMap.value.get('assistant')!, {
            content: messageContent,
          })

          props.isNotShowBackBottomBtn && messageListScrollToBottomThrottle()
        }

        // 插件
        if (data.function && data.function.name) {
          emit('updateSpecifyMessageItem', currentLatestMessageItemKeyMap.value.get('assistant')!, {
            pluginName: data.function.name,
          })

          props.isNotShowBackBottomBtn && messageListScrollToBottomThrottle()
        }
      },
      onend: () => {
        setTimeout(() => {
          emit('updateSpecifyMessageItem', currentLatestMessageItemKeyMap.value.get('assistant')!, {
            isAnswerLoading: false,
          })

          props.isNotShowBackBottomBtn && messageListScrollToBottomThrottle()
        }, 100)
      },
      onclose: () => {
        isAgentResponding.value = false

        nextTick(() => {
          emit('historyRecordListUpdate')

          userStore.fetchUpdateEquityInfo()
        })
      },
      onerror: (err) => {
        err.message && window.$message.error(err.message)

        emit('deleteMessageItem', currentLatestMessageItemKeyMap.value.get('assistant')!)
      },
    },
  )

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

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

    if (!isQuestionSubmitBtnDisabled.value) {
      questionSubmit()
    }
  }
}

function handleFileUploadPopup() {
  inputFileRef.value && inputFileRef.value.click()
}

function handleFileUpload(e: Event) {
  const target = e.target as HTMLInputElement
  const file = target.files && target.files[0]

  if (file) {
    if (!['.pdf', '.txt', '.md', '.doc', '.docx'].some((fileType) => file.name.endsWith(fileType))) {
      window.$message.warning(t('personal_settings_module.the_file_type_cannot_be_uploaded'))

      return
    }

    if (file.size === 0) {
      window.$message.warning(t('equity_module.file_empty_tip'))
      return
    } else if (file.size > 1024 * 1024 * 10) {
      window.$message.warning(t('common_module.file_size_limit', { size: '10MB' }))
      return
    }

    currentInputFileInfo.value = {
      fileName: file.name,
      url: URL.createObjectURL(file),
      percentage: 5,
      uploading: true,
    }

    fileUploadController.value && fileUploadController.value.abort()

    fileUploadController.value = new AbortController()

    const formData = new FormData()
    formData.append('file', file)

    fetchFileUpload<string>(formData, {
      onUploadProgress: (progressEvent) => {
        const percentage = Number.parseInt((progressEvent?.progress || 0).toFixed(2)) * 100

        currentInputFileInfo.value.percentage = percentage < 5 ? 5 : percentage

        if (progressEvent?.progress === 1) {
          currentInputFileInfo.value.uploading = false
        }
      },
      signal: fileUploadController.value.signal,
    }).then((res) => {
      currentInputFileInfo.value.url = res.data
    })
  }
}

function handleFileUploadCancel() {
  currentInputFileInfo.value = {
    url: '',
    fileName: '',
    percentage: 0,
    uploading: false,
  }

  inputFileRef.value && (inputFileRef.value.value = '')

  if (fileUploadController.value) {
    fileUploadController.value.abort()
    fileUploadController.value = null
  }
}

function handleFileUploadReplace() {
  handleFileUploadPopup()
}

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

  cb()
}

const selectMenuRenderLabel: SelectRenderLabel = (option) => {
  return h(
    'div',
    {
      style: {
        display: 'flex',
        alignItems: 'center',
        width: '100%',
      },
    },
    [
      h(NAvatar, {
        src: option.agentAvatar as string,
        size: 22,
        color: '#fff',
        'object-fit': 'contain',
        style: {
          marginRight: '6px',
          flexShrink: 0,
        },
      }),
      h(NEllipsis, { style: { width: '100%', marginRight: '6px' } }, { default: () => option.agentTitle }),
      h(
        'span',
        { style: { justifySelf: 'end', fontSize: '12px', color: '#0B7DFF', textAlign: 'end', marginLeft: 'auto' } },
        [`${option.points as string}${t('equity_module.points2')}/${t('common_module.time')}`],
      ),
    ],
  )
}

const selectMenuRenderTag: SelectRenderTag = ({ option }) => {
  return h(
    'div',
    {
      style: {
        display: 'flex',
        alignItems: 'center',
      },
    },
    [
      h(NAvatar, {
        src: option.agentAvatar as string,
        size: 22,
        color: '#fff',
        'object-fit': 'contain',
        style: {
          marginRight: '6px',
          flexShrink: 0,
        },
      }),
      h(NEllipsis, { style: { width: '100%' } }, { default: () => option.agentTitle }),
    ],
  )
}

function onAgentApplicationInfoUpdate(_key: string, option: AgentApplicationRecordItem) {
  currentAgentApplication.value = toValue(option)

  if (currentInputFileInfo.value.url) {
    currentInputFileInfo.value = {
      url: '',
      fileName: '',
      percentage: 5,
      uploading: false,
    }
  }

  if (uploadImageList.value.length > 0) {
    uploadImageList.value = []
  }

  if (questionContent.value) {
    questionContent.value = ''
  }
}

defineExpose({
  clearSessionReferenceFile: handleFileUploadCancel,
})
</script>

<template>
  <div class="mx-auto mt-auto w-[750px] pr-[10px]">
    <div class="mb-[10px] flex select-none justify-between">
      <div class="w-[190px]">
        <n-select
          :value="currentAgentApplication.agentId"
          class="application-menu"
          :options="agentApplicationSelectList"
          :render-label="selectMenuRenderLabel"
          :render-tag="selectMenuRenderTag"
          label-field="agentTitle"
          value-field="agentId"
          placement="top-start"
          :consistent-menu-width="false"
          :menu-props="{ style: { maxWidth: '220px' }, class: 'application-menu-popup' }"
          :on-update:value="onAgentApplicationInfoUpdate"
        />
      </div>

      <div class="flex items-center">
        <Transition name="file-upload" mode="out-in">
          <div v-if="!currentInputFileInfo.fileName">
            <n-popover trigger="hover">
              <template #trigger>
                <n-button
                  v-show="currentAgentApplication.isDocumentParsing === 'Y'"
                  class="application-select-btn !mr-[8px] !h-[34px] !rounded-[10px] !p-0"
                  @click="handleFileUploadPopup"
                >
                  <div class="box-border flex w-full items-center justify-between px-[12px]">
                    <i class="iconfont icon-upload mr-[5px] text-[14px]"></i>
                    <span class="text-[14px]">{{ t('personal_settings_module.upload_file') }}</span>
                  </div>
                </n-button>
              </template>
              <span>{{ t('personal_settings_module.file_type_restriction_doc') }}</span>
            </n-popover>
          </div>

          <div v-else class="relative !mr-[14px] flex !h-[34px] items-center rounded-[10px] bg-[#ECEFFF] px-[14px]">
            <div
              class="mr-[5px] h-[18px] w-[18px] bg-[url('https://gsst-poe-sit.gz.bcebos.com/icon/doc.svg')] bg-contain bg-no-repeat"
            ></div>

            <div
              class="text-[14px]"
              :class="{
                'w-[90px]': isEnglishLanguage && isEnableUploadImage,
                'w-[190px]': !isEnglishLanguage && isEnableUploadImage,
                'w-[240px]': !isEnableUploadImage,
              }"
            >
              <NEllipsis :tooltip="{ width: 400 }">
                {{ currentInputFileInfo.fileName }}
              </NEllipsis>
            </div>

            <div class="ml-[10px]">
              <i
                class="iconfont icon-huanyihuan hover:text-theme-color mr-[10px] cursor-pointer text-[14px] transition"
                @click="handleFileUploadReplace"
              ></i>
              <i
                class="iconfont icon-close hover:text-theme-color cursor-pointer text-[14px] transition"
                @click="handleFileUploadCancel"
              ></i>
            </div>

            <div v-show="currentInputFileInfo.uploading" class="absolute bottom-[1px] left-[40px] w-[86%]">
              <n-progress
                type="line"
                :percentage="currentInputFileInfo.percentage"
                :show-indicator="false"
                processing
                :fill-border-radius="0"
                :border-radius="0"
                :height="3"
                rail-color="#DCDCDC"
              />
            </div>
          </div>
        </Transition>

        <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>
                <n-button
                  v-show="isEnableUploadImage"
                  class="upload-image-btn !mr-[8px] !h-[34px] !rounded-[10px] !p-0"
                  @click="handleSelectImage(handleClick)"
                >
                  <div class="box-border flex w-full items-center justify-between px-[12px]">
                    <i class="iconfont icon-upload-image mr-[5px] text-[14px]"></i>
                    <span class="text-[14px]">{{ t('common_module.upload_image') }}</span>
                  </div>
                </n-button>
              </template>

              <span>{{ t('common_module.dialogue_module.upload_image_limit') }}</span>
            </n-popover>
          </n-upload-trigger>
        </n-upload>

        <n-button class="application-select-btn !h-[34px] !rounded-[10px] !p-0" @click="handleCreateNewSession">
          <div class="box-border flex w-full items-center justify-between px-[12px]">
            <i class="iconfont icon-session mr-[5px] text-[14px]"></i>
            <span class="text-[14px]">{{ t('home_module.starting_a_new_session') }}</span>
          </div>
        </n-button>

        <input
          ref="inputFileRef"
          type="file"
          class="hidden"
          accept=".pdf,.txt,.md,.doc,.docx"
          @change="handleFileUpload"
        />
      </div>
    </div>

    <div class="relative">
      <n-input
        v-model:value.trim="questionContent"
        class="content-input"
        type="textarea"
        :autosize="{ minRows: isHasUploadImage ? 2 : 4, maxRows: isHasUploadImage ? 2 : 4 }"
        :class="isHasUploadImage ? 'carry-image' : ''"
        :placeholder="t('home_module.please_enter_a_question')"
        @keydown="handleQuestionSubmitEnter"
      >
        <template #suffix>
          <div :class="isHasUploadImage ? 'absolute bottom-[9px] right-[11px]' : 'flex h-full items-end pb-[10px]'">
            <n-button type="primary" :disabled="isQuestionSubmitBtnDisabled" @click="() => questionSubmit()">
              <i class="iconfont icon-send-icon"></i>
            </n-button>
          </div>
        </template>
      </n-input>

      <div v-show="isHasUploadImage" class="absolute bottom-[9px] left-[11px] flex gap-[11px]">
        <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>
    </div>

    <div class="mt-[14px] text-center text-[13px] text-[#999]">
      {{ t('home_module.all_of_the_above_content_is_generated_by_ai_and_is_for_reference_only') }}
    </div>
  </div>
</template>

<style lang="scss" scoped>
.content-input {
  :deep(.n-input__border) {
    border-color: #9ea3ff;
  }
}

:deep(.carry-image .n-input-wrapper) {
  padding-bottom: 84px;
}

:deep(.upload-image-btn),
:deep(.application-select-btn) {
  .n-button__border {
    border-color: #9ea3ff;
  }
}

.file-upload-enter-active,
.file-upload-leave-active {
  transition-timing-function: ease-in-out;
  transition-duration: 0.2s;
  transition-property: opacity;
}

.file-upload-enter-from,
.file-upload-leave-to {
  opacity: 0;
}

:deep(.application-menu) {
  .n-base-selection {
    border-radius: 10px;
  }

  .n-base-selection__border {
    border-color: #9ea3ff;
  }

  .n-base-selection-label {
    height: 34px;
  }

  .application-menu-inner .select-item-image {
    width: 16px;
    height: 16px;
  }
}

:global(.application-menu-item .n-base-select-option__content) {
  width: 100%;
}
</style>
