text editor inspired vim and yi
namespace ViE.Color

/-- Represents terminal colors. -/
inductive Color
  | black | red | green | yellow | blue | magenta | cyan | white
  | brightBlack | brightRed | brightGreen | brightYellow | brightBlue | brightMagenta | brightCyan | brightWhite
  | rgb (r g b : UInt8)
  | grayscale (level : UInt8) -- 0-23 (maps to 232-255)
  | default
  deriving Repr, BEq, Inhabited

/-- Convert Color to FG ANSI string -/
def toFg (c : Color) : String :=
  match c with
  | .black => "\x1b[30m"
  | .red => "\x1b[31m"
  | .green => "\x1b[32m"
  | .yellow => "\x1b[33m"
  | .blue => "\x1b[34m"
  | .magenta => "\x1b[35m"
  | .cyan => "\x1b[36m"
  | .white => "\x1b[37m"
  | .brightBlack => "\x1b[90m"
  | .brightRed => "\x1b[91m"
  | .brightGreen => "\x1b[92m"
  | .brightYellow => "\x1b[93m"
  | .brightBlue => "\x1b[94m"
  | .brightMagenta => "\x1b[95m"
  | .brightCyan => "\x1b[96m"
  | .brightWhite => "\x1b[97m"
  | .rgb r g b => s!"\x1b[38;2;{r};{g};{b}m"
  | .grayscale level =>
      let idx := 232 + (if level > 23 then 23 else level)
      s!"\x1b[38;5;{idx}m"
  | .default => "\x1b[39m"

/-- Convert Color to BG ANSI string -/
def toBg (c : Color) : String :=
  match c with
  | .black => "\x1b[40m"
  | .red => "\x1b[41m"
  | .green => "\x1b[42m"
  | .yellow => "\x1b[43m"
  | .blue => "\x1b[44m"
  | .magenta => "\x1b[45m"
  | .cyan => "\x1b[46m"
  | .white => "\x1b[47m"
  | .brightBlack => "\x1b[100m"
  | .brightRed => "\x1b[101m"
  | .brightGreen => "\x1b[102m"
  | .brightYellow => "\x1b[103m"
  | .brightBlue => "\x1b[104m"
  | .brightMagenta => "\x1b[105m"
  | .brightCyan => "\x1b[106m"
  | .brightWhite => "\x1b[107m"
  | .rgb r g b => s!"\x1b[48;2;{r};{g};{b}m"
  | .grayscale level =>
      let idx := 232 + (if level > 23 then 23 else level)
      s!"\x1b[48;5;{idx}m"
  | .default => "\x1b[49m"

def reset : String := "\x1b[0m"

/-- Helper to parse hex string like "#RRGGBB" or "RRGGBB". -/
def fromHex (hex : String) : Option Color :=
  let s := if hex.startsWith "#" then hex.drop 1 else hex
  if s.positions.count != 6 then none
  else
    let rStr := s.take 2
    let gStr := (s.drop 2).take 2
    let bStr := (s.drop 4).take 2
    match (String.toNat? ("0x" ++ rStr)), (String.toNat? ("0x" ++ gStr)), (String.toNat? ("0x" ++ bStr)) with
    | some r, some g, some b =>
       if r < 256 && g < 256 && b < 256 then
         some (.rgb r.toUInt8 g.toUInt8 b.toUInt8)
       else none
    | _, _, _ => none

end ViE.Color