///|
/// Outline helpers for Markdown documents: foldable outline filtering,
/// labels, indent geometry, active-heading tracking, and reading labels.
/// Extracted from MoMark's app layer.

///|
/// Filter outline items to honor folded headings: a folded heading hides all
/// subsequent items until the next heading at the same or higher level.
pub fn markdown_folded_outline(
  items : Array[@richtext.MarkdownEditorOutlineItem],
  folded_starts : Array[Int],
) -> Array[@richtext.MarkdownEditorOutlineItem] {
  if folded_starts.length() == 0 {
    return items
  }
  let visible : Array[@richtext.MarkdownEditorOutlineItem] = []
  let mut hide_until_level : Int? = None
  for item in items {
    match hide_until_level {
      Some(limit) =>
        if item.level <= limit {
          // Reached a same-or-higher heading: stop hiding.
          hide_until_level = None
          visible.push(item)
          if folded_starts.contains(item.source_start) {
            hide_until_level = Some(item.level)
          }
        } else {
          // Still inside a folded region: skip this nested item.
          ()
        }
      None => {
        visible.push(item)
        if folded_starts.contains(item.source_start) {
          hide_until_level = Some(item.level)
        }
      }
    }
  }
  visible
}

///|
/// The indent width in points for a heading at `level` (12pt per level
/// below the first).
pub fn markdown_outline_indent_width(level : Int) -> Double {
  @richtext.markdown_editor_max_int(0, level - 1).to_double() * 12.0
}

///|
/// The bounded outline label for an item, e.g. "H2 Install". Titles longer
/// than 24 characters are truncated to 21 + "..."; empty titles become
/// "Untitled".
pub fn markdown_outline_label(
  item : @richtext.MarkdownEditorOutlineItem,
) -> String {
  let title = if item.title == "" { "Untitled" } else { item.title }
  "H" + item.level.to_string() + " " + markdown_outline_truncate_title(title)
}

///|
/// Truncate a heading title to the outline label budget.
pub fn markdown_outline_truncate_title(title : String) -> String {
  if @richtext.markdown_editor_text_length(title) <= 24 {
    title
  } else {
    @richtext.markdown_editor_substring(title, 0, 21) + "..."
  }
}

///|
/// The index of the outline item containing `caret` (the last heading whose
/// source start is at or before the caret), or -1 when no heading precedes it.
pub fn markdown_active_outline_index(
  outline : Array[@richtext.MarkdownEditorOutlineItem],
  caret : Int,
) -> Int {
  let mut active = -1
  for index in 0.. Bool {
  if active_index < 0 || active_index >= full_outline.length() {
    false
  } else {
    full_outline[active_index].source_start == item.source_start
  }
}

///|
/// "N words / M lines / T min" reading label from a session.
pub fn markdown_session_reading_label(
  session : @richtext.MarkdownDocumentSession,
) -> String {
  markdown_reading_label_parts(
    session.word_count,
    session.line_count,
    session.reading_minutes,
  )
}

///|
/// "N words / M lines / T min" reading label from a snapshot.
pub fn markdown_snapshot_reading_label(
  snapshot : @richtext.MarkdownEditorSnapshot,
) -> String {
  markdown_reading_label_parts(
    snapshot.word_count,
    snapshot.line_count,
    snapshot.reading_minutes,
  )
}

///|
fn markdown_reading_label_parts(
  word_count : Int,
  line_count : Int,
  reading_minutes : Int,
) -> String {
  let minutes = if reading_minutes == 0 {
    "less than 1 min"
  } else if reading_minutes == 1 {
    "1 min"
  } else {
    reading_minutes.to_string() + " min"
  }
  "\{word_count} words / \{line_count} lines / " + minutes
}

///|
/// The compact reading reference ("<1 min"/"1 min"/"N min") from a session.
pub fn markdown_session_reading_time_short_label(
  session : @richtext.MarkdownDocumentSession,
) -> String {
  markdown_reading_time_short_label(session.reading_minutes)
}

///|
/// The compact reading reference from a snapshot.
pub fn markdown_snapshot_reading_time_short_label(
  snapshot : @richtext.MarkdownEditorSnapshot,
) -> String {
  markdown_reading_time_short_label(snapshot.reading_minutes)
}

///|
fn markdown_reading_time_short_label(reading_minutes : Int) -> String {
  if reading_minutes == 0 {
    "<1 min"
  } else if reading_minutes == 1 {
    "1 min"
  } else {
    reading_minutes.to_string() + " min"
  }
}