<script setup lang="ts">
import { computed, nextTick, onMounted, onUnmounted, ref, watch } from 'vue'
import { onBeforeRouteLeave } from 'vue-router'
import { Button, Image, Spin, UploadDragger, message } from 'ant-design-vue'
import { SvgIcon } from '@/components/SvgIcon'
import { AppTextarea } from '@/components/AppTextarea'
import { AppContainerBox } from '@/components/AppContainerBox'
import { AppSubMenuTitle } from '@/components/AppSubMenuTitle'
import { AppSubMenuList } from '@/components/AppSubMenuList'
import { AppConversationDefault } from '@/components/AppConversationDefault'
import { AppUserInfo } from '@/components/AppUserInfo'
import { SubMenuActionEnum } from '@/components/AppSubMenuList/index.d'
import type { SubMenuItem } from '@/components/AppSubMenuList/index.d'
import type { PictureType } from '@/components/AppPicture/index.d'

import { AppMessage } from '@/components/AppMessage'
import type { MessageItem } from '@/components/AppMessage/index.d'
import type { TopPickItem } from '@/components/AppTopPicks/index.d'
import { AppModelSelect } from '@/components/AppModelSelect'
import type { ModelSelect } from '@/components/AppModelSelect/index.d'
import { useMessageStore } from '@/store/moules/messageStore/index'
import { MenuTypeEnum } from '@/enums/menuEnum'
import { MessageStatusEnum, MessageTypeEnum, ModelTypeEnum } from '@/enums/messageEnum'
import { addMessage, conversationList, conversationToTop, historyMessage, removeMessage, sendVisualAnalysis, updateMessage } from '@/api/base/message'
import { uploadApi } from '@/api/base/file'
import { useMqtt } from '@/hooks/useMqtt'
import { useMessage } from '@/hooks/useMessage'

const { createConfirm } = useMessage()

const messageStore = useMessageStore()

const sendBtnLoading = ref(false)
const subMenuActiveIndex = ref(-1) // 当前会话索引
const subMenuActionIndex = ref(-1) // 会话操作索引
const subMenuList = ref<SubMenuItem[]>([])
const subMenuInputValue = ref<string>('')
const appMessageRef = ref()

const modelOptions: ModelSelect[] = [
  {
    label: 'DALL-E 3',
    value: ModelTypeEnum.DALL_E3,
  },
]
const modelIndex = ref(0)
const fileList = ref<string[]>([])
const roleList = ref<PictureType[]>([])
const applyList = ref<PictureType[]>([])
const messageList = computed(() => messageStore.getMessageList)
const messageStatus = computed(() => messageStore.getMessageStatus)
const conversationData = computed(() => messageStore.getConversationData)
const historyMessageParams = ref({
  conversationId: '',
  current: 1,
  size: 10,
  total: 0,
})

const conversationDefaultShow = ref(false)
const appMessageShow = ref(true)
const spinning = ref(true)
const elIndex = ref(0) // 新分页数据的数量

watch(
  () => messageStatus.value,
  (val) => {
    if (val === MessageStatusEnum.END) {
      sendBtnLoading.value = false
    }
  },
)

/**
 * @description: 点击会话操作项
 */
function handlesubMenuActionIndex(type: SubMenuActionEnum, item: SubMenuItem, index: number) {
  subMenuActionIndex.value = index
  const subMenuActionData = subMenuList.value[index]
  if (type === SubMenuActionEnum.EDIT) {
    subMenuList.value.forEach((item) => {
      item.actionType = SubMenuActionEnum.NOT
    })
    subMenuList.value[index].actionType = SubMenuActionEnum.EDIT
    subMenuInputValue.value = item.title
  }

  if (type === SubMenuActionEnum.DELETE) {
    createConfirm({
      title: '删除',
      content: `确定要删除${item.title}会话吗？`,
      iconType: 'warning',
      onOk: () => {
        removeMessage(item.id).then(async () => {
          message.success('删除成功')
          subMenuActiveIndex.value = -1
          conversationDefaultShow.value = true
          await getConversationList()
        })
      },
    })
  }

  if (type === SubMenuActionEnum.TO_TOP) {
    conversationToTop(item.id).then(async () => {
      message.success('置顶成功')
      await getConversationList()
      const index = subMenuList.value.findIndex(item => item.id === subMenuActionData.id)
      await handleSubMenuChange(index)
      getHistoryMessage()
    })
  }
}

