import React from 'react';
import ReactDOM from 'react-dom';
import PropTypes from 'prop-types';
import _ from 'lodash';
import classnames from 'classnames';
import EventBus from '../../../utils/EventBus';
import {LocalStorage} from '../../../data/LocalStorage';
import {GridSorter, GridSorterDefaultProps, GridSorterProps} from './GridSorter';
import lang from '../../../../lang/common/index-en_US';

const RESIZE_OVERLAY_ID = 'GridHeaderCellResizeOverlay';
const COLUMNS_KEY = 'columns';
const RESIZE_EVENT = 'GridHeaderCellResize';
const DEFAULT_MIN_WIDTH = 60;
const NON_SORTABLE_DEFAULT_MIN_WIDTH = 40;

export interface GridHeaderCellState {
    minWidth: any;
    resizing: any;
}

type GridHeaderCellProps = {
    isSortable?: boolean;
    isResizable?: boolean;
    append?: React.ReactNode;
    minWidth?: number;
    maxWidth?: number;
    ariaDescription?: string;
    ariaDescriptionId?: string;
    skipContentForSR?: boolean;
    noRightBorder?: boolean;
} & GridSorterProps &
    Partial<GridHeaderCellDefaultProps> &
    React.HTMLProps<GridHeaderCell>;

type GridHeaderCellDefaultProps = {
    isSortable: boolean;
    isResizable: boolean;
    append: null;
    minWidth: null;
    maxWidth: null;
    skipContentForSR: boolean;
    ariaDescriptionId: null;
    noRightBorder: false;
} & GridSorterDefaultProps;

export class GridHeaderCell extends React.Component<GridHeaderCellProps, GridHeaderCellState> {
    static propTypes = {
        // ...GridSorter.propTypes,
        isSortable: PropTypes.bool,
        isResizable: PropTypes.bool,
        append: PropTypes.node,
        minWidth: PropTypes.number,
        maxWidth: PropTypes.number,
        ariaDescription: PropTypes.string,
        ariaDescriptionId: PropTypes.string,
        skipContentForSR: PropTypes.bool,
        noRightBorder: PropTypes.bool
    };

    static defaultProps = {
        // ...GridSorter.defaultProps,
        isSortable: true,
        isResizable: false,
        append: null,
        minWidth: 0,
        maxWidth: 500,
        skipContentForSR: false,
        ariaDescriptionId: null,
        ariaDescription: '',
        noRightBorder: false,
        get ascendingText() {
            return lang.SORT_BY_ASCENDING;
        },
        get descendingText() {
            return lang.SORT_BY_DESCENDING;
        },
        get sorterTitleText() {
            return lang.CLICK_TO_SORT;
        }
    };

    static contextTypes = {
        localStorage: PropTypes.instanceOf(LocalStorage)
    };

    state = {
        minWidth: null,
        resizing: null as any
    };

    cellRef: any;
    gridSorterRef: any;

    componentDidMount() {
        EventBus.on(RESIZE_EVENT, this.handleSiblingResize);
        const {localStorage} = this.context;
        if (localStorage && localStorage.prefix) {
            this.handleSiblingResize(localStorage.prefix);
        }
    }

    componentWillUnmount() {
        EventBus.off(RESIZE_EVENT, this.handleSiblingResize);
        this.cancelResizing();
    }

    getResizeColumnWidth() {
        const {resizing, minWidth} = this.state;
        if (resizing) {
            return resizing.width;
        }

        if (minWidth) {
            return minWidth;
        }

        const {localStorage} = this.context;
        const {property} = this.props;
        if (localStorage && property) {
            const columns = localStorage.getItem(COLUMNS_KEY);
            if (columns && columns[property]) {
                return columns[property].width;
            }
        }
    }

    getParentCell(element) {
        let cell = element;
        while (cell && cell.tagName !== 'TH' && cell.tagName !== 'TD') {
            cell = cell.parentNode;
        }
        return cell;
    }

    handleSiblingResize = localStoragePrefix => {
        const {localStorage} = this.context;
        const {resizing} = this.state;

        if (resizing || !localStorage || localStoragePrefix !== localStorage.prefix) {
            return;
        }

        let cell = this.cellRef || ReactDOM.findDOMNode(this.gridSorterRef);
        if (!cell) {
            return;
        }

        const {minWidth} = this.props as any;
        if (!minWidth) {
            return;
        }

        const width = cell.clientWidth;
        if (!width) {
            // cell is not visible, ignore its width
            if (this.state.minWidth) {
                this.setState({minWidth: null});
            }
            return;
        }
        if (width <= minWidth) {
            if (minWidth !== this.state.minWidth) {
                this.setState({minWidth});
            } else {
                // Reset minWidth -> Render -> Re-calculate minWidth
                this.setState({minWidth: null}, () => this.handleSiblingResize(localStoragePrefix));
            }
        } else if (this.state.minWidth) {
            this.setState({minWidth: null});
        }
    };

    createResizeOverlay() {
        const container = document.createElement('div');
        const overlay = document.createElement('div');
        container.id = RESIZE_OVERLAY_ID;
        container.className = 'bootstrap-rc';
        overlay.className = 'resizing-overlay resizing-overlay-ew';
        container.appendChild(overlay);
        return container;
    }

