<template>
  <div class="chat-container">
    <div class="mb15">
      <div v-if="isVoiceMode && newMessage" class="isVoiceModeText">
        <textarea class="textarea" placeholder="请输入内容" v-model="newMessage"></textarea>
      </div>
      <div class="new-session ">
        <button @click="startNewConversation">
          <svg-icon icon-class="add" class-name="chat-icon" style="color: #000;"></svg-icon>
          开启新会话
        </button>
      </div>
      <footer class="chat-footer pt10 pb10">
        <!-- 输入框 or 按住说话提示 -->
        <div class="input-wrapper">
          <input v-if="!isVoiceMode" type="text" v-model="newMessage" placeholder="你有什么想知道的？"
            @keyup.enter="beforeSend" />
          <div v-else class="voice-hint-container" :class="{ disabled: messageStatus === 'send' }"
            @mousedown="startRecording" @selectstart="() => false" @mouseup="stopRecording" @mouseleave="stopRecording"
            @touchend="stopRecording" @touchstart="startRecording">
            <div class="waveform" :class="{ active: isRecording }">
              <span class="bar"></span>
              <span class="bar"></span>
              <span class="bar"></span>
              <span class="bar"></span>
              <span class="bar"></span>
            </div>
            <!--<div class="hint-text" v-if='!isRecording'>按住说话</div>-->
          </div>
          <!-- 发送按钮 -->
          <button @click="beforeSend" :disabled="messageStatus === 'send'"
            :class="{ disabled: messageStatus === 'send' }" class="ml10 active send">
            发送
          </button>
          <!-- <button @click="cancelSend">取消</button> -->
        </div>
        <!-- 语音按钮：按住说话 -->
        <button @click="isVoiceMode = !isVoiceMode" class="mic-button ml10 mr10" v-if="false">
          <!-- <svg-icon v-if="!isVoiceMode" icon-class="voice" class-name="chat-icon ml10 wh25"></svg-icon>
        <span v-else class="ml15 mr5 input-icon">⌨</span> -->
        </button>
      </footer>
    </div>
  </div>
</template>

<script>
import SvgIcon from '@/components/svg-icon/index.vue'
import { audioToText, gwcsChat } from '@/api/generatedApi'

