import EventEmitter from 'events'
import Color from 'color'
import ejs from 'ejs'
import { State } from 'xstate'
import { ChatClient, SlowModeRateLimiter, LoginError } from 'dank-twitch-irc'

export default class TwitchBot extends EventEmitter {
  constructor(config) {
    super()
    const { username, token } = config
    this.config = config
    this.announceTemplate = ejs.compile(config.announce.template)
    const client = new ChatClient({
      username,
      password: `oauth:${token}`,
      rateLimits: 'default',
    })
    client.use(new SlowModeRateLimiter(client, 0))
    this.client = client

    this.streams = null
    this.listeningURL = null
    this.dwellTimeout = null
    this.announceTimeouts = new Map()

    client.on('ready', () => {
      this.onReady()
    })
    client.on('error', (err) => {
      console.error('Twitch connection error:', err)
      if (err instanceof LoginError) {
        client.close()
      }
    })
    client.on('close', (err) => {
      console.log('Twitch bot disconnected.')
      if (err != null) {
        console.error('Twitch bot disconnected due to error:', err)
      }
    })
  }

  connect() {
    const { client } = this
    client.connect()
  }

  async onReady() {
    const { client } = this
    const { channel, color } = this.config
    await client.setColor(Color(color).object())
    await client.join(channel)
    this.emit('connected')
  }

  onState({ views, streams }) {
    this.streams = streams

    const listeningView = views.find(({ state, context }) =>
      State.from(state, context).matches('displaying.running.audio.listening'),
    )
    if (!listeningView) {
      return
    }

    const listeningURL = listeningView.context.content.url
    if (listeningURL === this.listeningURL) {
      return
    }
    this.listeningURL = listeningURL
    this.onListeningURLChange(listeningURL)
  }

  onListeningURLChange(listeningURL) {
    const { announce } = this.config
    clearTimeout(this.dwellTimeout)
    this.dwellTimeout = setTimeout(() => {
      if (!this.announceTimeouts.has(listeningURL)) {
        this.announce()
      }
    }, announce.delay * 1000)
  }

  async announce() {
    const { client, listeningURL, streams } = this
    const { channel, announce } = this.config

    if (!client.ready) {
      return
    }

    const stream = streams.find((s) => s.link === listeningURL)
    if (!stream) {
      return
    }

    const msg = this.announceTemplate({ stream })
    await client.say(channel, msg)
    this.emit('sent', msg)

    const timeout = setTimeout(() => {
      this.announceTimeouts.delete(listeningURL)
      if (this.listeningURL === listeningURL) {
        this.announce()
      }
    }, announce.interval * 1000)
    this.announceTimeouts.set(listeningURL, timeout)
  }
}
