<script setup lang="ts">
import { computed, onMounted, onUnmounted, ref, shallowRef } from 'vue'
import { useRouter } from 'vue-router'
import { useI18n } from 'vue-i18n'
import { Howl } from 'howler'
import type { ValueOf } from 'type-fest'
import PageHeader from './components/web-page-header.vue'
import Preamble from './components/preamble.vue'
import MessageList from './components/message-list.vue'
import FooterInput from './components/footer-input.vue'
import {
  fetchCreateContinueQuestions,
  fetchCreateDialogues,
  fetchGetApplicationInfo,
  fetchGetAutoPlayByAgentId,
  fetchUpdateAutoPlay,
} from '@/apis/agent-application'
import { PersonalAppConfigState } from '@/store/types/personal-app-config'
import { defaultPersonalAppConfigState } from '@/store/modules/personal-app-config'
import { createDefaultUserInfoFactory, useUserStore } from '@/store/modules/user'
import { useLayoutConfig } from '@/composables/useLayoutConfig'
import { fetchGetMemberInfoById } from '@/apis/user'
import { UserInfo } from '@/store/types/user'
import { validBrowser } from '@/utils/browser-detection'
import { PluginType } from '@/enums/plugin'

const { t } = useI18n()

const router = useRouter()

const userStore = useUserStore()

const { isMobile } = useLayoutConfig()

const fullScreenLoading = ref(false)
const agentId = ref('')
const dialogsId = ref('')
const agentMemberInfo = ref<UserInfo>(createDefaultUserInfoFactory())

const agentApplicationConfig = ref<PersonalAppConfigState>(defaultPersonalAppConfigState())

const messageListRef = ref<InstanceType<typeof MessageList> | null>(null)
const footerInputRef = ref<InstanceType<typeof FooterInput> | null>(null)

const messageList = ref(new Map<string, ConversationMessageItem>())

const continuousQuestionStatus = ref<'default' | 'close'>('default')
const continueQuestionList = ref<string[]>([])
const answerAudioAutoPlay = ref(false)
const answerAudioPlaying = ref(false) // 语音播放中
const currentPlayMessageItem = ref<ConversationMessageItem | null>(null)
const currentPlayAudioFragmentSerialNo = ref(0)
const currentSoundCtl = shallowRef<Howl | null>(null)
const isSoundCtlCreated = ref(false)
const isAnswerResponseLoading = ref(false)
const createContinueQuestionsException = ref(false)

const isEnableDocumentParse = computed(() => {
  return agentApplicationConfig.value.knowledgeConfig.isDocumentParsing === 'Y'
})

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

const isEnableVoice = computed(() => {
  return !!agentApplicationConfig.value.voiceConfig.timbreId
})

onMounted(async () => {
  if (router.currentRoute.value.params.agentId) {
    agentId.value = router.currentRoute.value.params.agentId as string
  }

  if (isMobile.value) {
    router.replace({
      name: 'ShareMobileApplication',
      params: { agentId: agentId.value },
      query: router.currentRoute.value.query,
    })
  }

  if (agentId.value) {
    fullScreenLoading.value = true
    await handleGetApplicationDetail()
    if (userStore.isLogin) {
      await handleCreateDialogues()
      await handleGetAutoPlayByAgentId()
    }

    fullScreenLoading.value = false
    return
  }

  router.replace({ name: 'Home' })
})

onUnmounted(() => {
  handleAudioPause()
  footerInputRef.value?.blockMessageResponse()
})

async function handleGetApplicationDetail() {
  fetchGetApplicationInfo<PersonalAppConfigState>(agentId.value)
    .then((res) => {
      agentApplicationConfig.value = res.data
      continuousQuestionStatus.value = res.data.commConfig.continuousQuestionStatus
      document.title = agentApplicationConfig.value.baseInfo.agentTitle
      handleGetMemberInfo()
    })
    .catch(() => {
      router.replace({ name: 'Home' })
    })
}

async function handleGetMemberInfo() {
  const res = await fetchGetMemberInfoById<UserInfo>(agentApplicationConfig.value.baseInfo.memberId)

  if (res.code === 0) {
    agentMemberInfo.value = res.data
  }
}

async function handleCreateDialogues() {
  const res = await fetchCreateDialogues(agentId.value)

  if (res.code === 0) {
    dialogsId.value = res.data as string
  }
}

