export interface AccelerometerControllerCallbacks {
  onSample: (x: number, y: number, z: number) => void
  onError: (message: string) => void
}

const ACCELEROMETER_START_RETRY_DELAY_MS = 120

export class AccelerometerController {
  callbacks: AccelerometerControllerCallbacks
  listening: boolean
  starting: boolean
  accelerometerCallback: ((result: WechatMiniprogram.OnAccelerometerChangeCallbackResult) => void) | null
  retryTimer: number

  constructor(callbacks: AccelerometerControllerCallbacks) {
    this.callbacks = callbacks
    this.listening = false
    this.starting = false
    this.accelerometerCallback = null
    this.retryTimer = 0
  }

  start(): void {
    if (this.listening || this.starting) {
      return
    }

    if (typeof wx.startAccelerometer !== 'function' || typeof wx.onAccelerometerChange !== 'function') {
      this.callbacks.onError('当前环境不支持加速度计监听')
      return
    }

    this.clearRetryTimer()
    this.starting = true
    this.detachCallback()
    wx.stopAccelerometer({
      complete: () => {
        this.startAfterStop(false)
      },
    })
  }

  private startAfterStop(retried: boolean): void {
    const callback = (result: WechatMiniprogram.OnAccelerometerChangeCallbackResult) => {
      if (
        typeof result.x !== 'number'
        || typeof result.y !== 'number'
        || typeof result.z !== 'number'
        || Number.isNaN(result.x)
        || Number.isNaN(result.y)
        || Number.isNaN(result.z)
      ) {
        return
      }

      this.callbacks.onSample(result.x, result.y, result.z)
    }

    this.accelerometerCallback = callback
    wx.onAccelerometerChange(callback)
    wx.startAccelerometer({
      interval: 'ui',
      success: () => {
        this.starting = false
        this.listening = true
      },
      fail: (res) => {
        const errorMessage = res && res.errMsg ? res.errMsg : 'startAccelerometer failed'
        if (!retried && errorMessage.indexOf('has enable') >= 0) {
          this.detachCallback()
          this.clearRetryTimer()
          this.retryTimer = setTimeout(() => {
            this.retryTimer = 0
            wx.stopAccelerometer({
              complete: () => {
                this.startAfterStop(true)
              },
            })
          }, ACCELEROMETER_START_RETRY_DELAY_MS) as unknown as number
          return
        }

        this.starting = false
        this.detachCallback()
        this.callbacks.onError(errorMessage)
      },
    })
  }

  stop(): void {
    this.clearRetryTimer()
    this.detachCallback()
    wx.stopAccelerometer({
      complete: () => {},
    })
    this.starting = false
    this.listening = false
  }

  destroy(): void {
    this.stop()
  }

  private clearRetryTimer(): void {
    if (!this.retryTimer) {
      return
    }

    clearTimeout(this.retryTimer)
    this.retryTimer = 0
  }

  private detachCallback(): void {
    if (!this.accelerometerCallback) {
      return
    }

    if (typeof wx.offAccelerometerChange === 'function') {
      wx.offAccelerometerChange(this.accelerometerCallback)
    }

    this.accelerometerCallback = null
  }
}
