import type { DiscoveredLocalServer, EnvironmentApi, EnvironmentId, ThreadId, } from "@t3tools/contracts"; import { useMemo } from "react"; import { create } from "zustand"; const EMPTY_PORTS: ReadonlyArray = Object.freeze([]); interface PortDiscoveryState { readonly byEnvironment: Record>; setPorts: (environmentId: EnvironmentId, ports: ReadonlyArray) => void; clearEnvironment: (environmentId: EnvironmentId) => void; reset: () => void; } export const usePortDiscoveryStore = create((set) => ({ byEnvironment: {}, setPorts: (environmentId, ports) => set((state) => ({ byEnvironment: { ...state.byEnvironment, [environmentId]: ports, }, })), clearEnvironment: (environmentId) => set((state) => { if (!(environmentId in state.byEnvironment)) return state; const { [environmentId]: _removed, ...byEnvironment } = state.byEnvironment; return { byEnvironment }; }), reset: () => set({ byEnvironment: {} }), })); export function subscribePortDiscovery(input: { readonly environmentId: EnvironmentId; readonly previewApi: Pick; }): () => void { usePortDiscoveryStore.getState().clearEnvironment(input.environmentId); return input.previewApi.subscribePorts((snapshot) => { usePortDiscoveryStore.getState().setPorts(input.environmentId, snapshot.servers); }); } export function useDiscoveredPorts( environmentId: EnvironmentId | null, ): ReadonlyArray { return usePortDiscoveryStore( (state) => (environmentId ? state.byEnvironment[environmentId] : undefined) ?? EMPTY_PORTS, ); } export function useThreadDiscoveredPorts(input: { readonly environmentId: EnvironmentId | null; readonly threadId: ThreadId | null; }): ReadonlyArray { const ports = useDiscoveredPorts(input.environmentId); return useMemo( () => input.threadId ? ports.filter((port) => port.terminal?.threadId === input.threadId) : EMPTY_PORTS, [input.threadId, ports], ); } export function useTerminalDiscoveredPorts(input: { readonly environmentId: EnvironmentId | null; readonly threadId: ThreadId | null; readonly terminalId: string | null; }): ReadonlyArray { const ports = useDiscoveredPorts(input.environmentId); return useMemo( () => input.threadId && input.terminalId ? ports.filter( (port) => port.terminal?.threadId === input.threadId && port.terminal.terminalId === input.terminalId, ) : EMPTY_PORTS, [input.terminalId, input.threadId, ports], ); }