import get from 'lodash-es/get';

/**
 * @type {TestOverwatch}
 */
window.TestOverwatch = class TestOverwatch {
  /**
   * @param {Object} domStructure
   */
  constructor(domStructure) {
    /**
     * @type {{document: Document, window: Window}}
     */
    this.ownContextWinDoc = { window, document };
    this.wbMessages = { load: false };
    this.domStructure = domStructure;

    /**
     * @type {HTMLIFrameElement}
     */
    this.sandbox = domStructure.sandbox;
    window.addEventListener(
      'message',
      event => {
        if (event.data) {
          const { data } = event;
          switch (data.wb_type) {
            case 'load':
              this.wbMessages.load =
                this.wbMessages.load || data.readyState === 'complete';
              this.domStructure.load.url.data = data.url;
              this.domStructure.load.title.data = data.title;
              this.domStructure.load.readyState.data = data.readyState;
              break;
            case 'replace-url':
              this.wbMessages['replace-url'] = data;
              this.domStructure.replaceURL.url.data = data.url;
              this.domStructure.replaceURL.title.data = data.title;
              break;
            case 'title':
              this.wbMessages.title = data.title;
              this.domStructure.titleMsg.data = data.title;
              break;
            case 'hashchange':
              this.domStructure.hashchange.data = data.title;
              this.wbMessages.hashchange = data.hash;
              break;
            case 'cookie':
              this.domStructure.cookie.domain = data.domain;
              this.domStructure.cookie.cookie = data.cookie;
              this.wbMessages.cookie = data;
              break;
            default:
              this.domStructure.unknown.data = JSON.stringify(data);
              break;
          }
        }
      },
      false
    );
  }

  /**
   * This function initializes the wombat in the sandbox and ads a single
   * additional property to the sandbox's window WombatTestUtil, an object that provides
   * any functionality required for tests. This is done in order to ensure testing
   * environment purity.
   */
  initSandbox() {
    this.domStructure.reset();
    this.wbMessages = { load: false };
    this.sandbox.contentWindow._WBWombatInit(this.sandbox.contentWindow.wbinfo);
    this.sandbox.contentWindow.WombatTestUtil = {
      didWombatSendTheLoadMsg: () => this.wbMessages.load,
      wombatSentTitleUpdate: () => this.wbMessages.title,
      wombatSentReplaceUrlMsg: () => this.wbMessages['replace-url'],
      createUntamperedWithElement: init => this.createElement(init),
      getElementPropertyAsIs: (elem, prop) =>
        this.getElementPropertyAsIs(elem, prop),
      getElementNSPropertyAsIs: (elem, ns, prop) =>
        this.getElementNSPropertyAsIs(elem, ns, prop),
      getOriginalWinDomViaPath: objectPath =>
        this.getOriginalWinDomViaPath(objectPath),
      getViaPath: (obj, objectPath) => get(obj, objectPath),
      getOriginalPropertyDescriptorFor: (elem, prop) =>
        this.ownContextWinDoc.window.Reflect.getOwnPropertyDescriptor(
          elem,
          prop
        ),
      getStylePropertyAsIs: (elem, prop) =>
        this.getStylePropertyAsIs(elem, prop),
      getCSSPropertyAsIs: (elem, prop) => this.getCSSPropertyAsIs(elem, prop)
    };
  }

  maybeInitSandbox() {
    if (this.sandbox.contentWindow.WB_wombat_location != null) return;
    this.initSandbox();
  }

  /**
   * Creates a DOM element(s) based on the supplied creation options.
   * @param {Object} init - Options for new element creation
   * @return {HTMLElement|Node} - The newly created element
   */
  createElement(init) {
    if (typeof init === 'string') {
      return this.ownContextWinDoc.document.createElement(init);
    }
    if (init.tag === 'textNode') {
      var tn = this.ownContextWinDoc.document.createTextNode(init.value || '');
      if (init.ref) init.ref(tn);
      return tn;
    }
    var elem = this.ownContextWinDoc.document.createElement(init.tag);
    if (init.id) elem.id = init.id;
    if (init.className) elem.className = init.className;
    if (init.style) elem.style = init.style;
    if (init.innerText) elem.innerText = init.innerText;
    if (init.innerHTML) elem.innerHTML = init.innerHTML;
    if (init.attributes) {
      var atts = init.attributes;
      for (var attributeName in atts) {
        elem.setAttribute(attributeName, atts[attributeName]);
      }
    }
    if (init.dataset) {
      var dataset = init.dataset;
      for (var dataName in dataset) {
        elem.dataset[dataName] = dataset[dataName];
      }
    }
    if (init.events) {
      var events = init.events;
      for (var eventName in events) {
        elem.addEventListener(eventName, events[eventName]);
      }
    }
    if (init.child) {
      elem.appendChild(this.createElement(init.child));
    }
    if (init.children) {
      var kids = init.children;
      for (var i = 0; i < kids.length; i++) {
        elem.appendChild(this.createElement(kids[i]));
      }
    }
    if (init.ref) init.ref(elem);
    return elem;
  }

  /**
   * Returns the value of an elements property bypassing wombat rewriting
   * @param {Node} elem
   * @param {string} prop
   * @return {*}
   */
  getElementPropertyAsIs(elem, prop) {
    return this.ownContextWinDoc.window.Element.prototype.getAttribute.call(
      elem,
      prop
    );
  }

  getElementNSPropertyAsIs(elem, ns, prop) {
    return this.ownContextWinDoc.window.Element.prototype.getAttributeNS.call(
      elem,
      ns,
      prop
    );
  }

  getStylePropertyAsIs(elem, prop) {
    var getter = this.ownContextWinDoc.window.CSSStyleDeclaration.prototype.__lookupGetter__(
      prop
    );
    return getter.call(elem, prop);
  }

  getCSSPropertyAsIs(elem, prop) {
    return this.ownContextWinDoc.window.CSSStyleDeclaration.prototype.getPropertyValue.call(
      elem,
      prop
    );
  }

  getOriginalWinDomViaPath(objectPath) {
    return get(this.ownContextWinDoc, objectPath);
  }
};