import * as React from 'react';
import classNames from 'classnames/bind';
import _ from 'lodash';
import TextItem from 'components/items/TextItem';
import ModuleButton from 'components/items/ModuleButton';
import ReactPaginate from 'react-paginate';
import { withRouter } from 'react-router-dom';
import VirtualTable from 'components/pageItems/VirtualTable';
import Loading from 'components/items/Loading';
import { RiOrderPlayFill } from 'react-icons/ri';

import '!style-loader!css-loader!react-virtualized/styles.css';

import qs from 'query-string';

import filter from 'lodash/filter';
import { config } from 'config/general';
import { clone } from 'store/utils/helper';
import styleIdentifiers from './customFixedTable.scss';

const styles = classNames.bind(styleIdentifiers);

export interface CustomFixedTableProps {
  noCheckbox?: boolean;
  items: any[];
  headers: Record<string, any>[];
  loaded?: boolean;
  loading?: boolean;
  customOrder?: Function;
  itemProps?: Record<string, any>;
  noShadow?: boolean;
  location: Record<string, any>;
  history: Record<string, any>;
  url?: string;
  loadFunc?: Function;
  handleFilters?: Function;
  subhead?: React.Element<any>;
  actions?: React.Element<any>;
  legend?: React.Element<any>;
  actionDelete?: Function;
  title?: string;
  loaded?: boolean;
  virtual?: boolean;
  loading?: boolean;
  loadMore?: Function;
  noMore?: boolean;
  itemProps?: Record<string, any>;
  // filters
  displayFilters?: boolean;
  filtersProps?: Record<string, any>;
  filtersInitialValues?: Record<string, any>;
  // reset
  pagination?: Record<string, any>;
  currentPage?: number;
  noResultMessage?: string;
  // language
  lg?: string;
  // More
  inModule?: boolean;
  // legend
  legend?: object;
  transformFunction?: Function;
}

interface CustomFixedTableState {
  list: any[];
  filters: Record<string, any>;
  masterCheck: boolean;
  order: string;
  asc: boolean;
}