/**
 * @description: 切换会话
 */
async function handleSubMenuChange(index: number, item?: SubMenuItem) {
  if (messageStatus.value !== MessageStatusEnum.END) {
    message.warn('请先结束对话')
    return
  }
  if (!subMenuList.value.length) {
    return
  }
  subMenuActiveIndex.value = index
  historyMessageParams.value.current = 1
  historyMessageParams.value.total = 0
  messageStore.setConversationData(subMenuList.value[subMenuActiveIndex.value])
  messageStore.setMessageClear()
  const modelIdx = modelOptions.findIndex(v => v.value === Number(subMenuList.value[subMenuActiveIndex.value].modelType))
  if (modelIdx === -1) {
    message.warn('未找到该对话下的模型信息')
    return
  }
  modelIndex.value = modelIdx
  useMqtt().end()
  useMqtt().connect()

  // item有的话说明是点击切换，需要重新获取历史消息，否则是不是点击切换调用，不需要获取历史记录
  if (item) {
    getHistoryMessage()
    fileList.value = []
  }
}

/**
 * @description: 发送消息
 */
async function handleSend(value: string) {
  sendBtnLoading.value = true
  conversationDefaultShow.value = false

  if (subMenuActiveIndex.value === -1) {
    try {
      spinning.value = true
      messageStore.setMessageClear()
      await addMessage({ type: MenuTypeEnum.VISUAL_ANALYSIS, title: '新的对话', sort: subMenuList.value.length + 1, modelType: modelOptions[modelIndex.value].value })
      await getConversationList()
      await nextTick()
      subMenuActiveIndex.value = subMenuList.value.length - 1
      await handleSubMenuChange(subMenuActiveIndex.value)
      if (!conversationData.value) {
        message.error('对话发送，请稍后重试！')
        return
      }
      sendMessage(conversationData.value.id, value)
      spinning.value = false
    }
    catch (error) {
      message.error('新建对话失败，请稍后重试！')
    }
  }
  else {
    if (!conversationData.value) {
      return
    }
    sendMessage(conversationData.value.id, value)
    fileList.value = []
  }
}

/**
 * @description: 获取会话列表
 */
async function getConversationList() {
  const res = await conversationList(MenuTypeEnum.VISUAL_ANALYSIS)
  res.forEach((item: SubMenuItem) => {
    item.actionType = SubMenuActionEnum.NOT
  })
  subMenuList.value = res
}

/**
 * @description: 获取历史对话记录
 */
async function getHistoryMessage() {
  if (!conversationData.value) {
    spinning.value = false
    conversationDefaultShow.value = true
    return
  }
  spinning.value = true
  historyMessageParams.value.conversationId = conversationData.value.id
  const res = await historyMessage(historyMessageParams.value)
  spinning.value = false
  if (!res || !res.records || !res.records.length) {
    if (!res.records.length && historyMessageParams.value.total === 0) {
      conversationDefaultShow.value = true
    }
    return
  }

  res.records.forEach((item: any) => {
    const itemData: MessageItem = {
      messageType: item.roleType === MessageTypeEnum.USER ? MessageTypeEnum.USER : MessageTypeEnum.AI,
      content: item.messageContent,
      time: item.messageTime,
      avatar: '',
      messageStatus: MessageStatusEnum.END,
    }
    historyMessageParams.value.total = res.total
    messageStore.setMessageUnshiftItem(itemData)
  })
  elIndex.value = res.records.length

  conversationDefaultShow.value = false
}

