<script setup lang="ts">
import { computed, onMounted, onUnmounted, ref, shallowRef, watch } 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 { SmartFormTypes } from '../personal-space/personal-app-setting/components/agent-config/types'
import DHIframe from '@bddh/starling-dhiframe'
import DigitalHuman from './components/digital-human.vue'

const { t } = useI18n()

const router = useRouter()

const userStore = useUserStore()

const { isMobile } = useLayoutConfig()

const dhIframe = shallowRef<DHIframe | null>(null)
const dhIframeMessageSubscription = ref(false)

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 isAnswerResponseInterrupt = ref(false) // 回答响应被中断

const onDigitalHumanMessage = (msg: any) => {
  if (msg.origin === 'https://open.xiling.baidu.com') {
    const { type, content } = msg.data

    switch (type) {
      case 'rtcState':
        break
      case 'msg': {
        const { action } = content

        if (action === 'RENDER_START') {
          if (currentPlayMessageItem.value) {
            currentPlayMessageItem.value.isVoiceLoading = false
            currentPlayMessageItem.value.isVoicePlaying = true
          }
        } else if (action === 'RENDER_COMPLETED') {
          currentPlayAudioFragmentSerialNo.value += 1

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

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

          if (audioFragmentUrl) {
            dhIframe.value?.sendMessage({
              action: 'TEXT_RENDER',
              body: `<speak><audio src="${audioFragmentUrl}"/></speak>`,
              // requestId: requestId || nanoid(),
            })
          } 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)

                dhIframe.value?.sendMessage({
                  action: 'TEXT_RENDER',
                  body: `<speak><audio src="${audioFragmentUrl}"/></speak>`,
                  // requestId: requestId || nanoid(),
                })
              }
            }

            timerId = setInterval(audioFragmentCheck, 600)
          }
        }

        break
      }
      default:
        break
    }
  }
}

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

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

watch(dhIframe, (newDhIframe) => {
  if (newDhIframe && !dhIframeMessageSubscription.value) {
    newDhIframe.registerMessageReceived(onDigitalHumanMessage)

    dhIframeMessageSubscription.value = true
  }
})

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 getAutoPlayByAgentId()
    }

    fullScreenLoading.value = false
    return
  }

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

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

  dhIframe.value?.removeMessageReceived(onDigitalHumanMessage)
})

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()

      if (agentApplicationConfig.value.digitalhumanConfig.enable === 'Y') {
        dhIframe.value = new DHIframe('digital-human-iframe')
      }
    })
    .catch(() => {
      router.replace({ name: 'Root' })
    })
}

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
  }
}

function getAutoPlayByAgentId() {
  fetchGetAutoPlayByAgentId<'Y' | 'N'>(agentId.value).then((res) => {
    if (res.code === 0) {
      answerAudioAutoPlay.value = res.data === 'Y'
    }
  })
}

function handleBack() {
  if (!history.state.back) {
    router.replace({
      name: 'Root',
    })
  } 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]) => {
      ;(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]

  if (!dhIframe.value) {
    howlSoundFactory(audioUrl)

    isSoundCtlCreated.value = true
    return
  }

  dhIframe.value.sendCommand({
    subType: 'muteAudio',
    subContent: false,
  })

  dhIframe.value?.sendMessage({
    action: 'TEXT_RENDER',
    body: `<speak><audio src="${audioUrl}"/></speak>`,
    // requestId: requestId || nanoid(),
  })
}

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

    currentSoundCtl.value = null

    isSoundCtlCreated.value = false
  }

  dhIframe.value?.sendMessage({
    action: 'TEXT_RENDER',
    body: '<interrupt></interrupt>',
  })

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

  if (isClearMessageList) {
    messageList.value.clear()
    footerInputRef.value?.blockMessageResponse()
  }
}

function onSmartFormsStatusFreezeCheck(smartFormType: SmartFormTypes) {
  /* 重置智能表单项目 */
  if (smartFormType) {
    messageList.value.forEach((item) => {
      if (item.smartFormInfo && item.smartFormInfo.type === smartFormType) {
        item.smartFormInfo.isDisabled = true
      }
    })
  }
}
</script>

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

      <div
        id="share-agent-web-container"
        class="relative h-[calc(100%-68px)] w-full overflow-hidden bg-[#f2f5f9]"
        :class="dhIframe ? 'flex' : ''"
      >
        <div
          class="relative flex h-full flex-col overflow-hidden"
          :class="dhIframe ? 'ml-[150px] mt-auto w-[900px]' : 'mx-auto w-[1000px]'"
        >
          <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"
                @update-specify-message-item="handleUpdateSpecifyMessageItem"
              />
            </div>
          </div>

          <div class="px-5">
            <FooterInput
              ref="footerInputRef"
              v-model:is-answer-response-loading="isAnswerResponseLoading"
              v-model:is-answer-response-interrupt="isAnswerResponseInterrupt"
              :message-list="messageList"
              :dialogs-id="dialogsId"
              :agent-id="agentApplicationConfig.baseInfo.agentId"
              :continuous-question-status="continuousQuestionStatus"
              :is-enable-document-parse="isEnableDocumentParse"
              :is-enable-voice="isEnableVoice"
              :answer-audio-auto-play="answerAudioAutoPlay"
              :answer-audio-playing="answerAudioPlaying"
              :dh-iframe="dhIframe"
              :agent-application-config="agentApplicationConfig"
              @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"
              @smart-forms-status-freeze-check="onSmartFormsStatusFreezeCheck"
            />
          </div>
        </div>

        <DigitalHuman
          v-if="dhIframe"
          :agent-id="agentApplicationConfig.baseInfo.agentId"
          :digitalhuman-config="agentApplicationConfig.digitalhumanConfig"
          :dh-iframe="dhIframe"
        />
      </div>
    </main>
  </div>
</template>

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