import { invoke } from '@tauri-apps/api/tauri'
import { listen } from '@tauri-apps/api/event'
import { byteToString } from './string'

export default class DownloadHandler {
  downloads: {
    path: string,
    progress: number,
    total: number,
    status: string,
    startTime: number,
    error?: string,
    speed?: string,
    onFinish?: () => void,
  }[]

  // Pass tauri invoke function
  constructor() {
    this.downloads = []

    listen('download_progress', ({ payload }) => {
      // @ts-expect-error Payload may be unknown but backend always returns this object
      const obj: {
        downloaded: string,
        total: string,
        path: string,
      } = payload

      const index = this.downloads.findIndex(download => download.path === obj.path)
      this.downloads[index].progress = parseInt(obj.downloaded, 10)
      this.downloads[index].total = parseInt(obj.total, 10)

      // Set download speed based on startTime
      const now = Date.now()
      const timeDiff = now - this.downloads[index].startTime
      const speed = (this.downloads[index].progress / timeDiff) * 1000
      this.downloads[index].speed = byteToString(speed) + '/s'
    })

    listen('download_finished', ({ payload }) => {
      // Remove from array
      const filename = payload

      // set status to finished
      const index = this.downloads.findIndex(download => download.path === filename)
      this.downloads[index].status = 'finished'

      // Call onFinish callback
      if (this.downloads[index]?.onFinish) {
        // @ts-expect-error onFinish is checked for existence before being called
        this.downloads[index]?.onFinish()
      }
    })

    listen('download_error', ({ payload }) => {
      // @ts-expect-error shut up typescript
      const errorData: {
        path: string,
        error: string,
      } = payload

      // Set download to error
      const index = this.downloads.findIndex(download => download.path === errorData.path)
      this.downloads[index].status = 'error'
      this.downloads[index].error = errorData.error
    })

    // Extraction events
    listen('extract_start', ({ payload }) => {
      // Find the download that is no extracting and set it's status as such
      const index = this.downloads.findIndex(download => download.path === payload)
      this.downloads[index].status = 'extracting'
    })

    listen('extract_end', ({ payload }) => {
      // Find the download that is no extracting and set it's status as such
      const index = this.downloads.findIndex(download => download.path === payload)
      this.downloads[index].status = 'finished'
    })
  }

  getDownloads() {
    return this.downloads
  }

  downloadingJar() {
    // Kinda hacky but it works
    return this.downloads.some(d => d.path.includes('grasscutter.zip'))
  }

  downloadingResources() {
    // Kinda hacky but it works
    return this.downloads.some(d => d.path.includes('resources'))
  }
  
  downloadingRepo() {
    return this.downloads.some(d => d.path.includes('grasscutter_repo.zip'))
  }

  addDownload(url: string, path: string, onFinish?: () => void) {
    // Begin download from rust backend, don't add if the download addition fails
    invoke('download_file', { url, path })
    const obj = {
      path,
      progress: 0,
      total: 0,
      status: 'downloading',
      startTime: Date.now(),
      onFinish,
    }

    this.downloads.push(obj)
  }

  stopDownload(path: string) {
    // Stop download and remove from list.
    invoke('stop_download', { path })

    // Remove from list
    const index = this.downloads.findIndex(download => download.path === path)
    this.downloads.splice(index, 1)
  }

  getDownloadProgress(path: string) {
    const index = this.downloads.findIndex(download => download.path === path)
    return this.downloads[index] || null
  }

  getDownloadSize(path: string) {
    const index = this.downloads.findIndex(download => download.path === path)
    return byteToString(this.downloads[index].total) || null
  }

  getTotalAverage() {
    const files = this.downloads.filter(d => d.status === 'downloading')
    const total = files.reduce((acc, d) => acc + d.total, 0)
    const progress = files.reduce((acc, d) => acc + d.progress, 0)
    let speedStr = '0 B/s'

    // Get download speed based on startTimes
    if (files.length > 0) {
      const now = Date.now()
      const timeDiff = now - files[0].startTime
      const speed = (progress / timeDiff) * 1000
      speedStr = byteToString(speed) + '/s'
    }

    return {
      average: (progress / total) * 100 || 0,
      files: this.downloads.filter(d => d.status === 'downloading').length,
      extracting: this.downloads.filter(d => d.status === 'extracting').length,
      totalSize: total,
      speed: speedStr
    }
  }
}