import Mixin from 'ember-metal/mixin';
import on from 'ember-evented/on';
import injectService from 'ember-service/inject';
import run from 'ember-runloop';

/**
 * Route Mixin transition-benchmark-tracking (mixed into Ember.Route in ext/route)
 *
 * All routes must emit the following 3 transition benchmarks:
 *   1.) validate (i.e. we have begun validating that transition is possible by fetching relevant data, ie model hooks)
 *   2.) execute  (i.e. we are executing the transition by activating the route and scheduling render tasks)
 *   3.) interactive (i.e. we have completed the transition and the route is now interactive)
 */
export default Mixin.create({
  tracking: injectService(),
  interactivity: injectService(),
  documentVisibility: injectService('document-visibility'),

  // True when monitoring is active; do not send benchmark events when false
  monitoringInteractivity: false,

  /**
   * Property for storing the transition object to be access in
   * lifecycle hooks that do not have it passed in as a parameter
   */
  _latestTransition: null,

  /**
   * A route may implement the method isInteractive, which returns true if all conditions for interactivity have been met
   *
   * If isInteractive is not defined, the transition complete benchmark fires in the afterRender queue.
   *
   * If isInteractive is defined, it is used to see if conditions are met and then fires the transition complete benchmark.
   *
   * @method isInteractive
   * @param {Object} interactiveComponents - Hash with truthy values tied to each component name as a key
   * @returns {Boolean} True if all interactivity conditions have been met
   */
  isInteractive: null,

  /**
   * @method isLeafRoute
   * @param {Object} transition - http://emberjs.com/api/classes/Transition.html
   * @returns {Boolean} True if this route is the target of the current transition
   */
  isLeafRoute(transition = this.get('_latestTransition')) {
    return transition && transition.targetName === this.get('routeName');
  },

  /**
   * @method sendTransitionBenchmark
   * @param {String} phase - The phase of the transition that this event tracks, used to construct the event name
   * @param {String} targetName - The destination route for the current transition
   * @param {Float} timestamp [Optional] - The client time at which the event happened.
   *                  Optional because the benchmark library will create its own by default
   */
  sendTransitionBenchmark(phase, targetName, timestamp) {
    let routeName = this.get('routeName');
    this.get('tracking.benchmark').transition(phase, {
      destination: targetName,
      route_name: routeName,
      lost_visibility: this.get('documentVisibility.lostVisibility'),
      client_time: timestamp
    });
  },

  /**
   * @method sendTransitionCompleteBenchmark
   * @param {Float} timestamp [Optional] The client time at which the event happened.
   */
  sendTransitionCompleteBenchmark(timestamp) {
    this.sendTransitionBenchmark('complete', this.get('routeName'), timestamp);
  },

  /**
   * Capture the incoming transition and send a benchmark event for the validate phase of that transition
   * @method beforeModel
   * @param {Object} transition
   */
  beforeModel(transition) {
    this.set('_latestTransition', transition);
    this.sendTransitionBenchmark('validate', transition.targetName);
    return this._super(...arguments);
  },

  /**
   * Send a benchmark event for the execute phase of a transition
   * @method trackRouteExecution
   */
  trackRouteExecution: on('activate', function () {
    let transition = this.get('_latestTransition');
    if (transition) {
      this.sendTransitionBenchmark('execute', transition.targetName);
    }
  }),

  /**
   * Initiate monitoring with the interactivity service and send benchmark events upon resolution
   *
   * @method monitorInteractivity
   */
  monitorInteractivity() {
    let options = {
      name: this.get('routeName'),
      isInteractive: run.bind(this, this.isInteractive)
    };

    this.set('monitoringInteractivity', true);
    this.get('interactivity').monitor(options).then((timestamp) => {
      if (this.get('monitoringInteractivity')) {
        this.set('monitoringInteractivity', false);
        this.sendTransitionCompleteBenchmark(timestamp);
      }
    }).catch((/* error */) => {
      if (this.get('monitoringInteractivity')) {
        this.set('monitoringInteractivity', false);
        // todo: record benchmark error (most likely, something timed out)
      }
    });
  },

  actions: {
    /**
     * Schedule interactivity tracking for leaf routes
     *
     * @method didTransition
     * @returns {boolean} Bubble the action unless a lower-order action stopped it
     */
    didTransition() {
      if (this.isLeafRoute()) {
        if (typeof(this.isInteractive) === 'function') {
          this.monitorInteractivity();
        } else {
          run.scheduleOnce('afterRender', this, this.sendTransitionCompleteBenchmark);
        }
      }

      return this._super(...arguments) !== false; // Check explicitly for falsey value
    },

    /**
     * Resent interactivity monitoring and fire a benchmark event if a new transition occurred before monitoring completed
     *
     * @method willTransition
     * @returns {boolean} Bubble the action unless a lower-order action stopped it
     */
    willTransition() {
      if (this.isLeafRoute()) {
        if (this.get('monitoringInteractivity')) {
          this.set('monitoringInteractivity', false);
          // todo: record benchmark error (transitioned away before transition completion)
        }
        this.get('interactivity').reset();
      }

      return this._super(...arguments) !== false; // Check explicitly for falsey value
    }
  }
});