/**
 * @description: 滚动监听
 */
async function onScrollTop(scrollTop: number) {
  if (scrollTop !== 0) {
    return
  }

  if (historyMessageParams.value.current * historyMessageParams.value.size >= historyMessageParams.value.total) {
    return
  }

  if (historyMessageParams.value.current < historyMessageParams.value.total) {
    historyMessageParams.value.current++
    await getHistoryMessage()

    // 无缝滚动
    await appMessageRef.value.getElementOffsetTop()
    await appMessageRef.value.seamlessScrollToTop()
  }
}

/**
 * @description: 发送消息hook
 */
async function sendMessage(conversationId: string, question: string): Promise<void> {
  if (!messageStore.getConversationData) {
    return
  }

  conversationDefaultShow.value = false
  messageStore.setMessageStatus(MessageStatusEnum.LOADING)
  messageStore.setMessagePushItem({
    messageType: MessageTypeEnum.USER,
    content: `${question}![Picture](${fileList.value[0]})`,
    time: String(new Date().getTime()),
    avatar: '',
  })
  messageStore.setMessagePushItem({
    messageType: MessageTypeEnum.AI,
    content: '正在思考中...',
    time: String(new Date().getTime()),
    avatar: '',
    messageStatus: MessageStatusEnum.LOADING,
  })

  sendVisualAnalysis({
    conversationId,
    question,
    fileUrl: fileList.value[0],
  }).catch(() => {
    messageStore.getMessageList.splice(-2)
    messageStore.setMessageStatus(MessageStatusEnum.END)
  })
}

/**
 * @description: 重新回答
 */
function reloadMessage() {
  if (!conversationData.value) {
    return
  }
  const question = messageList.value[messageList.value.length - 2]?.content
  sendMessage(conversationData.value.id, question)
}

/**
 * @description: 点击新建会话按钮
 */
function handleAddMessage() {
  if (messageStatus.value !== MessageStatusEnum.END) {
    message.warn('请先结束对话')
    return
  }
  conversationDefaultShow.value = true
  subMenuActiveIndex.value = -1
}

/**
 * @description: 点击默认看板的精选话题
 */
function handlePick(_index: number, item: TopPickItem) {
  handleSend(item.label)
}

/**
 * @description: gpt模型切换
 */
function handleModel(index: number) {
  modelIndex.value = index
}

/**
 * @description: 图片上传前的处理
 */
function handleBeforeUpload(file: File) {
  const isJpgOrPng = file.type === 'image/jpeg' || file.type === 'image/png' || file.type === 'image/gif'
  if (!isJpgOrPng) {
    message.error('只能上传JPG、JPEG、PNG、GIF格式图片!')
  }
  const isLt10M = file.size / 1024 / 1024 < 10
  if (!isLt10M) {
    message.error('图片尺寸不能超过10MB!')
  }
  return isJpgOrPng && isLt10M
}

/**
 * @description: 自定义图片上传服务器
 */
async function handleCustomRequest(option: any) {
  const formData = new FormData()
  formData.append('file', option.file)

  uploadApi(formData).then((res) => {
    fileList.value.push(res.link)
  })
}

// 下面是子菜单操作项
function handleSubMenuInputAffirm(index: number, item: SubMenuItem, inputValue: string) {
  updateMessage({ ...item, title: inputValue }).then(() => {
    message.success('修改成功')
    subMenuList.value[index].title = inputValue
    subMenuList.value[index].actionType = SubMenuActionEnum.NOT
  })
}
function handleSubMenuInputClose(index: number) {
  subMenuList.value[index].actionType = SubMenuActionEnum.NOT
}
function handleSubMenuInputBlur(index: number, item: SubMenuItem, inputValue: string) {
  subMenuActionIndex.value = -1
  handleSubMenuInputAffirm(index, item, inputValue)
}

