// ANSI colouring: the semantic style categories, the two source palettes, and
// the rule deciding whether to emit escape codes at all.

///|
/// Whether to emit ANSI colour.
pub(all) enum Flag {
  Never
  Always
  /// Decide from the environment and whether the destination is a terminal.
  Auto
} derive(Eq, Debug)

///|
/// A semantic category for syntax highlighting.
///
/// Semantic rather than literal -- a `Type` is whatever the active palette says
/// a type looks like, which is why Wax types come out cyan and WAT types red
/// from the very same printer.
pub(all) enum Style {
  /// `fn`, `let`, `if`.
  Keyword
  /// A wasm instruction, `i32.add`.
  Instruction
  /// `offset=4`, `#[export]`.
  Attribute
  /// `i32`, `&func`.
  Type
  Identifier
  /// A numeric or boolean literal.
  Constant
  Str
  /// `+`, `=`, `!`.
  Operator
  /// `(@custom)`.
  Annotation
  Comment
  /// `{`, `(`, `:`, `;`.
  Punctuation
} derive(Eq, Debug)

///|
/// The ANSI escape code to introduce each style, plus the one that ends it.
///
/// An empty string means "this style is not coloured", which is how `no_color`
/// disables everything and how a palette opts one category out.
pub(all) struct Theme {
  keyword : String
  instruction : String
  attribute : String
  type_ : String
  identifier : String
  constant : String
  str : String
  operator : String
  annotation : String
  comment : String
  punctuation : String
  reset : String
}

///|
pub fn Theme::escape_sequence(self : Theme, style : Style) -> String {
  match style {
    Keyword => self.keyword
    Instruction => self.instruction
    Attribute => self.attribute
    Type => self.type_
    Identifier => self.identifier
    Constant => self.constant
    Str => self.str
    Operator => self.operator
    Annotation => self.annotation
    Comment => self.comment
    Punctuation => self.punctuation
  }
}

///|
/// Raw ANSI codes.
pub let reset : String = "\u{1b}[0m"

///|
pub let bold : String = "\u{1b}[1m"

///|
pub let red : String = "\u{1b}[31m"

///|
pub let high_red : String = "\u{1b}[91m"

///|
pub let green : String = "\u{1b}[32m"

///|
pub let yellow : String = "\u{1b}[33m"

///|
pub let high_yellow : String = "\u{1b}[93m"

///|
pub let blue : String = "\u{1b}[34m"

///|
pub let magenta : String = "\u{1b}[35m"

///|
pub let cyan : String = "\u{1b}[36m"

///|
pub let white : String = "\u{1b}[37m"

///|
pub let grey : String = "\u{1b}[90m"

///|
/// The Wax source palette.
///
/// One palette per surface language, each the single source of truth: the
/// language's own printer renders whole modules with it, and a diagnostic
/// colours an embedded AST fragment with it. So a message about Wax code
/// colours its types the same way Wax source does.
pub let wax_theme : Theme = {
  keyword: bold + magenta,
  operator: bold + white,
  annotation: blue,
  attribute: magenta,
  type_: cyan,
  identifier: yellow,
  constant: bold + blue,
  str: green,
  comment: grey,
  punctuation: white,
  instruction: "",
  reset,
}

///|
/// The WebAssembly-text palette. Types are red here against Wax's cyan.
pub let wat_theme : Theme = {
  keyword: bold + magenta,
  instruction: white,
  attribute: magenta,
  type_: red,
  identifier: yellow,
  constant: bold + blue,
  str: green,
  annotation: blue,
  comment: grey,
  punctuation: cyan,
  operator: "",
  reset,
}

///|
/// A palette that colours nothing.
pub let no_color : Theme = {
  keyword: "",
  instruction: "",
  attribute: "",
  type_: "",
  identifier: "",
  constant: "",
  str: "",
  annotation: "",
  comment: "",
  punctuation: "",
  operator: "",
  reset: "",
}

///|
/// Whether to emit colour on a destination.
///
/// `is_tty` is passed in rather than probed here, so the decision stays
/// testable and this package needs no knowledge of file descriptors. Under
/// `Auto` colour is suppressed by NO_COLOR, by a `dumb` or absent TERM, and by
/// the destination not being a terminal -- all three, not any one.
pub fn should_use_color(color : Flag, is_tty? : Bool = false) -> Bool {
  match color {
    Never => false
    Always => true
    Auto =>
      is_tty &&
      @env.get_env_var("NO_COLOR") is None &&
      (match @env.get_env_var("TERM") {
        None | Some("dumb") => false
        _ => true
      })
  }
}