'use client'

import { useCallback, useEffect, useRef, useState } from 'react'
import Link from 'next/link'

import { useI18n } from '@/components/providers/i18n-provider'
import { useLiveGrid } from '@/components/providers/live-grid-provider'
import { api } from '@/lib/api/client'
import { cn } from '@/lib/cn'
import { styleForAlertLevel } from '@/lib/ui/state-styles'

interface NotificationItem {
  id: string
  level: string
  title: string
  titleAr: string
  body: string
  bodyAr: string
  link: string | null
  createdAt: number
  readAt: number | null
  alertCode: string | null
}

/**
 * The notification queue in the top bar.
 *
 * It refreshes off the live grid tick rather than polling on its own timer: the console
 * already has one stream open, and a second interval would mean two clocks disagreeing
 * about what "now" is. Refreshes are throttled so a three-second tick does not turn into
 * a three-second query.
 */
export function NotificationBell() {
  const { t, locale, relative } = useI18n()
  const { snapshot } = useLiveGrid()

  const [open, setOpen] = useState(false)
  const [items, setItems] = useState<NotificationItem[]>([])
  const [unread, setUnread] = useState(0)
  const [loaded, setLoaded] = useState(false)
  const lastFetch = useRef(0)
  const containerRef = useRef<HTMLDivElement>(null)

  const load = useCallback(async () => {
    const now = Date.now()
    if (now - lastFetch.current < 20_000) return
    lastFetch.current = now
    try {
      const data = await api.get<{ unread: number; items: NotificationItem[] }>(
        '/api/notifications?limit=12',
      )
      setItems(data.items)
      setUnread(data.unread)
      setLoaded(true)
    } catch {
      // A failing queue must never break the top bar; the next tick retries. `loaded` is
      // set either way so an opened popover shows the empty state instead of spinning on
      // "loading" forever when the request never succeeds.
      setLoaded(true)
    }
  }, [])

  useEffect(() => {
    void load()
  }, [load, snapshot?.ts])

  // Close on outside click and on Escape, the two ways people expect a popover to go away.
  useEffect(() => {
    if (!open) return
    const onPointerDown = (event: MouseEvent) => {
      if (!containerRef.current?.contains(event.target as Node)) setOpen(false)
    }
    const onKeyDown = (event: KeyboardEvent) => {
      if (event.key === 'Escape') setOpen(false)
    }
    document.addEventListener('mousedown', onPointerDown)
    document.addEventListener('keydown', onKeyDown)
    return () => {
      document.removeEventListener('mousedown', onPointerDown)
      document.removeEventListener('keydown', onKeyDown)
    }
  }, [open])

  const markAllRead = async () => {
    const now = Date.now()
    setItems((current) => current.map((item) => ({ ...item, readAt: item.readAt ?? now })))
    setUnread(0)
    try {
      await api.patch('/api/notifications', {})
    } catch {
      lastFetch.current = 0
      void load()
    }
  }

  return (
    <div ref={containerRef} className="relative">
      <button
        type="button"
        onClick={() => setOpen((current) => !current)}
        aria-label={
          unread > 0 ? `${t('notifications.open')} — ${t('notifications.unread', { count: unread })}` : t('notifications.open')
        }
        aria-expanded={open}
        aria-haspopup="dialog"
        className={cn(
          'relative rounded-lg p-2 transition-colors',
          open ? 'bg-surface-2 text-text' : 'text-text-muted hover:bg-surface-2 hover:text-text',
        )}
      >
        <svg
          viewBox="0 0 24 24"
          className="size-4"
          fill="none"
          stroke="currentColor"
          strokeWidth="1.6"
          strokeLinecap="round"
          aria-hidden
        >
          <path d="M6 9a6 6 0 1 1 12 0c0 4 1.5 5.5 1.5 5.5h-15S6 13 6 9Z" />
          <path d="M10.5 18a1.8 1.8 0 0 0 3 0" />
        </svg>
        {unread > 0 ? (
          <span className="tnum absolute -top-0.5 flex min-w-4 items-center justify-center rounded-full bg-critical px-1 text-[9px] font-semibold text-white" style={{ insetInlineEnd: '-0.125rem' }}>
            {unread > 9 ? '9+' : unread}
          </span>
        ) : null}
      </button>

      {open ? (
        <div
          role="dialog"
          aria-label={t('notifications.title')}
          className="nabdh-enter absolute top-full z-50 mt-2 w-80 overflow-hidden rounded-[--radius-panel] border border-border-strong bg-surface shadow-2xl"
          style={{ insetInlineEnd: 0 }}
        >
          <div className="flex items-center justify-between gap-2 border-b border-border px-3.5 py-2.5">
            <h2 className="text-xs font-semibold text-text">{t('notifications.title')}</h2>
            {unread > 0 ? (
              <button
                type="button"
                onClick={markAllRead}
                className="text-[11px] font-medium text-brand transition-colors hover:underline"
              >
                {t('notifications.markAllRead')}
              </button>
            ) : null}
          </div>

          <div className="max-h-80 overflow-y-auto">
            {!loaded ? (
              <p className="px-3.5 py-6 text-center text-xs text-text-faint">{t('common.loading')}</p>
            ) : items.length === 0 ? (
              <div className="px-3.5 py-8 text-center">
                <p className="text-xs font-medium text-text">{t('notifications.empty')}</p>
                <p className="mt-1 text-[11px] text-text-faint">{t('notifications.emptyBody')}</p>
              </div>
            ) : (
              <ul className="divide-y divide-border/60">
                {items.map((item) => {
                  const style = styleForAlertLevel(item.level as never)
                  const content = (
                    <>
                      <div className="flex items-start gap-2.5">
                        <span className={cn('mt-1.5 size-1.5 shrink-0 rounded-full', style.dot)} />
                        <div className="min-w-0 flex-1">
                          <p
                            className={cn(
                              'truncate text-xs',
                              item.readAt ? 'text-text-muted' : 'font-medium text-text',
                            )}
                          >
                            {locale === 'ar' ? item.titleAr : item.title}
                          </p>
                          <p className="mt-0.5 line-clamp-2 text-[11px] leading-relaxed text-text-faint">
                            {locale === 'ar' ? item.bodyAr : item.body}
                          </p>
                          <p className="mt-1 text-[10px] text-text-faint">{relative(item.createdAt)}</p>
                        </div>
                        {!item.readAt ? (
                          <span className="mt-1.5 size-1.5 shrink-0 rounded-full bg-brand" aria-hidden />
                        ) : null}
                      </div>
                    </>
                  )

                  return (
                    <li key={item.id}>
                      {item.link ? (
                        <Link
                          href={item.link}
                          onClick={() => setOpen(false)}
                          className="block px-3.5 py-2.5 transition-colors hover:bg-surface-2/60"
                        >
                          {content}
                        </Link>
                      ) : (
                        <div className="px-3.5 py-2.5">{content}</div>
                      )}
                    </li>
                  )
                })}
              </ul>
            )}
          </div>

          <div className="border-t border-border px-3.5 py-2">
            <Link
              href="/alerts"
              onClick={() => setOpen(false)}
              className="block text-center text-[11px] font-medium text-brand transition-colors hover:underline"
            >
              {t('notifications.viewAll')}
            </Link>
          </div>
        </div>
      ) : null}
    </div>
  )
}
