Ssh pty

use "../ssh_transport"

primitive SshTerminalModes
  """RFC 4254 §8 terminal mode opcodes and parsing."""
  fun tty_op_end(): U8 => 0
  fun icrnl(): U8 => 13

  fun parse_modes(mode_data: Array[U8] val): Array[(U8, U32)] val ? =>
    """Parse encoded terminal modes from raw bytes."""
    recover val
      let r = SshWireReader(mode_data)
      let result = Array[(U8, U32)]
      while r.remaining() > 0 do
        let opcode = r.read_byte()?
        if opcode == tty_op_end() then break end
        if (opcode >= 1) and (opcode <= 159) then
          let value = r.read_u32()?
          result.push((opcode, value))
        else
          break
        end
      end
      result
    end

class val SshPtyState
  """Immutable PTY state. Replaced (not mutated) on window-change."""
  let term: String val
  let width_chars: U32
  let height_rows: U32
  let width_pixels: U32
  let height_pixels: U32
  let modes: Array[(U8, U32)] val

  new val create(term': String val, width_chars': U32, height_rows': U32,
    width_pixels': U32, height_pixels': U32, modes': Array[(U8, U32)] val)
  =>
    term = term'
    width_chars = width_chars'
    height_rows = height_rows'
    width_pixels = width_pixels'
    height_pixels = height_pixels'
    modes = modes'

  new val none() =>
    term = ""
    width_chars = 0
    height_rows = 0
    width_pixels = 0
    height_pixels = 0
    modes = []


  new val with_dimensions(original: SshPtyState val, width_chars': U32,
    height_rows': U32, width_pixels': U32, height_pixels': U32)
  =>
    """Create a new SshPtyState with updated dimensions, keeping term and modes."""
    term = original.term
    width_chars = width_chars'
    height_rows = height_rows'
    width_pixels = width_pixels'
    height_pixels = height_pixels'
    modes = original.modes

  fun val mode_value(opcode: U8): U32 =>
    """Look up a mode value by opcode. Returns 0 if not found."""
    for (op, value) in modes.values() do
      if op == opcode then return value end
    end
    0

  fun val transform(data: Array[U8] val): Array[U8] val =>
    """Apply active terminal mode transformations to incoming data."""
    var result = data
    if mode_value(SshTerminalModes.icrnl()) != 0 then
      result = _apply_icrnl(result)
    end
    result

  fun val _apply_icrnl(data: Array[U8] val): Array[U8] val =>
    """Replace lone \r with \n. \r\n sequences pass through unchanged."""
    // Fast path: if no \r present, return unchanged
    var has_cr: Bool = false
    for byte in data.values() do
      if byte == '\r' then has_cr = true; break end
    end
    if not has_cr then return data end

    recover val
      let out = Array[U8](data.size())
      var i: USize = 0
      while i < data.size() do
        try
          let byte = data(i)?
          if byte == '\r' then
            // Check if next byte is \n
            if ((i + 1) < data.size()) and (data(i + 1)? == '\n') then
              // \r\n — pass through both
              out.push('\r')
              out.push('\n')
              i = i + 2
            else
              // Lone \r — replace with \n
              out.push('\n')
              i = i + 1
            end
          else
            out.push(byte)
            i = i + 1
          end
        else
          break
        end
      end
      out
    end