import React, { Component, memo, useRef, useEffect } from 'react';
import { SortByArrowDown, SortByArrowUp, } from '@wix/wix-ui-icons-common/system';
import classNames from 'classnames';
import { VariableSizeList as List } from 'react-window';
import { ScrollSyncPane } from 'react-scroll-sync';
import { classes, st } from './DataTable.st.css.js';
import InfiniteScroll from '../../utils/InfiniteScroll';
import InfoIcon from '../../InfoIcon';
import Text from '../../Text';
import { virtualRowsAreEqual } from './DataTable.utils';
import { WixStyleReactMaskingContext } from '../../WixStyleReactMaskingProvider/context';
import DataTableRow from './components/DataTableRow';
import { TableFloatingScrollBarInitializer } from './TableFloatingScrollBarInitializer';
import TableResizeHandle from '../components/TableResizeHandle';
import { ColumnResizeConsumer } from '../ColumnResize';
export const DataTableHeader = props => {
    const { dataHook, horizontalScroll, leftShadowVisible, rightShadowVisible, stickyColumns, rowVerticalPadding, } = props;
    const headerContainerRef = React.useRef(null);
    const wrapWithHorizontalScroll = table => (React.createElement("div", { className: classNames(classes.scrollWrapper, {
            [classes.leftShadowVisible]: !!leftShadowVisible,
            [classes.rightShadowVisible]: !!rightShadowVisible,
            [classes.withStickyColumns]: !!stickyColumns,
        }) },
        React.createElement(ScrollSyncPane, { attachTo: headerContainerRef }, table)));
    const table = (React.createElement(ColumnResizeConsumer, null, ({ getTableWidth }) => (React.createElement("div", { "data-hook": dataHook, ref: headerContainerRef, className: classNames({
            [classes.tableHeaderScrollContent]: horizontalScroll,
        }) },
        React.createElement("table", { style: { width: getTableWidth(props.width) }, className: st(classes.table, {
                rowVerticalPadding,
            }) },
            React.createElement(TableHeader, { ...props }))))));
    return horizontalScroll ? wrapWithHorizontalScroll(table) : table;
};
class DataTable extends React.Component {
    constructor(props) {
        super(props);
        this._updateScrollShadows = () => {
            const { onUpdateScrollShadows } = this.props;
            if (!onUpdateScrollShadows) {
                return;
            }
            const { scrollLeft, scrollWidth, clientWidth } = this.contentRef.current;
            const leftShadowVisible = scrollLeft > 0;
            const rightShadowVisible = scrollWidth - scrollLeft > clientWidth;
            onUpdateScrollShadows(leftShadowVisible, rightShadowVisible);
        };
        this.wrapWithInfiniteScroll = table => {
            return (React.createElement(InfiniteScroll, { ref: this.props.infiniteScrollRef, pageStart: 0, loadMore: this.loadMore, data: this.props.data, hasMore: !this.props.controlled
                    ? this.state.currentPage < this.state.lastPage || this.props.hasMore
                    : this.props.hasMore, loader: this.props.loader, useWindow: this.props.useWindow, scrollElement: this.props.scrollElement, initialLoad: this.props.initialLoad }, table));
        };
        this.wrapWithHorizontalScroll = (table, attachTo) => {
            const { leftShadowVisible, rightShadowVisible, stickyColumns } = this.props;
            return (React.createElement("div", { className: classNames(this.style.scrollWrapper, {
                    [this.style.leftShadowVisible]: !!leftShadowVisible,
                    [this.style.rightShadowVisible]: !!rightShadowVisible,
                    [this.style.withStickyColumns]: !!stickyColumns,
                }) },
                React.createElement(ScrollSyncPane, { attachTo: attachTo }, table)));
        };
        this.renderTableWithWidthContext = rowsToRender => {
            return (React.createElement(ColumnResizeConsumer, null, ({ getTableWidth }) => this.renderTable(rowsToRender, getTableWidth)));
        };
        this.renderTable = (rowsToRender, getTableWidth) => {
            const { dataHook, showLastRowDivider, horizontalScroll, rowVerticalPadding, removeRowDetailsPadding, dragAndDrop, onDragStart, onDragEnd, onDragCancel, isDragAndDropDisabled, data, tableRef, resizable, columns, } = this.props;
            const style = { width: getTableWidth(this.props.width) };
            const className = st(classes.table, {
                removeRowDetailsPadding,
                showLastRowDivider,
                rowVerticalPadding,
            });
            let table = (React.createElement("table", { id: this.props.id, style: style, className: className, ref: tableRef },
                !this.props.hideHeader && React.createElement(TableHeader, { ...this.props }),
                this.renderBody(rowsToRender)));
            if (dragAndDrop) {
                const { DroppableTableContext } = dragAndDrop;
                table = (React.createElement(DroppableTableContext, { items: data, onDragStart: onDragStart, onDragEnd: onDragEnd, onDragCancel: onDragCancel, isDragAndDropDisabled: isDragAndDropDisabled, horizontalScroll: horizontalScroll, className: className, style: style, renderRow: this.renderRowWithMaskingClassNames, renderTableContainer: table => {
                        let child = (React.createElement("div", { className: classNames({
                                [classes.tableHeaderScrollContent]: horizontalScroll,
                            }) }, table));
                        if (horizontalScroll) {
                            child = this.wrapWithHorizontalScroll(child);
                        }
                        return child;
                    } }, table));
            }
            table = horizontalScroll ? (React.createElement(TableFloatingScrollBarInitializer, null, table)) : (table);
            table = (React.createElement("div", { "data-hook": dataHook, ...(horizontalScroll
                    ? {
                        className: classes.tableBodyScrollContent,
                        ref: this.contentRef,
                        onScroll: this._updateScrollShadows,
                    }
                    : undefined) }, table));
            return table;
        };
        this.renderBody = rows => {
            const { BodyElementType = 'tbody' } = this.props;
            return (React.createElement(WixStyleReactMaskingContext.Consumer, null, ({ maskingClassNames }) => (React.createElement(BodyElementType, null, rows.map((rowData, index) => this.renderRow({ rowData, rowNum: index, maskingClassNames }))))));
        };
        this.renderRowWithMaskingClassNames = ({ rowData, rowNum, style, isDragOverlay, }) => (React.createElement(WixStyleReactMaskingContext.Consumer, null, ({ maskingClassNames }) => this.renderRow({
            rowData,
            rowNum,
            style,
            maskingClassNames,
            isDragOverlay,
        })));
        this.renderRow = rowProps => {
            return (React.createElement(DataTableRow, { key: rowProps.rowNum, ...rowProps, ...this.props, toggleRowDetails: this.toggleRowDetails, showDetails: !!this.state.selectedRows.get(rowProps.rowData) }));
        };
        this.calcLastPage = ({ data, itemsPerPage }) => Math.ceil(data.length / itemsPerPage) - 1;
        this.loadMore = () => {
            if (!this.props.controlled &&
                this.state.currentPage < this.state.lastPage) {
                this.setState({ currentPage: this.state.currentPage + 1 });
            }
            else {
                this.props.loadMore && this.props.loadMore();
            }
        };
        this.toggleRowDetails = selectedRow => {
            const { selectedRows } = this.state;
            const allowMultipleRowDetails = this.props.allowMultiDetailsExpansion && !this.props.virtualized;
            const newSelectedRows = new Map([
                ...(allowMultipleRowDetails ? [...selectedRows] : []),
                [selectedRow, !selectedRows.get(selectedRow)],
            ]);
            this.setState({ selectedRows: newSelectedRows });
        };
        this.renderVirtualizedRow = ({ data, index, style }) => this.renderRow({ rowData: data.data[index], rowNum: index, style });
        this.renderVirtualizedMemoizedRow = memo(this.renderVirtualizedRow, virtualRowsAreEqual);
        this.getVirtualRowHeight = () => this.props.virtualizedLineHeight;
        this.virtualizedTableElementWithRefForward = React.forwardRef((props, ref) => (React.createElement(ColumnResizeConsumer, null, ({ getTableWidth }) => this.renderVirtualizedTableElement({ ...props, ref }, getTableWidth))));
        this.renderVirtualizedTableElement = ({ children, ...rest }, getTableWidth) => {
            const { dragAndDrop, data, onDragStart, onDragEnd, onDragCancel, isDragAndDropDisabled, horizontalScroll, resizable, columns, } = this.props;
            const tableStyle = {
                ...rest.style,
                width: getTableWidth(rest.style?.width || '100%'),
            };
            let table = (React.createElement("table", { ...rest, style: tableStyle },
                React.createElement(TableHeader, { ...this.props }),
                children));
            if (dragAndDrop) {
                const { DroppableTableContext } = dragAndDrop;
                table = (React.createElement(DroppableTableContext, { items: data, onDragStart: onDragStart, onDragEnd: onDragEnd, onDragCancel: onDragCancel, isDragAndDropDisabled: isDragAndDropDisabled, horizontalScroll: horizontalScroll, className: classNames(this.style.table, this.style.virtualized), renderRow: this.renderRow }, table));
            }
            return table;
        };
        this.renderVirtualizedTable = () => {
            const { dataHook, data, virtualizedTableHeight, virtualizedListRef } = this.props;
            return (React.createElement("div", { "data-hook": dataHook },
                React.createElement(List, { ref: virtualizedListRef, className: classNames(this.style.table, this.style.virtualized), height: virtualizedTableHeight, itemCount: data.length, itemData: this.props, width: "100%", itemSize: this.getVirtualRowHeight, outerElementType: this.virtualizedTableElementWithRefForward, innerElementType: "tbody" }, this.renderVirtualizedMemoizedRow)));
        };
        let state = {
            selectedRows: new Map(),
        };
        if (!props.controlled && props.infiniteScroll) {
            state = { ...state, ...this.createInitialScrollingState(props) };
        }
        this.state = state;
        this.contentRef = React.createRef();
        if (props.horizontalScroll && 'ResizeObserver' in window) {
            this.contentResizeObserver = new ResizeObserver(this._updateScrollShadows);
        }
    }
    componentDidMount() {
        const { contentResizeObserver, contentRef } = this;
        if (contentResizeObserver && contentRef.current) {
            contentResizeObserver.observe(contentRef.current);
        }
    }
    componentWillUnmount() {
        const { contentResizeObserver, contentRef } = this;
        if (contentResizeObserver && contentRef.current) {
            contentResizeObserver.unobserve(contentRef.current);
        }
    }
    get style() {
        return classes;
    }
    UNSAFE_componentWillReceiveProps(nextProps) {
        let isLoadingMore = false;
        if (!this.props.controlled &&
            this.props.infiniteScroll &&
            nextProps.data !== this.props.data) {
            if (nextProps.data instanceof Array && this.props.data instanceof Array) {
                isLoadingMore = true;
                const lastPage = this.calcLastPage(nextProps);
                const currentPage = this.state.currentPage < lastPage
                    ? this.state.currentPage + 1
                    : this.state.currentPage;
                this.setState({ lastPage, currentPage });
            }
            if (!isLoadingMore) {
                this.setState(this.createInitialScrollingState(nextProps));
            }
        }
    }
    createInitialScrollingState(props) {
        return { currentPage: 0, lastPage: this.calcLastPage(props) };
    }
    render() {
        const { virtualized, data, showHeaderWhenEmpty, horizontalScroll, infiniteScroll, itemsPerPage, controlled, } = this.props;
        if (!data.length && !showHeaderWhenEmpty) {
            return null;
        }
        if (virtualized) {
            return this.renderVirtualizedTable(data);
        }
        const rowsToRender = !controlled && infiniteScroll
            ? data.slice(0, (this.state.currentPage + 1) * itemsPerPage)
            : data;
        let table = this.renderTableWithWidthContext(rowsToRender);
        if (horizontalScroll) {
            table = this.wrapWithHorizontalScroll(table, this.contentRef);
        }
        if (infiniteScroll) {
            table = this.wrapWithInfiniteScroll(table);
        }
        return table;
    }
}
class TableHeader extends Component {
    constructor() {
        super(...arguments);
        this.renderSortingArrow = (sortDescending, colNum) => {
            if (sortDescending === undefined) {
                return null;
            }
            const Arrow = sortDescending ? SortByArrowDown : SortByArrowUp;
            return (React.createElement("span", { "data-hook": `${colNum}_title`, className: this.style.sortArrow },
                React.createElement(Arrow, { height: 12, "data-hook": sortDescending ? 'sort_arrow_dec' : 'sort_arrow_asc' })));
        };
        this.renderInfoTooltip = (tooltipProps, colNum) => {
            if (tooltipProps === undefined) {
                return null;
            }
            const { content, ...otherTooltipProps } = tooltipProps;
            return (React.createElement(InfoIcon, { content: content, tooltipProps: otherTooltipProps, dataHook: `${colNum}_info_tooltip`, className: this.style.infoTooltipWrapper }));
        };
        this.renderTitleSuffix = (titleSuffix, colNum) => {
            if (!titleSuffix) {
                return null;
            }
            return (React.createElement("span", { "data-hook": `${colNum}_title_suffix`, className: this.style.titleSuffix, onClick: e => e.stopPropagation(), onKeyDown: e => e.stopPropagation(), role: "presentation" }, titleSuffix));
        };
        this.renderHeaderCell = (column, colNum, resizeHelpers) => {
            const { stickyColumns, columns, isApplyColumnWidthStyle, resizable: isTableResizable, } = this.props;
            const { resizeColumn, startColumnResize, endColumnResize, getEffectiveColumnWidth, getStickyColumnStyle, } = resizeHelpers;
            const { id, disabled, hideDisabledResizeHandle } = column.resizeProps ?? {};
            const isSticky = colNum < stickyColumns;
            const isColumnResizable = isTableResizable && id !== undefined;
            const isLastColumn = colNum === columns.length - 1;
            const shouldShowHandle = isColumnResizable &&
                !(disabled && hideDisabledResizeHandle) &&
                !isLastColumn;
            const stickyColumnStyle = isSticky
                ? getStickyColumnStyle(columns, column)
                : undefined;
            const currentWidth = getEffectiveColumnWidth(column);
            const widthStyle = isApplyColumnWidthStyle
                ? currentWidth
                    ? { width: currentWidth }
                    : {}
                : { width: currentWidth };
            const style = {
                ...(isApplyColumnWidthStyle && typeof column.style !== 'function'
                    ? column.style
                    : undefined),
                ...widthStyle,
                padding: this.props.thPadding,
                height: this.props.thHeight,
                fontSize: this.props.thFontSize,
                border: this.props.thBorder,
                boxShadow: this.props.thBoxShadow,
                color: this.props.thColor,
                opacity: this.props.thOpacity,
                letterSpacing: this.props.thLetterSpacing,
                cursor: column.sortable === undefined ? 'auto' : 'pointer',
                position: isColumnResizable && !isSticky ? 'relative' : undefined,
                ...stickyColumnStyle,
            };
            const optionalHeaderCellProps = {};
            const hasTitleSuffix = !!column.titleSuffix;
            const Element = column.sortable && !hasTitleSuffix ? 'button' : 'div';
            if (column.sortable) {
                optionalHeaderCellProps.onClick = () => this.props.onSortClick && this.props.onSortClick(column, colNum);
            }
            const elementProps = column.sortable
                ? hasTitleSuffix
                    ? {
                        role: 'button',
                        tabIndex: 0,
                        onKeyDown: e => {
                            if (e.key === 'Enter' || e.key === ' ') {
                                e.preventDefault();
                                this.props.onSortClick?.(column, colNum);
                            }
                        },
                    }
                    : { type: 'button' }
                : undefined;
            return (React.createElement("th", { key: column.key ?? colNum, "data-hook": column.dataHook, style: style, "data-sticky": isSticky, "aria-sort": column.sortable
                    ? column.sortDescending === true
                        ? 'descending'
                        : column.sortDescending === false
                            ? 'ascending'
                            : 'none'
                    : undefined, className: classNames(this.style.thText, {
                    [this.style.thSkinStandard]: !this.props.skin || this.props.skin === 'standard',
                    [this.style.thSkinNeutral]: this.props.skin === 'neutral',
                    [this.style.sticky]: isSticky,
                    [this.style.lastSticky]: colNum === stickyColumns - 1,
                    [this.style.stickyActionCell]: column.stickyActionCell,
                }), ...optionalHeaderCellProps },
                React.createElement(Element, { ...elementProps, className: classNames(this.style.thContainer, {
                        [this.style.alignStart]: !column.align || column.align === 'start',
                        [this.style.alignCenter]: column.align === 'center',
                        [this.style.alignEnd]: column.align === 'end',
                    }) },
                    typeof column.title === 'string' &&
                        column.key !== 'bulk-selection-cell' ? (React.createElement(Text, { className: this.style.titleText, ellipsis: true, showTooltip: true, size: "small", weight: "thin" }, column.title)) : (column.title),
                    this.renderSortingArrow(column.sortDescending, colNum),
                    this.renderInfoTooltip(column.infoTooltipProps, colNum),
                    this.renderTitleSuffix(column.titleSuffix, colNum)),
                shouldShowHandle && (React.createElement(TableResizeHandle, { columnId: id, currentWidth: currentWidth, onResize: resizeColumn, onResizeStart: startColumnResize, onResizeEnd: endColumnResize, dataHook: "table-resize-handle", disabled: disabled, skin: this.props.skin }))));
        };
    }
    get style() {
        return classes;
    }
    render() {
        const { columns, dragAndDrop, isDragAndDropDisabled, headerRowClass, headerClass, } = this.props;
        return (React.createElement(ColumnResizeConsumer, null, ({ getEffectiveColumnWidth, resizeColumn, startColumnResize, endColumnResize, getStickyColumnStyle, }) => (React.createElement("thead", { style: {
                display: this.props.hideHeaderAccessible ? 'none' : undefined,
            }, className: classNames(headerClass) },
            React.createElement("tr", { className: classNames(headerRowClass) }, columns.map((column, colNum) => {
                const resizeHelpers = {
                    getEffectiveColumnWidth,
                    resizeColumn,
                    startColumnResize,
                    endColumnResize,
                    getStickyColumnStyle,
                };
                return this.renderHeaderCell(column, colNum, resizeHelpers);
            }))))));
    }
}
function validateData(props, propName) {
    if (props[propName]) {
        if (props[propName].constructor &&
            props[propName].constructor.name &&
            props[propName].constructor.name.toLowerCase().indexOf('array') > -1) {
            return null;
        }
        else {
            return Error('Data element must be an array type');
        }
    }
    return null;
}
DataTable.defaultProps = {
    data: [],
    columns: [],
    selectedRowsIds: [],
    isRowSelected: null,
    showHeaderWhenEmpty: false,
    infiniteScroll: false,
    itemsPerPage: 20,
    width: '100%',
    loadMore: null,
    hasMore: false,
    initialLoad: true,
    loader: React.createElement("div", { className: "loader" }, "Loading ..."),
    scrollElement: null,
    useWindow: true,
    showLastRowDivider: true,
    virtualizedLineHeight: 60,
    skin: 'standard',
    horizontalScroll: false,
    stickyColumns: 0,
    isRowDisabled: () => false,
    rowVerticalPadding: 'small',
    removeRowDetailsPadding: false,
    dragAndDrop: null,
};
DataTable.displayName = 'DataTable';
export default DataTable;
//# sourceMappingURL=DataTable.js.map