function CustomFixedTable(
  ItemComponent: React.ComponentType<any>,
  FiltersForm: React.Component<any>,
) {
  class FixedTable extends React.Component<CustomFixedTableProps, CustomFixedTableState> {
    constructor(props: CustomFixedTableProps) {
      super(props);

      const { location } = props;

      this.state = {
        // used to check the item internaly (could have used the store as well)
        list: [],
        filters: qs.parse(location.search),
        masterCheck: false,
        checkedItems: [],
        order: '',
        asc: true,
        selectedLegends: [],
        // checked: [],
      };
    }

    componentDidMount() {
      this.updateList();
      this.loadData();
    }

    componentDidUpdate(prevProps: CustomFixedTableProps, prevState) {
      this.updateList(prevProps);
      this.onUpdate(prevProps, prevState);
    }

    // Could be done in reducers
    onItemClick = (id: number, targetValue?: boolean) => {
      const list = this.items();

      let { checkedItems } = this.state;

      checkedItems = checkedItems || [];

      const index = checkedItems.indexOf(id);

      if (targetValue === true) {
        // force true
        if (index < 0) checkedItems.push(id);
        // else keep
      } else if (targetValue === false && index >= 0) {
        if (index >= 0) checkedItems.splice(index, 1);
        // else don't change anything
      } else if (index < 0) {
        checkedItems.push(id);
      } else {
        checkedItems.splice(index, 1);
      }

      let masterCheck = false;
      if (checkedItems.length === list.length) {
        masterCheck = true;
      }

      this.setState({
        checkedItems,
        masterCheck,
      });

      // const index = _.findIndex(list, e => e[config.idKey] === id);
      // const newElement = list[index];

      // let masterCheck = true;
      // // toggle part
      // if (targetValue !== undefined) newElement.checked = targetValue;
      // else if (newElement.checked === true) {
      //   newElement.checked = false;
      // } else {
      //   newElement.checked = true;
      // }
      // list.splice(index, 1, newElement);

      // if (!targetValue) {
      //   list.forEach(element => {
      //     if (!element.checked) masterCheck = false;
      //   });
      //   this.setState({
      //     list,
      //     masterCheck,
      //   });
      // }
    };

    onUpdate = (prevProps?: CustomFixedTableProps, prevState) => {
      const { location, handleChecked } = this.props;
      const { checkedItems } = this.state;

      // provided checked items to parent
      if (checkedItems && handleChecked) {
        handleChecked(checkedItems);
      }

      if (!prevProps || location !== prevProps.location) {
        this.setState(
          {
            checkedItems: [],
            masterCheck: false,
            order: '',
            asc: true,
            filters: qs.parse(location.search),
          },
          this.loadData,
        );
      }
    };

    // getCheckedItems = () => {
    //   const { checked } = this.state;
    //   const list = this.items();

    //   const data = [];
    //   if (!list || !Array.isArray(list)) return data;
    //   list.forEach(element => {
    //     if (element.checked) data.push(element);
    //   });

    //   if (checked.length !== data.length) this.setState({ checked: data });

    //   return data;
    // };

    // Update location
    setFilters = filters => {
      const { history, url } = this.props;

      history.push({
        pathname: url,
        search: qs.stringify(filters),
      });
    };

    // Update the list displayed internaly
    updateList = (prevProps?: Record<string, any>) => {
      const { items, resourceData } = this.props;

      if (!prevProps || prevProps.items !== items) {
        this.setState({
          list: items,
        });
        this.resetSelectedLegend();
      }
    };

    loadData = (more?: boolean) => {
      const { loadFunc } = this.props;

      const { filters } = this.state;

      if (more && loadFunc) {
        loadFunc({
          ...filters,
          more: true,
        });
      } else if (loadFunc) {
        loadFunc(filters);
      }
    };

    applyFilters = values => {
      const { filters } = this.state;
      const { handleFilters } = this.props;

      const newFilters = handleFilters ? handleFilters(values, filters) : values;

      if (newFilters) this.setFilters(newFilters);
    };

    loadPage = page => {
      const currentPage = parseInt(page.selected, 10) + 1;

      const { filters } = this.state;

      const newFilters = {
        ...filters,
        page: currentPage,
      };

      this.setFilters(newFilters);
    };

    masterCheck = () => {
      const { masterCheck } = this.state;

      const list = this.items();

      list.forEach(element => {
        this.onItemClick(element[config.idKey], !masterCheck);
      });

      this.setState({
        masterCheck: !masterCheck,
      });
    };

    // Get internal list
    items = () => {
      const { list } = this.state;
      return list || [];
    };

    // Get actually displayed data
    filtered = () => {
      const { order, asc, selectedLegends } = this.state;
      const { customOrder, lg, transformFunction } = this.props;

      let filtered = this.items();

      if (selectedLegends.length > 0) {
        filtered = filter(filtered, item => {
          const transformed =
            typeof transformFunction === 'function' ? transformFunction(item) : item;
          let dismiss = false;
          for (let i = 0; i < selectedLegends.length; i++) {
            if (transformed && transformed.infos && !transformed.infos[selectedLegends[i]]) {
              dismiss = true;
            }
          }
          return !dismiss;
        });
      }

      const sorted = _.orderBy(
        filtered,
        (item: Record<string, any>) => {
          if (customOrder) return customOrder(item, order) || false;
          const val = _.get(item, order);
          /* eslint-disable */

          if (new Date(val) != 'Invalid Date') return new Date(val).getTime();
          /* eslint-enable */
          if (!isNaN(val)) return parseFloat(val);
          if (typeof val === 'string') return val.toUpperCase();
          // case multilingual
          if (typeof val === 'object' && typeof val[lg] === 'string') return val[lg].toUpperCase();
          return '';
        },
        [asc ? 'asc' : 'desc'],
      );

      return sorted;
    };

    orderBy = (field?: string) => {
      const { order, asc } = this.state;

      if (order !== field) {
        this.setState({
          order: field,
          asc: true,
        });
      } else {
        this.setState({
          asc: !asc,
        });
      }
    };

    listHeadItem = (item: Record<string, any>) => {
      const { order, asc } = this.state;
      if (item.key) {
        return (
          <div
            className={styles('headItem', item.center && 'center', 'sortable-head')}
            onClick={() => this.orderBy(item.key)}
          >
            <TextItem path={item.title} />
            {order === item.key && (
              <span className={styles('arrow', !asc && 'inverted')}>
                <RiOrderPlayFill />
              </span>
            )}
          </div>
        );
      }
      return (
        <div
          className={styles(
            'headItem',
            item.center && 'center',
            item.type === 'empty' && 'empty',
            'not-sortable-head',
          )}
        >
          <TextItem path={item.title} />
        </div>
      );
    };

    setSort = options => {
      const { sortBy } = options;
      this.orderBy(sortBy);
    };

    handleCheckVirtual = res => {
      if (res.all) this.masterCheck();

      if (res.item) this.onItemClick(res.item[config.idKey]);
    };

    /**
     * Legend features
     */

    resetSelectedLegend = () => {
      this.setState({
        selectedLegends: [],
      });
    };

    selectLegend = key => {
      const { selectedLegends } = this.state;

      const newList = selectedLegends.slice();
      const index = newList.indexOf(key);

      if (index >= 0) {
        newList.splice(index, 1);
      } else {
        newList.push(key);
      }

      this.setState({
        selectedLegends: newList,
      });
    };

    renderStaticTable = () => {
      const { noCheckbox, itemProps, headers } = this.props;
      const { masterCheck } = this.state;
      const list = this.filtered();
      return (
        <React.Fragment>
          <div className={styles('table-wrapper')}>
            <div className={styles('table-header')}>
              <table>
                <thead>
                  <tr>
                    {!noCheckbox && (
                      <th onClick={this.masterCheck} className={styles('box-wrapper')}>
                        <div className={styles('box', masterCheck && 'checked')} />
                      </th>
                    )}
                    <th className={styles('index')}>
                      {this.listHeadItem({
                        title: '#',
                      })}
                    </th>
                    {headers &&
                      headers.map((item, key) => (
                        <th
                          className={styles(item.class, item.type)}
                          colSpan={item.colSpan || 1}
                          key={item.title || key}
                        >
                          {this.listHeadItem(item)}
                        </th>
                      ))}
                    <th className={styles('edit')} />
                  </tr>
                </thead>
              </table>
            </div>
            <div className={styles('table-content')}>
              <table>
                <tbody>
                  {list.map((item, index) => (
                    <ItemComponent
                      action={() => this.onItemClick(item.id)}
                      item={item}
                      index={index}
                      key={item.id || index}
                      noCheckbox={noCheckbox}
                      {...itemProps}
                    />
                  ))}
                </tbody>
              </table>
            </div>
          </div>
        </React.Fragment>
      );
    };

    /**
     * Render legend
     */

    renderLegend = () => {
      const { legend } = this.props;

      if (!legend || !legend.infos) return <span />;
      const { selectedLegends } = this.state;
      const keys = Object.keys(legend.infos);

      return (
        <div className={styles('legend')}>
          {legend.title && (
            <div className={styles('title')}>
              <TextItem path={legend.title} />
            </div>
          )}
          {keys.map((key, index) => {
            const text = legend.infos[key];
            if (key.indexOf('Detail') >= 0) return false;
            return (
              <div
                key={key || index}
                onClick={() => this.selectLegend(key)}
                className={styles('item', key, selectedLegends.indexOf(key) >= 0 && 'selected')}
              >
                <div className={styles('text')}>{text}</div>
              </div>
            );
          })}
        </div>
      );
    };

    /**
     * Table
     */
    renderVirtualTable = () => {
      const { itemProps, headers, noCheckbox, inModule, options, noModule, ref } = this.props;
      const { order, asc, masterCheck, checkedItems } = this.state;
      const list = this.filtered();

      return (
        <VirtualTable
          order={order}
          asc={asc}
          sort={this.setSort}
          headers={headers}
          items={list}
          noCheckbox={noCheckbox}
          onCheck={val => this.handleCheckVirtual(val)}
          masterCheck={masterCheck}
          checkedItems={checkedItems}
          inModule={inModule}
          noModule={noModule}
          moduleOptions={options}
          {...itemProps}
        />
      );
    };

    render() {
      const {
        subhead,
        actions,
        actionDelete,
        title,
        loaded,
        loading,
        legend,
        loadMore,
        noMore,
        virtual,
        // filters
        displayFilters,
        filtersProps,
        filtersInitialValues,
        // reset
        pagination,
        currentPage,
        noResultMessage,
        noHeader,
      } = this.props;

      const { filters, checkedItems } = this.state;
      const list = this.filtered();

      // const checkedList = this.getCheckedItems();

      return (
        <div
          className={styles(
            'CustomFixedTable',
            displayFilters && 'filters',
            noHeader && 'no-header',
          )}
        >
          {!noHeader && (
            <div className={styles('header')}>
              <div className={styles('title')}>
                <h1>
                  <TextItem path={title} />
                </h1>
                {subhead && <div className={styles('subhead')}>{subhead}</div>}
              </div>
              <div className={styles('actions')}>
                {!checkedItems || checkedItems.length === 0 ? (
                  actions
                ) : (
                  <ModuleButton
                    noMargin
                    relative
                    shadow
                    className={styles('delete')}
                    color="red"
                    label="general.messages.deleteSelection"
                    action={() => actionDelete && actionDelete(checkedItems)}
                  />
                )}
              </div>
            </div>
          )}
          {displayFilters && (
            <div className={styles('filters')}>
              <FiltersForm
                onSubmit={this.applyFilters}
                {...filtersProps}
                initialValues={{
                  ...filters,
                  ...filtersInitialValues,
                }}
              />
            </div>
          )}
          <div className={styles('listing')}>
            {loaded && list.length === 0 && (
              <div className={styles('no-result')}>
                <TextItem path={noResultMessage || 'admin.messages.noResult'} />
              </div>
            )}
            {list.length === 0 && loading && (
              <div className={styles('loading')}>
                <div className={styles('loading-item')}>
                  <Loading loader={config.loader} />
                </div>
              </div>
            )}
            {list.length > 0 && !virtual && this.renderStaticTable()}
            {list.length > 0 && virtual && this.renderVirtualTable()}
          </div>
          <div className={styles('footer')}>
            <div className={styles('left')}>
              <div className={styles('stats')}>
                <TextItem path="admin.messages.numDisplayed" />
                <b>{(list && list.length) || '...'}</b>
              </div>
              {this.renderLegend()}
            </div>
            <div className={styles('right')}>
              {list.length > 0 && !noMore && (
                <div className={styles('buttons')}>
                  <ModuleButton
                    noMargin
                    small
                    relative
                    loading={loading}
                    label="Load more"
                    action={() => !loading && this.loadData(true)}
                  />
                </div>
              )}
              {pagination && pagination.last_page > 1 && (
                <ReactPaginate
                  pageCount={pagination.last_page}
                  pageRangeDisplayed={3}
                  marginPagesDisplayed={1}
                  // initialPage={parseInt(currentPage - 1, 10)}
                  forcePage={parseInt((currentPage || 1) - 1, 10)}
                  disableInitialCallback
                  breakLabel="..."
                  onPageChange={this.loadPage}
                  previousLabel="Previous"
                  nextLabel="Next"
                  containerClassName={styles('pages')}
                  pageClassName={styles('page')}
                  activeClassName={styles('active')}
                  disabledClassName={styles('disabled')}
                />
              )}
            </div>
          </div>
        </div>
      );
    }
  }

  return withRouter(FixedTable);
}

export default CustomFixedTable;
