import { PureComponent } from 'react';
import isEqual from 'react-fast-compare';

import PortaledModal, { ModalProps } from '@/components/elements/Modal';
import ModalContext, { ModalContextValues } from '@/context/ModalContext';

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

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

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

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

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

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

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

            /**
             * @this {React.PureComponent<P & AsModalProps, State>}
             */
            override componentDidUpdate(prevProps: Readonly<P & AsModalProps>, prevState: Readonly<State>) {
                if (prevProps.visible && !this.props.visible) {
                    this.setState({ visible: false, propOverrides: {} });
                } 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>}
             */
            override 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;
