import { Utility } from '../../core/utility';
import { StorageManager, LOCATION } from '../../lib/storage-manager/storage-manager';
import './hide-columns.sass';

const HIDE_COLUMNS_CONTAINER_IDENT = 'uw-hide-columns';
const TABLE_HEADER_IDENT = 'uw-hide-column-header';
const HIDE_COLUMNS_HIDER_LABEL = 'uw-hide-columns--hider-label';
const HIDE_COLUMNS_NO_HIDE = 'uw-hide-columns--no-hide';

const TABLE_UTILS_ATTR = 'table-utils';
const TABLE_UTILS_CONTAINER_SELECTOR = `[${TABLE_UTILS_ATTR}]`;

const TABLE_HIDER_CLASS = 'table-hider';
const TABLE_HIDER_VISIBLE_CLASS = 'table-hider--visible';

const TABLE_PILL_CLASS = 'table-pill';

const CELL_HIDDEN_CLASS = 'hide-columns--hidden-cell';
const CELL_ORIGINAL_COLSPAN = 'uw-hide-column-original-colspan';

@Utility({
  selector: `[${HIDE_COLUMNS_CONTAINER_IDENT}] table`,
})
export class HideColumns {

  _storageManager = new StorageManager('HIDE_COLUMNS', '1.1.0', { location: LOCATION.LOCAL });

  _element;
  _elementWrapper;
  _tableUtilContainer;

  _autoHide;

  headerToHider = new Map();
  hiderToHeader = new Map();

  addHeaderHider(th, hider) {
    this.headerToHider.set(th, hider);
    this.hiderToHeader.set(hider, th);
  }

  constructor(element) {
    this._autoHide  = this._storageManager.load('autoHide', {}) || false;

    if (!element) {
      throw new Error('Hide Columns utility cannot be setup without an element!');
    }

    // do not provide hide-column ability in tables inside modals, async forms with response or tail.datetime instances
    if (element.closest('[uw-modal], .async-form__response, .tail-datetime-calendar')) {
      return false;
    }

    this._element = element;

    const hideColumnsContainer = this._element.closest(`[${HIDE_COLUMNS_CONTAINER_IDENT}]`);
    if (!hideColumnsContainer) {
      throw new Error('Hide Columns utility needs to be setup on a table inside a hide columns container!');
    }
    this._elementWrapper = hideColumnsContainer;

    // get or create table utils container
    this._tableUtilContainer = hideColumnsContainer.querySelector(TABLE_UTILS_CONTAINER_SELECTOR);
    if (!this._tableUtilContainer) {
      this._tableUtilContainer = document.createElement('div');
      this._tableUtilContainer.setAttribute(TABLE_UTILS_ATTR, '');
      const tableContainer = this._element.closest(`[${HIDE_COLUMNS_CONTAINER_IDENT}] > *`);
      hideColumnsContainer.insertBefore(this._tableUtilContainer, tableContainer);
    }

    [...this._element.querySelectorAll('th')].filter(th => !th.hasAttribute(HIDE_COLUMNS_NO_HIDE)).forEach(th => this.setupHideButton(th));
  }

  setupHideButton(th) {
    const preHidden = this.isHiddenColumn(th);

    const hider = document.createElement('span');

    const hiderIcon = document.createElement('i');
    hiderIcon.classList.add('fas', 'fa-fw');
    hider.appendChild(hiderIcon);

    const hiderContent = document.createElement('span');
    hiderContent.classList.add('table-hider__label');
    hiderContent.innerHTML = th.getAttribute(HIDE_COLUMNS_HIDER_LABEL) || th.innerText;
    hider.appendChild(hiderContent);

    this.addHeaderHider(th, hider);

    th.addEventListener('mouseover', () => {
      hider.classList.add(TABLE_HIDER_VISIBLE_CLASS);
    });
    th.addEventListener('mouseout', () => {
      if (hider.classList.contains(TABLE_HIDER_CLASS)) {
        hider.classList.remove(TABLE_HIDER_VISIBLE_CLASS);
      }
    });

    hider.addEventListener('click', (event) => {
      event.preventDefault();
      event.stopPropagation();
      this.switchColumnDisplay(th, hider);
      // this._tableHiderContainer.getElementsByClassName(TABLE_HIDER_CLASS).forEach(hider => this.hideHiderBehindHeader(hider));
    });

    hider.addEventListener('mouseover', () => {
      hider.classList.add(TABLE_HIDER_VISIBLE_CLASS);
      const currentlyHidden = this.isHiddenColumn(th);
      this.updateHiderIcon(hider, !currentlyHidden);
    });
    hider.addEventListener('mouseout', () => {
      if (hider.classList.contains(TABLE_HIDER_CLASS)) {
        hider.classList.remove(TABLE_HIDER_VISIBLE_CLASS);
      }
      const currentlyHidden = this.isHiddenColumn(th);
      this.updateHiderIcon(hider, currentlyHidden);
    });

    new ResizeObserver(() => { this.repositionHider(hider); }).observe(th);

    // reposition hider on each window resize event
    // window.addEventListener('resize', () => this.repositionHider(hider));

    this.updateColumnDisplay(this.colIndex(th), preHidden);
    this.updateHider(hider, preHidden);

    if (preHidden) {
      this._tableUtilContainer.appendChild(hider);
    } else {
      this.hideHiderBehindHeader(hider);
    }
  }

