<script setup lang="ts">
import { computed, inject, onMounted, onUnmounted, ref, shallowRef } from 'vue'
import Preamble from './components/preamble.vue'
import { useI18n } from 'vue-i18n'
import { useRouter } from 'vue-router'
import { Emitter } from 'mitt'
import { Howl } from 'howler'
import type { ValueOf } from 'type-fest'
import MessageList from './components/message-list.vue'
import FooterInput from './components/footer-input.vue'
import MemoryPreviewModal from './components/memory-preview-modal.vue'
import { fetchCreateContinueQuestions } from '@/apis/agent-application'
import { usePersonalAppConfigStore } from '@/store/modules/personal-app-config'
import { useSystemLanguageStore } from '@/store/modules/system-language'
import { Brain, Down } from '@icon-park/vue-next'
import { validBrowser } from '@/utils/browser-detection'

const { t } = useI18n()

const router = useRouter()

const personalAppConfigStore = usePersonalAppConfigStore()
const systemLanguageStore = useSystemLanguageStore()

const emitter = inject<Emitter<MittEvents>>('emitter')

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'>(personalAppConfigStore.commConfig.continuousQuestionStatus)

const continuousQuestionList = ref<string[]>([])
const isShowMemoryPreviewModal = ref(false)
const selectedMemoryTabName = ref('memoryVariable')
const answerAudioAutoPlay = ref(personalAppConfigStore.voiceConfig.defaultOpen === 'Y')
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 isEnLanguage = computed(() => {
  return systemLanguageStore.currentLanguageInfo.key === 'en'
})

onMounted(() => {
  emitter?.on('resetAgent', () => {
    handleAudioPause()
    footerInputRef.value?.blockMessageResponse()
    messageList.value.clear()
  })
})

onUnmounted(() => {
  emitter?.off('resetAgent')
  handleAudioPause()
  footerInputRef.value?.blockMessageResponse()
  messageList.value.clear()
})

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) {
        continuousQuestionList.value = res.data
        handleUpdatePageScroll()
      }
    })
    .catch(() => {
      createContinueQuestionsException.value = true
    })
}

function handleUpdateContinueQuestionStatus(status: 'default' | 'close') {
  continuousQuestionStatus.value = status
  continuousQuestionList.value = []
}

function handleUpdateAudioAutoPlaying(isAutoPlaying: boolean) {
  personalAppConfigStore.voiceConfig.defaultOpen = isAutoPlaying ? 'Y' : 'N'
}

function handleTurnMultiModelDialogue() {
  if (!personalAppConfigStore.baseInfo.agentId) return

  router.push({
    name: 'MultiModelDialogue',
    params: {
      agentId: personalAppConfigStore.baseInfo.agentId,
    },
  })
}

