///|
struct ReadmeApiVerifyBlock {
  interface_path : String
  signatures : Array[String]
} derive(Eq, Debug)

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

///|
fn ReadmeApiVerifyBlock::new(
  interface_path : String,
  signatures : Array[String],
) -> ReadmeApiVerifyBlock {
  { interface_path, signatures, }
}

///|
fn ras_has_prefix(text : String, prefix : String) -> Bool {
  if text.length() < prefix.length() {
    return false
  }
  let head = text.sub(end=prefix.length()).to_owned()
  head == prefix
}

///|
fn ras_has_suffix(text : String, suffix : String) -> Bool {
  if text.length() < suffix.length() {
    return false
  }
  let start = text.length() - suffix.length()
  let tail = text.sub(start~).to_owned()
  tail == suffix
}

///|
fn ras_extract_marker_path(line : String) -> String? {
  let marker = line.trim().to_owned()
  let prefix = ""
  if !ras_has_prefix(marker, prefix) {
    return None
  }
  if !ras_has_suffix(marker, suffix) {
    return None
  }
  let start = prefix.length()
  let end = marker.length() - suffix.length()
  if end <= start {
    return None
  }
  let path = marker.sub(start~, end~).to_owned()
  let normalized = path.trim().to_owned()
  if normalized.length() == 0 {
    None
  } else {
    Some(normalized)
  }
}

///|
/// Join collected error lines into one newline-separated diagnostic block.
fn ras_join_lines_impl(lines : Array[String]) -> String {
  let out = StringBuilder::new()
  let mut i = 0
  while i < lines.length() {
    if i > 0 {
      out.write_string("\n")
    }
    out.write_string(lines[i])
    i += 1
  }
  out.to_string()
}

///|
/// Parse all ````README_API_VERIFY`` blocks in one pass, then validate block shape:
/// marker -> optional blank lines -> opening fence -> at least one signature -> closing fence.
/// This keeps failure reasons actionable and local to the declaration layout.
/// Marker syntax is strict for reproducible checks:
/// `` only, with optional whitespace and optional
/// blank lines before the following ````mbti` block.
fn parse_readme_api_verify_blocks(
  readme_text : String,
) -> Result[Array[ReadmeApiVerifyBlock], String] {
  let lines : Array[String] = []
  for line in readme_text.split("\n") {
    lines.push(line.to_owned())
  }
  let blocks : Array[ReadmeApiVerifyBlock] = []
  let mut i = 0
  while i < lines.length() {
    let line = lines[i]
    match ras_extract_marker_path(line) {
      Some(interface_path) => {
        i += 1
        while i < lines.length() && lines[i].trim().to_owned().length() == 0 {
          i += 1
        }
        if i >= lines.length() || lines[i].trim().to_owned() != "```mbti" {
          return Err(
            "README_API_VERIFY marker for \{interface_path} must be followed by a ```mbti code block",
          )
        }
        i += 1
        let signatures : Array[String] = []
        while i < lines.length() {
          let sig = lines[i].trim().to_owned()
          if sig == "```" {
            break
          }
          if sig.length() > 0 {
            signatures.push(sig)
          }
          i += 1
        }
        if i >= lines.length() {
          return Err(
            "README_API_VERIFY marker for \{interface_path} has an unterminated code block",
          )
        }
        if signatures.length() == 0 {
          return Err(
            "README_API_VERIFY marker for \{interface_path} has no signatures",
          )
        }
        blocks.push(ReadmeApiVerifyBlock::new(interface_path, signatures))
      }
      None => ()
    }
    i += 1
  }
  if blocks.length() == 0 {
    return Err("README contains no README_API_VERIFY blocks")
  }
  Ok(blocks)
}

///|
fn ras_find_interface_text(
  interface_text_by_path : Array[(String, String)],
  path : String,
) -> String? {
  for pair in interface_text_by_path {
    if pair.0 == path {
      return Some(pair.1)
    }
  }
  None
}

///|
fn ras_has_block_for_path(
  blocks : Array[ReadmeApiVerifyBlock],
  path : String,
) -> Bool {
  for block in blocks {
    if block.interface_path == path {
      return true
    }
  }
  false
}

///|
/// Resolve required blocks and validate that every listed signature appears in the
/// corresponding interface text, while collecting all diagnostics and reporting one
/// aggregated error message when inconsistencies exist.
pub fn verify_readme_api_signatures_with_required_blocks(
  readme_text : String,
  interface_text_by_path : Array[(String, String)],
  required_block_paths : Array[String],
) -> Result[Unit, String] {
  let blocks = match parse_readme_api_verify_blocks(readme_text) {
    Ok(v) => v
    Err(e) => return Err(e)
  }
  let diagnostics : Array[String] = []
  for raw_path in required_block_paths {
    let path = raw_path.trim().to_owned()
    if path.length() == 0 {
      continue
    }
    if !ras_has_block_for_path(blocks, path) {
      diagnostics.push("missing required README_API_VERIFY block: \{path}")
    }
  }
  for block in blocks {
    let interface_text = match
      ras_find_interface_text(interface_text_by_path, block.interface_path) {
      Some(v) => v
      None => {
        diagnostics.push(
          "missing interface file for README_API_VERIFY block: \{block.interface_path}",
        )
        continue
      }
    }
    for signature in block.signatures {
      if !interface_text.contains(signature) {
        diagnostics.push(
          "missing signature in \{block.interface_path}: \{signature}",
        )
      }
    }
  }
  if diagnostics.length() == 0 {
    Ok(())
  } else {
    Err(ras_join_lines_impl(diagnostics))
  }
}

///|
pub fn verify_readme_api_signatures(
  readme_text : String,
  interface_text_by_path : Array[(String, String)],
) -> Result[Unit, String] {
  verify_readme_api_signatures_with_required_blocks(
    readme_text,
    interface_text_by_path,
    [],
  )
}