import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { Loader } from '../loader';
import { TableHeader } from './header';
import { CommonPagination, Pagination } from '../pagination';
import { STable } from '../../s-components/table/s-table';
import { appearingAnimation } from '../../constants';
import { bem } from 'utils/bem';
import { AnimatePresence, motion, useMotionValue } from 'framer-motion';
import { TableFooter } from './table-footer';
import { Sort } from 'utils/sort';
import { TableEmptyView } from './table-empty-view';
import { TableLoader } from './table-loader';
import { BodyContent, StickyTableConfig, TableContent, TableElementId, TableInfiniteScroll } from './constants';
import { TableBody } from './body';
import { TableTheme } from 'app/theme/table';
import { InfiniteScroll } from '../infinite-scroll';

interface Props<BodyType extends BodyContent, SortField extends string> {
  sort: Sort<SortField>;
  pagination?: Pagination;
  loading?: boolean;
  dataSelector?: string;
  expandedIds?: TableElementId[];
  checkedIds?: TableElementId[];
  onCheckItem?: (isChecked: boolean, id: TableElementId) => void;
  onSelectAll?: (isChecked: boolean) => void;
  onExpandItem?: (item: BodyType, isExpanded: boolean) => void;
  className?: string;
  tableContainerClassName?: string;
  paginationClassName?: string;
  onPaginationChange?(pagination: Pagination): void;
  bodyContent: BodyType[];
  headerContent: TableContent<BodyType, SortField>[];
  stickyContent?: StickyTableConfig<BodyType, SortField>;
  renderExpandedRow?: (content: BodyType) => React.ReactNode;
  onSortChange(sort: Sort<SortField>): void;
  isCheckable?: boolean;
  isLoading?: boolean;
  isLoadingFailed?: boolean;
  isSearching?: boolean;
  isExpandable?: boolean;
  isScrollable?: boolean;
  withFooter?: boolean;
  withoutRoundCorners?: boolean;
  theme: TableTheme;
  notification?: React.ReactElement;
  isNotificationVisible?: boolean;
  noResultComponent?: React.ReactElement;
  shouldDisableExpandForRow?: (rowData: BodyType) => boolean;
  infiniteScroll?: TableInfiniteScroll;
}

const classes = bem('table-container');

