(function collonadeClosure() {
  'use strict';

  /**
   *
   * Async Table Utility
   *  makes table filters, sorting and pagination behave asynchronously via AJAX calls
   *
   * Attribute: uw-async-table
   *
   * Example usage:
  *   (regular table)
   */

  var INPUT_DEBOUNCE = 600;
  var HEADER_HEIGHT = 80;

  var ASYNC_TABLE_UTIL_NAME = 'asyncTable';
  var ASYNC_TABLE_UTIL_SELECTOR = '[uw-async-table]';

  var ASYNC_TABLE_LOCAL_STORAGE_KEY = 'ASYNC_TABLE';
  var ASYNC_TABLE_SCROLLTABLE_SELECTOR = '.scrolltable';
  var ASYNC_TABLE_INITIALIZED_CLASS = 'async-table--initialized';
  var ASYNC_TABLE_LOADING_CLASS = 'async-table--loading';

  var ASYNC_TABLE_FILTER_FORM_SELECTOR = '.table-filter-form';
  var ASYNC_TABLE_FILTER_FORM_ID_SELECTOR = '[name="form-identifier"]';


  var asyncTableUtil = function(element) {
    var asyncTableHeader;
    var asyncTableId;

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

    var tableFilterInputs = {
      search: [],
      input: [],
      change: [],
      select: [],
    }

    function init() {
      if (!element) {
        throw new Error('Async Table utility cannot be setup without an element!');
      }

      // param asyncTableDbHeader
      if (element.dataset.asyncTableDbHeader !== undefined) {
        asyncTableHeader = element.dataset.asyncTableDbHeader;
      }

      asyncTableId = element.querySelector('table').id;

      // find scrolltable wrapper
      scrollTable = element.querySelector(ASYNC_TABLE_SCROLLTABLE_SELECTOR);
      if (!scrollTable) {
        throw new Error('Async Table cannot be set up without a scrolltable element!');
      }

      setupSortableHeaders();
      setupPagination();
      setupPageSizeSelect();
      setupTableFilter();

      processLocalStorage();

      // clear currentTableUrl from previous requests
      setLocalStorageParameter('currentTableUrl', null);

      // mark initialized
      element.classList.add(ASYNC_TABLE_INITIALIZED_CLASS);

      return {
        name: ASYNC_TABLE_UTIL_NAME,
        element: element,
        destroy: function() {},
      };
    }

    function setupSortableHeaders() {
      ths = Array.from(scrollTable.querySelectorAll('th.sortable')).map(function(th) {
        return { element: th };
      });

      ths.forEach(function(th) {
        th.clickHandler = function(event) {
          setLocalStorageParameter('horizPos', (scrollTable || {}).scrollLeft);
          linkClickHandler(event);
        };
        th.element.addEventListener('click', th.clickHandler);
      });
    }

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

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

    function setupPageSizeSelect() {
      // pagesize form
      pagesizeForm = element.querySelector('#' + asyncTableId + '-pagesize-form');

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

    function setupTableFilter() {
      var tableFilterForm = element.querySelector(ASYNC_TABLE_FILTER_FORM_SELECTOR);

      if (tableFilterForm) {
        gatherTableFilterInputs(tableFilterForm);
        addTableFilterEventListeners(tableFilterForm);
      }
    }

    function gatherTableFilterInputs(tableFilterForm) {
      Array.from(tableFilterForm.querySelectorAll('input[type="search"]')).forEach(function(input) {
        tableFilterInputs.search.push(input);
      });

      Array.from(tableFilterForm.querySelectorAll('input[type="text"]')).forEach(function(input) {
        tableFilterInputs.input.push(input);
      });

      Array.from(tableFilterForm.querySelectorAll('input:not([type="text"]):not([type="search"])')).forEach(function(input) {
        tableFilterInputs.change.push(input);
      });

      Array.from(tableFilterForm.querySelectorAll('select')).forEach(function(input) {
        tableFilterInputs.select.push(input);
      });
    }

    function addTableFilterEventListeners(tableFilterForm) {
      tableFilterInputs.search.forEach(function(input) {
        var debouncedInput = debounce(function() {
          if (input.value.length === 0 || input.value.length > 2) {
            updateFromTableFilter();
          }
        }, INPUT_DEBOUNCE);
        input.addEventListener('input', debouncedInput);
      });

      tableFilterInputs.input.forEach(function(input) {
        var debouncedInput = debounce(function() {
          if (input.value.length === 0 || input.value.length > 2) {
            updateFromTableFilter();
          }
        }, INPUT_DEBOUNCE);
        input.addEventListener('input', debouncedInput);
      });

      tableFilterInputs.change.forEach(function(input) {
        input.addEventListener('change', function() {
          updateFromTableFilter();
        });
      });

      tableFilterInputs.select.forEach(function(input) {
        input.addEventListener('change', function() {
          updateFromTableFilter();
        });
      });

      tableFilterForm.addEventListener('submit', function(event) {
        event.preventDefault();
        updateFromTableFilter();
      });
    }

    function updateFromTableFilter() {
      var url = serializeTableFilterToURL();
      var callback = null;

      var focusedSearch = tableFilterInputs.search.reduce(function(acc, input) {
        return acc || (input.matches(':focus') && input);
      }, null);
      // focus search input
      if (focusedSearch) {
        var selectionStart = focusedSearch.selectionStart;
        callback = function(wrapper) {
          var search = wrapper.querySelector('input[type="search"]');
          if (search) {
            search.focus();
            search.selectionStart = selectionStart;
          }
        };
      }
      updateTableFrom(url, callback);
    }

    function serializeTableFilterToURL() {
      var url = new URL(getLocalStorageParameter('currentTableUrl') || window.location.href);

      var formIdElement = element.querySelector(ASYNC_TABLE_FILTER_FORM_ID_SELECTOR);
      if (!formIdElement) {
        // cannot serialize the filter form without an identifier
        return;
      }

      url.searchParams.set('form-identifier', formIdElement.value);
      url.searchParams.set('_hasdata', 'true');
      url.searchParams.set(asyncTableId + '-page', '0');

      tableFilterInputs.search.forEach(function(input) {
        url.searchParams.set(input.name, input.value);
      });

      tableFilterInputs.input.forEach(function(input) {
        url.searchParams.set(input.name, input.value);
      });

      tableFilterInputs.change.forEach(function(input) {
        if (input.checked) {
          url.searchParams.set(input.name, input.value);
        }
      });

      tableFilterInputs.select.forEach(function(select) {
        var options = Array.from(select.querySelectorAll('option'));
        var selected = options.find(function(option) { return option.selected });
        if (selected) {
          url.searchParams.set(select.name, selected.value);
        }
      });

      return url;
    }

    function processLocalStorage() {
      var scrollTo = getLocalStorageParameter('scrollTo');
      if (scrollTo && scrollTable) {
        window.scrollTo(scrollTo);
      }
      setLocalStorageParameter('scrollTo', null);

      var horizPos = getLocalStorageParameter('horizPos');
      if (horizPos && scrollTable) {
        scrollTable.scrollLeft = horizPos;
      }
      setLocalStorageParameter('horizPos', null);
    }

    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=' + asyncTableId + '-pagesize]')
        pagesizeSelect.removeEventListener('change', changePagesizeHandler);
      }
    }

    function linkClickHandler(event) {
      event.preventDefault();
      var url = getClickDestination(event.target);
      if (!url.match(/^http/)) {
        url = window.location.origin + window.location.pathname + url;
      }
      updateTableFrom(url);
    }

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

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

    // fetches new sorted element from url with params and replaces contents of current element
    function updateTableFrom(url, callback) {
      if (!HttpClient) {
        throw new Error('HttpClient not found!');
      }

      element.classList.add(ASYNC_TABLE_LOADING_CLASS);

      var headers = {
        'Accept': 'text/html',
        [asyncTableHeader]: asyncTableId
      };

      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) {
        setLocalStorageParameter('currentTableUrl', url.href);
        // reset table
        removeListeners();
        element.classList.remove(ASYNC_TABLE_INITIALIZED_CLASS);
        // update table with new
        updateWrapperContents(data);

        if (callback && typeof callback === 'function') {
          callback(element);
        }

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

    function updateWrapperContents(newHtml) {
      var newPage = document.createElement('div');
      newPage.innerHTML = newHtml;
      var newWrapperContents = newPage.querySelector('#' + element.id);
      element.innerHTML = newWrapperContents.innerHTML;

      if (UtilRegistry) {
        UtilRegistry.setupAll(element);
      }
    }

    return init();
  };

  function setLocalStorageParameter(key, value) {
    var currentLSState = JSON.parse(window.localStorage.getItem(ASYNC_TABLE_LOCAL_STORAGE_KEY)) || {};
    if (value !== null) {
      currentLSState[key] = value;
    } else {
      delete currentLSState[key];
    }
    window.localStorage.setItem(ASYNC_TABLE_LOCAL_STORAGE_KEY, JSON.stringify(currentLSState));
  }

  function getLocalStorageParameter(key) {
    var currentLSState = JSON.parse(window.localStorage.getItem(ASYNC_TABLE_LOCAL_STORAGE_KEY)) || {};
    return currentLSState[key];
  }

  // debounce function, taken from Underscore.js
  function debounce(func, wait, immediate) {
    var timeout;
    return function() {
      var context = this, args = arguments;
      var later = function() {
        timeout = null;
        if (!immediate) func.apply(context, args);
      };
      var callNow = immediate && !timeout;
      clearTimeout(timeout);
      timeout = setTimeout(later, wait);
      if (callNow) func.apply(context, args);
    };
  }

  // register async table utility
  if (UtilRegistry) {
    UtilRegistry.register({
      name: ASYNC_TABLE_UTIL_NAME,
      selector: ASYNC_TABLE_UTIL_SELECTOR,
      setup: asyncTableUtil,
    });
  }
})();
