import Service from 'ember-service';
import run from 'ember-runloop';
import injectService from 'ember-service/inject';
import RSVP from 'rsvp';
import { assert } from 'ember-metal/utils';
import ENV from 'web-client/config/environment';

/**
 * HINT: If you triggered the developer environment warning that your route did not report interactive, your route
 * either broke the way interactive is triggered, or the page took too long to render critical components.
 *
 * Please reach out to the site performance team if you require assistance.
 */
const DEV_ENV_TIMEOUT_MILLISECONDS = 10000;
const DEV_ENV_TIMEOUT_MESSAGE = 'CRITICAL: This route did not report interactive.  You must resolve before merging.';
const DEV_ENV_TIMEOUT_OPTIONS = { timeout: 0 };

/**
 * This service keeps track of all rendered components that have reported that they
 * are ready for user interaction. This service also allows a Route to monitor for a
 * custom interactivity condition to be met.
 *
 * Use with these mixins: interactivity-deferred-component & transition-benchmark-tracking
 */
export default Service.extend({

  notify: injectService(),
  _devEnvTimeoutMiliseconds: DEV_ENV_TIMEOUT_MILLISECONDS,
  _developerEnvWarningTimeout: null,
  _env: ENV,

  // The list of components that are rendered and interactive.
  _interactiveComponents: {},

  // The parent establishing the critical path of its child components
  _subscriber: null,

  /**
   * Begin monitoring for interactivity reports.
   * Returns a Promise which resolves when the criteria for interactivity are met.
   *
   * @method monitor
   * @param {Object} subscriber - Single configuration parameter that expects the following attributes
   * @param {String} name - The name of the subscriber (used in testing)
   * @param {Function} isInteractive - Method for checking interactivity conditions as reports come in
   * @returns {Promise} Resolves when interactivity conditions are met, rejects under timeout in dev environment
   */
  monitor(subscriber) {
    assert('Every subscriber must provide an isInteractive method', typeof(subscriber.isInteractive) === 'function');

    this.reset();

    this._subscriber = subscriber;

    return new RSVP.Promise((resolve, reject) => {
      this._subscriber.resolve = resolve;
      this._subscriber.reject = reject;

      this.beginTimer();
      this.checkInteractivity();
    });
  },

  /**
   * @method reportInteractive
   * @param {String} name - The name of the component that is now interactive
   */
  reportInteractive(name) {
    this._interactiveComponents[name] = true;
    this.checkInteractivity();
  },

  /**
   * @method reportNonInteractive
   * @param {String} name - The name of the component that is no longer interactive
   */
  reportNonInteractive(name) {
    delete this._interactiveComponents[name];
    this.checkInteractivity();
  },

  checkInteractivity() {
    let subscriber = this._subscriber;
    let timestamp = new Date().getTime() / 1000; // Current time as float

    if (subscriber && subscriber.isInteractive(this._interactiveComponents)) {
      subscriber.resolve(timestamp);
      this.reset();
    }
  },

  beginTimer() {
    if (this._env.environment === 'development') {
      this.cancelTimer();
      this._developerEnvWarningTimeout = run.later(() => {
        this.get('notify').error(DEV_ENV_TIMEOUT_MESSAGE, DEV_ENV_TIMEOUT_OPTIONS);
      }, this._devEnvTimeoutMiliseconds);
    }
  },

  cancelTimer() {
    if (this._developerEnvWarningTimeout) {
      run.cancel(this._developerEnvWarningTimeout);
    }
  },

  reset() {
    this._subscriber = null;
    this.cancelTimer();
  }
});
