///|
/// Parse Markdown citation blocks of the form:
/// > 原文:“quoted text”
/// > 来源:[title](https://example.org/page)
/// Inline-code backticks are Markdown formatting, not part of rendered quotes.
pub fn parse_markdown_report(input : String) -> CitationNotes {
  let notes : Array[CitationNote] = []
  let diagnostics : Array[Diagnostic] = []
  let mut heading = "Markdown report"
  let mut pending_quote : String? = None
  let mut pending_line = 0
  let mut in_code_block = false
  let lines = input.split("\n").to_array()
  for index = 0; index < lines.length(); index = index + 1 {
    let line_number = index + 1
    let line = lines[index].trim()
    if line.has_prefix("```") || line.has_prefix("~~~") {
      in_code_block = !in_code_block
      continue
    }
    if in_code_block {
      continue
    }
    if line.has_prefix("#") {
      if pending_quote is Some(_) {
        add_diagnostic(
          diagnostics, pending_line, "MISSING_REPORT_SOURCE", "quote has no following source",
        )
        pending_quote = None
      }
      heading = match line.split_once(" ") {
        Some((_, title)) => title.trim().to_owned()
        None => line.to_owned()
      }
      continue
    }
    match report_quote_line(line) {
      Some(quote) => {
        if pending_quote is Some(_) {
          add_diagnostic(
            diagnostics, pending_line, "MISSING_REPORT_SOURCE", "quote has no following source",
          )
        }
        if quote == "" {
          add_diagnostic(
            diagnostics, line_number, "EMPTY_REPORT_QUOTE", "quote is empty",
          )
          pending_quote = None
        } else {
          pending_quote = Some(quote)
          pending_line = line_number
        }
        continue
      }
      None => ()
    }
    match report_source_line(line) {
      Some(url) => {
        match pending_quote {
          Some(quote) =>
            if url == "" {
              add_diagnostic(
                diagnostics, line_number, "INVALID_REPORT_SOURCE", "source needs an HTTP URL",
              )
            } else {
              notes.push({ claim: heading, quote, url, line: pending_line, })
            }
          None =>
            add_diagnostic(
              diagnostics, line_number, "SOURCE_WITHOUT_QUOTE", "source has no preceding quote",
            )
        }
        pending_quote = None
        continue
      }
      None => ()
    }
    if pending_quote is Some(_) && line != "" && line != ">" {
      add_diagnostic(
        diagnostics, pending_line, "MISSING_REPORT_SOURCE", "quote has no following source",
      )
      pending_quote = None
    }
    collect_inline_report_quotes(line, heading, line_number, notes, diagnostics)
  }
  if pending_quote is Some(_) {
    add_diagnostic(
      diagnostics, pending_line, "MISSING_REPORT_SOURCE", "quote has no following source",
    )
  }
  if notes.is_empty() && diagnostics.is_empty() {
    add_diagnostic(
      diagnostics, 1, "NO_REPORT_CITATIONS", "no quote/source blocks found",
    )
  }
  { notes, diagnostics, }
}

///|
/// Find ordinary Markdown citations such as “quoted text” ([source](https://...)).
/// A nearby link without an explicit quotation is deliberately not treated as
/// proof for the surrounding claim.
fn collect_inline_report_quotes(
  line : StringView,
  heading : String,
  line_number : Int,
  notes : Array[CitationNote],
  diagnostics : Array[Diagnostic],
) -> Unit {
  let mut remaining = line
  while true {
    let (before_open, quote, after_close) = match next_report_quote(remaining) {
      Some(parts) => parts
      None => break
    }
    remaining = after_close
    let source = match inline_report_source(after_close) {
      Some(url) => Some(url)
      None => source_before_inline_quote(before_open)
    }
    match source {
      Some(url) =>
        if url == "" {
          add_diagnostic(
            diagnostics, line_number, "INVALID_REPORT_SOURCE", "source needs an HTTP URL",
          )
        } else {
          let cleaned = clean_report_quote(quote)
          if cleaned == "" {
            add_diagnostic(
              diagnostics, line_number, "EMPTY_REPORT_QUOTE", "quote is empty",
            )
          } else {
            notes.push({
              claim: heading,
              quote: cleaned,
              url,
              line: line_number,
            })
          }
        }
      None => ()
    }
  }
}