export default {
  components: {
    SvgIcon,
  },
  props: {
    messages: {
      type: Array,
      default: () => [],
    },
    messageStatus: {
      type: String,
      default: 'stop',
    },
    isDeep: {
      type: Boolean,
      default: false,
    },
    isSearching: {
      type: Boolean,
      default: false,
    },
    conversationId: {
      type: String,
      default: '',
    },
    productName: {
      type: String,
      default: '',
    },
    autoScrollEnabled: {
      type: Boolean,
      default: false,
    },
    chatData: {
      type: Object,
      default: () => ({}),
    },
    // action: {
    //   type: String,
    //   default: 'normal_chat',
    // }
  },
  data() {
    return {
      requestIndex: 1,
      requestSingle: undefined,
      // 管控单次请求，当 abort 之后， while 后面的会进行重复请求
      single: false,
      isThink: false,
      newMessage: '',
      isRecording: false,
      mediaRecorder: null,
      audioChunks: [],
      isRecognizing: false,
      isVoiceMode: false,
      answerMap: '',
      currentMessage: null,
      // 随后生成的消息
      genMessage: null,
      messageInfo: {
        is_complete: false.toString(),
        information: '',
      },
      currentMessageID: "",
      // 打字机相关
      typingText: '',
      typingQueue: [],
      typingQueueText: [],
      isTyping: false,
      typingSpeed: 30,
      typingTimeout: null,
    }
  },
  watch: {
    isTyping(value) {
      // 通过 typing 控制发送的状态 
      // console.log(`typing status change `, value)
      if (!value) this.$emit('update:messageStatus', 'stop')
      else this.$emit('update:messageStatus', 'send')
    }
  },
  methods: {
    deepInternet() {
      this.$emit('update:isDeep', !this.isDeep)
    },
    searchInternet() {
      this.$emit('update:isSearching', !this.isSearching)
    },
    startNewConversation() {
      this.$emit('initBotMessage')
      // this.$emit('update:messages', [])
      this.$emit('update:conversationId', '')
      this.$emit('update:productName', '')
      this.$emit('update:messageStatus', 'stop')
      this.typingQueue = []
    },
    async startRecording() {
      if (this.messageStatus === 'send') return
      if (this.isRecognizing) return
      try {
        const stream = await navigator.mediaDevices.getUserMedia({ audio: true })
        this.mediaRecorder = new MediaRecorder(stream)
        this.audioChunks = []
        this.mediaRecorder.ondataavailable = (event) => {
          if (event.data.size > 0) {
            this.audioChunks.push(event.data)
          }
        }
        this.mediaRecorder.onstop = () => {
          this.handleStopRecording()
        }
        this.mediaRecorder.start()
        this.isRecording = true
      } catch (err) {
        alert('无法访问麦克风，请检查权限')
        console.error(err)
      }
    },
    stopRecording() {
      if (this.mediaRecorder && this.isRecording) {
        this.mediaRecorder.stop()
        this.isRecording = false
      }
    },
    async handleStopRecording() {
      this.isRecognizing = true
      const blob = new Blob(this.audioChunks, { type: 'audio/webm' })
      try {
        const text = await this.callVoiceRecognitionAPI(blob)
        if (text) {
          this.newMessage = text
          this.$emit('update:messageStatus', 'stop')
        }
      } catch (error) {
        console.error('语音识别失败:', error)
        this.newMessage = ''
      } finally {
        this.isRecognizing = false
      }
    },
    callVoiceRecognitionAPI(blob) {
      return new Promise((resolve, reject) => {
        const formData = new FormData()
        formData.append('file', blob)
        formData.append('appType', 'haslBigHelper')
        formData.append('user', 'chenyuda')
        audioToText(formData)
          .then((res) => {
            if (res) {
              resolve(res.content)
            }
          })
          .catch((err) => {
            reject(err)
          })
      })
    },
    axiosGetAiChat() {
      const abortController = new AbortController()
      this.requestSingle = abortController
      this.messageInfo.information = this.single ? this.messageInfo.information : this.newMessage

      // 重置 answerMap
      this.answerMap = ''
      // this.typingQueue = []
      this.currentMessage = JSON.parse(JSON.stringify(this.messageInfo))
      let params = {
        appType: "gwcsHelper",
        conversationId: this.conversationId,
        message: JSON.stringify(this.messageInfo),
        user: "gwcs-test",
        inputs: {},
      }
      this.currentMessage = {
        ...this.messageInfo,
        type: 'bot',
        text: '',
        think: '',
        isThink: false,
        showThink: false,
        isLike: false,
        isDisLike: false,
      }
      if (this.single) {
        // this.$set(this.messages, this.messages.length - 1, { ...this.currentMessage })
        this.$set(this.messages, this.messages.length, this.genMessage = { ...this.currentMessage })
      } else {
        this.messages.push(this.currentMessage)
      }
      // 如果有自定义参数
      if (this.chatData) {
        for (let k in this.chatData) {
          params[k] = this.chatData[k]
        }
      }
      this.newMessage = ''
      fetch(gwcsChat(), {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        signal: abortController.signal,
        body: JSON.stringify(params),
        timeout: 60000,
      })
        .then(async (res) => {
          await this.processStreamResponse(res, this.requestIndex)
          this.single = false
        })
        .catch((err) => {
          // debugger
          this.$emit('update:messageStatus', 'stop')
        })
    },
    async processStreamResponse(response, requestIndex) {
      if (!response.ok) throw new Error(`HTTP错误: ${response.status}`)
      if (!response.body) {
        console.error('响应体不存在:', response)
        return
      }
      const reader = response.body.getReader()
      let buffer = ''
      while (true) {
        try {
          const { done, value } = await reader.read()
          if (done) break
          buffer += new TextDecoder().decode(value)
          const lines = buffer.split('\n')
          lines.slice(0, -1).forEach((line) => {
            const parsed = this.parseStreamLine(line)
            if (parsed) this.updateMessageContent(parsed, requestIndex)
          })
          buffer = lines[lines.length - 1] || ''
        } catch (error) {
          console.error('读取流数据时发生错误:', error)
          break
        }
      }
    },
    parseStreamLine(line) {
      try {
        const cleanLine = line.replace(/^data:\s*/, '')
        if (!cleanLine) return null
        const data = JSON.parse(cleanLine)
        // debugger/
        // this.conversationId = data.conversation_id
        this.$emit('update:conversationId', data.conversation_id)
        if (data.answer) {
          this.answerMap += data.answer

          const is_complete = /<is_complete>([^<]*)(?:<\/is_complete>)?/.exec(this.answerMap)
          const information = /<information>([^<]*)(?:<\/information>)?/.exec(this.answerMap)
          const text = /<text>([^<]*)(?:<\/text>)?/.exec(this.answerMap)
          // console.log(this.answerMap);

          this.messageInfo.information = information ? information[1].trim() : this.newMessage
          this.messageInfo.is_complete = is_complete ? is_complete[1].trim() : 'false'
          if (is_complete && is_complete[1] === 'true' && text && text[1].trim() === '') {
            this.requestSingle.abort()
            this.single = true
            this.axiosGetAiChat()
            this.requestIndex++
            // this.typingQueue = []
            return null
          }
        }

        return this.updateConversationState(data)
      } catch (error) {
        console.error('流数据解析失败:', error)
        return null
      }
    },
    updateConversationState(data) {
      this.$emit('update:conversationId', data.conversation_id || '')
      if (data.answer && data.answer.indexOf('<think>') !== -1) {
        data.isThink = true
        this.isThink = true
        this.$emit('getIsThink', true)
      }
      if (data.answer && data.answer.indexOf('</think>') !== -1) {
        data.isThink = true
        this.isThink = false
        this.$emit('getIsThink', false)
      }
      if (data.answer && this.isThink) {
        data.isThink = true
      }
      if (data.answer && !this.isThink) {
        data.isThink = false
      }

      return data
    },
    updateMessageContent(parse, requestIndex) {
      let { event, answer, isThink, message_id } = parse
      this.currentMessageID = message_id
      if (!this.currentMessage || !answer) return
      if (event !== 'message') return

      const mode = isThink ? 'think' : 'text'
      const chars = {
        answer: answer,
        isThink: isThink,
        message_id
      }

      this.typingQueue.push(chars)
      if (!this.isTyping) {
        this.startTypingAnimation(mode, requestIndex)
      }
    },
    startTypingAnimation(mode, requestIndex) {
      this.isTyping = true
      const typeNextChar = () => {
        if (this.typingQueue.length === 0) {
          this.isTyping = false
          return
        }

        // 取出一个完整文本块
        const chunk = this.typingQueue.shift()
        if (chunk.message_id !== this.currentMessageID) {
          console.log('message_id !== this.currentMessageID');
          typeNextChar()
          return
        }
        // console.log(this.messages);

        const chars = Array.from(chunk.answer)

        const isThink = chunk.isThink
        // 内部递归函数，用于逐字输出当前块
        const outputChar = () => {
          if (chars.length === 0) {
            // 当前块输出完毕，继续处理下一个
            setTimeout(typeNextChar, 10)
            return
          }
          const char = chars.shift() || ''
          // this.$set(this.currentMessage, isThink ? 'think' : 'text', this.currentMessage[isThink ? 'think' : 'text'] + char)
          if (requestIndex === 2) {
            console.log('requestIndex === 2', char);

            this.$set(this.genMessage, 'text', this.genMessage.text + char)
            // this.messages.splice(this.messages.length - 1, 1, this.genMessage)
          } if (requestIndex === 1) {
            console.log('requestIndex === 1', char);

            this.$set(this.currentMessage, 'text', this.currentMessage.text + char)
          }
          const delay = this.getTypingDelay(char)
          setTimeout(outputChar, delay)
        }

        outputChar()
      }

      typeNextChar()
    },
    getTypingDelay(char) {
      if (['。', '！', '？', '，', '\n'].includes(char)) {
        return this.typingSpeed * 3
      }
      return this.typingSpeed
    },
    beforeSend() {
      if (this.messageStatus === 'send') return
      if (this.newMessage.trim() === '') return
      this.newMessage = this.newMessage.replace(/<[^>]+>/g, '')
      this.messages.push({ type: 'user', text: this.newMessage })
      this.sendMessage()
    },
    cellClick(item) {
      this.newMessage = item.title
      this.messages.push({ type: 'user', text: this.newMessage })
      this.sendMessage()
    },
    sendMessage() {
      this.$emit('update:messageStatus', 'send')
      this.$emit('update:autoScrollEnabled', true)
      this.axiosGetAiChat()
    },

    cancelSend() {
      this.requestSingle.abort()
    }
  }
}
</script>
<style lang="scss" scoped>
$primary-color: #57a6fc;
$primary-text-color: #f6aa21;
$primary-trans-color: rgba(135, 162, 208, 0.5);

