import { createContext, useState } from 'react' import type { OutboundResolver, ResolvedOutbound } from '../../types' /** Current outbound sources keyed by the outbound record id stored on each message. */ export type OutboundsByRecordId = Readonly> export const OutboundContext = createContext( undefined ) const outboundRecordIdFromMessage = (message: { metadata?: { outbound_id?: unknown } }): string | undefined => { const outboundRecordId = message.metadata?.outbound_id return typeof outboundRecordId === 'string' ? outboundRecordId : undefined } /** * Resolve outbound sources for the messages currently in view. * * Built outside memoized Stream rows so a new resolver reference can publish * current automation details without remounting the Stream message rows. */ export const buildOutboundsByRecordId = ( messages: readonly { metadata?: { outbound_id?: unknown } }[] | undefined, resolveOutbound: OutboundResolver | undefined ): OutboundsByRecordId | undefined => { if (!resolveOutbound || !messages?.length) { return undefined } const seen = new Set() let outbounds: Record | undefined for (const message of messages) { const outboundRecordId = outboundRecordIdFromMessage(message) if (!outboundRecordId || seen.has(outboundRecordId)) { continue } seen.add(outboundRecordId) const resolved = resolveOutbound(outboundRecordId) if (!resolved) { continue } outbounds ??= {} // Snapshot resolver-owned values so a host that mutates and reuses its // cached object cannot also mutate the previously published context map. outbounds[outboundRecordId] = { id: resolved.id, ...(resolved.name !== undefined ? { name: resolved.name } : {}), } } return outbounds } const areOutboundsEqual = ( previous: OutboundsByRecordId | undefined, next: OutboundsByRecordId | undefined ): boolean => { if (previous === next) { return true } if (!previous || !next) { return false } const previousIds = Object.keys(previous) const nextIds = Object.keys(next) if (previousIds.length !== nextIds.length) { return false } return previousIds.every( (outboundRecordId) => previous[outboundRecordId].id === next[outboundRecordId]?.id && previous[outboundRecordId].name === next[outboundRecordId]?.name ) } /** * Re-invokes the resolver on every render, but only returns a new object when * the resolved id/name pairs actually change — so context consumers update for * late-arriving outbounds without thrashing on unrelated renders. */ export const useOutboundsByRecordId = ( messages: readonly { metadata?: { outbound_id?: unknown } }[] | undefined, resolveOutbound: OutboundResolver | undefined ): OutboundsByRecordId | undefined => { const outbounds = buildOutboundsByRecordId(messages, resolveOutbound) const [publishedOutbounds, setPublishedOutbounds] = useState< OutboundsByRecordId | undefined >(outbounds) if (!areOutboundsEqual(publishedOutbounds, outbounds)) { setPublishedOutbounds(outbounds) return outbounds } return publishedOutbounds }