<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/mobile-page-header.vue'
import Preamble from './components/preamble.vue'
import MessageList from './components/message-list.vue'
import FooterInput from './components/footer-input.vue'
import { PersonalAppConfigState } from '@/store/types/personal-app-config'
import { useUserStore } from '@/store/modules/user'
import { defaultPersonalAppConfigState } from '@/store/modules/personal-app-config'
import {
  fetchCreateContinueQuestions,
  fetchCreateDialogues,
  fetchGetApplicationInfo,
  fetchGetAutoPlayByAgentId,
  fetchUpdateAutoPlay,
} from '@/apis/agent-application'
import { useLayoutConfig } from '@/composables/useLayoutConfig'
import { validBrowser } from '@/utils/browser-detection'

const { t } = useI18n()

const router = useRouter()

const userStore = useUserStore()

const { isMobile } = useLayoutConfig()

const fullScreenLoading = ref(false)
const agentId = ref('')
const dialogsId = ref('')

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 isEnableDocumentParse = computed(() => {
  return agentApplicationConfig.value.knowledgeConfig.isDocumentParsing === 'Y'
})

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: 'ShareWebApplication',
      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
    })
    .catch(() => {
      router.replace({ name: 'Home' })
    })
}

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 handleToLoginPage() {
  router.push({
    name: 'Login',
    query: { redirect: encodeURIComponent(router.currentRoute.value.fullPath) },
  })
}

function handleCreateApplicationPage() {
  window.$dialog.info({
    title: t('share_agent_module.create_agent_dialogue_title'),
    content: t('share_agent_module.create_agent_dialogue_content'),
    positiveText: t('share_agent_module.create_agent_dialogue_positive_text'),
  })
}

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) {
  const res = await fetchCreateContinueQuestions<string[]>({ input: replyTextContent })

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

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="h-full w-full">
    <PageHeader
      :agent-title="agentApplicationConfig.baseInfo.agentTitle"
      @to-login="handleToLoginPage"
      @to-create-application="handleCreateApplicationPage"
    />

    <div class="flex h-[calc(100%-48px)] w-full flex-col bg-[#f2f5f9]">
      <div class="mt-5 flex select-none justify-end px-4">
        <div v-show="isEnableVoice" class="flex 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>

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

      <div v-if="messageList.size > 0" class="flex w-full flex-1 flex-col overflow-hidden pt-5">
        <div class="flex-1 overflow-auto">
          <MessageList
            ref="messageListRef"
            :agent-application-config="agentApplicationConfig"
            :message-list="messageList"
            :continuous-question-status="continuousQuestionStatus"
            :continuous-question-list="continueQuestionList"
            @audio-play="handleAudioPlay"
            @audio-pause="handleAudioPause"
          />
        </div>
      </div>

      <div class="footer-operation px-4">
        <FooterInput
          ref="footerInputRef"
          :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"
          :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>
</template>

<style lang="scss" scoped>
.footer-operation {
  padding-bottom: constant(safe-area-inset-bottom);
  padding-bottom: env(safe-area-inset-bottom);
}

@include custom-scrollbar(4px);
</style>
