// Port of rich/theme.py, rich/themes.py and rich/default_styles.py.

///|
/// The default styles, by name.
pub fn default_styles() -> Map[String, Style] {
  let m : Map[String, Style] = Map([])
  for item in default_style_definitions {
    m[item.0] = Style::parse(item.1) catch { _ => abort("bad default style") }
  }
  m
}

///|
let default_styles_map : Map[String, Style] = default_styles()

///|
/// A container for style information, used by `Console`.
pub struct Theme {
  styles : Map[String, Style]
}

///|
/// Create a theme. With `inherit`, the default styles are included.
/// Style definitions are parsed (raising on syntax errors).
pub fn Theme::new(
  styles? : Map[String, &IntoStyle],
  inherit_styles? : Bool = true,
) -> Theme raise RichError {
  let m : Map[String, Style] = if inherit_styles {
    default_styles_map.copy()
  } else {
    Map([])
  }
  if styles is Some(styles) {
    for name, style in styles {
      m[name] = match style.into_style_type() {
        Style(s) => s
        Def(d) => Style::parse(d)
      }
    }
  }
  { styles: m, }
}

///|
/// Contents of a config file for this theme.
pub fn Theme::config(self : Theme) -> String {
  let items = self.styles.to_array()
  items.sort_by((a, b) => a.0.compare(b.0))
  "[styles]\n" + items.map(kv => "\{kv.0} = \{kv.1}").join("\n")
}

///|
/// Load a theme from the contents of an INI-style config file with a
/// `[styles]` section (upstream `Theme.from_file`, which reads the file
/// with Python's `configparser`: keys are lower-cased, `[DEFAULT]` values
/// are inherited, values may continue on indented lines and `%%` /
/// `%(name)s` are interpolated). Configparser errors are `ValueError`s
/// with the upstream message.
pub fn Theme::from_config(
  config : String,
  inherit_styles? : Bool = true,
) -> Theme raise RichError {
  let parsed : Map[String, &IntoStyle] = Map([])
  for item in config_items(config, "styles") {
    parsed[item.0] = Style::parse(item.1)
  }
  Theme::new(styles=parsed, inherit_styles~)
}

///|
/// The `(name, value)` items of a section of an INI file, as Python's
/// `ConfigParser().read_string(config); config.items(section)`.
fn config_items(
  config : String,
  section_name : String,
) -> Array[(String, String)] raise RichError {
  // section name -> ordered option names and their value lines
  let sections : Map[String, Map[String, Array[String]]] = Map([])
  let defaults : Map[String, Array[String]] = Map([])
  let errors = []
  let mut cursect : Map[String, Array[String]]? = None
  let mut sectname = ""
  let mut optname : String? = None
  let mut indent_level = 0
  let lines = config.split("\n").map(l => l.to_owned()).to_array()
  // `split` gives a trailing empty line for a final "\n"
  if lines.length() > 0 && lines[lines.length() - 1] == "" {
    lines.pop() |> ignore
  }
  for index, line in lines {
    let lineno = index + 1
    let value = py_strip(line)
    if value == "" || value.has_prefix("#") || value.has_prefix(";") {
      if value == "" && cursect is Some(sect) && optname is Some(name) {
        // empty lines are kept in multi-line values
        if sect.get(name) is Some(v) {
          v.push("")
        }
      } else {
        indent_level = 0x7fffffff
      }
      continue
    }
    let stripped_left = line.trim_start(chars=" \t").to_owned()
    let cur_indent_level = line.length() - stripped_left.length()
    if cursect is Some(sect) &&
      optname is Some(name) &&
      cur_indent_level > indent_level {
      if sect.get(name) is Some(v) {
        v.push(value)
      }
      continue
    }
    indent_level = cur_indent_level
    // SECTCRE = r"\[(?P
.+)\]" (matched at the start) if value.has_prefix("[") && value.rev_find("]") is Some(close) && close > 1 { let name = py_slice(value, 1, close) if name == "DEFAULT" { cursect = Some(defaults) } else { if sections.contains(name) { raise ValueError( "While reading from '' [line \{pad2(lineno)}]: section \{py_repr(name)} already exists", ) } let sect = Map([]) sections[name] = sect cursect = Some(sect) } sectname = name optname = None continue } guard cursect is Some(sect) else { raise ValueError( "File contains no section headers.\nfile: '', line: \{lineno}\n\{py_repr(line + "\n")}", ) } // OPTCRE = r"(?P