/**
 * Customize header. Refer to <a href='https://docs.apryse.com/documentation/web/guides/customizing-header/' target='_blank'>Customizing header</a> for details.
 * @method UI.setHeaderItems
 * @param {UI.headerCallback} headerCallback Callback function to perform different operations on the header.
 * @example
// Adding save annotations button to the end of the top header
WebViewer(...)
  .then(function(instance) {
    instance.UI.setHeaderItems(function(header) {
      var myCustomButton = {
        type: 'actionButton',
        img: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M0 0h24v24H0z" fill="none"/><path d="M17 3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V7l-4-4zm-5 16c-1.66 0-3-1.34-3-3s1.34-3 3-3 3 1.34 3 3-1.34 3-3 3zm3-10H5V5h10v4z"/></svg>',
        onClick: function() {

        }
      }

      header.push(myCustomButton);
    });
  });
 * @example
// Removing existing buttons from the top header
WebViewer(...)
  .then(function(instance) {
    instance.UI.setHeaderItems(function(header) {
      header.update([]);
    });
  });
 * @example
// Appending logo to the 'Annotate' toolbar group and shifting existing buttons to the right
WebViewer(...)
  .then(function(instance) {
    instance.UI.setHeaderItems(function(header) {
      header.getHeader('toolbarGroup-Annotate').unshift({
        type: 'customElement',
        render: function() {
          var logo = document.createElement('img');
          logo.src = '/logo.svg';
          logo.style.width = '200px';
          logo.style.marginLeft = '10px';
          logo.style.cursor = 'pointer';
          logo.onclick = function() {
            window.open('https://www.apryse.com', '_blank');
          }
          return logo;
        }
      }, {
        type: 'spacer'
      });
    });
  });
 * @example
// Moving the line tool from the 'Shapes' toolbar group to the 'Annotate' toolbar group
WebViewer(...)
  .then(function(instance) {
    instance.UI.setHeaderItems(function(header) {
      header.getHeader('toolbarGroup-Annotate').push({ type: 'toolGroupButton', toolGroup: 'lineTools', dataElement: 'lineToolGroupButton', title: 'annotation.line' });
      header.getHeader('toolbarGroup-Shapes').delete(6);
    });
  });
 */

/**
 * Callback that gets passed to {@link UI.setHeaderItems setHeaderItems}.
 * @callback UI.headerCallback
 * @param {UI.Header} header Header instance with helper functions
 */

export default (store) => (callback) => {
  const headerGroups = {
    headers: store.headers,
    rightHeaders: store.rightHeaders,
    tools: store.tools,
    toolItems: store.toolItems
  };
  // const headerGroups = Object.keys(store.header);
  const header = Object.create(Header).initialize(store, headerGroups);

  callback(header);
  // headerGroups.forEach((headerGroup) => {
    // store.dispatch(actions.setHeaderItems(headerGroup, [...header.headers[headerGroup]]));
  //   console.log(headerGroup, [...header[headerGroup]])
  // });
  Object.keys(headerGroups).forEach((key) => {
    const headerGroup = headerGroups[key];
    store[key] = headerGroup;
  });
};

/**
 * A class which contains header APIs.<br/><br/>
 * <span style="color: red; font-size: 1.2em; font-weight: bold">⚠</span> You must NOT instantiate this yourself. Access the header instance in {@link UI.setHeaderItems setHeaderItems} as follows:
 * @name Header
 * @memberof UI
 * @class
 * @example
WebViewer(...)
  .then(function(instance) {
    instance.UI.setHeaderItems(function(header) {
      // instance of Header is passed to the callback
    });
  });
 */
