import {
    ReactNode,
    ComponentPropsWithoutRef,
    Context,
    useContext,
    useState,
} from 'react';

import { uuid } from '@/utility/getUniqueId';

import {
    NotificationContainer,
    NotificationContainerProps,
} from './NotificationContainer';
import { NotificationItem, NotificationItemProps } from './NotificationItem';

export type NotificationValue<S> = {
    /** List of notifications */
    notifications: { id: string; state: S }[];
    /** Add a new item to the notifications */
    add: (state: S, id?: string) => void;
    /** Remove a notification from the list */
    remove: (id: string) => void;
};

export type NotificationContext<S> = Context<NotificationValue<S> | undefined>;

export interface NotificationProviderProps<S>
    extends ComponentPropsWithoutRef<'div'> {
    /** Content to render the notifications in */
    children?: ReactNode;
    /** Method to Render the alert */
    render: (value: NotificationValue<S>, state: S, id: string) => ReactNode;
    /** Container to render the notifications in */
    container?: NotificationContainerProps['container'];
    /** Position of the notifications */
    position?: NotificationContainerProps['position'];
    /** Allow the notifications to be autoclosed */
    autoClose?: NotificationItemProps['autoClose'];
    /** Time till the notification is closed */
    delay?: NotificationItemProps['delay'];
    /** Maximum number of notifications to render at one time */
    limit?: number;
}

/**
 * Create a hook to access the current Notification's context
 * @returns a hook that accesses the container
 */
export function createUseNotification<S>(
    Context: NotificationContext<S>,
): () => NotificationValue<S> {
    return () => {
        const c = useContext(Context);
        if (c === undefined) {
            throw new Error(
                'useNotification must be used within a Notification',
            );
        }

        return c;
    };
}

/**
 * Create a provider for the current Notification's context
 * @returns a component to provide the Notification
 */
export const createNotificationProvider = <S,>(
    Context: NotificationContext<S>,
): ((props: NotificationProviderProps<S>) => JSX.Element) => {
    const NotificationProvider = (props: NotificationProviderProps<S>) => {
        const {
            children,
            render = () => null,
            autoClose = true,
            delay = 5000,
            limit = 5,
            ...otherProps
        } = props;

        const [notifications, setNotifications] = useState<
            NotificationValue<S>['notifications']
        >([]);

        /**
         * Add a new notification
         * @param state - state of the Notification Item
         */
        const addNotification = (state: S, id: string = uuid()) => {
            setNotifications([
                ...notifications,
                {
                    id: id,
                    state: state,
                },
            ]);
        };

        /**
         * Remove a notification
         * @param id - id of the notification to remove
         */
        const removeNotification = (id: string) => {
            setNotifications(notifications.filter((n) => n.id !== id));
        };

        // construct the value to pass to the notifications
        const value: NotificationValue<S> = {
            notifications: notifications,
            add: addNotification,
            remove: removeNotification,
        };

        return (
            <Context.Provider value={value}>
                <NotificationContainer {...otherProps}>
                    {notifications.slice(0, limit).map((n) => (
                        <NotificationItem
                            key={n.id}
                            autoClose={autoClose}
                            delay={delay}
                            onClose={() => {
                                removeNotification(n.id);
                            }}
                        >
                            {render(value, n.state, n.id)}
                        </NotificationItem>
                    ))}
                </NotificationContainer>
                {children}
            </Context.Provider>
        );
    };

    return NotificationProvider;
};
