import Service from '@ember/service';
import { getOwner } from '@ember/application';
import { guidFor } from '@ember/object/internals';

// selecting
import qsaFactory from 'consul-ui/utils/dom/qsa-factory';
// TODO: sibling and closest seem to have 'PHP-like' guess the order arguments
// ie. one `string, element` and the other has `element, string`
// see if its possible to standardize
import sibling from 'consul-ui/utils/dom/sibling';
import closest from 'consul-ui/utils/dom/closest';
import isOutside from 'consul-ui/utils/dom/is-outside';
import getComponentFactory from 'consul-ui/utils/dom/get-component-factory';

// events
import normalizeEvent from 'consul-ui/utils/dom/normalize-event';
import createListeners from 'consul-ui/utils/dom/create-listeners';
import clickFirstAnchorFactory from 'consul-ui/utils/dom/click-first-anchor';

// ember-eslint doesn't like you using a single $ so use double
// use $_ for components
const $$ = qsaFactory();
let $_;
let inViewportCallbacks;
const clickFirstAnchor = clickFirstAnchorFactory(closest);
export default Service.extend({
  doc: document,
  win: window,
  init: function() {
    this._super(...arguments);
    inViewportCallbacks = new WeakMap();
    $_ = getComponentFactory(getOwner(this));
  },
  willDestroy: function() {
    this._super(...arguments);
    inViewportCallbacks = null;
    $_ = null;
  },
  document: function() {
    return this.doc;
  },
  viewport: function() {
    return this.win;
  },
  guid: function(el) {
    return guidFor(el);
  },
  // TODO: should this be here? Needs a better name at least
  clickFirstAnchor: clickFirstAnchor,
  closest: closest,
  sibling: sibling,
  isOutside: isOutside,
  normalizeEvent: normalizeEvent,
  setEventTargetProperty: function(e, property, cb) {
    const target = e.target;
    return new Proxy(e, {
      get: function(obj, prop, receiver) {
        if (prop === 'target') {
          return new Proxy(target, {
            get: function(obj, prop, receiver) {
              if (prop === property) {
                return cb(e.target[property]);
              }
              return target[prop];
            },
          });
        }
        return Reflect.get(...arguments);
      },
    });
  },
  listeners: createListeners,
  root: function() {
    return this.doc.documentElement;
  },
  // TODO: Should I change these to use the standard names
  // even though they don't have a standard signature (querySelector*)
  elementById: function(id) {
    return this.doc.getElementById(id);
  },
  elementsByTagName: function(name, context) {
    context = typeof context === 'undefined' ? this.doc : context;
    return context.getElementsByTagName(name);
  },
  elements: function(selector, context) {
    // don't ever be tempted to [...$$()] here
    // it should return a NodeList
    return $$(selector, context);
  },
  element: function(selector, context) {
    if (selector.substr(0, 1) === '#') {
      return this.elementById(selector.substr(1));
    }
    // TODO: This can just use querySelector
    return [...$$(selector, context)][0];
  },
  // ember components aren't strictly 'dom-like'
  // but if you think of them as a web component 'shim'
  // then it makes more sense to think of them as part of the dom
  // with traditional/standard web components you wouldn't actually need this
  // method as you could just get to their methods from the dom element
  component: function(selector, context) {
    if (typeof selector !== 'string') {
      return $_(selector);
    }
    return $_(this.element(selector, context));
  },
  components: function(selector, context) {
    return [...this.elements(selector, context)]
      .map(function(item) {
        return $_(item);
      })
      .filter(function(item) {
        return item != null;
      });
  },
  isInViewport: function($el, cb, threshold = 0) {
    inViewportCallbacks.set($el, cb);
    let observer = new IntersectionObserver(
      (entries, observer) => {
        entries.map(item => {
          const cb = inViewportCallbacks.get(item.target);
          if (typeof cb === 'function') {
            cb(item.isIntersecting);
          }
        });
      },
      {
        rootMargin: '0px',
        threshold: threshold,
      }
    );
    observer.observe($el); // eslint-disable-line ember/no-observers
    // observer.unobserve($el);
    return () => {
      observer.unobserve($el); // eslint-disable-line ember/no-observers
      if (inViewportCallbacks) {
        inViewportCallbacks.delete($el);
      }
      observer.disconnect(); // eslint-disable-line ember/no-observers
      observer = null;
    };
  },
});
