import { sleep } from './utils'

const synth = window.speechSynthesis

export class TTS {
  currentText = ''
  speakText = ''
  private controller = new AbortController()
  speaking = false
  get isSpeaking() {
    return this.speaking
  }
  finished = false
  constructor() {}
  abort = () => {
    this.controller.abort()
  }

  reset = () => {
    this.speaking = false
    this.finished = true
    this.currentText = ''
    this.speakText = ''
    this.abort()
  }

  speak = (text: string) => {
    if (!synth || text?.trim()?.length < 2) {
      return
    }
    this.currentText = text.replace(/[^\u4e00-\u9fa5_a-zA-Z0-9,。?,:;\.,:]+/g, '')
    this.finished = false
    this.loop()
  }

  private async doSpeek() {
    return new Promise((resolve) => {
      const endIndex = this.finished ? this.currentText.length :
        Math.max(
          this.currentText.lastIndexOf('。'),
          this.currentText.lastIndexOf(';'),
          this.currentText.lastIndexOf('、'),
          this.currentText.lastIndexOf('?'),
          this.currentText.lastIndexOf('\n')
        )
      const startIndex = this.speakText.length ? Math.max(0, this.currentText.lastIndexOf(this.speakText) + this.speakText.length) : 0

      if (startIndex >= endIndex) {
        return resolve(true)
      }
      const text = this.currentText.slice(startIndex, endIndex)
      this.speakText = text
      const utterThis = new SpeechSynthesisUtterance(text)
      this.controller.signal.onabort = () => {
        synth.cancel()
        this.finished = true
        resolve(false)
      }

      utterThis.onend = function (event) {
        resolve(true)
      }

      utterThis.onerror = function (event) {
        resolve(false)
      }

      const voice = synth.getVoices().find(v => v.name.includes('Microsoft Yunxi Online')) ?? null
      utterThis.voice = voice
      synth.speak(utterThis)
    })
  }

  private async loop() {
    if (this.speaking) return
    this.speaking = true
    while(!this.finished) {
      await Promise.all([sleep(1000), this.doSpeek()])
    }
    this.speaking = false
  }
}