import type { AnnotationDTO, BookmarkDTO, ReplyDTO } from "@/db/schema"; // pub/sub in-process (PLAN.md §4): Server Actions publish หลังเขียน DB สำเร็จ // → SSE route (app/api/note/annotations/stream) subscribe แล้วยิง event ให้ client ที่เปิดไฟล์เดียวกัน // ⚠ in-memory ของ process เดียว (dev / next start ธรรมดา) — ถ้าวันหนึ่งรันหลาย instance // ต้องเพิ่มตัวกลาง (Redis pub/sub หรือ Postgres LISTEN/NOTIFY) ก่อน ห้ามแอบ sync ผ่าน DB polling export type AnnotateEvent = | { table: "annotations"; action: "create"; row: AnnotationDTO } | { table: "annotations"; action: "update"; row: AnnotationDTO } | { table: "annotations"; action: "delete"; id: string } | { table: "replies"; action: "create"; row: ReplyDTO } | { table: "replies"; action: "delete"; id: string } | { table: "bookmarks"; action: "create"; row: BookmarkDTO } | { table: "bookmarks"; action: "update"; row: BookmarkDTO } | { table: "bookmarks"; action: "delete"; id: string }; type Listener = (event: AnnotateEvent) => void; // ⚠ เก็บบน globalThis เพราะ Next dev bundle "use server" (actions) กับ route handler // แบบคนละส่วน — module-level state จะถูก instantiate เป็น 2 อินสแตนซ์แล้ว event ไม่ไหลถึงกัน // (ยังช่วยให้ state รอดจาก HMR recompile ด้วย) type EventsGlobal = { __annotateEventListeners?: Map> }; const g = globalThis as typeof globalThis & EventsGlobal; const listenersByFile: Map> = g.__annotateEventListeners ?? (g.__annotateEventListeners = new Map()); /** คืนฟังก์ชัน unsubscribe */ export function subscribeAnnotateEvents( fileId: string, listener: Listener, ): () => void { let set = listenersByFile.get(fileId); if (!set) { set = new Set(); listenersByFile.set(fileId, set); } set.add(listener); return () => { set.delete(listener); if (set.size === 0) listenersByFile.delete(fileId); }; } export function publishAnnotateEvent(fileId: string, event: AnnotateEvent): void { const set = listenersByFile.get(fileId); if (!set) return; for (const listener of Array.from(set)) { try { listener(event); } catch (err) { console.error("annotate-events listener error", err); } } }