import { describe, test, expect } from 'vitest'
import {
  initialStreamState,
  nextStreamState,
  isTerminal,
  type StreamState,
  type StreamTransition,
} from '../mux/stream.js'

function legal(t: StreamTransition): StreamState {
  if ('illegal' in t) throw new Error('expected legal transition')
  return t.next
}

describe('stream state machine (T3, §4.1 lifecycle)', () => {
  test('legal path: idle --OPEN--> open --DATA*--> open --CLOSE--> closed', () => {
    let s = initialStreamState()
    expect(s).toBe('idle')
    s = legal(nextStreamState(s, 'open', false, 'outbound'))
    expect(s).toBe('open')
    s = legal(nextStreamState(s, 'data', false, 'outbound'))
    s = legal(nextStreamState(s, 'data', false, 'inbound'))
    expect(s).toBe('open')
    s = legal(nextStreamState(s, 'close', true, 'outbound'))
    expect(s).toBe('closed')
    expect(isTerminal(s)).toBe(true)
  })

  test('DATA with fin half-closes the SENDING direction; both-side FIN ⇒ closed', () => {
    const outFin = legal(nextStreamState('open', 'data', true, 'outbound'))
    expect(outFin).toBe('halfClosedLocal')
    const inFin = legal(nextStreamState('open', 'data', true, 'inbound'))
    expect(inFin).toBe('halfClosedRemote')
    // The other side FINs ⇒ fully closed.
    expect(legal(nextStreamState('halfClosedLocal', 'data', true, 'inbound'))).toBe('closed')
    expect(legal(nextStreamState('halfClosedRemote', 'data', true, 'outbound'))).toBe('closed')
  })

  test('illegal: DATA before OPEN', () => {
    expect(nextStreamState('idle', 'data', false, 'inbound')).toEqual({ illegal: true })
  })

  test('illegal: any frame after closed', () => {
    for (const t of ['data', 'close', 'windowUpdate', 'open'] as const) {
      expect(nextStreamState('closed', t, false, 'inbound')).toEqual({ illegal: true })
    }
  })

  test('illegal: OPEN on an already-open stream', () => {
    expect(nextStreamState('open', 'open', false, 'inbound')).toEqual({ illegal: true })
  })

  test('illegal: CLOSE before OPEN', () => {
    expect(nextStreamState('idle', 'close', false, 'inbound')).toEqual({ illegal: true })
  })

  test('WINDOW_UPDATE keeps state and is legal while flowing', () => {
    expect(legal(nextStreamState('open', 'windowUpdate', false, 'inbound'))).toBe('open')
    expect(legal(nextStreamState('halfClosedLocal', 'windowUpdate', false, 'inbound'))).toBe(
      'halfClosedLocal',
    )
  })

  test('illegal: re-FIN on the already-closed direction', () => {
    expect(nextStreamState('halfClosedLocal', 'data', true, 'outbound')).toEqual({ illegal: true })
  })
})
