/* global SystemJS, UB */
const _ = require('lodash')

const SHOW_TIMEOUT = 300
const HIDE_TIMEOUT = 300

class LoadingOverlayManager {
  constructor () {
    this._loaderServiceInstance = undefined
    this._showTimeout = undefined
    this._hideTimeout = undefined

    /**
     * @type {Object<string, Array>}
     * @private
     */
    this._loadersByText = {}
  }

  /**
   * Add a loader from loaders list
   *
   * @param {string} text
   */
  show (text) {
    const exists = this._loadersByText[text]
    if (!exists) {
      this._loadersByText[text] = []
    }

    this._loadersByText[text].push({
      text,
      started: Date.now()
    })

    this._calcText()
  }

  /**
   * Remove a loader from loaders list
   *
   * @param {string} text
   */
  hide (text) {
    const exists = this._loadersByText[text]
    if (!exists) {
      console.warn('There is no loader with text %s', text)
      return
    }
    exists.shift()
    if (exists.length === 0) {
      delete this._loadersByText[text]
    }

    this._calcText()
  }

  /**
   * Recalculate text to show as a loading text after add or remove a loader
   *
   * @private
   */
  _calcText () {
    const text = _.maxBy(
      Object.entries(this._loadersByText),
      ([, loaders]) => _.maxBy(loaders, 'started')
    )?.[0]

    if (this._text !== text) {
      this._text = text

      const needShow = !!text

      if (needShow) {
        if (this._loaderServiceInstance) {
          this._loaderServiceInstance.text = this._buildText(text)
          if (this._hideTimeout) {
            this._removeHideTimeout()
          }
        } else if (!this._showTimeout) {
          this._setShowTimeout()
        }
      } else {
        if (!this._loaderServiceInstance && this._showTimeout) {
          this._removeShowTimeout()
        }
        if (this._loaderServiceInstance && !this._hideTimeout) {
          this._setHideTimeout()
        }
      }
    }
  }

  _setShowTimeout () {
    this._showTimeout = setTimeout(
      () => {
        this._showTimeout = undefined
        this._showLoader()
      },
      SHOW_TIMEOUT
    )
  }

  _removeShowTimeout () {
    clearTimeout(this._showTimeout)
    this._showTimeout = undefined
  }

  _setHideTimeout () {
    this._hideTimeout = setTimeout(
      () => {
        this._hideTimeout = undefined
        this._hideLoader()
      },
      HIDE_TIMEOUT
    )
  }

  _removeHideTimeout () {
    clearTimeout(this._hideTimeout)
    this._hideTimeout = undefined
  }

  _showLoader () {
    if (this._loaderServiceInstance) {
      this._loaderServiceInstance.close()
    }

    const ElementUI = SystemJS.get('element-ui')

    this._loaderServiceInstance = ElementUI.Loading.service({ text: this._buildText(this._text) })
    // hide all nested spinners when the fullscreen one is displayed
    document.body.classList.add('ub-with-loading-overlay')
  }

  _hideLoader () {
    if (this._loaderServiceInstance) {
      this._loaderServiceInstance.close()
      this._loaderServiceInstance = undefined
      document.body.classList.remove('ub-with-loading-overlay')
      this._text = undefined
    }
  }

  _buildText (textResourceId) {
    return UB.i18n(textResourceId)
  }
}

/**
 * Subscribe to 'portal:loader' event and manage the loader overlay using ElementUI
 */
function subscribe () {
  const manager = new LoadingOverlayManager()
  /**
   * `portal:loader` event, which shall be set or remove global loader (overlay) over UI, so that user won't trigger
   *   action in the process loading, for example, big components over a poor network connection.
   *
   * @example
   *     try {
   *       $App.fireEvent('portal:loader', {
   *         text: 'portal.loader.form',
   *         show: true
   *       })
   *
   *       // Do action which takes long.  Await for it, if just return unresolved promise, the "finally" block execute
   *       // before action finish
   *     } finally {
   *       // ALWAYS use try-finally to avoid loading overlay hanging in case of errors
   *       $App.fireEvent('portal:loader', {
   *         text: 'portal.loader.form',
   *         show: false
   *       })
   *     }
   *
   * @event 'portal:loader'
   * @memberOf module:@unitybase/adminui-vue
   */
  window.$App.on('portal:loader', ({ show, text = 'portal.loader.default' }) => {
    if (show) {
      manager.show(text)
    } else {
      manager.hide(text)
    }
  })
}

module.exports = {
  subscribe
}