import Service from 'ember-service';
import injectService from 'ember-service/inject';
import run from 'ember-runloop';
import $ from 'jquery';

const IMPRESSION_TRACKING_DATA_ATTR = 'impression-tracking-id';

/*
  Impression Tracking:

  The goal of this service is to allow components to register to be notified
  when they enter the viewport.

  We will first attempt to detect if they are visible, if they are we send them the action that they register with.

  If they are not we monitor their parent scroll container for scroll activity to determine if
  they ever do enter the viewport.  Once they do we then emit the registered `actionName`.

  We also try to proactively degregister from scroll elements that we are no longer interested in.
*/
export default Service.extend({
  scrollActivity: injectService(),
  idCounter: 1,

  init() {
    this._super(...arguments);

    // Maps scrollElements to array of potential visible components.
    this.componentSet = {};
  },

  getScrollableContainer(component) {
    return component.$().closest('.tse-scroll-content') || $(window);
  },

  registerComponent(component, actionName) {
    let scrollParent = this.getScrollableContainer(component);
    let scrollParentID = scrollParent.data(IMPRESSION_TRACKING_DATA_ATTR);
    if (!scrollParentID) {
      scrollParentID = this.get('idCounter');
      this.set('idCounter', scrollParentID + 1);
      scrollParent.data(IMPRESSION_TRACKING_DATA_ATTR, scrollParentID);
    }

    if (!this.componentSet[scrollParentID]) {
      this.componentSet[scrollParentID] = [];

      this.get('scrollActivity').subscribe(scrollParent, scrollParent, () => {
        run.debounce(this, this.validateVisible, scrollParentID, 300);
      });
    }

    this.componentSet[scrollParentID].push({component, actionName});

    if (this.isComponentVisible(component)) {
      component.send(actionName);
    }
  },

  deregisterComponent(component) {
    let scrollParent = this.getScrollableContainer(component);
    let scrollParentID = scrollParent.data(IMPRESSION_TRACKING_DATA_ATTR);
    if (!scrollParentID) { return; }

    let componentBucket = this.componentSet[scrollParentID];

    // Allows multiple deregistration
    if (!componentBucket) { return; }

    componentBucket = componentBucket.filter((element) => {
      return element.component !== component;
    });
    if (!componentBucket.length) {
      delete this.componentSet[scrollParentID];
      this.get('scrollActivity').unsubscribe(scrollParent);
    } else {
      this.componentSet[scrollParentID] = componentBucket;
    }
  },

  isComponentVisible(component) {
    let element = component.get('element');
    if (!element) { return false; }
    let boundingBox = element.getBoundingClientRect();
    return (boundingBox.top > 0 && boundingBox.top < window.innerHeight) || (boundingBox.bottom > 0 && boundingBox.bottom < window.innerHeight);
  },

  validateVisible(bucket) {
    let componentBucket = this.componentSet[bucket];

    // This bucket may have been deregistered between the time the check was
    // scheduled and when debouncing finished
    if (!componentBucket) { return; }

    let components = componentBucket.slice();
    for (let i = 0, len = components.length; i < len; i++) {
      let component = components[i].component;
      let action = components[i].actionName;
      if (this.isComponentVisible(component)) {
        component.send(action);
      }
    }
  }
});