async function handleGetAutoPlayByAgentId() {
  const res = await fetchGetAutoPlayByAgentId<'Y' | 'N'>(agentId.value)

  if (res.code === 0) {
    answerAudioAutoPlay.value = res.data === 'Y'
  }
}

function handleBack() {
  if (!history.state.back) {
    router.replace({
      name: 'Home',
    })
  } else {
    router.back()
  }
}

function handleToLoginPage() {
  router.replace({
    name: 'Login',
    query: { redirect: encodeURIComponent(router.currentRoute.value.fullPath) },
  })
}

function handleCreateApplicationPage() {
  router.push({
    name: 'PersonalAppSetting',
  })
}

async function handleUpdateAutoPlaying(isAutoPlaying: boolean) {
  const autoplay = isAutoPlaying ? 'Y' : 'N'
  await fetchUpdateAutoPlay(agentId.value, autoplay)
}

function handleAddMessageItem(messageId: string, messageItem: ConversationMessageItem) {
  messageList.value.set(messageId, messageItem)
}

function handleUpdateSpecifyMessageItem(messageId: string, newMessageItem: Partial<ConversationMessageItem>) {
  const currentMessageItemInfo = messageList.value.get(messageId)

  if (currentMessageItemInfo) {
    const updatePropertyLength = Object.keys(newMessageItem).length

    if (updatePropertyLength > 4) {
      messageList.value.set(messageId, Object.assign({}, currentMessageItemInfo, newMessageItem))
      return
    }

    Object.entries<ValueOf<typeof newMessageItem>>(newMessageItem).forEach(([key, value]) => {
      if (Object.prototype.hasOwnProperty.call(currentMessageItemInfo, key)) {
        ;(currentMessageItemInfo as any)[key as keyof ConversationMessageItem] = value
      }
    })
  }
}

function handleDeleteMessageItem(messageId: string) {
  messageList.value.delete(messageId)
}

function handleUpdatePageScroll() {
  messageListRef.value?.scrollToBottom()
}

function handleClearAllMessage() {
  window.$message
    .ctWarning(
      t('common_module.dialogue_module.clear_message_dialog_content'),
      t('common_module.dialogue_module.clear_message_dialog_title'),
    )
    .then(() => {
      handleAudioPause()
      footerInputRef.value?.blockMessageResponse()
      messageList.value.clear()
      answerAudioPlaying.value = false
      window.$message.success(t('common_module.clear_success_message'))
    })
}

async function handleCreateContinueQuestions(replyTextContent: string) {
  createContinueQuestionsException.value = false

  fetchCreateContinueQuestions<string[]>({ input: replyTextContent })
    .then((res) => {
      if (res.code === 0) {
        continueQuestionList.value = res.data
        handleUpdatePageScroll()
      }
    })
    .catch(() => {
      createContinueQuestionsException.value = true
    })
}

function handleResetContinueQuestionList() {
  continueQuestionList.value = []
}

function howlSoundFactory(url: string) {
  const soundCtl = new Howl({
    src: [url],
    format: ['mpeg'],
    html5: !validBrowser(),
    preload: true,
    autoplay: true,
    onplay: () => {
      answerAudioPlaying.value = true
      currentSoundCtl.value = soundCtl

      if (currentPlayMessageItem.value) {
        currentPlayMessageItem.value.isVoiceLoading = false
        currentPlayMessageItem.value.isVoicePlaying = true
      }
    },
    onend: () => {
      soundCtl.unload()

      currentPlayAudioFragmentSerialNo.value += 1

      if (
        currentPlayMessageItem.value &&
        currentPlayAudioFragmentSerialNo.value > currentPlayMessageItem.value.voiceFragmentUrlList.length - 1
      ) {
        currentPlayMessageItem.value.isVoicePlaying = false
        answerAudioPlaying.value = false
      }

      let audioFragmentUrl = currentPlayMessageItem.value?.voiceFragmentUrlList[currentPlayAudioFragmentSerialNo.value]

      if (audioFragmentUrl) {
        howlSoundFactory(audioFragmentUrl)
      } else if (
        (currentPlayMessageItem.value?.voiceFragmentUrlList || []).length > currentPlayAudioFragmentSerialNo.value
      ) {
        let timerId: NodeJS.Timeout | null = null

        const audioFragmentCheck = () => {
          audioFragmentUrl = currentPlayMessageItem.value?.voiceFragmentUrlList[currentPlayAudioFragmentSerialNo.value]

          if (audioFragmentUrl) {
            timerId && clearInterval(timerId)

            howlSoundFactory(audioFragmentUrl)
          }
        }

        timerId = setInterval(audioFragmentCheck, 600)
      }
    },
  })

  return soundCtl
}