onMounted(async () => {
  await getConversationList()
  if (subMenuList.value.length) {
    subMenuActiveIndex.value = 0
  }
  await handleSubMenuChange(subMenuActiveIndex.value)
  getHistoryMessage()
})

onUnmounted(() => {
  messageStore.setMessageClear()
  messageStore.setConversationData(undefined)
  useMqtt().end()
})

// 路由离开时的操作
onBeforeRouteLeave(() => {
  if (messageStatus.value !== MessageStatusEnum.END) {
    message.warn('请先结束对话')
    return false
  }
})
</script>

<template>
  <AppContainerBox>
    <template #subMenu>
      <!-- 标题 -->
      <AppSubMenuTitle></AppSubMenuTitle>

      <!-- 按钮 -->
      <div class="px-5 mb-5">
        <Button type="primary" class="w-full" @click="handleAddMessage">
          新建会话
        </Button>
      </div>

      <!-- 会话列表 -->
      <AppSubMenuList
        v-model:input-value="subMenuInputValue"
        :list="subMenuList"
        :active-index="subMenuActiveIndex"
        :action-index="subMenuActionIndex"
        @change="handleSubMenuChange"
        @handle-action="handlesubMenuActionIndex"
        @input-affirm="handleSubMenuInputAffirm"
        @input-close="handleSubMenuInputClose"
        @input-blur="handleSubMenuInputBlur"
      >
      </AppSubMenuList>
      <AppUserInfo />
    </template>
    <template #content>
      <Spin :spinning="spinning" wrapper-class-name="app-content-spin">
        <AppModelSelect
          v-if="conversationDefaultShow"
          :active-index="modelIndex"
          :options="modelOptions"
          @change="handleModel"
        >
        </AppModelSelect>
        <!-- 默认导语 -->
        <AppConversationDefault
          v-if="conversationDefaultShow"
          :is-pick="true"
          :is-hot="false"
          :role-list="roleList"
          :apply-list="applyList"
          height="calc(100% - 160px)"
          @handle-pick="handlePick"
        >
        </AppConversationDefault>

        <div class="h-full flex flex-col">
          <!-- 消息列表 -->
          <AppMessage
            v-if="!conversationDefaultShow && appMessageShow"
            ref="appMessageRef"
            class="pl-27 pr-5"
            height="calc(100% - 200px)"
            :el-index="elIndex"
            :list="messageList"
            @on-scroll-top="onScrollTop"
            @reload-message="reloadMessage"
          >
          </AppMessage>

          <!-- 发送框 -->
          <div v-if="!fileList.length" class="upload-box absolute right-0 bottom-5 pl-44 pr-24 mt-10s w-full">
            <UploadDragger
              name="file"
              :multiple="false"
              :max-count="1"
              :show-upload-list="false"
              :custom-request="handleCustomRequest"
              :before-upload="handleBeforeUpload"
            >
              <SvgIcon class="upload-icon" name="image"></SvgIcon>
              <p class="ant-upload-text">
                拖拽图片到这里，或者点击添加
              </p>
              <p class="ant-upload-hint">
                图片格式支持jpg、jpeg、png、gif、webp
              </p>
            </UploadDragger>
          </div>
          <div
            v-else
            class="w-full"
          >
            <div class="pl-44 pr-24 mt-2">
              <Image
                class="rounded"
                :width="60"
                :height="60"
                :src="fileList[0]"
                fallback="data:image/png;base64,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"
              ></Image>
            </div>
            <AppTextarea
              class="pl-44 pr-24 mt-2"
              :btn-loading="sendBtnLoading"
              @send="handleSend"
            ></AppTextarea>
          </div>
        </div>
      </Spin>
    </template>
  </AppContainerBox>
</template>

<style lang="scss" scoped>
.upload-box {
  .upload-icon {
    width: 50px;
    height: 50px;
  }
}
</style>