.chat-container {
  background-color: #fafafa;
  display: flex;

  &>div {
    margin: 0 auto;
    background-color: #fff;
    width: 90%;
    border-radius: 10px;
  }
}

.input-icon {
  font-size: 20px;
  color: #707070;
}

.wh25 {
  width: 25px;
  height: 25px;
}

.isVoiceModeText {
  display: flex;

  textarea {
    flex: 1;
    max-height: 80px;
    resize: none;
    outline: none;
    overflow-y: auto;
    padding: 10px;
    border: none;
  }
}

.section {
  font-size: 13px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 10px 10px 0 10px;

  gap: 10px;

  button {
    transition: all 0.3s ease-in-out;
    padding: 4px 8px;
    border: none;
    background-color: $primary-trans-color;
    color: #fff;
    font-weight: 600;
    border-radius: 20px;
    cursor: pointer;
  }

  .send {}

  .active {
    background-color: $primary-color;
    color: #fff;
  }
}

.new-session {
  color: white;
  margin-left: 10px;
  margin-top: 10px;
  background-color: $primary-color;
  border-radius: 10px;
  padding: 2px 4px;
  width: fit-content;
  height: fit-content;

  button {
    border-radius: 10px;
    background-color: transparent;
    border: none;

    svg {
      color: #090909;
    }
  }
}

