import React from 'react';
import cn from 'classnames';
import moment from 'moment';

import './scss/table.scss';
import SearchPanel from './searchPanel';
import TableRow from './tableRow';
import { genUniqId, sort, sortData } from './helpers';

export default class Table extends React.Component {
  static defaultProps = {
    order: 'asc',
    filtering: false,
    bottomIndent: 0,
    onRowClick: () => null,
    loadingMessage: '',
  };

  constructor(props) {
    super(props);
    this.state = {
      data: [],
      columns: props.columns,
      footer: props.footer,
      filtering: props.filtering,
      filterValues: {},
      order: props.order,
      sortableColumn: props.sortableColumn,
      calculatedHeight: null,
      tableHeight: props.height,
      selected: [],
    };
  }

  componentDidMount() {
    const { sortableColumn, columns, order } = this.state;
    const { bottomIndent, data } = this.props;

    /* проверка на то, что указанный для сортировки столбец находится есть в шапке */
    const sortColInColumns = columns.map(({ key }) => key).includes(sortableColumn);
    const dataWithId = data.map((row) => ({ ...row, rowId: genUniqId() }));
    const sortedData = sortableColumn && sortColInColumns
      ? sort(sortableColumn, dataWithId, columns, order)
      : dataWithId;

    const windowHeight = document.documentElement.clientHeight;
    const { top } = this.tableEl.getBoundingClientRect();
    let footerHeight = this.footerEl ? this.footerEl.offsetHeight : 0;
    /* console.log('footerEl', this.footerEl); */
    const footerRowsHeight = this.footerEl && [...this.footerEl.childNodes].map((elem) => {
      const { offsetHeight: height } = elem;
      footerHeight -= height;
      return footerHeight;
    });
    const calculatedHeight = windowHeight - top - bottomIndent;

    this.setState({
      data: sortedData,
      order,
      calculatedHeight,
      sortableColumn,
      footerRowsHeight,
    });
  }

  componentDidUpdate(prevProps) {
    const { sortableColumn, order } = this.state;
    const { data, footer, columns } = this.props;
    if (data !== prevProps.data || columns !== prevProps.columns) {
      const sortColInColumns = columns.map(({ key }) => key).includes(sortableColumn);
      const dataWithId = data.map((row) => ({ ...row, rowId: genUniqId() }));
      const sortedData = sortableColumn && sortColInColumns
        ? sort(sortableColumn, dataWithId, columns, order)
        : dataWithId;
      this.setState({ data: sortedData, selected: [], columns });
    }
    /* if (columns !== prevProps.columns) {
      this.setState({ columns });
    } */
    if (footer !== prevProps.footer) {
      this.setState({ footer });
    }
  }

  handleClickSort = (column) => {
    const { data, columns, order } = this.state;
    const newOrder = order === 'asc' ? 'desc' : 'asc';
    const currentColumn = columns.find((c) => c.key === column);
    const sortedData = currentColumn.sortingFunctions
      ? sortData(data, newOrder, column, currentColumn.sortingFunctions)
      : sortData(data, newOrder, column);
    this.setState({ data: sortedData, order: newOrder, sortableColumn: column });
  }

  handleSelectionChanged = (rowId, checked) => {
    const { onSelectionChanged } = this.props;
    if (onSelectionChanged !== undefined) {
      const { selected, data } = this.state;
      const newSelectedIds = checked
        ? [...selected, rowId]
        : selected.filter((id) => id !== rowId);
      this.setState({ selected: newSelectedIds });
      const selectedData = data
        .filter((row) => newSelectedIds.includes(row.rowId))
        .map((row) => {
          const filteredKeys = Object.keys(row).filter((key) => key !== 'rowId');
          return filteredKeys.reduce((acc, key) => ({ ...acc, [key]: row[key] }), {});
        });
      onSelectionChanged(selectedData);
    }
  }

  handleClickFilter = () => {
    const { filtering } = this.state;
    this.setState({ filtering: !filtering, filterValues: {} });
  }

  handleChangeSearch = (columnKey) => {
    const t = this;
    const { filterValues } = this.state;
    return (filterValue) => {
      const values = { ...filterValues, [columnKey]: filterValue };
      if (filterValue === undefined) {
        delete values[columnKey];
      }
      t.setState({ filterValues: values });
    };
  }

