text editor inspired vim and yi
import ViE.Unicode

namespace ViE.Terminal

/-- Enable raw mode using stty.
    This relies on the availability of the `stty` command in the environment. -/
def enableRawMode : IO Unit := do
  -- We ignore the output.
  -- Explicitly target /dev/tty because IO.Process might redirect stdin
  -- min 0 time 0 for non-blocking read
  let _ ← IO.Process.run { cmd := "stty", args := #["-F", "/dev/tty", "raw", "-echo", "min", "0", "time", "0"] }
  return ()

/-- Disable raw mode using stty. -/
def disableRawMode : IO Unit := do
  let _ ← IO.Process.run { cmd := "stty", args := #["-F", "/dev/tty", "sane"] }
  return ()

/-- Clear the screen and position cursor at top-left. -/
def clearScreen : IO Unit := do
  IO.print "\x1b[2J"
  IO.print "\x1b[H"

/-- Get window size (rows, cols) using stty size. -/
def getWindowSize : IO (Nat × Nat) := do
  let out ← IO.Process.run { cmd := "stty", args := #["-F", "/dev/tty", "size"] }
  let parts := out.trimAscii.toString.splitOn " "
  match parts with
  | [rows, cols] =>
    let r := rows.toNat!
    let c := cols.toNat!
    return (r, c)
  | _ => return (24, 80) -- Fallback

/-- Move cursor to specific row and column (0-indexed). -/
def moveCursor (row col : Nat) : IO Unit := do
  -- ANSI escape codes are 1-indexed.
  IO.print s!"\x1b[{row + 1};{col + 1}H"

def moveCursorStr (row col : Nat) : String :=
  s!"\x1b[{row + 1};{col + 1}H"

/-- Hide cursor. -/
def hideCursor : IO Unit := do
  IO.print "\x1b[?25l"

def hideCursorStr : String := "\x1b[?25l"

/-- Show cursor. -/
def showCursor : IO Unit := do
  IO.print "\x1b[?25h"

def showCursorStr : String := "\x1b[?25h"

/-- Clear from cursor to end of line. -/
def clearLineStr : String := "\x1b[K"

/-- Home cursor. -/
def homeCursorStr : String := "\x1b[H"

/-- Clear entire screen. -/
def clearScreenStr : String := "\x1b[2J"

/-- Read a single byte from stdin.
    Returns none on EOF or if no input available. -/
def readKey : IO (Option Char) := do
  let stdin ← IO.getStdin
  let b1_arr ← stdin.read 1
  if b1_arr.size == 0 then
    return none

  let b1 := b1_arr[0]!
  let len := ViE.Unicode.utf8ByteLength b1

  if len == 1 then
    return some (Char.ofNat b1.toNat)
  else if len == 0 then
    -- Invalid start byte, ignore
    return none
  else
    -- Read remaining bytes (blocking for completion of valid UTF-8 char)
    -- If we got the first byte of a multi-byte sequence, the rest should be there immediately.
    let rest ← stdin.read (len - 1).toUSize
    if rest.size != len - 1 then return none

    let b1n := b1.toNat

    if len == 2 then
        let b2 := rest[0]!.toNat
        let u := ((b1n &&& 0x1F) <<< 6) ||| (b2 &&& 0x3F)
        return some (Char.ofNat u)
    else if len == 3 then
        let b2 := rest[0]!.toNat
        let b3 := rest[1]!.toNat
        let u := ((b1n &&& 0x0F) <<< 12) ||| ((b2 &&& 0x3F) <<< 6) ||| (b3 &&& 0x3F)
        return some (Char.ofNat u)
    else if len == 4 then
        let b2 := rest[0]!.toNat
        let b3 := rest[1]!.toNat
        let b4 := rest[2]!.toNat
        let u := ((b1n &&& 0x07) <<< 18) ||| ((b2 &&& 0x3F) <<< 12) ||| ((b3 &&& 0x3F) <<< 6) ||| (b4 &&& 0x3F)
        return some (Char.ofNat u)
    else
        return none

end ViE.Terminal