const Header = {
  initialize(viewerState) {
    // viewerState.initHeader()
    // this.headers = viewerState.header;
    this.headers = {
      headers: viewerState.headers,
      rightHeaders: viewerState.rightHeaders,
      tools: viewerState.tools,
      toolItems: viewerState.toolItems
    };
    this.toolButtonObjects = viewerState.toolItems;
    this.headerGroup = 'headers';
    this.index = -1;

    return this;
  },
  /**
   * Select a button from header to edit.
   * @method UI.Header#get
   * @param {string} dataElement data-element of the button.
   * @returns {UI.Header} Header object for chaining. You can call {@link UI.Header#insertBefore insertBefore}, {@link UI.Header#insertAfter insertAfter} and {@link UI.Header#delete delete} to perform an operation on the button.
   */
  get(dataElement) {
    if (this.index !== -1) {
      // get(dataElement) has been called before so we need to reset this
      const item = this.headers[this.headerGroup][this.index];
      Object.keys(item).forEach((key) => {
        delete this[key];
      });
    }

    this._setIndex(dataElement);

    if (this.index === -1) {
      console.warn(`${dataElement} does not exist in ${this.headerGroup} header`);
    } else {
      const item = this.headers[this.headerGroup][this.index];
      Object.keys(item).forEach((key) => {
        this[key] = item[key];
      });
    }

    return this;
  },
  /**
   * Get all list of header items from a group selected from {@link UI.Header#getHeader getHeader}. By default, it returns the items from 'default' group.
   * @method UI.Header#getItems
   * @returns {Array.<object>} List of header item objects. You can edit it using normal array operations and update the whole header by passing it to {@link UI.Header#update update}.
   */
  getItems() {
    return this.headers[this.headerGroup];
  },
  /**
   * Select a header group to edit.
   * @method UI.Header#getHeader
   * @param {string} headerGroup Name of the header group. Possible options are 'default', 'small-mobile-more-buttons', 'toolbarGroup-View', 'toolbarGroup-Annotate', 'toolbarGroup-Shapes', 'toolbarGroup-Insert', 'toolbarGroup-Measure', and 'toolbarGroup-Edit'
   * @returns {UI.Header} Header object for chaining. You can call {@link UI.Header#get get}, {@link UI.Header#getItems getItems}, {@link UI.Header#shift shift}, {@link UI.Header#unshift unshift}, {@link UI.Header#push push}, {@link UI.Header#pop pop} and {@link UI.Header#update update}.
   */
  getHeader(headerGroup) {
    const headerGroups = Object.keys(this.headers);

    if (headerGroups.includes(headerGroup)) {
      this.headerGroup = headerGroup;
      this._resetIndex();
    } else {
      console.warn(`Header must be one of: ${headerGroups.join(' or ')}.`);
    }

    return this;
  },
  /**
   * Insert a button before the selected button from {@link UI.Header#get get}.
   * @method UI.Header#insertBefore
   * @param {object} obj A header object. See <a href='https://docs.apryse.com/documentation/web/guides/customizing-header/#header-items' target='_blank'>Header items</a> for details.
   * @returns {UI.Header} Header object for chaining. You can call {@link UI.Header#get get}, {@link UI.Header#getItems getItems}, {@link UI.Header#shift shift}, {@link UI.Header#unshift unshift}, {@link UI.Header#push push}, {@link UI.Header#pop pop} and {@link UI.Header#update update}.
   */
  insertBefore(newItem) {
    if (this.index === -1) {
      console.warn('Please use .get(dataElement) first before using insertBefore');
    } else {
      this.headers[this.headerGroup].splice(this.index, 0, newItem);
    }

    return this;
  },
  /**
   * Insert a button after the selected button from {@link UI.Header#get get}.
   * @method UI.Header#insertAfter
   * @param {object} obj A header object. See <a href='https://docs.apryse.com/documentation/web/guides/customizing-header/#header-items' target='_blank'>Header items</a> for details.
   * @returns {UI.Header} Header object for chaining. You can call {@link UI.Header#get get}, {@link UI.Header#getItems getItems}, {@link UI.Header#shift shift}, {@link UI.Header#unshift unshift}, {@link UI.Header#push push}, {@link UI.Header#pop pop} and {@link UI.Header#update update}.
   */
  insertAfter(newItem) {
    if (this.index === -1) {
      console.warn('Please use .get(dataElement) first before using insertAfter');
    } else {
      this.index++;
      this.headers[this.headerGroup].splice(this.index, 0, newItem);
    }

    return this;
  },
  /**
   * Delete a button.
   * @method UI.Header#delete
   * @param {(number|string)} [id] You can either pass an index or `data-element` of the button to delete. If you already selected a button from {@link UI.Header#get get}, passing null would delete the selected button.
   * @returns {UI.Header} Header object for chaining. You can call {@link UI.Header#get get}, {@link UI.Header#getItems getItems}, {@link UI.Header#shift shift}, {@link UI.Header#unshift unshift}, {@link UI.Header#push push}, {@link UI.Header#pop pop} and {@link UI.Header#update update}.
   */
  delete(arg) {
    let index;

    if (typeof arg === 'number') {
      index = arg;
    } else if (typeof arg === 'string') {
      if (this._getIndexOfElement(arg) === -1) {
        console.warn(`${arg} does not exist in ${this.headerGroup} header`);
      } else {
        index = this._getIndexOfElement(arg);
      }
    } else if (typeof arg === 'undefined') {
      if (this.index === -1) {
        console.warn('Please use .get(dataElement) first before using delete()');
      } else {
        index = this.index;
      }
    } else if (Array.isArray(arg)) {
      arg.forEach((arg) => {
        if (typeof arg === 'number' || typeof arg === 'string') {
          this.delete(arg);
        }
      });
    } else {
      console.warn('Argument must be empty, a number, a string or an array');
    }

    if (index !== undefined && index !== -1) {
      this.headers[this.headerGroup].splice(index, 1);
      this._resetIndex();
    }

    return this;
  },
  /**
   * Removes the first button in the header.
   * @method UI.Header#shift
   * @returns {UI.Header} Header object for chaining. You can call {@link UI.Header#get get}, {@link UI.Header#getItems getItems}, {@link UI.Header#shift shift}, {@link UI.Header#unshift unshift}, {@link UI.Header#push push}, {@link UI.Header#pop pop} and {@link UI.Header#update update}.
   */
  shift() {
    this.headers[this.headerGroup].shift();

    return this;
  },
  /**
   * Adds a button (or buttons) to the beginning of the header.
   * @method UI.Header#unshift
   * @param {object|Array.<object>} obj Either one or array of header objects. See <a href='https://docs.apryse.com/documentation/web/guides/customizing-header/#header-items' target='_blank'>Header items</a> for details.
   * @returns {UI.Header} Header object for chaining. You can call {@link UI.Header#get get}, {@link UI.Header#getItems getItems}, {@link UI.Header#shift shift}, {@link UI.Header#unshift unshift}, {@link UI.Header#push push}, {@link UI.Header#pop pop} and {@link UI.Header#update update}.
   */
  unshift(...newItem) {
    this.headers[this.headerGroup].unshift(...newItem);

    return this;
  },
  /**
   * Adds a button (or buttons) to the end of the header.
   * @method UI.Header#push
   * @param {object|Array.<object>} obj Either one or array of header objects. See <a href='https://docs.apryse.com/documentation/web/guides/customizing-header/#header-items' target='_blank'>Header items</a> for details.
   * @returns {UI.Header} Header object for chaining. You can call {@link UI.Header#get get}, {@link UI.Header#getItems getItems}, {@link UI.Header#shift shift}, {@link UI.Header#unshift unshift}, {@link UI.Header#push push}, {@link UI.Header#pop pop} and {@link UI.Header#update update}.
   */
  push(...newItem) {
    this.headers[this.headerGroup].push(...newItem);

    return this;
  },
  /**
   * Removes the last button in the header.
   * @method UI.Header#pop
   * @returns {UI.Header} Header object for chaining. You can call {@link UI.Header#get get}, {@link UI.Header#getItems getItems}, {@link UI.Header#shift shift}, {@link UI.Header#unshift unshift}, {@link UI.Header#push push}, {@link UI.Header#pop pop} and {@link UI.Header#update update}.
   */
  pop() {
    this.headers[this.headerGroup].pop();

    return this;
  },
  /**
   * Updates the header with new list of header items.
   * @method UI.Header#update
   * @param {Array.<object>} headerObjects List of header objects to replace the exising header. You can use {@link UI.Header#getItems getItems} to refer to existing header objects.
   * @returns {UI.Header} Header object for chaining. You can call {@link UI.Header#get get}, {@link UI.Header#getItems getItems}, {@link UI.Header#shift shift}, {@link UI.Header#unshift unshift}, {@link UI.Header#push push}, {@link UI.Header#pop pop} and {@link UI.Header#update update}.
   */
  update(arg) {
    if (Array.isArray(arg)) {
      this._updateItems(arg);
    } else {
      console.warn('Argument must be an array');
    }

    return this;
  },
  _updateItems(items) {
    this.headers[this.headerGroup].length = 0
    this.headers[this.headerGroup].push(...items)

    return this;
  },
  _setIndex(dataElement) {
    this.index = this._getIndexOfElement(dataElement);
  },
  _getIndexOfElement(dataElement) {
    return this.headers[this.headerGroup].findIndex((item) => {
      let dataElementOfItem;

      if (item.type === 'toolButton') {
        dataElementOfItem = this.toolButtonObjects[item.toolName].dataElement;
      } else {
        dataElementOfItem = item.dataElement;
      }

      return dataElementOfItem === dataElement;
    });
  },
  _resetIndex() {
    this.index = -1;
  },
};
