/**
 * T3 · Stream state machine (§4.1) — a PURE reducer over the per-stream lifecycle.
 *
 * Lifecycle: `OPEN → (DATA | WINDOW_UPDATE)* → CLOSE`. An illegal transition returns
 * `{ illegal: true }` so the caller RSTs that ONE stream (never the tunnel) — this is what
 * lets P4's end-to-end `seq` monotonicity mean something (INV13). No I/O, no input mutation.
 */
import type { MuxFrameType } from 'relay-contracts'

export type StreamState =
  | 'idle'
  | 'open'
  | 'halfClosedLocal'
  | 'halfClosedRemote'
  | 'closed'

export type StreamDirection = 'inbound' | 'outbound'

export type StreamTransition = { readonly next: StreamState } | { readonly illegal: true }

const ILLEGAL: StreamTransition = { illegal: true }

export function initialStreamState(): StreamState {
  return 'idle'
}

export function isTerminal(s: StreamState): boolean {
  return s === 'closed'
}

/** True when the state still accepts DATA/WINDOW_UPDATE flow in at least one direction. */
function isFlowing(s: StreamState): boolean {
  return s === 'open' || s === 'halfClosedLocal' || s === 'halfClosedRemote'
}

/** The half-closed state produced when `dir` sends FIN while fully open. */
function halfCloseFor(dir: StreamDirection): StreamState {
  return dir === 'outbound' ? 'halfClosedLocal' : 'halfClosedRemote'
}

/**
 * Total transition function. Returns a NEW state (never mutates). `fin` marks a DATA/CLOSE
 * frame that half/fully closes the sending direction; `dir` is which side sent the frame.
 */
export function nextStreamState(
  current: StreamState,
  type: MuxFrameType,
  fin: boolean,
  dir: StreamDirection,
): StreamTransition {
  if (current === 'closed') return ILLEGAL

  switch (type) {
    case 'open':
      // OPEN is only legal from idle; re-OPEN of a live stream is illegal.
      return current === 'idle' ? { next: 'open' } : ILLEGAL

    case 'data':
    case 'windowUpdate': {
      if (current === 'idle') return ILLEGAL // DATA/WU before OPEN
      if (!isFlowing(current)) return ILLEGAL
      // WINDOW_UPDATE never carries FIN; only DATA may half-close its direction.
      if (type === 'windowUpdate') return { next: current }
      if (!fin) return { next: current }
      return finTransition(current, dir)
    }

    case 'close': {
      if (current === 'idle') return ILLEGAL // CLOSE before OPEN
      return { next: 'closed' }
    }

    // ping/pong/goaway are connection-level (streamId 0), never per-stream.
    default:
      return ILLEGAL
  }
}

/** Apply a FIN from `dir` to a flowing state; both-side FIN ⇒ closed. */
function finTransition(current: StreamState, dir: StreamDirection): StreamTransition {
  const half = halfCloseFor(dir)
  if (current === 'open') return { next: half }
  // Already half-closed on the OTHER side, and now this side FINs ⇒ fully closed.
  if (current === 'halfClosedLocal' && dir === 'inbound') return { next: 'closed' }
  if (current === 'halfClosedRemote' && dir === 'outbound') return { next: 'closed' }
  // Re-FIN on the already-closed direction is illegal.
  return ILLEGAL
}
