(function collonadeClosure() {
  'use strict';

  window.utils = window.utils || {};

  var HEADER_HEIGHT = 80;
  var RESET_OPTIONS = [ 'scrollTo' ];
  var TABLE_FILTER_FORM_CLASS = 'table-filter-form';
  var ASYNC_TABLE_CONTENT_CHANGED_CLASS = 'async-table--changed';
  var ASYNC_TABLE_LOADING_CLASS = 'async-table--loading';
  var JS_INITIALIZED_CLASS = 'js-async-table-initialized';

  window.utils.asyncTable = function(wrapper, options) {

    options = options || {};
    var tableIdent = options.dbtIdent;
    var shortCircuitHeader = options ? options.headerDBTableShortcircuit : null;

    var ths = [];
    var pageLinks = [];
    var pagesizeForm;
    var scrollTable;

    var utilInstances = [];

    function init() {
      var table = wrapper.querySelector('#' + tableIdent);

      if (!table) {
        return;
      }

      scrollTable = wrapper.querySelector('.scrolltable');

      // sortable table headers
      ths = Array.from(table.querySelectorAll('th.sortable')).map(function(th) {
        return { element: th };
      });

      // pagination links
      var pagination = wrapper.querySelector('#' + tableIdent + '-pagination');
      if (pagination) {
        pageLinks = Array.from(pagination.querySelectorAll('.page-link')).map(function(link) {
          return { element: link };
        });
      }

      // pagesize form
      pagesizeForm = wrapper.querySelector('#' + tableIdent + '-pagesize-form');

      // check all
      utilInstances.push(window.utils.setup('checkAll', wrapper));

      // showhide
      utilInstances.push(window.utils.setup('showHide', wrapper));

      // filter
      var filterForm = wrapper.querySelector('.' + TABLE_FILTER_FORM_CLASS);
      if (filterForm) {
        options.updateTableFrom = updateTableFrom;
        utilInstances.push(window.utils.setup('asyncTableFilter', filterForm, options));
      }

      // take options into account
      if (options.scrollTo) {
        window.scrollTo(options.scrollTo);
      }

      if (options.horizPos && scrollTable) {
        scrollTable.scrollLeft = options.horizPos;
      }

      setupListeners();
      wrapper.classList.add(JS_INITIALIZED_CLASS);
    }

    function setupListeners() {
      ths.forEach(function(th) {
        th.clickHandler = function(event) {
          var boundClickHandler = clickHandler.bind(this);
          var horizPos = (scrollTable || {}).scrollLeft;
          boundClickHandler(event, { horizPos });
        };
        th.element.addEventListener('click', th.clickHandler);
      });

      pageLinks.forEach(function(link) {
        link.clickHandler = function(event) {
          var boundClickHandler = clickHandler.bind(this);
          var tableBoundingRect = scrollTable.getBoundingClientRect();
          var tableOptions = {};
          if (tableBoundingRect.top < HEADER_HEIGHT) {
            tableOptions.scrollTo = {
              top: (scrollTable.offsetTop || 0) - HEADER_HEIGHT,
              left: scrollTable.offsetLeft || 0,
              behavior: 'smooth',
            };
          }
          boundClickHandler(event, tableOptions);
        }
        link.element.addEventListener('click', link.clickHandler);
      });

      if (pagesizeForm) {
        var pagesizeSelect = pagesizeForm.querySelector('[name=' + tableIdent + '-pagesize]');
        pagesizeSelect.addEventListener('change', changePagesizeHandler);
      }
    }

    function removeListeners() {
      ths.forEach(function(th) {
        th.element.removeEventListener('click', th.clickHandler);
      });

      pageLinks.forEach(function(link) {
        link.element.removeEventListener('click', link.clickHandler);
      });

      if (pagesizeForm) {
        var pagesizeSelect = pagesizeForm.querySelector('[name=' + tableIdent + '-pagesize]')
        pagesizeSelect.removeEventListener('change', changePagesizeHandler);
      }
    }

    function clickHandler(event, tableOptions) {
      event.preventDefault();
      var url = getClickDestination(this);
      if (!url.match(/^http/)) {
        url = new URL(window.location.origin + window.location.pathname + getClickDestination(this));
      }
      updateTableFrom(url, tableOptions);
    }

    function getClickDestination(el) {
      if (!el.querySelector('a')) {
        return '';
      }
      return el.querySelector('a').getAttribute('href');
    }

    function changePagesizeHandler(event) {
      var pagesizeParamKey = tableIdent + '-pagesize';
      var pageParamKey = tableIdent + '-page';
      var url = new URL(options.currentUrl || window.location.href);
      url.searchParams.set(pagesizeParamKey, event.target.value);
      url.searchParams.set(pageParamKey, 0);
      updateTableFrom(url);
    }

    // fetches new sorted table from url with params and replaces contents of current table
    function updateTableFrom(url, tableOptions, callback) {
      if (!window.utils.httpClient) {
        throw new Error('httpClient not found!');
      }

      wrapper.classList.add(ASYNC_TABLE_LOADING_CLASS);

      tableOptions = tableOptions || {};
      var headers = {
        'Accept': 'text/html',
        [shortCircuitHeader]: tableIdent
      };
      window.utils.httpClient.get(url, headers).then(function(response) {
        if (!response.ok) {
          throw new Error('Looks like there was a problem fetching ' + url.href + '. Status Code: ' + response.status);
        }
        return response.text();
      }).then(function(data) {
        tableOptions.currentUrl = url.href;
        removeListeners();
        updateWrapperContents(data, tableOptions);
        if (callback && typeof callback === 'function') {
          callback(wrapper);
        }

        wrapper.classList.remove(ASYNC_TABLE_LOADING_CLASS);
      }).catch(function(err) {
        console.error(err);
      });
    }

    function updateWrapperContents(newHtml, tableOptions) {
      tableOptions = tableOptions || {};
      wrapper.innerHTML = newHtml;
      wrapper.classList.remove(JS_INITIALIZED_CLASS);
      wrapper.classList.add(ASYNC_TABLE_CONTENT_CHANGED_CLASS);

      destroyUtils();

      // merge global options and table specific options
      var resetOptions = {};
      Object.keys(options)
        .filter(function(key) {
          return !RESET_OPTIONS.includes(key);
        })
        .forEach(function(key) {
          resetOptions[key] = options[key];
        });
      var combinedOptions = {};
      combinedOptions = Object.keys(tableOptions)
        .filter(function(key) {
          return tableOptions.hasOwnProperty(key);
        })
        .map(function(key) {
          return { key, value: tableOptions[key] }
        })
        .reduce(function(cumulatedOpts, opt) {
          cumulatedOpts[opt.key] = opt.value;
          return cumulatedOpts;
        }, resetOptions);

      window.utils.setup('asyncTable', wrapper, combinedOptions);

      Array.from(wrapper.querySelectorAll('form')).forEach(function(form) {
        utilInstances.push(window.utils.setup('form', form));
      });
      Array.from(wrapper.querySelectorAll('.modal')).forEach(function(modal) {
        utilInstances.push(window.utils.setup('modal', modal));
      });
    }

    function destroyUtils() {
      utilInstances.filter(function(utilInstance) {
        return !!utilInstance;
      }).forEach(function(utilInstance) {
        utilInstance.destroy();
      });
    }

    init();

    return {
      scope: wrapper,
      destroy: destroyUtils,
    };
  };
})();
