import {PluginKey} from 'prosemirror-state';
import {columnTypesMap, types} from '../columnsTypes/types.config';
import {
  getBaseName,
  getColIndex,
  removeInvisibleCharacterFromText
} from '../util';

export const tableFiltersMenuKey = new PluginKey('TableFiltersMenu');

export const generateMenuPopup = () => {
  const menuElement = document.createElement('div');
  menuElement.className = `table-filters-menu`;
  menuElement.dataset.test = `table-filters-menu`;
  menuElement.style.display = 'none';
  menuElement.style.position = 'absolute';
  menuElement.style.zIndex = '200';

  return menuElement;
};

export const displayPopup = (view, popupDOM) => {
  const menuData = tableFiltersMenuKey.getState(view.state);

  if (menuData) {
    popupDOM.style.display = 'flex';
    return menuData;
  }

  return null;
};

export const calculateMenuPosition = (menuDOM, {dom: tableDOM}) => {
  const {style} = menuDOM;
  const {left, height: cellHeight, top} = tableDOM.getBoundingClientRect();

  if (left === 0 || top === 0 || cellHeight === 0) return;

  // scroll offset
  const [scrolledEl] = document.getElementsByClassName(
    `${getBaseName()}-editor-frame-body`
  );
  const {x: EDITOR_LEFT_OFFSET, y: EDITOR_TOP_OFFSET} =
    scrolledEl.getBoundingClientRect();

  style.top = `${top - EDITOR_TOP_OFFSET + (scrolledEl.scrollTop || 0) + 8}px`;
  style.left = `${left - EDITOR_LEFT_OFFSET + 8}px`;
};

export const createDefaultFilter = (state, table, headerPos) => {
  const colHeader = headerPos
    ? table.firstChild.child(getColIndex(state, headerPos))
    : table.firstChild.firstChild;

  const {type: headerType} = colHeader.attrs;
  const typeConfig = types.find((type) => type.id === headerType);
  const typeFirstFilter = typeConfig.filters.find((filter) => filter.default);

  return {
    headerId: colHeader.attrs.id,
    filterId: typeFirstFilter.id,
    filterValue: typeFirstFilter.defaultValue
  };
};

export const getColsOptions = (table) => {
  const headersRow = table.firstChild;
  const headers = headersRow.content.content.map((headerNode) => {
    return {
      label: removeInvisibleCharacterFromText(headerNode.textContent).length
        ? headerNode.textContent
        : 'Untitled',
      value: headerNode.attrs.id,
      itemStyleClass: `colItem ${headerNode.attrs.type}Type`,
      hasIcon: true
    };
  });
  return headers;
};

const filterColumn = (
  tableRows,
  colIndex,
  colType,
  filter,
  filterGroupIndex
) => {
  if (colIndex === null) return;
  const colCells = tableRows.map((row) => row.node.child(colIndex));

  const filterConfig = colType.filters.find(
    (filterConfig) => filterConfig.id === filter.filterId
  );

  if (!filterConfig) return;
  const filterLogic = filterConfig.logic;

  colCells.forEach((cell, rowIndex) => {
    if (!tableRows[rowIndex].hidden[filterGroupIndex]) {
      tableRows[rowIndex].hidden[filterGroupIndex] = false;
    }
    if (!filterLogic(cell, filter.filterValue)) {
      tableRows[rowIndex].hidden[filterGroupIndex] = true;
    }
  });
};

const checkIfFilterMatchColType = (headers, headerId, filterId) => {
  const filterHeaderNode = headers.find(
    (header) => header.attrs.id === headerId
  );
  if (!filterHeaderNode) return false;
  const filterColType = columnTypesMap[filterHeaderNode.attrs.type];

  return filterColType.filters.map((filter) => filter.id).includes(filterId);
};

export const executeFilters = (table, tablePos, state, filters) => {
  const tableFilterGroups = filters || table.attrs.filters;

  const headersRow = table.firstChild;
  const tableRows = [];

  // get all rows and their pos
  table.descendants((node, pos, parent) => {
    if (parent.type.name !== 'table') return false; // go over the rows only and not their content
    tableRows.push({node, pos: pos + tablePos, hidden: []});
    return false;
  });

  tableRows.splice(0, 1); // remove headers row

  const tableHeaders = {};
  // get all headers and their position
  headersRow.descendants((header, pos, parent) => {
    if (parent.type.name !== 'table_row') return false; // go over the headers only and not their content
    const colType = columnTypesMap[header.attrs.type];
    const colIndex = getColIndex(state, pos + tablePos + 1);
    tableHeaders[header.attrs.id] = {colType, colIndex};
    return false;
  });

  // apply filters on each column
  tableFilterGroups.forEach((filterGroup, groupIndex) => {
    filterGroup.forEach((filter) => {
      const {colType, colIndex} = tableHeaders[filter.headerId];
      filterColumn(tableRows, colIndex, colType, filter, groupIndex);
    });
  });

  // check OR condition between filterGroups
  tableRows.forEach((row) => {
    row.hidden = row.hidden.length
      ? row.hidden.every((groupResult) => !!groupResult)
      : false;
  });

  const {tr} = state;

  tableRows.forEach((row) => {
    tr.setNodeMarkup(row.pos, undefined, {
      ...row.node.attrs,
      hidden: !!row.hidden
    });
  });

  // update table attrs with new filters
  if (filters) {
    tr.setNodeMarkup(tablePos - 1, undefined, {
      ...table.attrs,
      filters: tableFilterGroups
    });
  } else {
    const headers = headersRow.content.content;

    // delete all filters that dont have matching column + remove empty groups
    const relevantFilters = tableFilterGroups
      .map((filterGroup) =>
        filterGroup.filter((filter) => {
          const hasMatchingCol = headers
            .map((header) => header.attrs.id)
            .includes(filter.headerId);

          if (!hasMatchingCol) return false;

          const matchToColType = checkIfFilterMatchColType(
            headers,
            filter.headerId,
            filter.filterId
          );
          return hasMatchingCol && matchToColType;
        })
      )
      .filter((filterGroup) => filterGroup.length);

    tr.setNodeMarkup(tablePos - 1, undefined, {
      ...table.attrs,
      filters: relevantFilters
    });
  }
  return tr;
};

const CONCATENATION_ITEMS = [
  {
    value: 'and',
    label: 'And'
  },
  {
    value: 'or',
    label: 'Or'
  }
];

export const getConcatenationItems = () => {
  return CONCATENATION_ITEMS;
};
