import * as React from 'react';

import {withOverlayStack} from '../overlays/OverlayStack';
import {isContainsInUpperOverlays, uniqueId} from './utils';
import {WithStackSynchronyzer, IProps, PropsWithDefault, IMixedProps, IInputProps} from './OverlayStackSynchronizer.types';

/**
 * HOC-decorator for syncing the overlay with glass.
 *
 * Tracks the state of the overlay and adds/deletes / changes overlay data from the stack.
 */
export const withOverlayStackSynchronyzer: WithStackSynchronyzer = (Child: React.ComponentType<IMixedProps & IInputProps>) => {
    const StackSynchronyzer: React.ComponentType<IProps> = class extends React.PureComponent<IProps> {
        static defaultProps = {
            hasBackdrop: false
        };

        static displayName = `WithStackSynchronyzer(${Child.displayName || Child.name || 'unknown'})`;

        /**
         * A unique ID is generated for each overlay when it is created.
         *
         * Thanks to ID, you can track the position of the overlay in the stack.
         *
         * ATTENTION! getting an id from this. props is only allowed during testing.
         */
        id: string = this.props.id || uniqueId();

        /**
         * Is the DOM element contained in the layers above?
         */
        private isContainsInUpperOverlays = (element: Element) => {
            return isContainsInUpperOverlays(this.id, element, this.props.overlayStack.stack);
        };

        override componentDidMount () {
            const {isOpen, hasBackdrop, overlayStack} = this.props as PropsWithDefault;

            if (isOpen) {
                overlayStack.add({
                    id: this.id,
                    hasBackdrop
                });
            }
        }

        override componentDidUpdate (prevProps: IProps) {
            const props = this.props as PropsWithDefault;
            const wasUpdatedBackdrop = prevProps.hasBackdrop !== props.hasBackdrop;
            const wasUpdatedVisibility = prevProps.isOpen !== props.isOpen;
            const isOpen = wasUpdatedVisibility && props.isOpen;
            const isClose = wasUpdatedVisibility && !props.isOpen;

            if (isOpen) {
                props.overlayStack.add({
                    id: this.id,
                    hasBackdrop: props.hasBackdrop
                });
            } else if (isClose) {
                props.overlayStack.remove(this.id);
            } else if (wasUpdatedBackdrop) {
                props.overlayStack.update({id: this.id, hasBackdrop: props.hasBackdrop});
            }
        }

        override componentWillUnmount () {
            const {isOpen, overlayStack} = this.props as PropsWithDefault;

            if (isOpen) {
                overlayStack.remove(this.id);
            }
        }

        override render () {
            const {overlayStack, ...otherProps} = this.props as PropsWithDefault;

            let isLast = false;
            let isLastBackdrop = false;

            const stackLength = overlayStack.stack.length;

            if (stackLength) {
                isLast = overlayStack.stack[stackLength - 1].id === this.id;
                const backdropStack = overlayStack.stack.filter(({hasBackdrop}) => hasBackdrop);

                if (backdropStack.length) {
                    isLastBackdrop = backdropStack[backdropStack.length - 1].id === this.id;
                }
            }

            const mixedProps = {
                id: this.id,
                isLast,
                isLastBackdrop,
                isContainsInUpperOverlays: this.isContainsInUpperOverlays
            };

            const childProps = {
                ...otherProps,
                ...mixedProps
            };

            return <Child {...childProps} />;
        }
    };

    return withOverlayStack((overlayStack) => ({overlayStack}))(StackSynchronyzer);
};
