/**
 * §4.2 FIX 4 — control→data-plane revocation teardown shapes (INV12).
 *
 * Frozen HERE (not in relay-auth) so P1 relay nodes can SUBSCRIBE without importing P5,
 * keeping the dependency DAG acyclic. P3/P5 PUBLISH a KillSignal on the one named bus;
 * every P1 relay node subscribes and injects a §4.1 CLOSE+RST (host/account scope) or a
 * connection-level GOAWAY (global scope) — no new frame type.
 */
import { z } from 'zod'

/** The single named control→data-plane Redis pub/sub channel (§4.2 FIX 4). */
export const RELAY_REVOCATIONS_CHANNEL = 'relay:revocations' as const

/** Teardown budget: a live tunnel must drop within this many ms of revocation (INV12). */
export const REVOCATION_PUSH_BUDGET_MS = 2000 as const

/** What a KillSignal targets (§4.2). */
export type RevocationScope =
  | { readonly kind: 'host'; readonly hostId: string }
  | { readonly kind: 'account'; readonly accountId: string }
  | { readonly kind: 'global' }

export const RevocationScopeSchema = z.discriminatedUnion('kind', [
  z.object({ kind: z.literal('host'), hostId: z.string().uuid() }).strict(),
  z.object({ kind: z.literal('account'), accountId: z.string().uuid() }).strict(),
  z.object({ kind: z.literal('global') }).strict(),
])

/** Signal published on `relay:revocations` (§4.2). `reason` is metadata only, ZERO payload (INV10). */
export interface KillSignal {
  readonly scope: RevocationScope
  readonly at: number // epoch seconds the revocation was issued
  readonly reason: string
}

export const KillSignalSchema = z
  .object({
    scope: RevocationScopeSchema,
    at: z.number().int().nonnegative(),
    reason: z.string(),
  })
  .strict()
  .readonly()

/** Transport contract: P3/P1 implement the `relay:revocations` publish side. */
export interface RevocationBus {
  publish(signal: KillSignal): Promise<void>
}