function handleOpenMemoryPreviewModal(MemoryTabName: string) {
  selectedMemoryTabName.value = MemoryTabName
  isShowMemoryPreviewModal.value = true
}

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 class="flex h-full min-w-[300px] flex-[2_2_0%] flex-col overflow-hidden bg-[#f2f5f9]">
    <div class="flex items-center justify-between">
      <div class="mb-[18px] flex w-full items-center justify-between px-5 py-[18px]">
        <p class="text-base">
          {{ t('personal_space_module.agent_module.agent_setting_module.agent_config_module.preview') }}
        </p>

        <div class="flex items-center" :class="isEnLanguage ? 'gap-1.5 2xl:gap-4' : 'gap-4'">
          <n-popover placement="bottom" trigger="hover" :show-arrow="false">
            <template #trigger>
              <div v-show="personalAppConfigStore.voiceConfig.timbreId" class="text-font-color cursor-pointer">
                <i class="iconfont icon-yuyin mr-1 text-sm" />
                <span>{{ t('common_module.voice') }}</span>
              </div>
            </template>
            <div class="flex items-center gap-2.5">
              <span>{{ t('common_module.voice_auto_play') }}</span>
              <n-switch v-model:value="answerAudioAutoPlay" size="small" @update:value="handleUpdateAudioAutoPlaying">
                <template #checked> {{ t('common_module.open') }} </template>
                <template #unchecked> {{ t('common_module.close') }} </template>
              </n-switch>
            </div>
          </n-popover>

          <div
            :class="
              personalAppConfigStore.baseInfo.agentId
                ? 'text-font-color hover:text-theme-color cursor-pointer'
                : 'text-gray-font-color cursor-not-allowed'
            "
            @click="handleTurnMultiModelDialogue"
          >
            <i class="iconfont icon-moxing mr-1 text-sm" />
            <span>{{ t('common_module.multi_model_debug') }}</span>
          </div>

          <div
            v-show="
              personalAppConfigStore.commConfig.variableStructure !== null ||
              personalAppConfigStore.commConfig.isLongMemory === 'Y'
            "
            class="cursor-pointer"
          >
            <n-popover placement="bottom" trigger="hover" class="p-[4px]!" :show-arrow="false">
              <template #trigger>
                <div class="flex items-center justify-center text-[14px]">
                  <Brain theme="outline" size="15" fill="#333" />
                  <div class="mx-[4px]">
                    {{ t('personal_space_module.agent_module.agent_setting_module.agent_config_module.memory') }}
                  </div>
                  <Down theme="outline" size="15" fill="#333" class="mt-[2px] inline" />
                </div>
              </template>
              <div>
                <div
                  v-show="personalAppConfigStore.commConfig.variableStructure !== null"
                  class="cursor-pointer px-[8px] py-[5px] hover:bg-[#f2f5f9]"
                  @click="handleOpenMemoryPreviewModal('memoryVariable')"
                >
                  {{ t('personal_space_module.agent_module.agent_setting_module.agent_config_module.memory_variable') }}
                </div>
                <div
                  v-show="personalAppConfigStore.commConfig.isLongMemory === 'Y'"
                  class="cursor-pointer px-[8px] py-[5px] hover:bg-[#f2f5f9]"
                  @click="handleOpenMemoryPreviewModal('memoryFragment')"
                >
                  {{ t('personal_space_module.agent_module.agent_setting_module.agent_config_module.memory_fragment') }}
                </div>
              </div>
            </n-popover>
          </div>
        </div>
      </div>
    </div>

    <div class="flex w-full flex-1 overflow-hidden">
      <div v-show="messageList.size === 0" class="flex w-full">
        <Preamble />
      </div>

      <div v-show="messageList.size > 0" class="w-full">
        <MessageList
          ref="messageListRef"
          :message-list="messageList"
          :continuous-question-status="continuousQuestionStatus"
          :continuous-question-list="continuousQuestionList"
          :is-answer-response-loading="isAnswerResponseLoading"
          :create-continue-questions-exception="createContinueQuestionsException"
          @audio-play="handleAudioPlay"
          @audio-pause="handleAudioPause"
        />
      </div>
    </div>

    <FooterInput
      ref="footerInputRef"
      v-model:is-answer-response-loading="isAnswerResponseLoading"
      :continuous-question-status="continuousQuestionStatus"
      :message-list="messageList"
      :answer-audio-auto-play="answerAudioAutoPlay"
      :answer-audio-playing="answerAudioPlaying"
      @add-message-item="handleAddMessageItem"
      @update-specify-message-item="handleUpdateSpecifyMessageItem"
      @delete-message-item="handleDeleteMessageItem"
      @update-page-scroll="handleUpdatePageScroll"
      @clear-all-message="handleClearAllMessage"
      @create-continue-questions="handleCreateContinueQuestions"
      @update-continuous-question-status="handleUpdateContinueQuestionStatus"
      @audio-play="handleAudioPlay"
      @audio-pause="handleAudioPause"
    />

    <MemoryPreviewModal v-model="isShowMemoryPreviewModal" :data="selectedMemoryTabName" />
  </div>
</template>

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