import React from 'react';
import PropTypes from 'prop-types';
import { ResizeSensor } from 'css-element-queries';
import ReactComponent from '../../PropTypes/ReactComponent';
import getDisplayName from '../getDisplayName';

const initialState = {
    size: {
        width: 0,
        height: 0,
    },
};

const ResizeDetectContext = React.createContext(initialState);

/**
 * Wrap a component with the ResizeSensor
 * Usage modes:
 *  1. children prop is the wrapped component
 *      -> add a container around it and apply the ResizeSensor on the wrapper container
 *  2. children prop is a render prop
 *      -> define the dom element reference for the ResizeSensor container inside the render prop with containerRef
 */
export class ResizeDetect extends React.Component {
    static propTypes = {
        onSizeChange: PropTypes.func,
        WrapperComponent: PropTypes.oneOfType([ReactComponent, PropTypes.string]),
        wrapperClassName: PropTypes.string,
        wrapperProps: PropTypes.object,
        children: PropTypes.any,
    };

    state = initialState;

    componentDidMount() {
        const container = this.getContainer();
        this.resizeSensor = new ResizeSensor(container, this.updateState);
        // trigger change for the first time
        this.updateState();
    }

    componentWillUnmount() {
        // releasing resize sensor
        this.resizeSensor.detach();
    }

    getContainer() {
        const container = this.containerRef.current;
        if (!container) {
            throw new TypeError(
                'ResizeDetect can only work with children as a render prop or with the wrapper mode on.'
            );
        }
        return container;
    }

    updateState = () => {
        const container = this.getContainer();
        const { width, height } = container.getBoundingClientRect();
        const size = {
            width: Math.floor(width),
            height: Math.floor(height),
        };
        this.setState({ size });
        const { onSizeChange } = this.props;
        onSizeChange && onSizeChange(size);
    };

    containerRef = React.createRef();

    getProps() {
        // eslint-disable-next-line no-unused-vars
        const { onSizeChange, WrapperComponent, ...otherProps } = this.props;
        return otherProps;
    }

    render() {
        const { children, WrapperComponent, wrapperClassName, wrapperProps } = this.props;
        const Container = WrapperComponent || 'div';
        return typeof children !== 'function' ? (
            <ResizeDetectContext.Provider value={this.state}>
                <Container className={wrapperClassName} ref={this.containerRef} {...wrapperProps}>
                    {children}
                </Container>
            </ResizeDetectContext.Provider>
        ) : (
            children({
                containerRef: this.containerRef,
                size: this.state.size,
                containerProps: this.getProps(),
            })
        );
    }
}

/**
 * HOC for the 2. usage mode (provides the Component as a render function directly, so it can get the props)
 * Hint: in this case don't forget to use the containerRef inside the component to define the container element
 * for the ResizeSensor.
 * @param Component
 */
export const withResizeDetect = Component => {
    const WithResizeDetect = () => <ResizeDetect>{props => <Component {...props} />}</ResizeDetect>;
    WithResizeDetect.displayName = `WithResizeDetect(${getDisplayName(Component)})`;
    return WithResizeDetect;
};

/**
 * HOC for the 1. usage mode (adds a wrapper div around the component)
 * @param Component
 */
export const withResizeDetectWrapper = Component => props => (
    <ResizeDetect {...props}>
        <ResizeDetectContext.Consumer>
            {state => <Component {...state} {...props} />}
        </ResizeDetectContext.Consumer>
    </ResizeDetect>
);
