import axios from 'axios'
import { playAndDownload } from '@/api/aiSipCall/aiSipCallPhone'

/** 录音体积上限 80MB */
const MAX_AUDIO_BYTES = 80 * 1024 * 1024

/**
 * AI SIP 录音播放/下载共用逻辑（playAndDownload 二进制流）
 */
export default {
  data() {
    return {
      downloadLoading: false,
      downloadTarget: null,
      playDialogVisible: false,
      playDialogTitle: '通话回放',
      playAudioUrl: '',
      audioObjectUrl: null,
      audioLoading: false,
      chatMessages: [],
      chatVisibleCount: 8,
      chatPageSize: 8,
      playRequestId: 0,
      playCancelSource: null,
      downloadCancelSource: null,
      downloadObjectUrl: null,
      downloadRevokeTimer: null,
      playMeta: {
        subtitle: ''
      }
    }
  },
  computed: {
    visibleChatMessages() {
      return this.chatMessages.slice(0, this.chatVisibleCount)
    },
    hasMoreChat() {
      return this.chatMessages.length > this.chatVisibleCount
    },
    canCollapseChat() {
      return this.chatVisibleCount > this.chatPageSize
    }
  },
  beforeDestroy() {
    this.teardownMediaResources()
  },
  methods: {
    getRecordingFileName(wavfile) {
      if (!wavfile) return 'audio.wav'
      const str = String(wavfile).trim()
      let raw = ''
      const match = str.match(/[?&]filename=([^&]*)/i)
      if (match && match[1]) {
        try {
          raw = decodeURIComponent(match[1]).trim()
        } catch (e) {
          raw = match[1].trim()
        }
      }
      if (!raw) {
        raw = str.split('?')[0].split('/').pop() || 'audio.wav'
      }
      raw = raw.replace(/\\/g, '/').split('/').pop() || 'audio.wav'
      raw = raw.replace(/[<>:"|?*\u0000-\u001f]/g, '_').replace(/^\.+/, '')
      if (!raw) raw = 'audio.wav'
      if (!/\.(wav|mp3|ogg|m4a|webm)$/i.test(raw)) {
        raw = raw + '.wav'
      }
      return raw.length > 180 ? raw.slice(-180) : raw
    },
    isRequestCanceled(err) {
      return !!(err && (err.__CANCEL__ || (axios.isCancel && axios.isCancel(err))))
    },
    cancelPlayRequest() {
      if (this.playCancelSource) {
        try {
          this.playCancelSource.cancel('play canceled')
        } catch (e) {}
        this.playCancelSource = null
      }
    },
    cancelDownloadRequest() {
      if (this.downloadCancelSource) {
        try {
          this.downloadCancelSource.cancel('download canceled')
        } catch (e) {}
        this.downloadCancelSource = null
      }
    },
    clearDownloadObjectUrl() {
      if (this.downloadRevokeTimer) {
        clearTimeout(this.downloadRevokeTimer)
        this.downloadRevokeTimer = null
      }
      if (this.downloadObjectUrl) {
        try {
          URL.revokeObjectURL(this.downloadObjectUrl)
        } catch (e) {}
        this.downloadObjectUrl = null
      }
    },
    getPlayAudioEl() {
      const dialog = this.$refs.playDialog
      return dialog && dialog.$refs ? dialog.$refs.playAudio : null
    },
    getChatPanelEl() {
      const dialog = this.$refs.playDialog
      return dialog && dialog.$refs ? dialog.$refs.chatPanel : null
    },
    stopPlayAudioElement() {
      const el = this.getPlayAudioEl()
      if (!el) return
      try {
        el.pause()
        el.removeAttribute('src')
        el.load()
      } catch (e) {}
    },
    revokeAudioObjectUrl() {
      if (this.audioObjectUrl) {
        try {
          URL.revokeObjectURL(this.audioObjectUrl)
        } catch (e) {}
        this.audioObjectUrl = null
      }
      this.playAudioUrl = ''
    },
    teardownMediaResources() {
      this.playRequestId += 1
      this.cancelPlayRequest()
      this.cancelDownloadRequest()
      this.clearDownloadObjectUrl()
      this.stopPlayAudioElement()
      this.revokeAudioObjectUrl()
      this.chatMessages = []
      this.audioLoading = false
      this.downloadLoading = false
      this.downloadTarget = null
      this.playDialogVisible = false
    },
    parseChatMessages(dialogue) {
      if (!dialogue) return []
      let list = dialogue
      if (typeof dialogue === 'string') {
        const text = dialogue.trim()
        if (!text) return []
        try {
          list = JSON.parse(text)
        } catch (e) {
          return []
        }
      }
      if (!Array.isArray(list)) return []
      // 兼容 AI 外呼 assistant / 手动外呼 agent
      const roleMap = {
        user: 'user',
        assistant: 'assistant',
        agent: 'agent'
      }
      return list
        .filter(item => item && roleMap[item.role])
        .map(item => {
          let content = item.content == null ? '' : String(item.content)
          content = content
            .replace(/\s*hangupCall\s*/gi, '')
            .replace(/\s*transferToAgent\s*/gi, '')
            .trim()
          return {
            // 布局：非 user 一律左侧；保留原始角色用于文案
            role: item.role === 'user' ? 'user' : 'assistant',
            rawRole: item.role,
            content
          }
        })
        .filter(item => item.content)
    },
    /** 兼容 AI 外呼 dialogue / 手动外呼 chatContent */
    resolveChatSource(row) {
      if (!row) return null
      if (row.dialogue != null && row.dialogue !== '') return row.dialogue
      if (row.chatContent != null && row.chatContent !== '') return row.chatContent
      if (row.chatMessages != null && row.chatMessages !== '') return row.chatMessages
      return null
    },
    isRiffWav(head) {
      return head && head.length >= 12 &&
        head[0] === 0x52 && head[1] === 0x49 && head[2] === 0x46 && head[3] === 0x46 &&
        head[8] === 0x57 && head[9] === 0x41 && head[10] === 0x56 && head[11] === 0x45
    },
    async ensureAudioBlob(blob) {
      if (!blob || blob.size === 0) {
        throw new Error('录音文件为空')
      }
      if (blob.size > MAX_AUDIO_BYTES) {
        throw new Error('录音文件过大，已取消加载')
      }
      const head = new Uint8Array(await blob.slice(0, 64).arrayBuffer())
      if (this.isRiffWav(head)) {
        return (blob.type || '') === 'audio/wav'
          ? blob
          : blob.slice(0, blob.size, 'audio/wav')
      }
      const headText = new TextDecoder('utf-8').decode(head).replace(/^\uFEFF/, '').trim()
      if (headText.startsWith('{') || headText.startsWith('[')) {
        let msg = '获取录音失败'
        try {
          const json = JSON.parse(await blob.text())
          msg = json.msg || json.message || msg
          if (json.code === 401) {
            msg = '登录状态已过期，请重新登录'
          } else if (json.code) {
            msg = msg + '（code: ' + json.code + '）'
          }
        } catch (e) {}
        throw new Error(msg)
      }
      const type = (blob.type || '').toLowerCase()
      if (type.indexOf('json') !== -1 || type.indexOf('text') !== -1 || type.indexOf('html') !== -1) {
        throw new Error('获取录音失败：接口返回的不是音频文件')
      }
      if (type.startsWith('audio/')) {
        return blob
      }
      throw new Error('获取录音失败：不是有效的录音文件')
    },
    async fetchRecordingBlob(row, cancelSource) {
      if (!row || !row.wavfile) {
        throw new Error('录音文件不存在')
      }
      const config = cancelSource ? { cancelToken: cancelSource.token } : {}
      const blob = await playAndDownload({ wavfile: row.wavfile }, config)
      return this.ensureAudioBlob(blob)
    },
    loadMoreChat() {
      this.chatVisibleCount = Math.min(
        this.chatVisibleCount + this.chatPageSize,
        this.chatMessages.length
      )
      this.$nextTick(() => {
        const panel = this.getChatPanelEl()
        if (panel) {
          panel.scrollTop = panel.scrollHeight
        }
      })
    },
    collapseChat() {
      this.chatVisibleCount = this.chatPageSize
      this.$nextTick(() => {
        const panel = this.getChatPanelEl()
        if (panel) {
          panel.scrollTop = 0
        }
      })
    },
    /** 回放副标题：按 executeType 取对应展示名 */
    resolvePlaySubtitleName(row) {
      if (!row) return ''
      const executeType = row.executeType
      let name
      if (executeType == null || executeType === '') {
        name = row.opnum
      } else {
        const type = String(executeType)
        if (type === '0') {
          name = row.externalUserName
        } else if (type === '1' || type === '2') {
          name = row.fsUserNickName
        }
      }
      return name || row.uuid || ''
    },
    async openPlayDialog(row) {
      if (!row || !row.wavfile) {
        this.$message.error('录音文件不存在')
        return
      }
      this.cancelPlayRequest()
      this.stopPlayAudioElement()
      this.revokeAudioObjectUrl()

      const requestId = ++this.playRequestId
      this.playCancelSource = axios.CancelToken.source()

      const name = this.resolvePlaySubtitleName(row)
      this.playDialogTitle = '通话回放'
      this.playMeta = {
        subtitle: name
      }
      this.chatMessages = this.parseChatMessages(this.resolveChatSource(row))
      this.chatVisibleCount = this.chatPageSize
      this.playDialogVisible = true
      this.audioLoading = true

      try {
        const blob = await this.fetchRecordingBlob(row, this.playCancelSource)
        if (requestId !== this.playRequestId || !this.playDialogVisible) {
          return
        }
        this.audioObjectUrl = URL.createObjectURL(blob)
        this.playAudioUrl = this.audioObjectUrl
        this.$nextTick(() => {
          if (requestId !== this.playRequestId || !this.playDialogVisible) {
            this.revokeAudioObjectUrl()
            return
          }
          const el = this.getPlayAudioEl()
          if (el) {
            el.play().catch(() => {})
          }
        })
      } catch (e) {
        if (this.isRequestCanceled(e) || requestId !== this.playRequestId) {
          return
        }
        this.$message.error(e.message || '录音加载失败')
      } finally {
        if (requestId === this.playRequestId) {
          this.audioLoading = false
          this.playCancelSource = null
        }
      }
    },
    closePlayDialog() {
      this.playRequestId += 1
      this.cancelPlayRequest()
      this.stopPlayAudioElement()
      this.revokeAudioObjectUrl()
      this.chatMessages = []
      this.chatVisibleCount = this.chatPageSize
      this.audioLoading = false
      this.playMeta = { subtitle: '' }
    },
    async downloadVoice(row) {
      if (!row || !row.wavfile) {
        this.$message.error('录音文件不存在')
        return
      }
      if (this.downloadLoading) return

      this.cancelDownloadRequest()
      this.clearDownloadObjectUrl()
      this.downloadCancelSource = axios.CancelToken.source()
      this.downloadLoading = true
      this.downloadTarget = row.wavfile
      try {
        const blob = await this.fetchRecordingBlob(row, this.downloadCancelSource)
        this.downloadObjectUrl = URL.createObjectURL(blob)
        const link = document.createElement('a')
        link.href = this.downloadObjectUrl
        link.setAttribute('download', this.getRecordingFileName(row.wavfile))
        link.style.display = 'none'
        document.body.appendChild(link)
        link.click()
        document.body.removeChild(link)
        const urlToRevoke = this.downloadObjectUrl
        this.downloadRevokeTimer = setTimeout(() => {
          if (this.downloadObjectUrl === urlToRevoke) {
            try {
              URL.revokeObjectURL(urlToRevoke)
            } catch (e) {}
            this.downloadObjectUrl = null
          }
          this.downloadRevokeTimer = null
        }, 2000)
      } catch (e) {
        this.clearDownloadObjectUrl()
        if (!this.isRequestCanceled(e)) {
          this.$message.error(e.message || '录音下载失败')
        }
      } finally {
        this.downloadLoading = false
        this.downloadTarget = null
        this.downloadCancelSource = null
      }
    }
  }
}