.chat-footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 5px 5px 0 5px;
  //padding-bottom: constant(safe-area-inset-bottom);
  //padding-bottom: env(safe-area-inset-bottom);
  // background-color: #fff;

  .input-wrapper {
    flex: 1;
    // display:  flex;
    // align-items: center;
    // justify-content: space-between;
    display: grid;
    grid-template-columns: 1fr 50px;

    &>button {
      // width: 50px;
      color: $primary-color;
    }

    input {
      width: 100%;
      padding: 10px;
      border: none;
      background: #fff;
      border-radius: 5px;
      font-size: 14px;
    }

    .voice-hint-container {
      width: 100%;
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      padding: 10px;
      background-color: #f5f5f5;
      border-radius: 5px;
      color: #888;
      font-size: 14px;

      //-webkit-user-select: none;
      //-moz-user-select: none;
      //-ms-user-select: none;
      //user-select: none;
      &:active {
        background-color: #eaeaea;
      }

      .waveform {
        display: flex;
        align-items: flex-end;
        align-items: center;
        //height: 30px;
        //width: 60px;
        gap: 2px;

        &.disabled {
          cursor: not-allowed;
        }

        .bar {
          width: 4px;
          background-color: $primary-color;
          margin: 0 1px;

          &:nth-child(1) {
            height: 10px;
            animation-delay: 0s;
          }

          &:nth-child(2) {
            height: 16px;
            animation-delay: 0.1s;
          }

          &:nth-child(3) {
            height: 12px;
            animation-delay: 0.2s;
          }

          &:nth-child(4) {
            height: 18px;
            animation-delay: 0.3s;
          }

          &:nth-child(5) {
            height: 14px;
            animation-delay: 0.4s;
          }
        }
      }

      .hint-text {
        margin-top: 4px;
        -webkit-user-select: none;
        -moz-user-select: none;
        -ms-user-select: none;
        user-select: none;
      }
    }
  }

  .mic-button {
    display: flex;
    align-items: center;
    justify-content: center;
    flex-direction: column;
    border: none;
    background: transparent;
    color: $primary-color;
    cursor: pointer;
    font-size: 12px;
    margin-left: 5px;

    &:active {
      color: #e6454a;
      /* 按下变红 */
    }
  }

  button {
    border: none;
    outline: none;
    color: $primary-text-color;
    border-radius: 5px;
    background: transparent;
    font-weight: 600;
    cursor: pointer;
  }

  .disabled {
    pointer-events: none;
    opacity: 0.5;
  }
}

@keyframes wave-animation {

  0%,
  100% {
    transform: scaleY(1);
  }

  50% {
    transform: scaleY(1.5);
  }
}
</style>