export const Table = <BodyType extends BodyContent, SortField extends string>(props: Props<BodyType, SortField>) => {
  const {
    headerContent,
    onCheckItem,
    onSelectAll,
    bodyContent,
    expandedIds = [],
    checkedIds = [],
    pagination,
    loading,
    onSortChange,
    onPaginationChange,
    sort,
    dataSelector,
    theme,
    className,
    tableContainerClassName,
    isCheckable,
    stickyContent,
    isExpandable,
    renderExpandedRow,
    isScrollable,
    withFooter,
    isLoading,
    isLoadingFailed,
    isSearching,
    withoutRoundCorners,
    paginationClassName,
    onExpandItem,
    notification = <></>,
    isNotificationVisible = false,
    noResultComponent,
    shouldDisableExpandForRow,
    infiniteScroll,
  } = props;
  const offset = 245;
  const [minHeight, setMinHeight] = useState(0);
  const [paginationHeight, setPaginationHeight] = useState(0);

  const x = useMotionValue(stickyContent?.initialWidth || 0);

  useEffect(() => {
    setMinHeight(document.documentElement.clientHeight - offset);
  }, []);

  const handleChanges = useCallback(
    (isChecked: boolean, id: TableElementId) => {
      onCheckItem && onCheckItem(isChecked, id);
    },
    [onCheckItem]
  );

  const handleExpandChanges = useCallback(
    (isExpanded: boolean, item: BodyType) => {
      if (onExpandItem) {
        onExpandItem(item, isExpanded);
      }
    },
    [onExpandItem]
  );

  const handleSelectAll = useCallback(
    (isChecked: boolean) => {
      onSelectAll && onSelectAll(isChecked);
    },
    [onSelectAll]
  );

  const handlePaginationChange = useCallback(
    (pagination: Pagination) => {
      const containerElement = containerRef && containerRef.current;

      if (containerElement) {
        containerElement.scrollTo(0, 0);
      }

      onPaginationChange && onPaginationChange(pagination);
    },
    [onPaginationChange]
  );

  const selectAllIsChecked = !!checkedIds.length && bodyContent.every(({ id }) => checkedIds.includes(id));

  const selectAllIsHalfChecked =
    !!checkedIds.length && bodyContent.some(({ id }) => checkedIds.includes(id)) && !selectAllIsChecked;

  const hasStickyColumns = !!stickyContent;

  const renderTable = useCallback(
    (headerContent: TableContent<BodyType, SortField>[], hasStickyColumns = false) => {
      const expandableExtraRow = isExpandable ? 1 : 0;
      const checkableExtraRow = isCheckable ? 1 : 0;

      const statesColSpan = headerContent.length + expandableExtraRow + checkableExtraRow;
      const containerMaxWidth = containerRef.current?.offsetWidth;

      return (
        <AnimatePresence initial={false}>
          <React.Fragment>
            <TableHeader
              sort={sort}
              headerContent={headerContent}
              onSortChange={onSortChange}
              selectAll={handleSelectAll}
              selectAllIsChecked={selectAllIsChecked}
              selectAllIsHalfChecked={selectAllIsHalfChecked}
              isCheckable={isCheckable}
              hasStickyColumns={hasStickyColumns}
              isExpandable={isExpandable}
              theme={theme}
              withoutRoundCorners={withoutRoundCorners}
              isNotificationVisible={isNotificationVisible}
            />
            {isLoading ? (
              <TableLoader maxWidth={containerMaxWidth} colSpan={statesColSpan} />
            ) : bodyContent.length > 0 ? (
              <TableBody
                headerContent={headerContent}
                sort={sort}
                bodyContent={bodyContent}
                onCheckboxChange={handleChanges}
                onExpanded={handleExpandChanges}
                isCheckable={isCheckable}
                isExpandable={isExpandable}
                isScrollable={isScrollable}
                theme={theme}
                expandedIds={expandedIds}
                checkedIds={checkedIds}
                hasStickyColumns={hasStickyColumns}
                renderExpandedRow={renderExpandedRow}
                withoutRoundCorners={withoutRoundCorners}
                shouldDisableExpandForRow={shouldDisableExpandForRow}
                isInfiniteLoading={infiniteScroll?.isLoading}
              />
            ) : (
              <TableEmptyView
                isSearching={isSearching}
                colSpan={statesColSpan}
                maxWidth={containerMaxWidth}
                hasError={isLoadingFailed}
                noResultComponent={noResultComponent}
              />
            )}
            {withFooter && (
              <TableFooter
                isCheckable={isCheckable}
                isExpandable={isExpandable}
                headerContent={headerContent}
                theme={theme}
                hasStickyColumns={hasStickyColumns}
              />
            )}
          </React.Fragment>
        </AnimatePresence>
      );
    },
    [
      isExpandable,
      isCheckable,
      sort,
      onSortChange,
      handleSelectAll,
      selectAllIsChecked,
      selectAllIsHalfChecked,
      theme,
      withoutRoundCorners,
      isNotificationVisible,
      isLoading,
      isScrollable,
      bodyContent,
      handleChanges,
      handleExpandChanges,
      expandedIds,
      checkedIds,
      renderExpandedRow,
      isSearching,
      isLoadingFailed,
      noResultComponent,
      withFooter,
      shouldDisableExpandForRow,
      infiniteScroll?.isLoading,
    ]
  );
  const hasContent = !isLoading && bodyContent.length > 0;
  const containerRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    const containerElement = containerRef.current;
    if (!containerElement) return;

    const positionX = containerElement.scrollLeft;
    if (!hasContent && positionX !== 0) {
      containerElement.scrollTo(0, 0);
    }
  }, [hasContent, containerRef]);

  const mainContent = useMemo(() => {
    const stickyWithMainContent = stickyContent ? stickyContent.content.concat(headerContent) : headerContent;

    return hasContent ? headerContent : stickyWithMainContent;
  }, [headerContent, stickyContent, hasContent]);

  const shouldUseScroll = !!isScrollable && hasContent;

  const paginationHeightRef = useCallback((pagination: HTMLDivElement | null) => {
    setPaginationHeight(pagination?.offsetHeight || 0);
  }, []);

  const tablesContainer = useMemo(
    () => (
      <div className={classes('scrolling-tables-container')}>
        {stickyContent && hasContent && (
          <>
            <div className={classes('sticked-table-wrapper')}>
              <motion.table className={classes('sticked-table')} data-selector={dataSelector} style={{ width: x }}>
                {renderTable(stickyContent.content)}
              </motion.table>
            </div>
            <motion.div
              dragConstraints={{ left: stickyContent.minWidth, right: stickyContent.maxWidth }}
              dragMomentum={false}
              dragElastic={0}
              drag="x"
              className={classes('resize-handle')}
              style={{ x }}
            />

            <motion.div className={classes('footer-divider')} style={{ x }} />
          </>
        )}
        <motion.table
          className={classes('table', {
            scrollable: shouldUseScroll,
            'with-sticky': hasStickyColumns,
          })}
          data-selector={dataSelector}
        >
          {renderTable(mainContent, hasStickyColumns && hasContent)}
        </motion.table>
        <Loader isActive={!!loading} />
      </div>
    ),
    [dataSelector, hasContent, hasStickyColumns, loading, mainContent, renderTable, shouldUseScroll, stickyContent, x]
  );

  return (
    <STable
      {...appearingAnimation}
      minHeight={minHeight}
      className={className}
      isScrollable={shouldUseScroll}
      hasSticky={hasStickyColumns}
      hasContent={hasContent}
      tableTheme={theme}
      withoutRoundCorners={withoutRoundCorners}
      paginationHeight={paginationHeight}
    >
      {isNotificationVisible && <div className={classes('notification')}>{notification}</div>}
      {infiniteScroll ? (
        <InfiniteScroll
          ref={containerRef}
          className={classes('container', {}, tableContainerClassName)}
          onScrollEnd={infiniteScroll.onScrollEnd}
          isActive={infiniteScroll.isActive}
        >
          {tablesContainer}
        </InfiniteScroll>
      ) : (
        <div ref={containerRef} className={classes('container', {}, tableContainerClassName)}>
          {tablesContainer}
        </div>
      )}

      {pagination && hasContent && (
        <div ref={paginationHeightRef}>
          <div className={classes('pagination-divider')} />
          <CommonPagination
            pagination={pagination}
            onChange={handlePaginationChange}
            className={paginationClassName}
            widgetsBgStyle={theme === TableTheme.WHITE ? 'white' : 'grey'}
          />
        </div>
      )}
    </STable>
  );
};