///|
fn next_report_quote(
  line : StringView,
) -> (StringView, StringView, StringView)? {
  let curly = line.split_once("“")
  let straight = line.split_once("\"")
  let (before, after, close) = match (curly, straight) {
    (Some((first, tail)), Some((second, rest))) =>
      if first.length() < second.length() {
        (first, tail, "”")
      } else {
        (second, rest, "\"")
      }
    (Some((first, tail)), None) => (first, tail, "”")
    (None, Some((first, tail))) => (first, tail, "\"")
    (None, None) => return None
  }
  match after.split_once(close) {
    Some((quote, rest)) => Some((before, quote, rest))
    None => None
  }
}

///|
/// In curated Markdown lists, the linked title often precedes its quotation.
/// Restrict this form to a list item whose first element is a linked title.
fn source_before_inline_quote(before_quote : StringView) -> String? {
  let before = before_quote.trim()
  let title = match before.strip_prefix("- [") {
    Some(rest) => rest
    None =>
      match before.strip_prefix("* [") {
        Some(rest) => rest
        None => return None
      }
  }
  let (_, url_tail) = match title.split_once("](") {
    Some(parts) => parts
    None => return Some("")
  }
  let (url, after_link) = match url_tail.split_once(")") {
    Some(parts) => parts
    None => return Some("")
  }
  let after_link = after_link.trim()
  if !after_link.has_prefix("-") && !after_link.has_prefix("—") {
    return None
  }
  let url = url.trim()
  if url.has_prefix("https://") || url.has_prefix("http://") {
    Some(url.to_owned())
  } else {
    Some("")
  }
}

///|
fn inline_report_source(after_quote : StringView) -> String? {
  let tail = after_quote.trim()
  let tail = match tail.strip_prefix("(") {
    Some(rest) => rest
    None =>
      match tail.strip_prefix("(") {
        Some(rest) => rest
        None =>
          match tail.split_once("(") {
            Some((between, rest)) if between.length() <= 80 &&
              !between.contains("。") &&
              !between.contains(";") &&
              !between.contains("“") &&
              rest.has_prefix("[") => rest
            _ =>
              match tail.split_once("(") {
                Some((between, rest)) if between.length() <= 80 &&
                  !between.contains(".") &&
                  !between.contains(";") &&
                  !between.contains("\"") &&
                  rest.has_prefix("[") => rest
                _ =>
                  if tail.has_prefix("[") {
                    tail
                  } else {
                    match tail.split_once("[") {
                      Some((between, rest)) if between.length() <= 80 &&
                        between.trim().has_prefix("-") => rest
                      _ => return None
                    }
                  }
              }
          }
      }
  }
  let (_, url_tail) = match tail.split_once("](") {
    Some(parts) => parts
    None => return Some("")
  }
  let (url, _) = match url_tail.split_once(")") {
    Some(parts) => parts
    None => return Some("")
  }
  let url = url.trim()
  if url.has_prefix("https://") || url.has_prefix("http://") {
    Some(url.to_owned())
  } else {
    Some("")
  }
}

///|
fn clean_report_quote(raw : StringView) -> String {
  let output = StringBuilder()
  for char in raw.iter() {
    if char != '`' {
      output.write_char(char)
    }
  }
  output.to_string().trim().to_owned()
}

///|
fn report_quote_line(line : StringView) -> String? {
  let content = match line.strip_prefix(">") {
    Some(rest) => rest.trim()
    None => return None
  }
  let raw = match content.strip_prefix("原文:") {
    Some(rest) => rest
    None =>
      match content.strip_prefix("引文:") {
        Some(rest) => rest
        None =>
          match content.strip_prefix("Quote:") {
            Some(rest) => rest
            None => return None
          }
      }
  }
  let raw = raw.trim()
  let unquoted = match raw.strip_prefix("“") {
    Some(rest) => rest.strip_suffix("”").unwrap_or(rest)
    None =>
      match raw.strip_prefix("\"") {
        Some(rest) => rest.strip_suffix("\"").unwrap_or(rest)
        None => raw
      }
  }
  clean_report_quote(unquoted) |> Some
}

///|
fn report_source_line(line : StringView) -> String? {
  let content = match line.strip_prefix(">") {
    Some(rest) => rest.trim()
    None => return None
  }
  let raw = match content.strip_prefix("来源:") {
    Some(rest) => rest.trim()
    None =>
      match content.strip_prefix("Source:") {
        Some(rest) => rest.trim()
        None => return None
      }
  }
  let url = if raw.has_prefix("[") {
    match raw.split_once("](") {
      Some((_, tail)) =>
        match tail.split_once(")") {
          Some((url, _)) => url.trim().to_owned()
          None => ""
        }
      None => ""
    }
  } else {
    raw.to_owned()
  }
  if url.has_prefix("https://") || url.has_prefix("http://") {
    Some(url)
  } else {
    Some("")
  }
}