// ST-1105: Client hook die de notificatie-SSE stream beheert (M11).
//
// Mount via <NotificationsBridge /> in (app)/layout zodat hij Server Action-
// refreshes overleeft. Opent EventSource('/api/realtime/notifications'),
// dispatcht state-/message-events naar de notifications-store.
//
// Vereenvoudigde versie van useSoloRealtime — geen view-transitions, geen
// connecting-indicator-debounce. Wel: reconnect met exponential backoff en
// pause bij hidden tab.

'use client'

import { useEffect, useRef } from 'react'
import { useRouter } from 'next/navigation'
import { useNotificationsStore, type NotificationQuestion } from '@/stores/notifications-store'
import { useIdeaStore } from '@/stores/idea-store'

const BACKOFF_START_MS = 1_000
const BACKOFF_MAX_MS = 30_000

// Question-payloads (M11 + M12). story_id en idea_id zijn mutually exclusive
// (DB-check-constraint). Voor story-questions blijft het pad onveranderd;
// idea-questions worden naar de idea-store doorgezet.
interface QuestionPayload {
  op: 'I' | 'U'
  entity: 'question'
  id: string
  product_id: string
  story_id: string | null
  task_id: string | null
  idea_id?: string | null
  assignee_id: string | null
  status: 'open' | 'answered' | 'cancelled' | 'expired'
}

// Idea-job-payloads (M12). Komen uit actions/ideas.ts pg_notify.
interface IdeaJobPayload {
  type: 'claude_job_enqueued' | 'claude_job_status'
  job_id: string
  idea_id: string
  user_id: string
  product_id?: string | null
  kind: 'IDEA_GRILL' | 'IDEA_MAKE_PLAN'
  status: string
  error?: string
}

type AnyPayload = QuestionPayload | IdeaJobPayload

function isQuestionPayload(p: AnyPayload): p is QuestionPayload {
  return 'entity' in p && p.entity === 'question'
}

function isIdeaJobPayload(p: AnyPayload): p is IdeaJobPayload {
  return (
    'type' in p &&
    (p.type === 'claude_job_enqueued' || p.type === 'claude_job_status') &&
    (p.kind === 'IDEA_GRILL' || p.kind === 'IDEA_MAKE_PLAN')
  )
}

interface StateEvent {
  questions: NotificationQuestion[]
}

export function useNotificationsRealtime() {
  const sourceRef = useRef<EventSource | null>(null)
  const backoffRef = useRef<number>(BACKOFF_START_MS)
  const reconnectTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null)
  const router = useRouter()

  useEffect(() => {
    const init = useNotificationsStore.getState().init
    const remove = useNotificationsStore.getState().remove

    const close = () => {
      if (sourceRef.current) {
        sourceRef.current.close()
        sourceRef.current = null
      }
      if (reconnectTimerRef.current) {
        clearTimeout(reconnectTimerRef.current)
        reconnectTimerRef.current = null
      }
    }

    const connect = () => {
      close()
      const source = new EventSource('/api/realtime/notifications', {
        withCredentials: true,
      })
      sourceRef.current = source

      source.addEventListener('open', () => {
        backoffRef.current = BACKOFF_START_MS
      })

      source.addEventListener('state', (ev) => {
        try {
          const data = JSON.parse((ev as MessageEvent).data) as StateEvent
          init(data.questions ?? [])
        } catch {
          // ignore malformed
        }
      })

      source.addEventListener('message', (ev) => {
        try {
          const payload = JSON.parse(ev.data) as AnyPayload

          // M12 — idea-job events naar idea-store dispatchen.
          if (isIdeaJobPayload(payload)) {
            useIdeaStore.getState().handleIdeaJobEvent({
              type: payload.type,
              job_id: payload.job_id,
              idea_id: payload.idea_id,
              user_id: payload.user_id,
              product_id: payload.product_id ?? null,
              kind: payload.kind,
              // The store-types narrow this; cast is safe because the server
              // emits valid statuses.
              status: payload.status as 'queued',
              error: payload.error,
            })
            // Refresh zodra job klaar is — server heeft nu grill_md/plan_md
            // geschreven en de idea-status bijgewerkt. router.refresh() triggert
            // een server-component re-fetch zodat de Timeline de nieuwe
            // GRILL_RESULT/PLAN_RESULT logs en de bijgewerkte status oppikt.
            if (payload.status === 'done' || payload.status === 'failed') {
              router.refresh()
            }
            return
          }

          if (!isQuestionPayload(payload)) return

          // M12 — idea-question events naar idea-store dispatchen.
          if (payload.idea_id) {
            useIdeaStore.getState().handleIdeaQuestionEvent({
              op: payload.op,
              entity: 'question',
              id: payload.id,
              product_id: payload.product_id,
              story_id: null,
              idea_id: payload.idea_id,
              task_id: payload.task_id,
              assignee_id: payload.assignee_id,
              status: payload.status,
            })
            // M12 hotfix: óók in notifications-bel. Open → reconnect zodat
            // initial-state de full question-detail levert; non-open → remove.
            if (payload.status === 'open') {
              close()
              connect()
            } else {
              remove(payload.id)
            }
            // M12 hotfix: refresh de current page (server-component) zodat de
            // IdeaTimeline-tab op /ideas/[id] de nieuwe vraag oppikt zonder
            // dat de gebruiker handmatig moet refreshen. Geen-op als de
            // gebruiker elders zit; goedkoop genoeg om altijd te triggeren.
            router.refresh()
            return
          }

          // Story-questions: bestaande bell-pad onveranderd.
          if (payload.status === 'open') {
            // Inkomende open vraag: we hebben de details nog niet — beste optie is
            // herfetchen door opnieuw te verbinden, of via een API. Voor v1
            // forceren we een reconnect zodat het volgende state-event de
            // volledige details meelevert.
            close()
            connect()
            return
          }
          // Niet-open status (answered/cancelled/expired) → verwijderen uit lijst
          remove(payload.id)
        } catch {
          // ignore malformed
        }
      })

      source.addEventListener('error', () => {
        // EventSource herconnect zelf bij netwerkfouten; voor server-close
        // (after 240s) doen we een eigen backoff
        if (sourceRef.current?.readyState === EventSource.CLOSED) {
          const delay = backoffRef.current
          backoffRef.current = Math.min(delay * 2, BACKOFF_MAX_MS)
          reconnectTimerRef.current = setTimeout(connect, delay)
        }
      })
    }

    // PBI-74: stream blijft open op hidden. Reconnect alleen als hij door
    // netwerkfout/server-close weg is. Bij visible-overgang en bij online
    // triggeren we router.refresh() zodat de notifications-bel verse state
    // pakt — gemiste vraag-events via NOTIFY-throttling worden hierdoor
    // alsnog zichtbaar.
    const onVisibilityChange = () => {
      if (document.visibilityState !== 'visible') return
      if (!sourceRef.current || sourceRef.current.readyState === EventSource.CLOSED) {
        connect()
      }
      router.refresh()
    }

    const onOnline = () => {
      router.refresh()
    }

    connect()
    document.addEventListener('visibilitychange', onVisibilityChange)
    window.addEventListener('online', onOnline)

    return () => {
      document.removeEventListener('visibilitychange', onVisibilityChange)
      window.removeEventListener('online', onOnline)
      close()
    }
  }, [router])
}
