import type { FC } from 'react';
import React, { createContext, useCallback, useContext, useMemo } from 'react';
import { ToastContainer } from './toastContainer';
import type { Message, MessageContent, ToastMessageVariant } from './types';

interface ToastContextProps {
    addToast?: (
        message: MessageContent,
        options?: { variant?: ToastMessageVariant; classNameProp?: string; persistent?: boolean }
    ) => void;
}

interface ToastProviderProps {
    children?: React.ReactNode;
    toastTime?: number;
}

interface MessageAddAction {
    type: 'add';
    content: MessageContent;
    options?: {
        variant?: ToastMessageVariant;
        classNameProp?: string;
        persistent?: boolean;
    };
}

interface MessageSetAction {
    type: 'set';
    messages: Message[];
}

interface MessageRemoveAction {
    type: 'remove';
    id: string;
}

type MessageActions = MessageAddAction | MessageSetAction | MessageRemoveAction;

const ToastContext = createContext<ToastContextProps>({});

export const ToastProvider: FC<ToastProviderProps> = ({ children, toastTime = 8000 }) => {
    const seq = React.useRef(0);
    const [messages, dispatch] = React.useReducer((state: Message[], action: MessageActions) => {
        if (action.type === 'set') return action.messages;

        if (action.type === 'add')
            return [
                ...state,
                {
                    id: (seq.current++).toString(),
                    content: action.content,
                    variant: action.options?.variant,
                    classNameProp: action.options?.classNameProp,
                    persistent: action.options?.persistent,
                },
            ];

        if (action.type === 'remove') return state.filter((message) => message.id !== action.id);

        return state;
    }, []);

    const providerProps = useMemo<ToastContextProps>(
        () => ({
            addToast: (content, options) => {
                dispatch({ type: 'add', content, options });
            },
        }),
        []
    );

    const setMessages = useCallback((messages: Message[]) => {
        dispatch({ type: 'set', messages });
    }, []);

    const removeToast = useCallback((id: string) => {
        dispatch({ type: 'remove', id });
    }, []);

    return (
        <ToastContext.Provider value={providerProps}>
            <ToastContainer
                messages={messages}
                setMessages={setMessages}
                removeToast={removeToast}
                toastTime={toastTime}
            />
            {children}
        </ToastContext.Provider>
    );
};

export function withToast<P>(Component: FC<P>) {
    return (props: P) => (
        <ToastContext.Consumer>
            {({ addToast }) => <Component {...props} addToast={addToast} />}
        </ToastContext.Consumer>
    );
}

export const useToast = () => {
    const { addToast } = useContext(ToastContext);

    if (!addToast) throw new Error('No parent "ToastProvider".');
    return addToast;
};
