import { useCallback, useEffect, useRef, useState } from 'react'
import type { NotificationRecord } from '@platform-modules/notifications/inbox'
import type { NotificationClient } from './client.js'

function toError(e: unknown): Error {
  return e instanceof Error ? e : new Error(String(e))
}

export function useNotifications(client: NotificationClient, opts?: { pollMs?: number }) {
  const [items, setItems] = useState<NotificationRecord[]>([])
  const [unread, setUnread] = useState(0)
  const [loading, setLoading] = useState(true)
  const [error, setError] = useState<Error | null>(null)
  const [hasMore, setHasMore] = useState(false)
  const [reloadToken, setReloadToken] = useState(0)

  const clientRef = useRef(client)
  clientRef.current = client
  const nextCursorRef = useRef<string | undefined>(undefined)

  const reload = useCallback(() => {
    setReloadToken((t) => t + 1)
  }, [])

  useEffect(() => {
    let cancelled = false
    setLoading(true)
    setError(null)
    nextCursorRef.current = undefined

    void Promise.all([clientRef.current.list(), clientRef.current.unreadCount()])
      .then(([page, count]) => {
        if (!cancelled) {
          setItems(page.items)
          nextCursorRef.current = page.nextCursor
          setHasMore(Boolean(page.nextCursor))
          setUnread(count)
          setLoading(false)
        }
      })
      .catch((e: unknown) => {
        if (!cancelled) {
          setError(toError(e))
          setLoading(false)
        }
      })

    return () => {
      cancelled = true
    }
  }, [reloadToken])

  const loadMore = useCallback(async () => {
    const cursor = nextCursorRef.current
    if (!cursor) return

    try {
      const page = await clientRef.current.list({ cursor })
      nextCursorRef.current = page.nextCursor
      setHasMore(Boolean(page.nextCursor))
      setItems((prev) => [...prev, ...page.items])
    } catch (e: unknown) {
      setError(toError(e))
    }
  }, [])

  const markRead = useCallback(
    async (ids: string[] | 'all') => {
      const snapshot = { items, unread }

      if (ids === 'all') {
        const now = new Date().toISOString()
        setItems((prev) => prev.map((item) => ({ ...item, readAt: item.readAt ?? now })))
        setUnread(0)
      } else {
        const now = new Date().toISOString()
        let newlyRead = 0
        setItems((prev) =>
          prev.map((item) => {
            if (ids.includes(item.id) && !item.readAt) {
              newlyRead++
              return { ...item, readAt: now }
            }
            return item
          }),
        )
        setUnread((u) => Math.max(0, u - newlyRead))
      }

      try {
        await clientRef.current.markRead(ids)
      } catch (e: unknown) {
        // Mutation error-split (same as content-react): BOTH capture AND reject.
        setItems(snapshot.items)
        setUnread(snapshot.unread)
        const err = toError(e)
        setError(err)
        throw err
      }
    },
    [items, unread],
  )

  useEffect(() => {
    const pollMs = opts?.pollMs
    if (!pollMs) return

    let cancelled = false
    const id = setInterval(() => {
      void clientRef.current
        .unreadCount()
        .then((count) => {
          if (!cancelled) setUnread(count)
        })
        .catch((e: unknown) => {
          if (!cancelled) setError(toError(e))
        })
    }, pollMs)

    return () => {
      cancelled = true
      clearInterval(id)
    }
  }, [opts?.pollMs])

  return { items, unread, loading, error, hasMore, loadMore, markRead, reload }
}
