import React from 'react';
import PortaledModal, { ModalProps } from '@/components/elements/Modal';
import ModalContext, { ModalContextValues } from '@/context/ModalContext';
import isEqual from 'react-fast-compare';

export interface AsModalProps {
    visible: boolean;
    onModalDismissed?: () => void;
}

export type SettableModalProps = Omit<ModalProps, 'appear' | 'visible' | 'onDismissed'>;

interface State {
    render: boolean;
    visible: boolean;
    showSpinnerOverlay?: boolean;
    propOverrides: Partial<SettableModalProps>;
}

type ExtendedComponentType<T> = (C: React.ComponentType<T>) => React.ComponentType<T & AsModalProps>;

// eslint-disable-next-line @typescript-eslint/ban-types
function asModal<P extends {}> (modalProps?: SettableModalProps | ((props: P) => SettableModalProps)): ExtendedComponentType<P> {
    return function (Component) {
        return class extends React.PureComponent <P & AsModalProps, State> {
            static displayName = `asModal(${Component.displayName})`;

            constructor (props: P & AsModalProps) {
                super(props);

                this.state = {
                    render: props.visible,
                    visible: props.visible,
                    showSpinnerOverlay: undefined,
                    propOverrides: {},
                };
            }

            get computedModalProps (): Readonly<SettableModalProps & { visible: boolean }> {
                return {
                    ...(typeof modalProps === 'function' ? modalProps(this.props) : modalProps),
                    showSpinnerOverlay: this.state.showSpinnerOverlay,
                    ...this.state.propOverrides,
                    visible: this.state.visible,
                };
            }

            /**
             * @this {React.PureComponent<P & AsModalProps, State>}
             */
            componentDidUpdate (prevProps: Readonly<P & AsModalProps>, prevState: Readonly<State>) {
                if (prevProps.visible && !this.props.visible) {
                    this.setState({ visible: false, showSpinnerOverlay: false });
                } else if (!prevProps.visible && this.props.visible) {
                    this.setState({ render: true, visible: true });
                }
                if (!this.state.render && !isEqual(prevState.propOverrides, this.state.propOverrides)) {
                    this.setState({ propOverrides: {} });
                }
            }

            dismiss = () => this.setState({ visible: false });

            setPropOverrides: ModalContextValues['setPropOverrides'] = value => this.setState(state => ({
                propOverrides: !value ? {} : (typeof value === 'function' ? value(state.propOverrides) : value),
            }));

            /**
             * @this {React.PureComponent<P & AsModalProps, State>}
             */
            render () {
                if (!this.state.render) return null;

                return (
                    <PortaledModal
                        appear
                        onDismissed={() => this.setState({ render: false }, () => {
                            if (typeof this.props.onModalDismissed === 'function') {
                                this.props.onModalDismissed();
                            }
                        })}
                        {...this.computedModalProps}
                    >
                        <ModalContext.Provider
                            value={{
                                dismiss: this.dismiss.bind(this),
                                setPropOverrides: this.setPropOverrides.bind(this),
                            }}
                        >
                            <Component {...this.props}/>
                        </ModalContext.Provider>
                    </PortaledModal>
                );
            }
        };
    };
}

export default asModal;