///|
/// Line style for drawn table borders.
pub(all) enum LineStyle {
  Ascii
  Unicode
} derive(Eq)

///|
/// The horizontal line character for a table under the given style.
fn line_style_h_bar(style : LineStyle) -> String {
  match style {
    Ascii => "-"
    Unicode => "─"
  }
}

///|
/// The crossing line character for a table under the given style.
fn line_style_cross(style : LineStyle) -> String {
  match style {
    Ascii => "+"
    Unicode => "┼"
  }
}

///|
/// The vertical line character for a table under the given style.
fn line_style_v_bar(style : LineStyle) -> String {
  match style {
    Ascii => "|"
    Unicode => "│"
  }
}

///|
/// Apply ANSI styling to `text`, or return it unchanged when `ansi` is off.
fn term_style(
  text : String,
  ansi~ : Bool,
  color? : @chalk.Color,
  background? : @chalk.BackgroundColor,
  modifier? : @chalk.Modifier,
  modifier2? : @chalk.Modifier,
) -> String {
  if !ansi {
    return text
  }
  let mut chalk = @chalk.chalk()
  if color is Some(c) {
    chalk = chalk.color(c)
  }
  if background is Some(b) {
    chalk = chalk.background(b)
  }
  if modifier is Some(m) {
    chalk = chalk.modifier(m)
  }
  if modifier2 is Some(m2) {
    chalk = chalk.modifier(m2)
  }
  chalk.render(text)
}

///|
pub fn render_terminal_document(
  blocks : Array[@parsing.Block],
  line_style? : LineStyle = Unicode,
  ansi? : Bool = true,
  disable_math? : Bool = false,
) -> String {
  blocks
  .map(b => term_render_block(b, line_style~, ansi~, disable_math~))
  .filter(s => !s.is_empty())
  .join("\n\n")
}

///|
fn term_render_block(
  block : @parsing.Block,
  line_style~ : LineStyle,
  ansi~ : Bool,
  disable_math~ : Bool,
) -> String {
  match block {
    Heading(level, inlines) => {
      let color = match level {
        1 => @chalk.Color::Red
        2 => @chalk.Color::Yellow
        3 => @chalk.Color::Green
        _ => @chalk.Color::White
      }
      let content = term_render_inlines(inlines, ansi~, disable_math~)
      term_style(
        content,
        ansi~,
        color~,
        modifier=@chalk.Modifier::Bold,
        modifier2=@chalk.Modifier::Underline,
      )
    }
    Paragraph(inlines) => term_render_inlines(inlines, ansi~, disable_math~)
    Blockquote(blocks) => {
      let rendered = blocks
        .map(b => term_render_block(b, line_style~, ansi~, disable_math~))
        .join("\n\n")
      let lines = rendered.split("\n")
      let joined = lines.map(fn(line) { "│ \{line}" }).join("\n")
      term_style(
        joined,
        ansi~,
        color=@chalk.Color::WhiteBright,
        modifier=@chalk.Modifier::Italic,
      )
    }
    UnorderedList(items) =>
      term_render_list(items, line_style~, ansi~, disable_math~)
    OrderedList(items) =>
      term_render_ordered_list(items, line_style~, ansi~, disable_math~)
    FencedCode(_, code) =>
      term_style(
        code.trim_end().to_owned(),
        ansi~,
        background=@chalk.BackgroundColor::BgWhiteBright,
        modifier=@chalk.Modifier::Dim,
      )
    ThematicBreak =>
      term_style("─".repeat(32), ansi~, modifier=@chalk.Modifier::Dim)
    Table(header, rows) =>
      term_render_table(header, rows, line_style~, ansi~, disable_math~)
    DisplayMath(latex) =>
      if disable_math {
        "$$\{latex}$$"
      } else {
        render_math(latex, true, target=Unicode)
      }
    FootnoteDef(label, blocks) => {
      let defined = blocks
        .map(b => term_render_block(b, line_style~, ansi~, disable_math~))
        .join("\n\n")
      let sb = StringBuilder()
      sb <+ "[\{label}]: \{defined}"
      sb.to_string()
    }
  }
}

///|
fn term_render_list(
  items : Array[@parsing.MdListItem],
  line_style~ : LineStyle,
  ansi~ : Bool,
  disable_math~ : Bool,
) -> String {
  items
  .map(i => term_render_list_item(i, "•", line_style~, ansi~, disable_math~))
  .join("\n")
}