function handleAudioPlay(currentMessageItem: ConversationMessageItem, specificPlayUrl?: string) {
  handleAudioPause()

  currentPlayMessageItem.value = currentMessageItem
  currentPlayAudioFragmentSerialNo.value = 0

  const audioUrl = specificPlayUrl || currentMessageItem.voiceFragmentUrlList[0]

  howlSoundFactory(audioUrl)

  isSoundCtlCreated.value = true
}

function handleAudioPause(isClearMessageList = false) {
  if (currentSoundCtl.value) {
    currentSoundCtl.value.pause()
    currentSoundCtl.value.unload()

    currentSoundCtl.value = null

    isSoundCtlCreated.value = false
  }

  currentPlayMessageItem.value && (currentPlayMessageItem.value.isVoicePlaying = false)
  answerAudioPlaying.value = false

  if (isClearMessageList) {
    messageList.value.clear()
    footerInputRef.value?.blockMessageResponse()
  }
}
</script>

<template>
  <div v-loading="fullScreenLoading" class="flex h-screen min-h-[500px] w-full flex-col overflow-y-hidden">
    <main class="h-full min-w-[1100px]">
      <PageHeader
        :agent-application-config="agentApplicationConfig"
        :agent-member-info="agentMemberInfo"
        @back="handleBack"
        @to-login="handleToLoginPage"
        @to-create-application="handleCreateApplicationPage"
      />

      <div class="h-[calc(100%-68px)] w-full bg-[#f2f5f9]">
        <div class="relative mx-auto flex h-full w-[1000px] flex-col overflow-hidden">
          <div v-show="isEnableVoice" class="absolute right-10 top-7 flex select-none items-center gap-2">
            <span>{{ t('common_module.voice_auto_play') }}</span>
            <n-switch v-model:value="answerAudioAutoPlay" size="small" @update:value="handleUpdateAutoPlaying">
              <template #checked> {{ t('common_module.open') }} </template>
              <template #unchecked> {{ t('common_module.close') }} </template>
            </n-switch>
          </div>

          <div v-if="messageList.size === 0" class="w-full flex-1 overflow-auto px-5">
            <Preamble :agent-application-config="agentApplicationConfig" />
          </div>

          <div v-if="messageList.size > 0" class="flex w-full flex-1 flex-col overflow-hidden">
            <div class="relative mt-20 flex-1 overflow-auto">
              <MessageList
                ref="messageListRef"
                :agent-application-config="agentApplicationConfig"
                :message-list="messageList"
                :continuous-question-status="continuousQuestionStatus"
                :continuous-question-list="continueQuestionList"
                :is-answer-response-loading="isAnswerResponseLoading"
                :create-continue-questions-exception="createContinueQuestionsException"
                @audio-play="handleAudioPlay"
                @audio-pause="handleAudioPause"
              />
            </div>
          </div>

          <div class="px-5">
            <FooterInput
              ref="footerInputRef"
              v-model:is-answer-response-loading="isAnswerResponseLoading"
              :message-list="messageList"
              :dialogs-id="dialogsId"
              :agent-id="agentApplicationConfig.baseInfo.agentId"
              :continuous-question-status="continuousQuestionStatus"
              :is-enable-document-parse="isEnableDocumentParse"
              :is-enable-upload-image="isEnableUploadImage"
              :is-enable-voice="isEnableVoice"
              :answer-audio-auto-play="answerAudioAutoPlay"
              :answer-audio-playing="answerAudioPlaying"
              :timbre-id="agentApplicationConfig.voiceConfig.timbreId"
              @add-message-item="handleAddMessageItem"
              @update-specify-message-item="handleUpdateSpecifyMessageItem"
              @delete-message-item="handleDeleteMessageItem"
              @update-page-scroll="handleUpdatePageScroll"
              @clear-all-message="handleClearAllMessage"
              @to-login="handleToLoginPage"
              @create-continue-questions="handleCreateContinueQuestions"
              @reset-continue-question-list="handleResetContinueQuestionList"
              @audio-play="handleAudioPlay"
              @audio-pause="handleAudioPause"
            />
          </div>
        </div>
      </div>
    </main>
  </div>
</template>

<style scoped lang="scss">
@include custom-scrollbar(6px);
</style>
