/**
 * OverlayStack-storage for the stack of layers (overlays).
 *
 * The stack is necessary for understanding whether there are open overlays and whether they contain a background.
 * Is responsible for storing the stack and provides methods for changing the stack. It works based on the Context API.
 * Allows you to get information about the layer stack from any part of the app using OVerlayStack.Consumer
 * and the withoverlaystack HOC decorator
 */

import * as React from 'react';

import {WithOverlayStack, IRenderProps, AddToStack, RemoveFromStack, UpdateInStack} from './OverlayStack.types';

const {Consumer, ...Context} = React.createContext<IRenderProps | null>(null);

/**
 * HOC decorator for getting the stack and methods for changing the stack.
 *
 * StackOverlay.Provider must be enabled to use it
 */
export const withOverlayStack: WithOverlayStack = (mapRenderProps) => {
    return (Child) => {
        const Wrapper: React.FC<any> = (props) => (
            <Consumer>
                {(renderProps) => {
                    if (renderProps === null) {
                        throw new Error(`Can't find OverlayStack.Provider in parents.`);
                    }

                    return <Child {...props} {...mapRenderProps(renderProps)} />;
                }}
            </Consumer>
        );

        Wrapper.displayName = `WithOverlayStack(${Child.displayName || Child.name || 'unknown'})`;

        return Wrapper;
    };
};

class Provider extends React.PureComponent<{}, IRenderProps> {
    private add: AddToStack = (stackItem) => {
        this.setState((prevState) => {
            const stack = [...prevState.stack];

            stack.push(stackItem);

            return {stack};
        });
    };

    private remove: RemoveFromStack = (id) => {
        this.setState((prevState) => {
            const stack = [...prevState.stack];
            const index = stack.findIndex((stackItem) => stackItem.id === id);

            if (index >= 0) {
                stack.splice(index, 1);

                return {stack};
            }

            return null;
        });
    };

    private update: UpdateInStack = (stackItem) => {
        this.setState((prevState) => {
            const stack = [...prevState.stack];
            const index = stack.findIndex(({id}) => stackItem.id === id);

            if (index >= 0) {
                stack[index] = stackItem;

                return {stack};
            }

            return null;
        });
    };

    override state: IRenderProps = {
        stack: [],
        add: this.add,
        remove: this.remove,
        update: this.update
    };

    override render () {
        return <Context.Provider value={this.state}>{this.props.children}</Context.Provider>;
    }
}

export const OverlayStack = {
    Consumer,
    Provider
};
