/**
 * §4.1 Mux frame format — shared types & frozen wire codes.
 *
 * Frame header is 15 bytes fixed (big-endian / network order), then `payloadLen` bytes.
 * All numeric wire codes below are FROZEN by PLAN_RELAY_INDEX §4.1 and MUST NOT be
 * redefined by any plan.
 */
import { z } from 'zod'

/** Fixed header size in bytes (§4.1). */
export const MUX_HEADER_BYTES = 15 as const

/** Protocol version byte (§4.1: version = 0x01). */
export const MUX_VERSION = 1 as const

/** Frame type as a string-literal union (NOT an enum), per §4.1. */
export type MuxFrameType =
  | 'open'
  | 'data'
  | 'close'
  | 'ping'
  | 'pong'
  | 'windowUpdate'
  | 'goaway'

/** Frozen label ⇄ wire-code maps (§4.1: 0x01 OPEN … 0x07 GOAWAY). */
export const MUX_TYPE_TO_CODE: Readonly<Record<MuxFrameType, number>> = {
  open: 0x01,
  data: 0x02,
  close: 0x03,
  ping: 0x04,
  pong: 0x05,
  windowUpdate: 0x06,
  goaway: 0x07,
}

export const MUX_CODE_TO_TYPE: Readonly<Record<number, MuxFrameType>> = {
  0x01: 'open',
  0x02: 'data',
  0x03: 'close',
  0x04: 'ping',
  0x05: 'pong',
  0x06: 'windowUpdate',
  0x07: 'goaway',
}

/** flags byte bit positions (§4.1: bit0 FIN, bit1 RST, others reserved=0). */
export const FLAG_FIN = 0b0000_0001 as const
export const FLAG_RST = 0b0000_0010 as const
export const FLAG_RESERVED_MASK = 0b1111_1100 as const

/** streamId 0 is connection-level control (PING/PONG/GOAWAY/WINDOW_UPDATE for the whole link). */
export const CONNECTION_STREAM_ID = 0 as const

export const UINT32_MAX = 0xffff_ffff as const

/** Frame header — 15 bytes fixed (§4.1). */
export interface MuxFrameHeader {
  readonly version: 1
  readonly type: MuxFrameType
  readonly fin: boolean
  readonly rst: boolean
  readonly streamId: number // uint32; 0 = connection-level
  readonly payloadLen: number // uint64 (safe-int guarded ≤ maxFrameBytes)
}

/** OPEN payload (relay→agent), carried as CBOR (§4.1). Carries NO terminal bytes. */
export interface MuxOpen {
  readonly streamId: number
  readonly subdomain: string
  readonly requestPath: string // e.g. '/term?join=<id>' — opaque passthrough
  readonly originHeader: string // real browser Origin, validated end-to-end
  readonly remoteAddrHash: string // salted hash of client IP (audit only, INV10)
  readonly capabilityTokenRef: string // jti of the token that authorized this stream (P5)
}

/**
 * Zod schema for MuxOpen — validated at the decode boundary (§4.1, "Input Validation").
 * streamId is a uint32 > 0 (0 is reserved for connection-level control, never a stream OPEN).
 */
export const MuxOpenSchema = z
  .object({
    streamId: z.number().int().gt(0).max(UINT32_MAX),
    subdomain: z.string(),
    requestPath: z.string(),
    originHeader: z.string(),
    remoteAddrHash: z.string(),
    capabilityTokenRef: z.string(),
  })
  .strict()

/** GOAWAY reason as a string-literal union (NOT an enum); wire code ⇄ label (§4.1). */
export type GoAwayReason = 'operatorDrain' | 'revoked' | 'shutdown'

/** Frozen reason label ⇄ wire-code maps (§4.1: 1 operatorDrain · 2 revoked · 3 shutdown). */
export const GOAWAY_REASON_TO_CODE: Readonly<Record<GoAwayReason, number>> = {
  operatorDrain: 1,
  revoked: 2,
  shutdown: 3,
}

export const GOAWAY_CODE_TO_REASON: Readonly<Record<number, GoAwayReason>> = {
  1: 'operatorDrain',
  2: 'revoked',
  3: 'shutdown',
}
