import { router, useHttp, usePage } from '@inertiajs/react';
import { useEcho } from '@laravel/echo-react';
import {
    createContext,
    useCallback,
    useContext,
    useEffect,
    useRef,
    useState,
} from 'react';
import type { ReactNode } from 'react';

import {
    index as notificationsIndex,
    open as openNotification,
    readAll as readAllNotifications,
} from '@/routes/notifications';
import type { Auth } from '@/types/auth';
import type {
    AppNotification,
    NotificationIndexResponse,
    TaskNotificationBroadcast,
} from '@/types/notification';

interface NotificationContextValue {
    /** Jumlah notifikasi belum dibaca (untuk badge lonceng). */
    unreadCount: number;
    /** Daftar notifikasi terbaru yang dimuat ke dropdown. */
    notifications: AppNotification[];
    /** Sedang memuat daftar dari server. */
    isLoading: boolean;
    /** Masih ada halaman berikutnya yang bisa dimuat. */
    hasMore: boolean;
    /** Muat ulang dari halaman pertama (dipanggil saat dropdown dibuka). */
    refresh: () => Promise<void>;
    /** Muat halaman berikutnya (dipanggil saat daftar discroll mendekati dasar). */
    loadMore: () => Promise<void>;
    /** Tandai semua notifikasi sebagai sudah dibaca. */
    markAllAsRead: () => Promise<void>;
}

const NotificationContext = createContext<NotificationContextValue | null>(
    null,
);

/**
 * Menyediakan state notifikasi (unread count + daftar) ke seluruh aplikasi dan
 * mendengarkan event realtime dari channel privat user.
 *
 * Sumber data:
 * - `unreadCount` awal di-seed dari shared prop Inertia (`notifications.unread_count`)
 *   agar badge langsung akurat tiap kali pindah halaman;
 * - daftar notifikasi diambil lewat GET /notifications saat dropdown dibuka;
 * - event Echo `TaskNotification` menambah count & menyisipkan item baru realtime,
 *   sekaligus menampilkan toast/OS notification.
 */
