/**
 * Centralized notification service and provider.
 *
 * Provides a single channel for displaying user-facing notifications (errors, success, warnings, info).
 * Prevents duplicate messaging, handles auto-dismiss, and maintains a queue of active notifications.
 * Consumers use the `useNotification` hook to access the service.
 */

import { createContext, useCallback, useContext, useMemo, useState } from "react";
import type { Notification, NotificationIntent, NotificationService } from "../types/notification";

/** Default auto-dismiss durations (milliseconds) for each notification type. */
const DEFAULT_DURATIONS: Record<NotificationIntent, number> = {
  success: 5000,
  error: 8000,
  warning: 6000,
  info: 5000,
};

/** Context holding active notifications and the service API. */
const NotificationContext = createContext<NotificationService | null>(null);

/**
 * NotificationProvider component.
 * Must wrap the entire application to make notifications available to all children.
 */
export function NotificationProvider({ children }: { children: React.ReactNode }): React.JSX.Element {
  const [notifications, setNotifications] = useState<Notification[]>([]);

  /**
   * Remove a notification by ID.
   */
  const dismiss = useCallback((id: string): void => {
    setNotifications((prev) => prev.filter((n) => n.id !== id));
  }, []);

  /**
   * Check if a notification with this message and intent already exists to prevent duplicates.
   */
  const isDuplicate = useCallback(
    (message: string, intent: NotificationIntent): boolean =>
      notifications.some((n) => n.message === message && n.intent === intent),
    [notifications],
  );

  /**
   * Generate a unique ID for a notification.
   */
  const generateId = useCallback((): string => {
    const timestamp = Date.now().toString(36);
    const random = Math.random().toString(36).substring(2, 9);
    return `notif-${timestamp}-${random}`;
  }, []);

  /**
   * Add a notification to the queue.
   */
  const add = useCallback(
    (intent: NotificationIntent, message: string, autoCloseMsec?: number): void => {
      // Prevent duplicate identical notifications
      if (isDuplicate(message, intent)) {
        return;
      }

      const id = generateId();
      const duration = autoCloseMsec !== undefined ? autoCloseMsec : DEFAULT_DURATIONS[intent];

      setNotifications((prev) => [...prev, { id, intent, message, autoCloseMsec: duration }]);

      // Schedule auto-dismiss if duration is set
      if (duration) {
        setTimeout(() => {
          dismiss(id);
        }, duration);
      }
    },
    [isDuplicate, generateId, dismiss],
  );

  /**
   * Public service API.
   */
  const service: NotificationService = useMemo((): NotificationService => {
    return {
      success: (message: string, autoCloseMsec?: number): void => {
        add("success", message, autoCloseMsec);
      },
      error: (message: string, autoCloseMsec?: number): void => {
        add("error", message, autoCloseMsec);
      },
      warning: (message: string, autoCloseMsec?: number): void => {
        add("warning", message, autoCloseMsec);
      },
      info: (message: string, autoCloseMsec?: number): void => {
        add("info", message, autoCloseMsec);
      },
      dismiss,
    };
  }, [add, dismiss]);

  return (
    <NotificationContext.Provider value={service}>
      <NotificationContextInternal.Provider value={{ notifications, service }}>
        {children}
      </NotificationContextInternal.Provider>
    </NotificationContext.Provider>
  );
}

/**
 * Internal context for accessing the notification queue (for rendering).
 * This is separate from the public context to keep the public API clean.
 */
interface NotificationContextValue {
  notifications: Notification[];
  service: NotificationService;
}

const NotificationContextInternal = createContext<NotificationContextValue | null>(null);

/**
 * Hook to access the notification service.
 * Use this in any component to show success, error, warning, or info messages.
 *
 * @returns NotificationService API for showing messages.
 * @throws Error if used outside NotificationProvider.
 *
 * @example
 * ```tsx
 * function MyComponent() {
 *   const notification = useNotification();
 *
 *   const handleSave = async () => {
 *     try {
 *       await saveData();
 *       notification.success("Data saved successfully");
 *     } catch (err) {
 *       notification.error("Failed to save data");
 *     }
 *   };
 *
 *   return <button onClick={handleSave}>Save</button>;
 * }
 * ```
 */
export function useNotification(): NotificationService {
  const context = useContext(NotificationContext);
  if (!context) {
    throw new Error("useNotification must be used within NotificationProvider");
  }
  return context;
}

/**
 * Internal hook to access the notifications queue for rendering.
 * Only used by NotificationContainer.
 */
export function useNotificationQueue(): Notification[] {
  const context = useContext(NotificationContextInternal);
  if (!context) {
    throw new Error("NotificationContextInternal must be used within NotificationProvider");
  }
  return context.notifications;
}