///|
fn term_render_ordered_list(
  items : Array[@parsing.MdListItem],
  line_style~ : LineStyle,
  ansi~ : Bool,
  disable_math~ : Bool,
) -> String {
  let rendered = Array::new()
  for i in 0.. String {
  let marker = match item.checked {
    Some(true) =>
      term_style(
        "✔",
        ansi~,
        color=@chalk.Color::Green,
        modifier=@chalk.Modifier::Bold,
      )
    Some(false) => term_style("○", ansi~, color=@chalk.Color::WhiteBright)
    None => marker
  }
  let content = term_render_inlines(item.content, ansi~, disable_math~)
  let children = item.children
    .map(b => term_render_block(b, line_style~, ansi~, disable_math~))
    .join("\n\n")
  let indented_children = children
    .split("\n")
    .map(fn(line) { "  \{line}" })
    .join("\n")

  let result = StringBuilder()
  result <+ "\{marker} \{content}"
  if !children.is_empty() {
    result.write_string("\n")
    result <+ "\{indented_children}"
  }
  result.to_string()
}

///|
fn term_render_table(
  header : @parsing.TableRow,
  rows : Array[@parsing.TableRow],
  line_style~ : LineStyle,
  ansi~ : Bool,
  disable_math~ : Bool,
) -> String {
  let header_cells = header.cells.map(c => {
    term_render_inlines(c, ansi~, disable_math~)
  })
  let body_cells = rows.map(r => {
    r.cells.map(c => term_render_inlines(c, ansi~, disable_math~))
  })
  let col_count = header_cells.length()
  let widths = term_calculate_widths(header_cells, body_cells, col_count)
  let header_row = term_table_row_internal(
    header_cells,
    widths,
    @chalk.Color::Blue,
    @chalk.Modifier::Bold,
    line_style~,
    ansi~,
  )
  let h_bar = line_style_h_bar(line_style)
  let cross = line_style_cross(line_style)
  let separator = term_style(
      widths.map(fn(w) { h_bar.repeat(w) }).join(cross),
      ansi~,
      modifier=@chalk.Modifier::Dim,
    )
    .trim()
    .to_owned()
  let body_rows = body_cells
    .map(r => {
      term_table_row_internal(
        r,
        widths,
        @chalk.Color::White,
        @chalk.Modifier::Reset,
        line_style~,
        ansi~,
      )
    })
    .join("\n")
  let sb = StringBuilder()
  sb <+ "\{header_row}"
  sb.write_string("\n")
  sb <+ "\{separator}"
  sb.write_string("\n")
  sb <+ "\{body_rows}"
  sb.to_string()
}

///|
fn term_calculate_widths(
  header : Array[String],
  body : Array[Array[String]],
  count : Int,
) -> Array[Int] {
  let widths = Array::make(count, 0)
  for i in 0.. widths[i] {
        widths[i] = row[i].length()
      }
    }
  }
  widths
}

///|
fn term_table_row_internal(
  cells : Array[String],
  widths : Array[Int],
  color : @chalk.Color,
  modifier : @chalk.Modifier,
  line_style~ : LineStyle,
  ansi~ : Bool,
) -> String {
  let parts = Array::new()
  for i in 0.. String {
  inlines.map(i => term_render_inline(i, ansi~, disable_math~)).join("")
}

///|
fn term_render_inline(
  inline : @parsing.Inline,
  ansi~ : Bool,
  disable_math~ : Bool,
) -> String {
  match inline {
    Plain(text) => text
    Strong(inlines) =>
      term_style(
        term_render_inlines(inlines, ansi~, disable_math~),
        ansi~,
        modifier=@chalk.Modifier::Bold,
      )
    Emph(inlines) =>
      term_style(
        term_render_inlines(inlines, ansi~, disable_math~),
        ansi~,
        modifier=@chalk.Modifier::Italic,
      )
    Code(text) =>
      term_style(text, ansi~, background=@chalk.BackgroundColor::BgWhiteBright)
    Link(inlines, url) =>
      term_style(
        "\{term_render_inlines(inlines, ansi~, disable_math~)} (\{url})",
        ansi~,
        color=@chalk.Color::Blue,
        modifier=@chalk.Modifier::Underline,
      )
    Image(alt, _url) =>
      term_style(
        "[img: \{alt}]",
        ansi~,
        color=@chalk.Color::Yellow,
        modifier=@chalk.Modifier::Italic,
      )
    SoftBreak => "\n"
    HardBreak => "\n"
    InlineMath(latex) =>
      if disable_math {
        "$\{latex}$"
      } else {
        render_math(latex, false, target=Unicode)
      }
    FootnoteRef(label) =>
      term_style("[\{label}]", ansi~, color=@chalk.Color::Yellow)
  }
}