///|
/// One `log.md` entry, such as `* **Update**: Added revenue metric.`
pub(all) struct LogEntry {
  /// The bold first word, such as `Update`, when it is present.
  label : String?
  text : String
  line : Int32
} derive(Eq, Debug)

///|
/// The heading of a `log.md` section. Only the log parser makes values, so
/// `Undated` never holds a date.
pub enum LogHeading {
  /// A calendar date, `YYYY-MM-DD`.
  Dated(LogDate)
  /// Any other heading text.
  Undated(String)
} derive(Eq, Debug)

///|
/// The heading as written.
pub fn LogHeading::to_string(self : LogHeading) -> String {
  match self {
    Dated(date) => date.to_string()
    Undated(text) => text
  }
}

///|
pub impl Show for LogHeading with fn output(self, logger) {
  logger.write_string(self.to_string())
}

///|
fn LogHeading::parse(text : String) -> LogHeading {
  try LogDate(text) catch {
    _ => Undated(text)
  } noraise {
    date => Dated(date)
  }
}

///|
/// One `log.md` section, from one `##` heading to the next.
pub struct LogSection {
  heading : LogHeading
  line : Int32
  entries : ReadOnlyArray[LogEntry]
} derive(Eq, Debug)

///|
/// The parsed body of a `log.md` (OKF v0.2, section 9).
pub struct LogModel {
  sections : ReadOnlyArray[LogSection]
} derive(Eq, Debug)

///|
fn parse_log_entry(line : String, number : Int32) -> LogEntry? {
  let t = line.trim_start(chars=" \t").to_owned()
  guard t.length() >= 2 && (t[0] == '*' || t[0] == '-') && t[1] == ' ' else {
    return None
  }
  let t = t[2:].trim(chars=" \t").to_owned()
  if t.has_prefix("**") {
    match t[2:].find("**") {
      Some(end) => {
        let label = t[2:2 + end].trim_end(chars=":").to_owned()
        let rest = t[4 + end:]
          .trim_start(chars=":")
          .trim(chars=" \t")
          .to_owned()
        return Some({ label: Some(label), text: rest, line: number, })
      }
      None => ()
    }
  }
  Some({ label: None, text: t, line: number, })
}

///|
/// Parses the body of a log document. Each `##` heading starts a section.
pub fn Document::log_model(self : Document) -> LogModel {
  let heads : Array[(LogHeading, Int32)] = []
  let entries : Array[Array[LogEntry]] = []
  for pair in body_lines(self) {
    let (number, line) = pair
    match parse_heading_line(line) {
      Some((level, text)) if level.to_int() == 2 => {
        heads.push((LogHeading::parse(text), number))
        entries.push([])
      }
      Some(_) => ()
      None =>
        match (entries.last(), parse_log_entry(line, number)) {
          (Some(list), Some(entry)) => list.push(entry)
          _ => ()
        }
    }
  }
  let sections = heads.mapi((i, head) => {
    heading: head.0,
    line: head.1,
    entries: ReadOnlyArray::from_array(entries[i]),
  })
  { sections: ReadOnlyArray::from_array(sections), }
}

///|
/// The first pair of dated sections that are not newest first, if any. The
/// order comes from the dates, not from the heading text.
pub fn LogModel::order_problem(self : LogModel) -> (LogSection, LogSection)? {
  for i in 1.. return Some((a, b))
      _ => ()
    }
  }
  None
}

///|
/// The sections whose heading is not a `YYYY-MM-DD` date.
pub fn LogModel::undated_sections(self : LogModel) -> Array[LogSection] {
  self.sections.iter().filter(s => s.heading is Undated(_)).collect()
}

///|
/// A region of a document that starts at a marker comment, such as
/// ``, and runs to the end of the document.
pub(all) struct Region {
  marker : String
  /// The text before the marker line, including the marker line.
  preamble : String
  /// The text after the marker line.
  content : String
  /// The file line of the marker.
  line : Int32
} derive(Eq, Debug)

///|
/// The comment form of a marker name: ``.
pub fn marker_comment(name : String) -> String {
  ""
}

///|
/// Finds the region that the marker `` starts.
pub fn find_region(text : String, name : String) -> Region? {
  let marker = marker_comment(name)
  for i, line in split_lines(text) {
    if line.text.trim(chars=" \t") == marker {
      let end = line.start + line.text.length() + line.ending_text().length()
      return Some({
        marker: name,
        preamble: text[:end].to_owned(),
        content: text[end:].to_owned(),
        line: i + 1,
      })
    }
  }
  None
}

///|
pub extend LogEntry with Eq::{not_equal, equal}

///|
pub extend LogEntry with @debug.Debug::{to_repr}

///|
pub extend LogSection with Eq::{not_equal, equal}

///|
pub extend LogSection with @debug.Debug::{to_repr}

///|
pub extend LogModel with Eq::{not_equal, equal}

///|
pub extend LogModel with @debug.Debug::{to_repr}

///|
pub extend Region with Eq::{not_equal, equal}

///|
pub extend Region with @debug.Debug::{to_repr}

///|
pub extend LogHeading with Eq::{not_equal, equal}

///|
pub extend LogHeading with @debug.Debug::{to_repr}

///|
pub extend LogHeading with Show::{output}