/**
 * agent-status-publish (#10) — builds + publishes the `agent_status_updated`
 * WS frame on the per-session channel (the same channel CoderPane subscribes to).
 *
 * Kept separate from normalize-agent-status.ts so that module stays a pure,
 * broker-free helper (trivially unit-testable; reused by the config-injection
 * follow-on). The frame contract is pinned in apps/server/src/types/ws-frames.ts
 * (`AgentStatusUpdatedFrame`) and mirrored byte-identical in apps/web.
 */
import type { Broker } from '@boocode/server/broker';
import type { WsFrame } from '@boocode/contracts/ws-frames';
import type { AgentStatus } from './normalize-agent-status.js';

// The exact slice of Broker we need — accepting just the bound method keeps call
// sites flexible (pass `broker.publishFrame.bind(broker)` or, since the broker's
// publishFrame doesn't read `this`, `broker.publishFrame` directly).
type PublishFrame = Broker['publishFrame'];

/**
 * Best-effort publish of a normalized agent status. The broker's publishFrame
 * already fail-closes (validates + logs + drops on bad input, never throws), but
 * we additionally swallow any unexpected error so a publish can NEVER break the
 * turn it's reporting on.
 *
 * @param publishFrame  the session channel publisher (broker.publishFrame)
 * @param sessionId     WS subscription channel (CoderPane subscribes per-session)
 * @param chatId        the (chat) half of the (chat, agent) status key
 * @param agent         the (agent) half of the key
 * @param status        normalized lifecycle status
 * @param reason        free-form discriminator (turn_start / turn_complete / …)
 * @param at            ISO timestamp; defaults to now
 */
export function publishAgentStatus(
  publishFrame: PublishFrame,
  sessionId: string,
  chatId: string,
  agent: string,
  status: AgentStatus,
  reason?: string,
  at: string = new Date().toISOString(),
): void {
  try {
    const frame: WsFrame = {
      type: 'agent_status_updated',
      chat_id: chatId,
      agent,
      status,
      ...(reason ? { reason } : {}),
      at,
    };
    publishFrame(sessionId, frame);
  } catch {
    // never let a status publish break the turn — best-effort only.
  }
}