  switchColumnDisplay(th, hider) {
    const hidden = !this.isHiddenColumn(th);
    const originalColspan = Math.max(1, th.getAttribute(CELL_ORIGINAL_COLSPAN)) || 1;
    const colspan = Math.max(1, th.colSpan) || 1;
    const columnIndex = this.colIndex(th);

    for (var i = 0; i < Math.max(colspan, originalColspan); i++) {
      this.updateColumnDisplay(columnIndex + i, hidden);
    }
    this.updateHider(hider, hidden);

    // persist new hidden setting for column
    if ((hidden && this.isEmptyColumn(columnIndex) && this._autoHide) || (!hidden && (!this.isEmptyColumn(columnIndex) || !this._autoHide))) {
      this._storageManager.remove(this.getStorageKey(th));
    } else {
      this._storageManager.save(this.getStorageKey(th), hidden);
    }
  }

  updateColumnDisplay(columnIndex, hidden) {
    this._element.getElementsByTagName('tr').forEach(row => {
      const cell = this.getCol(row, columnIndex);

      if (cell) {
        const originalColspan = cell.getAttribute(CELL_ORIGINAL_COLSPAN);
        const colspan = Math.max(1, cell.colSpan) || 1;

        if (hidden) {
          if (colspan > 1) {
            if (!originalColspan) {
              cell.setAttribute(CELL_ORIGINAL_COLSPAN, colspan);
            }
            cell.colSpan--;
          } else {
            cell.classList.add(CELL_HIDDEN_CLASS);
          }
        } else {
          if (cell.classList.contains(CELL_HIDDEN_CLASS)) {
            cell.classList.remove(CELL_HIDDEN_CLASS);
          } else if (originalColspan && colspan < originalColspan) {
            cell.colSpan++;
          }
        }
      }
    });
  }

  updateHider(hider, hidden) {
    if (hidden) {
      hider.classList.remove(TABLE_HIDER_CLASS);
      hider.classList.add(TABLE_PILL_CLASS);
      this._tableUtilContainer.appendChild(hider);
    } else {
      hider.classList.remove(TABLE_PILL_CLASS);
      hider.classList.add(TABLE_HIDER_CLASS);
      this.hideHiderBehindHeader(hider);
    }
    this.updateHiderIcon(hider, hidden);
  }

  updateHiderIcon(hider, hidden) {
    hider.getElementsByClassName('fas').forEach(hiderIcon => {
      hiderIcon.classList.remove(hidden ? 'fa-eye' : 'fa-eye-slash');
      hiderIcon.classList.add(hidden ? 'fa-eye-slash' : 'fa-eye');
    });
  }

  hideHiderBehindHeader(hider) {
    if (!this.hiderToHeader.get(hider).contains(hider)) {
      this.hiderToHeader.get(hider).appendChild(hider);
    }

    this.repositionHider(hider);

    // remove visible class if necessary
    hider.classList.remove(TABLE_HIDER_VISIBLE_CLASS);
  }

  repositionHider(hider) {
    const thR = this.hiderToHeader.get(hider).getBoundingClientRect(),
          hR  = hider.getBoundingClientRect();

    hider.style.left = (thR.width/2 - hR.width/2) + 'px';
    hider.style.top  = thR.height + 'px';
  }

  getStorageKey(th) {
    // get handler name
    const handlerIdent = document.querySelector('[uw-handler]').getAttribute('uw-handler');

    // get hide-columns container ident (if not present, use table index in document as fallback)
    let tIdent = this._elementWrapper.getAttribute(HIDE_COLUMNS_CONTAINER_IDENT);
    if (!tIdent) {
      const tablesInDocument = document.getElementsByTagName('TABLE');
      for (let i = 0; i < tablesInDocument.length; i++) {
        if (tablesInDocument[i] === this._element) {
          tIdent = i;
          break;
        }
      }
    }

    // check for unique table header ident from backend (if not present, use cell index as fallback)
    let thIdent = th.getAttribute(TABLE_HEADER_IDENT);
    if (!thIdent) {
      thIdent = this.colIndex(th);
    }

    return `${handlerIdent}__${tIdent}__${thIdent}`;
  }

  isEmptyColumn(columnIndex) {
    for (let row of this._element.getElementsByTagName('tr')) {
      const cell = this.getCol(row, columnIndex);
      if (cell.matches('th'))
        continue;
      if (cell.querySelector('.table__td-content')) {
        for (let child of cell.children) {
          if (!isEmptyElement(child))
            return false;
        }
        return true;
      } else {
        return isEmptyElement(cell);
      }
    }
  }

  isHiddenColumn(th) {
    const hidden      = this._storageManager.load(this.getStorageKey(th)),
          emptyColumn = this.isEmptyColumn(this.colIndex(th));
    return hidden === true || hidden === undefined && emptyColumn && this._autoHide;
  }

  colSpan(cell) {
    if (!cell)
      return 1;
    
    const originalColspan = cell.getAttribute(CELL_ORIGINAL_COLSPAN);
    const colspan = Math.max(1, cell.colSpan) || 1;

    return originalColspan ? Math.max(colspan, originalColspan) : colspan;
  }

  colIndex(cell) {
    if (!cell)
      return 0;

    const rowParent = cell.closest('tr');

    if (!rowParent)
      return 0;

    var i = 0;
    for (const sibling of Array.from(rowParent.cells).slice(0, cell.cellIndex)) {
      i += this.colSpan(sibling);
    }

    return i;
  }

  getCol(row, columnIndex) {
    var c = 0;

    for (const cell of row.cells) {
      c += cell ? this.colSpan(cell) : 1;

      if (columnIndex < c)
        return cell;
    }
  }
}

function isEmptyElement(element) {
  for (let child of element.childNodes) {
    if (child.nodeName !== '#comment')
      return false;
  }
  return true;
}

