'use client'; import { useEffect, useReducer, useSyncExternalStore, } from 'react'; import { Bell, BellOff } from 'lucide-react'; import { FACTIONS } from '@/game/catalog'; import { initialActionNotices, updateActionNotices, type AutomaticActionEvent, type QueuedActionNotice } from '@/lib/action-notice-queue'; import { Button } from './ui/button'; export type { AutomaticActionEvent } from '@/lib/action-notice-queue'; const PREFERENCE_KEY = 'dune.automatic-action-notices'; const PREFERENCE_EVENT = 'dune-automatic-action-notices-preference'; let memoryPreference: boolean | undefined; function readPreference() { if (memoryPreference !== undefined) return memoryPreference; try { return localStorage.getItem(PREFERENCE_KEY) !== 'false'; } catch { return true; } } function subscribePreference(listener: () => void) { window.addEventListener(PREFERENCE_EVENT, listener); return () => window.removeEventListener(PREFERENCE_EVENT, listener); } const serverPreference = () => true; /** Cosmetic feedback only. Supply public events with increasing, room-local sequences. */ export function AutomaticActionNotice({ events, }: { events: readonly AutomaticActionEvent[]; }) { const enabled = useSyncExternalStore( subscribePreference, readPreference, serverPreference, ); const [presentation, updatePresentation] = useReducer(updateActionNotices, events, initialActionNotices); useEffect(() => { updatePresentation({ type: 'events', events, enabled, visible: document.visibilityState !== 'hidden', }); }, [events, enabled]); useEffect(() => { const hide = () => { if (document.visibilityState !== 'hidden') return; updatePresentation({ type: 'hide' }); }; document.addEventListener('visibilitychange', hide); return () => document.removeEventListener('visibilitychange', hide); }, []); const toggle = () => { const next = !enabled; memoryPreference = next; updatePresentation({ type: 'clear' }); try { localStorage.setItem(PREFERENCE_KEY, String(next)); memoryPreference = undefined; } catch { // Do not turn a cosmetic preference into an error or blocking dialog. } window.dispatchEvent(new Event(PREFERENCE_EVENT)); }; return ( <> {enabled && presentation.active && ( updatePresentation({ type: 'continue' })} /> )} ); } /** The notice alone accepts input; it does not trap or move keyboard focus. */ export function ActionNoticeCard({ notice, onContinue }: { notice: QueuedActionNotice; onContinue: () => void }) { const house = FACTIONS.find((candidate) => candidate.id === notice.faction); return {house?.name ?? 'Automatic action'} {notice.name} {notice.count > 1 && {notice.count - 1} earlier {notice.count === 2 ? 'action' : 'actions'} also completed. Details are in the table chronicle. } ; }