  filterData = (data, columns, filterValues) => {
    if (Object.keys(filterValues).length === 0) {
      return data;
    }
    let filtered = data;
    Object.keys(filterValues).forEach((key) => {
      filtered = filtered.filter((dataItem) => {
        if (moment.isMoment(dataItem[key].value)) {
          const { displayFormat } = columns.find((col) => col.key === key);
          const momentValue = dataItem[key].value.format(displayFormat).toLowerCase();
          return momentValue.includes(String(filterValues[key]).toLowerCase());
        }
        const value = String(dataItem[key].value).toLowerCase();
        return value.includes(String(filterValues[key]).toLowerCase());
      });
    });
    return filtered;
  }

  renderHeaderRow = (columns) => {
    const { order, sortableColumn } = this.state;
    const { filtering } = this.props;
    const items = columns.map((item, index) => {
      const arrowDirectClassName = order === 'asc' ? 'omni-table__arrow_up' : 'omni-table__arrow_down';
      const arrowClassNames = cn({
        'omni-table__arrow': true,
        'omni-table__arrow_hidden': sortableColumn !== item.key,
      });
      return (
        <th
          key={`${item.key}_${index}`}
          style={ item.style }
        >
            <div className='omni-table__header-title' onClick={() => this.handleClickSort(item.key)}>
              <span>{item.header}</span>
              <div className={arrowClassNames}>
                <div className={arrowDirectClassName} />
              </div>
            </div>
            {filtering && <SearchPanel onChange={this.handleChangeSearch(item.key)}/>}
        </th>
      );
    });
    return <tr className='omni-table__header__row'>{items}</tr>;
  }

  renderBodyRows = (data, columns) => {
    if (!data || data.length === 0) {
      return null;
    }
    return data.map((rowData) => <TableRow
      rowData={rowData}
      columns={columns}
      checked={this.state.selected.includes(rowData.rowId)}
      key={rowData.rowId}
      onSelectionChanged={this.props.onSelectionChanged
        ? (checked) => this.handleSelectionChanged(rowData.rowId, checked)
        : null }
      onRowClick={this.props.onRowClick}
    />);
  }

  renderFooter = (data, columns) => {
    const { footerRowsHeight, footer } = this.state;

    if (!footer) {
      const rows = columns.map(({ key }) => <td key={key} className='omni-table__footer__border' />);
      return (
        <tfoot className='omni-table__footer'>
          <tr>{rows}</tr>
        </tfoot>
      );
    }

    const getFooterRow = (rowData, tableColumns, rowIndex, rowsHeight) => tableColumns
      .map(({ key }, index) => {
        const { value } = rowData[key];
        const dataStyle = { ...rowData[key].style };
        if (rowsHeight) {
          dataStyle.bottom = rowsHeight[rowIndex];
        }
        return <td style={dataStyle} key={index}>{value}</td>;
      });

    return (
      <tfoot ref={(el) => this.footerEl = el} className='omni-table__footer'>
        {data.map((row, index) => <tr
            key={index}
            className='omni-table__footer_row'
          >
            {getFooterRow(row, columns, index, footerRowsHeight)}
          </tr>)}
      </tfoot>
    );
  }

  renderMessage = (message) => (
      <td className='omni-table__message' colSpan={this.state.columns.length}>
        <span>{message}</span>
      </td>
  )

  render() {
    const {
      data,
      columns,
      footer,
      calculatedHeight,
      filterValues,
      tableHeight,
    } = this.state;
    const { loading, autoHeight, loadingMessage } = this.props;
    const filteredData = this.filterData(data, columns, filterValues);
    const tableClasses = cn({
      'omni-ui-general': true,
      'omni-table': true,
      'omni-layout-cssScrollbar': autoHeight || tableHeight,
    });
    const isDisplayMessage = () => ((!filteredData || filteredData.length === 0) && !loading);
    const message = isDisplayMessage() ? 'Нет данных для отображения' : '';
    const loadingMsg = <tr>{this.renderMessage(loadingMessage)}</tr>;
    const height = autoHeight ? calculatedHeight : tableHeight;

    return (
      <div
        className={tableClasses}
        ref={(el) => this.tableEl = el}
        style={{ height }}
      >
        <table className='omni-table__table'>
          <thead className='omni-table__header'>{this.renderHeaderRow(columns)}</thead>
          <tbody className='omni-table__body'>
            {loading ? loadingMsg : this.renderBodyRows(filteredData, columns)}
            {isDisplayMessage() && <tr>{this.renderMessage(message)}</tr>}
            <tr className='omni-table__placeholder'>
              {this.renderMessage('')}
            </tr>
          </tbody>
          {this.renderFooter(footer, columns)}
        </table>
      </div>
    );
  }
}