    startResizing = event => {
        event.stopPropagation();

        const cell = this.getParentCell(event.target);
        if (!cell) {
            return;
        }

        const overlay = this.createResizeOverlay();
        document.body.appendChild(overlay);
        document.addEventListener('mousemove', this.resize);
        document.addEventListener('mouseup', this.stopResizing);

        this.setState({
            minWidth: null,
            resizing: {
                x: event.pageX,
                width: cell.clientWidth,
                startWidth: cell.clientWidth
            }
        });
    };

    resize = event => {
        event.stopPropagation();

        let {minWidth, maxWidth, isSortable} = this.props as any;
        const {resizing} = this.state;

        if (!minWidth) {
            minWidth = isSortable ? DEFAULT_MIN_WIDTH : NON_SORTABLE_DEFAULT_MIN_WIDTH;
        }

        if (resizing) {
            let width = resizing.startWidth - resizing.x + event.pageX;
            if (width < minWidth) {
                width = minWidth;
            } else if (width > maxWidth) {
                width = maxWidth;
            }
            if (width !== resizing.width) {
                this.setState(
                    {
                        minWidth: null,
                        resizing: {...resizing, width}
                    },
                    this.notifySiblings
                );
            }
        }
    };

    stopResizing = event => {
        event.stopPropagation();

        const {localStorage} = this.context;
        const {property} = this.props;
        const {resizing} = this.state;

        if (resizing && localStorage && property) {
            const columns = localStorage.getItem(COLUMNS_KEY) || {};
            columns[property] = {
                ...columns[property],
                width: resizing.width
            };
            localStorage.setItem(COLUMNS_KEY, columns);
        }

        this.cancelResizing();
        this.setState({
            resizing: null
        });
    };

    cancelResizing() {
        const overlay = document.getElementById(RESIZE_OVERLAY_ID) as any;
        if (overlay) {
            overlay.parentNode.removeChild(overlay);
        }
        document.removeEventListener('mousemove', this.resize);
        document.removeEventListener('mouseup', this.stopResizing);
    }

    notifySiblings = () => {
        const {localStorage} = this.context;
        if (localStorage && localStorage.prefix) {
            EventBus.emit(RESIZE_EVENT, localStorage.prefix);
        }
    };

    isResizable() {
        const {localStorage} = this.context;
        const {isResizable, property} = this.props;
        return !!(localStorage && isResizable && property);
    }

    renderResizeHandler() {
        if (this.props.noRightBorder) {
            return null;
        }
        const isResizable = this.isResizable();
        const className = classnames('resize-handler', {active: isResizable});
        return (
            <span
                className={className}
                onMouseDown={isResizable ? this.startResizing : (null as any)}
            />
        );
    }

    renderChildren = (ariaDescription, skipContentForSR, ariaDescriptionId, children) => {
        return (
            <span>
                {children && <span aria-hidden={skipContentForSR}>{children}</span>}
                {ariaDescription && (
                    <span className="sr-only" id={ariaDescriptionId}>
                        {ariaDescription}
                    </span>
                )}
            </span>
        );
    };

    renderPrepend(prepend) {
        if (!prepend) {
            return null;
        }
        return <span>{prepend}</span>;
    }

    renderAppend(append) {
        if (!append) {
            return null;
        }
        return <span className="column-header-append">{append}</span>;
    }

    renderCellContent(prepend, children, append) {
        return (
            <div className="column-header-content">
                {this.renderPrepend(prepend)}
                <div className="column-header-label text-overflow">{children}</div>
                {this.renderAppend(append)}
            </div>
        );
    }

    render() {
        const {
            isSortable,
            isResizable: _isResizable,
            append,
            minWidth: _minWidth,
            maxWidth,
            noRightBorder,
            ariaDescription,
            ariaDescriptionId,
            skipContentForSR,
            displayName,
            className,
            style,
            children,
            ...props
        } = this.props;

        const {resizing, minWidth} = this.state;

        const child = this.renderChildren(
            ariaDescription,
            skipContentForSR,
            ariaDescriptionId,
            children
        );

        const isResizable = this.isResizable();
        const prepend = this.renderResizeHandler();

        const classNames = classnames(className, {
            resizable: isResizable,
            resizing: isResizable && resizing
        });

        let styles = style;
        if (isResizable) {
            const width = this.getResizeColumnWidth();
            if (width) {
                styles = {...styles, width};
            }
        } else if (minWidth) {
            styles = {...styles, width: minWidth};
        }

        if (isSortable) {
            const GridSorterTS: any = GridSorter;
            return (
                <GridSorterTS
                    ref={th => (this.gridSorterRef = th)}
                    displayName={(displayName || children) as any}
                    className={classNames}
                    style={styles}
                    {...props}
                    append={append}
                    prepend={prepend}>
                    {child}
                </GridSorterTS>
            );
        }
        const {testAutomationId, ...restProps} = props;
        const tableHeaderProps = _.omit(restProps, Object.keys(GridHeaderCell.defaultProps));
        return (
            <th
                ref={cell => (this.cellRef = cell)}
                data-test-automation-id={testAutomationId}
                className={classNames}
                style={styles}
                {...tableHeaderProps as any}>
                {this.renderCellContent(prepend, children, append)}
            </th>
        );
    }
}
