/**
 * T4 · INV10 zero-payload guard. Audit events are metadata ONLY; no field may carry terminal
 * keystrokes/output. This rejects any string value that contains control/ESC bytes (a redraw
 * sequence starts with `\x1b`) or exceeds a metadata length cap — a structural tripwire so a
 * payload blob can never be smuggled through a `reason`/`hostId` field.
 */
import type { AuditEvent } from '../types.js'

export const MAX_METADATA_LEN = 256 as const

export class ZeroPayloadViolation extends Error {
  constructor(message: string) {
    super(message)
    this.name = 'ZeroPayloadViolation'
  }
}

/** Any C0 control char (incl. ESC \x1b), DEL, or C1 range → looks like terminal bytes. */
const CONTROL_CHARS = /[\u0000-\u001F\u007F-\u009F]/

function checkField(name: string, value: string): void {
  if (value.length > MAX_METADATA_LEN) {
    throw new ZeroPayloadViolation(`audit field '${name}' exceeds ${MAX_METADATA_LEN} chars`)
  }
  if (CONTROL_CHARS.test(value)) {
    throw new ZeroPayloadViolation(`audit field '${name}' contains control/ESC bytes`)
  }
}

/** Throws if any value looks like terminal bytes (INV10). */
export function assertZeroPayload(e: AuditEvent): void {
  checkField('ts', e.ts)
  checkField('action', e.action)
  checkField('principalId', e.principalId)
  checkField('accountId', e.accountId)
  checkField('outcome', e.outcome)
  checkField('reason', e.reason)
  checkField('remoteAddrHash', e.remoteAddrHash)
  if (e.hostId !== null) checkField('hostId', e.hostId)
  if (e.sessionId !== null) checkField('sessionId', e.sessionId)
  if (e.jti !== null) checkField('jti', e.jti)
}