export function NotificationProvider({ children }: { children: ReactNode }) {
    const page = usePage<{
        auth: Auth;
        notifications?: { unread_count: number };
    }>();
    const userId = page.props.auth?.user?.id;
    const sharedUnread = page.props.notifications?.unread_count ?? 0;

    const [unreadCount, setUnreadCount] = useState<number>(sharedUnread);
    const [notifications, setNotifications] = useState<AppNotification[]>([]);
    const [isLoading, setIsLoading] = useState(false);
    // Penanda halaman berikutnya; null berarti daftar sudah habis.
    const [nextCursor, setNextCursor] = useState<string | null>(null);
    // Penjaga request beruntun untuk `loadMore` (lihat alasannya di sana).
    const loadingMoreRef = useRef(false);
    // Id notifikasi realtime yang sudah diproses. Daftar `notifications` tidak
    // bisa dipakai sebagai penjaga: selama dropdown belum pernah dibuka isinya
    // kosong, jadi event kembar dari replay Echo lolos dan badge menggelembung
    // tanpa satu pun baris bertambah.
    const seenRealtimeIdsRef = useRef(new Set<string>());

    const { submit } = useHttp();

    // Selalu sinkronkan badge dengan shared prop terbaru saat navigasi Inertia,
    // sehingga count tetap benar meski notifikasi ditandai dibaca di tab lain.
    useEffect(() => {
        setUnreadCount(sharedUnread);
    }, [sharedUnread]);

    const refresh = useCallback(async () => {
        setIsLoading(true);

        try {
            const response = (await submit(notificationsIndex())) as
                | NotificationIndexResponse
                | undefined;

            if (response) {
                setNotifications(response.data ?? []);
                setNextCursor(response.next_cursor ?? null);
                setUnreadCount(response.unread_count ?? 0);
            }
        } catch (error) {
            console.error('Failed to load notifications:', error);
        } finally {
            setIsLoading(false);
        }
    }, [submit]);

    const loadMore = useCallback(async () => {
        // Dua penjaga, dua celah berbeda: ref menutup beberapa event gulir yang
        // tiba sebelum React sempat render ulang (semuanya masih melihat
        // `isLoading` false), sementara `isLoading` tetap dibutuhkan agar
        // halaman berikutnya tidak ditarik di atas `refresh()` yang sedang
        // berjalan — kursornya sudah basi begitu refresh selesai.
        if (!nextCursor || loadingMoreRef.current || isLoading) {
            return;
        }

        loadingMoreRef.current = true;
        setIsLoading(true);

        try {
            const response = (await submit(
                notificationsIndex({ query: { cursor: nextCursor } }),
            )) as NotificationIndexResponse | undefined;

            if (response) {
                // Notifikasi baru yang masuk realtime menggeser isi halaman, jadi
                // baris yang sudah ada disaring agar tidak dobel.
                setNotifications((prev) => {
                    const seen = new Set(prev.map((item) => item.id));

                    return [
                        ...prev,
                        ...(response.data ?? []).filter(
                            (item) => !seen.has(item.id),
                        ),
                    ];
                });
                setNextCursor(response.next_cursor ?? null);
            }
        } catch (error) {
            console.error('Failed to load more notifications:', error);
        } finally {
            loadingMoreRef.current = false;
            setIsLoading(false);
        }
    }, [submit, nextCursor, isLoading]);

    const markAllAsRead = useCallback(async () => {
        // Optimistic agar UI responsif; kalau server menolak, tarik ulang
        // keadaan sebenarnya supaya badge tidak berbohong.
        setNotifications((prev) =>
            prev.map((item) => ({ ...item, is_read: true })),
        );
        setUnreadCount(0);

        try {
            await submit(readAllNotifications());
        } catch (error) {
            console.error('Failed to mark all notifications:', error);

            await refresh();
        }
    }, [submit, refresh]);

    // Simpan handler terbaru di ref agar listener Echo tidak perlu re-subscribe
    // setiap kali fungsi berubah.
    const handleIncoming = useCallback((event: TaskNotificationBroadcast) => {
        const {
            id,
            type,
            taskId,
            taskCode,
            taskName,
            divisionSlug,
            projectId,
            title,
            body,
        } = event;

        if (seenRealtimeIdsRef.current.has(id)) {
            return;
        }

        seenRealtimeIdsRef.current.add(id);

        // Sisipkan ke daftar (bila belum ada) dan naikkan badge.
        setNotifications((prev) => {
            if (prev.some((item) => item.id === id)) {
                return prev;
            }

            const incoming: AppNotification = {
                id,
                type,
                task_id: taskId,
                task_code: taskCode,
                task_name: taskName,
                division_slug: divisionSlug,
                project_id: projectId,
                title,
                body,
                is_read: false,
                created_at: new Date().toISOString(),
            };

            return [incoming, ...prev];
        });
        setUnreadCount((prev) => prev + 1);

        showOsNotification(event);
    }, []);

    const handleIncomingRef = useRef(handleIncoming);
    handleIncomingRef.current = handleIncoming;

    useEcho<TaskNotificationBroadcast>(
        // Channel privat milik user; cocok dengan definisi di routes/channels.php.
        // Placeholder saat userId belum tersedia agar hook tetap dipanggil tanpa
        // syarat (aturan React hooks) tanpa subscribe ke channel tak valid.
        userId ? `App.Models.User.${userId}` : 'notifications.pending',
        ['.TaskNotification'],
        (event) => handleIncomingRef.current(event),
        [userId],
    );

    return (
        <NotificationContext.Provider
            value={{
                unreadCount,
                notifications,
                isLoading,
                hasMore: nextCursor !== null,
                refresh,
                loadMore,
                markAllAsRead,
            }}
        >
            {children}
        </NotificationContext.Provider>
    );
}

/**
 * Akses state notifikasi. Harus dipakai di dalam {@see NotificationProvider}.
 */
export function useNotifications(): NotificationContextValue {
    const context = useContext(NotificationContext);

    if (!context) {
        throw new Error(
            'useNotifications must be used inside <NotificationProvider>.',
        );
    }

    return context;
}

/**
 * Menampilkan notifikasi OS (bila diizinkan) saat notifikasi task masuk realtime.
 *
 * Toast in-app sengaja tidak dipakai lagi: notifikasi kini punya lonceng +
 * dropdown khusus, dan badge unread sudah menandai notifikasi baru. OS
 * notification tetap ada agar user tetap sadar meski sedang membuka tab lain.
 *
 * Kliknya memfokuskan tab ini lalu membuka task lewat endpoint yang sama dengan
 * klik di dropdown, jadi notifikasi ikut ditandai sudah dibaca.
 */
function showOsNotification(event: TaskNotificationBroadcast): void {
    if (
        typeof window === 'undefined' ||
        !('Notification' in window) ||
        Notification.permission !== 'granted'
    ) {
        return;
    }

    try {
        const osNotification = new Notification(event.title, {
            body: event.body,
            // Tag per objek+jenis agar notif serupa saling menimpa, bukan
            // menumpuk. Notifikasi project tidak punya kode task, jadi
            // penandanya memakai id project.
            tag: event.taskCode
                ? `task-${event.taskCode}-${event.type}`
                : `project-${event.projectId}-${event.type}`,
        });

        osNotification.onclick = () => {
            window.focus();
            osNotification.close();
            router.visit(openNotification(event.id).url);
        };
    } catch {
        // Abaikan bila browser menolak (mis. konteks tidak aman); badge lonceng
        // tetap menandai notifikasi baru.
    }
}
