///|
/// A token stream: `(token type, text)` pairs, as produced by
/// `@lexer.Lexer::get_tokens`.
pub type Tokens = Array[(@token.TokenType, String)]

///|
/// Errors raised by formatters (Python's `ValueError`/`RuntimeError`/...).
pub suberror FormatterError {
  FormatterError(String)
} derive(Debug)

///|
/// Raised when no formatter matches a lookup (Python's `ClassNotFound`).
pub suberror ClassNotFound {
  ClassNotFound(String)
} derive(Debug)

///|
/// Static metadata of a formatter class.
pub(all) struct FormatterInfo {
  /// Python class name, e.g. `HtmlFormatter`.
  class_name : String
  /// Human-readable name (`HTML`).
  name : String
  aliases : Array[String]
  /// `fnmatch` patterns of the file names this formatter can produce.
  filenames : Array[String]
  /// First paragraph of the class docstring.
  description : String
} derive(Debug)

///|
/// The options every formatter accepts (Python's `Formatter.__init__`).
pub struct BaseOptions {
  style : @styles.Style
  full : Bool
  title : String
  /// Python's `encoding` (`None` when unset). Output is always a `String`;
  /// the encoding only shows up in document headers.
  encoding : String?
  options : @lexer.Options
}

///|
/// Parses the common options. `style` overrides the `style` option (like
/// passing a `Style` class in Python).
pub fn BaseOptions::new(
  options : @lexer.Options,
  style? : @styles.Style,
) -> BaseOptions raise {
  let style = match style {
    Some(s) => s
    None => @styles.get_style_by_name(options.get("style").unwrap_or("default"))
  }
  let full = @lexer.get_bool_opt(options, "full", false)
  let title = options.get("title").unwrap_or("")
  let mut encoding = match options.get("encoding") {
    Some("") | None => None
    Some("guess") | Some("chardet") => Some("utf-8")
    Some(e) => Some(e)
  }
  match options.get("outencoding") {
    Some("") | None => ()
    Some(e) => encoding = Some(e)
  }
  { style, full, title, encoding, options, }
}

///|
/// A formatter instance: converts a token stream to text.
pub struct Formatter {
  info : FormatterInfo
  base : BaseOptions
  priv format_fn : (Tokens) -> String raise
  priv style_defs_fn : (String?) -> String raise
  priv aux_files_fn : () -> Array[AuxFile]
}

///|
/// A file that Python's formatter writes as a side effect of `format`
/// (the HTML formatter's `cssfile`). This port does no file IO: the caller
/// writes `contents` to `path`, which Python resolves relative to the
/// directory of the output file (or the current directory when the output
/// has no name) unless it is absolute; with `noclobber` an existing file is
/// kept.
pub(all) struct AuxFile {
  path : String
  contents : String
  noclobber : Bool
} derive(Debug)

///|
/// Wraps the operations of a formatter class.
pub fn Formatter::new(
  info : FormatterInfo,
  base : BaseOptions,
  format : (Tokens) -> String raise,
  style_defs? : (String?) -> String raise = _ => "",
  aux_files? : () -> Array[AuxFile] = () => [],
) -> Formatter {
  {
    info,
    base,
    format_fn: format,
    style_defs_fn: style_defs,
    aux_files_fn: aux_files,
  }
}

///|
/// The files Python would write while formatting (see `AuxFile`).
pub fn Formatter::aux_files(self : Formatter) -> Array[AuxFile] {
  (self.aux_files_fn)()
}

///|
/// Python's `Formatter.format`: the formatted text of `tokens`.
pub fn Formatter::format(self : Formatter, tokens : Tokens) -> String raise {
  (self.format_fn)(tokens)
}

///|
/// Python's `get_style_defs(arg)`: style definitions for the current style
/// (e.g. CSS rules for HTML). Omitting `arg` is Python's `arg=None`; the
/// `pygmentize -S` command line passes `arg` (default `""`).
pub fn Formatter::get_style_defs(
  self : Formatter,
  arg? : String,
) -> String raise {
  (self.style_defs_fn)(arg)
}

///|
/// Python's `pygments.highlight`: lexes `code` with `lexer` (including its
/// filters) and formats the tokens with `formatter`.
pub fn highlight(
  code : String,
  lexer : @lexer.Lexer,
  formatter : Formatter,
) -> String raise {
  formatter.format(lexer.get_tokens(code))
}

///|
/// The formatter's human-readable name.
pub fn Formatter::name(self : Formatter) -> String {
  self.info.name
}

///|
/// The formatter's aliases.
pub fn Formatter::aliases(self : Formatter) -> Array[String] {
  self.info.aliases
}

///|
/// The formatter's file name patterns.
pub fn Formatter::filenames(self : Formatter) -> Array[String] {
  self.info.filenames
}

///|
/// The options the formatter was created with.
pub fn Formatter::options(self : Formatter) -> @lexer.Options {
  self.base.options
}

///|
/// The style the formatter uses.
pub fn Formatter::style(self : Formatter) -> @styles.Style {
  self.base.style
}