<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 { showDialog } from 'vant'
import 'vant/es/dialog/style'
import { useEventListener } from '@vueuse/core'
import type { ValueOf } from 'type-fest'
import PageHeader from './components/mobile/page-header.vue'
import Preamble from './components/mobile/preamble.vue'
import MessageList from './components/mobile/message-list.vue'
import FooterInput from './components/mobile/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'
import { PluginType } from '@/enums/plugin'

const { t } = useI18n()

const router = useRouter()

const userStore = useUserStore()

const { isMobile } = useLayoutConfig()

useEventListener(document, 'visibilitychange', () => {
  if (document.visibilityState === 'hidden') {
    handleExitPage()
    handleAudioPause()
  } else if (document.visibilityState === 'visible' && lastQuestionContent.value) {
    window.$dialog.warning({
      title: t('share_agent_module.create_agent_dialogue_title'),
      content: t('common_module.dialogue_module.content_interrupted'),
      negativeText: t('common_module.cancel_btn_text'),
      positiveText: t('common_module.confirm_btn_text'),
      onPositiveClick() {
        footerInputRef.value?.handleMessageSend(lastQuestionContent.value)
        lastQuestionContent.value = ''
      },
      onNegativeClick() {
        lastQuestionContent.value = ''
      },
      onAfterLeave() {
        lastQuestionContent.value = ''
      },
    })
  }
})

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 isAnswerResponseLoading = ref(false)
const createContinueQuestionsException = ref(false)
const lastQuestionContent = ref('') // 上一次提问内容
const isAnswerResponseInterrupt = ref(false) // 回答响应被中断

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

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

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()
      await handleInitSoundPlay()
    }
    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() {
  showDialog({
    title: t('share_agent_module.create_agent_dialogue_title'),
    message: t('share_agent_module.create_agent_dialogue_content'),
    confirmButtonText: 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() {
  showDialog({
    title: t('common_module.dialogue_module.clear_message_dialog_title'),
    message: t('common_module.dialogue_module.clear_message_dialog_content'),
    showCancelButton: true,
    cancelButtonText: t('common_module.cancel_btn_text'),
    confirmButtonText: t('common_module.confirm_btn_text'),
    confirmButtonColor: '#F25744',
  }).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()
  }
}

// 静默播放
function handleInitSoundPlay() {
  const sound = new Howl({
    src: ['https://gsst-modellink-prod.gz.bcebos.com/data/20250305/1741155741444.wav'],
    html5: !validBrowser(),
    volume: 0,
    preload: true,
    onload: () => {
      sound.play()
      setTimeout(() => sound.stop(), 100)
    },
    onplay: () => {
      sound.stop()
      sound.unload()
    },
  })
}

// 退出页面中断对话
function handleExitPage() {
  if (messageList.value.size > 0 && isAnswerResponseLoading.value) {
    const messageListArr = Array.from(messageList.value.values())
    if (messageListArr.length >= 2) {
      lastQuestionContent.value = messageListArr[messageListArr.length - 2]?.textContent || ''
    }

    isAnswerResponseInterrupt.value = true
    footerInputRef.value?.errorMessageResponse()
  }
}

function handleToLogoutPage() {
  showDialog({
    title: t('login_module.confirm_to_logout'),
    message: t('login_module.you_can_login_again_after_logout'),
    showCancelButton: true,
    cancelButtonText: t('common_module.cancel_btn_text'),
    confirmButtonText: t('common_module.logout'),
    confirmButtonColor: '#F25744',
  }).then(() => {
    userStore.logout()
    handleAudioPause()
    footerInputRef.value?.blockMessageResponse()
    messageList.value.clear()
    answerAudioPlaying.value = false
  })
}
</script>

<template>
  <div v-loading="fullScreenLoading" class="share-mobile-container">
    <PageHeader
      v-model:is-enable-voice="isEnableVoice"
      v-model:answer-audio-auto-play="answerAudioAutoPlay"
      :agent-title="agentApplicationConfig.baseInfo.agentTitle"
      @to-login="handleToLoginPage"
      @to-logout="handleToLogoutPage"
      @to-create-application="handleCreateApplicationPage"
      @update-auto-playing="handleUpdateAutoPlaying"
    />

    <div class="main-container">
      <div v-if="messageList.size === 0" class="w-full flex-1 overflow-auto">
        <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 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"
            :is-answer-response-interrupt="isAnswerResponseInterrupt"
            @audio-play="handleAudioPlay"
            @audio-pause="handleAudioPause"
          />
        </div>
      </div>

      <div class="footer-container">
        <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-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>
</template>

<style lang="scss" scoped>
.share-mobile-container {
  @apply bg-px-share-h5_bg-png h-full w-full bg-cover bg-no-repeat;

  .main-container {
    @apply flex h-[calc(100%-58px)] w-full flex-col;
  }

  .footer-container {
    padding-bottom: constant(safe-area-inset-bottom);
    padding-bottom: env(safe-area-inset-bottom);

    @apply bg-white px-[10px] py-[7px];
  }
}

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