///|
/// Parse a MoonBit interface file into a normalized public API list.
pub fn parse_interface(text : String) -> Array[ApiItem] {
  let items : Array[ApiItem] = []
  let parse_legacy_associated_items = is_legacy_moon_info_interface(text)
  let mut container_kind = ""
  let mut container_name = ""
  let mut pending_attributes : Array[String] = []
  for raw_line in split_lines(text[:]) {
    let line = normalize_space(strip_comment(raw_line).trim())
    if line.length() == 0 {
      pending_attributes = []
      continue
    }
    if line.has_prefix("#") {
      pending_attributes.push(line)
      continue
    }
    if container_kind.length() > 0 {
      if line.has_prefix("}") {
        for item in parse_closing_derives(container_name, line[:]) {
          items.push(item)
        }
        container_kind = ""
        container_name = ""
        pending_attributes = []
        continue
      }
      if !is_public_line(line[:]) {
        match
          parse_container_item(
            container_kind,
            container_name,
            line[:],
            pending_attributes,
          ) {
          Some(item) => items.push(item)
          None => ()
        }
        pending_attributes = []
        continue
      }
      container_kind = ""
      container_name = ""
    }
    if !is_public_line(line[:]) {
      match parse_interface_item(line[:], pending_attributes) {
        Some(item) => {
          items.push(item)
          for derive_item in parse_closing_derives(item.name, line[:]) {
            items.push(derive_item)
          }
        }
        None =>
          if parse_legacy_associated_items {
            match parse_legacy_generated_item(line[:], pending_attributes) {
              Some(item) => items.push(item)
              None => ()
            }
          }
      }
      pending_attributes = []
      continue
    }
    match parse_public_item(line[:], pending_attributes) {
      Some(item) => {
        if opens_public_container(item.kind, line[:]) {
          container_kind = item.kind
          container_name = item.name
        }
        items.push(item)
        for
          inline_item in parse_inline_container_items(
            item.kind,
            item.name,
            line[:],
          ) {
          items.push(inline_item)
        }
        for derive_item in parse_closing_derives(item.name, line[:]) {
          items.push(derive_item)
        }
      }
      None => ()
    }
    pending_attributes = []
  }
  sort_items(items)
  items
}

///|
fn is_legacy_moon_info_interface(text : String) -> Bool {
  text.contains("Generated using `moon info`") && text.contains("package \"")
}

///|
/// Parse a MoonBit interface file and namespace every public API name.
pub fn parse_interface_with_namespace(
  text : String,
  scope : String,
) -> Array[ApiItem] {
  let items = parse_interface(text)
  if scope.length() == 0 {
    return items
  }
  let namespaced : Array[ApiItem] = []
  for item in items {
    namespaced.push({
      kind: item.kind,
      name: "\{scope}::\{item.name}",
      signature: item.signature,
    })
  }
  sort_items(namespaced)
  namespaced
}

///|
fn parse_interface_item(
  line : StringView,
  attributes : Array[String],
) -> ApiItem? {
  if line.has_prefix("type ") {
    Some(make_type_item(line.view(start_offset=5), attributes))
  } else {
    None
  }
}

///|
fn parse_legacy_generated_item(
  line : StringView,
  attributes : Array[String],
) -> ApiItem? {
  if line.has_prefix("fn ") {
    Some(
      make_item_with_visibility(
        "fn",
        line.view(start_offset=3),
        "pub",
        attributes,
      ),
    )
  } else if line.has_prefix("fn[") {
    Some(
      make_item_with_visibility(
        "fn",
        line.view(start_offset=2),
        "pub",
        attributes,
      ),
    )
  } else if line.has_prefix("async fn ") || line.has_prefix("async fn[") {
    Some(make_item_with_visibility("fn", line, "pub", attributes))
  } else if line.has_prefix("impl ") {
    Some(make_impl_item(line.view(start_offset=5), "pub", attributes))
  } else if line.has_prefix("impl[") {
    Some(make_impl_item(line.view(start_offset=4), "pub", attributes))
  } else {
    None
  }
}

///|
fn parse_public_item(line : StringView, attributes : Array[String]) -> ApiItem? {
  let public_body = public_body(line)
  let visibility = public_visibility(line)
  if public_body.length() == 0 {
    return None
  }
  if public_body.has_prefix("fn ") {
    Some(
      make_item_with_visibility(
        "fn",
        public_body.view(start_offset=3),
        visibility,
        attributes,
      ),
    )
  } else if public_body.has_prefix("fn[") {
    Some(
      make_item_with_visibility(
        "fn",
        public_body.view(start_offset=2),
        visibility,
        attributes,
      ),
    )
  } else if public_body.has_prefix("async fn ") {
    Some(make_item_with_visibility("fn", public_body, visibility, attributes))
  } else if public_body.has_prefix("async fn[") {
    Some(make_item_with_visibility("fn", public_body, visibility, attributes))
  } else if public_body.has_prefix("typealias ") {
    Some(
      make_item_with_visibility(
        "typealias",
        public_body.view(start_offset=10),
        visibility,
        attributes,
      ),
    )
  } else if public_body.has_prefix("type ") {
    Some(
      make_type_item_with_visibility(
        public_body.view(start_offset=5),
        visibility,
        attributes,
      ),
    )
  } else if public_body.has_prefix("struct ") {
    Some(
      make_container_item_with_visibility(
        "struct",
        public_body.view(start_offset=7),
        visibility,
        attributes,
      ),
    )
  } else if public_body.has_prefix("enum ") {
    Some(
      make_container_item_with_visibility(
        "enum",
        public_body.view(start_offset=5),
        visibility,
        attributes,
      ),
    )
  } else if public_body.has_prefix("trait ") {
    Some(
      make_container_item_with_visibility(
        "trait",
        public_body.view(start_offset=6),
        visibility,
        attributes,
      ),
    )
  } else if public_body.has_prefix("impl ") {
    Some(
      make_impl_item(public_body.view(start_offset=5), visibility, attributes),
    )
  } else if public_body.has_prefix("impl[") {
    Some(
      make_impl_item(public_body.view(start_offset=4), visibility, attributes),
    )
  } else if public_body.has_prefix("let ") {
    Some(
      make_item_with_visibility(
        "let",
        public_body.view(start_offset=4),
        visibility,
        attributes,
      ),
    )
  } else if public_body.has_prefix("const ") {
    Some(
      make_item_with_visibility(
        "const",
        public_body.view(start_offset=6),
        visibility,
        attributes,
      ),
    )
  } else if public_body.has_prefix("suberror ") {
    Some(
      make_container_item_with_visibility(
        "suberror",
        public_body.view(start_offset=9),
        visibility,
        attributes,
      ),
    )
  } else if public_body.has_prefix("using ") {
    Some(
      make_using_item(public_body.view(start_offset=6), visibility, attributes),
    )
  } else {
    Some(make_unknown_item(public_body, visibility, attributes))
  }
}

///|
fn make_type_item(body : StringView, attributes : Array[String]) -> ApiItem {
  let signature = normalize_signature(strip_inline_derive(body))
  {
    kind: "type",
    name: extract_name(signature[:]),
    signature: apply_attributes(signature, attributes),
  }
}

///|
fn make_type_item_with_visibility(
  body : StringView,
  visibility : String,
  attributes : Array[String],
) -> ApiItem {
  let signature = normalize_signature(strip_inline_derive(body))
  {
    kind: "type",
    name: extract_name(signature[:]),
    signature: apply_attributes(
      apply_visibility(signature, visibility),
      attributes,
    ),
  }
}

///|
fn make_item_with_visibility(
  kind : String,
  body : StringView,
  visibility : String,
  attributes : Array[String],
) -> ApiItem {
  let signature = normalize_signature(body)
  {
    kind,
    name: extract_name(signature[:]),
    signature: apply_attributes(
      apply_visibility(signature, visibility),
      attributes,
    ),
  }
}

///|
fn make_container_item_with_visibility(
  kind : String,
  body : StringView,
  visibility : String,
  attributes : Array[String],
) -> ApiItem {
  let signature = normalize_signature(container_declaration(body))
  {
    kind,
    name: extract_name(signature[:]),
    signature: apply_attributes(
      apply_visibility(signature, visibility),
      attributes,
    ),
  }
}

///|
fn container_declaration(body : StringView) -> StringView {
  match body.find("{") {
    Some(index) => body.view(start_offset=0, end_offset=index).trim()
    None => strip_inline_derive(body)
  }
}

///|
fn make_named_item_with_attributes(
  kind : String,
  name : String,
  body : StringView,
  attributes : Array[String],
) -> ApiItem {
  {
    kind,
    name,
    signature: apply_attributes(normalize_signature(body), attributes),
  }
}

///|
fn make_impl_item(
  body : StringView,
  visibility : String,
  attributes : Array[String],
) -> ApiItem {
  let signature = normalize_signature(body)
  let name = trim_leading_generic_params(signature[:]).to_owned()
  {
    kind: "impl",
    name,
    signature: apply_attributes(
      apply_visibility(signature, visibility),
      attributes,
    ),
  }
}

///|
fn make_using_item(
  body : StringView,
  visibility : String,
  attributes : Array[String],
) -> ApiItem {
  let signature = normalize_signature(body)
  {
    kind: "using",
    name: extract_using_name(signature[:]),
    signature: apply_attributes(
      apply_visibility(signature, visibility),
      attributes,
    ),
  }
}

///|
fn make_unknown_item(
  body : StringView,
  visibility : String,
  attributes : Array[String],
) -> ApiItem {
  let signature = normalize_signature(body)
  {
    kind: "unknown",
    name: signature,
    signature: apply_attributes(
      apply_visibility(signature, visibility),
      attributes,
    ),
  }
}

///|
fn parse_container_item(
  kind : String,
  parent : String,
  line : StringView,
  attributes : Array[String],
) -> ApiItem? {
  if line.has_prefix("#") || line.has_prefix("priv ") {
    return None
  }
  if kind == "struct" {
    parse_struct_field(parent, line, attributes)
  } else if kind == "enum" || kind == "suberror" {
    parse_constructor(parent, line, attributes)
  } else if kind == "trait" {
    parse_trait_method(parent, line, attributes)
  } else {
    None
  }
}

///|
fn parse_inline_container_items(
  kind : String,
  parent : String,
  line : StringView,
) -> Array[ApiItem] {
  let items : Array[ApiItem] = []
  if !is_public_container_kind(kind) || !has_close_brace(line) {
    return items
  }
  match brace_body(line) {
    Some(body) =>
      for part in split_inline_container_members(body) {
        let part = part.trim()
        if part.length() > 0 {
          match parse_container_item(kind, parent, part, []) {
            Some(item) => items.push(item)
            None => ()
          }
        }
      }
    None => ()
  }
  items
}

///|
fn split_inline_container_members(text : StringView) -> Array[StringView] {
  let parts : Array[StringView] = []
  let mut start = 0
  let mut round_depth = 0
  let mut square_depth = 0
  let mut curly_depth = 0
  for i in 0.. ApiItem? {
  match line.find(":") {
    Some(_) => {
      let field_name = extract_field_name(line)
      if field_name.length() == 0 {
        None
      } else {
        Some(
          make_named_item_with_attributes(
            "field",
            "\{parent}.\{field_name}",
            line,
            attributes,
          ),
        )
      }
    }
    None => None
  }
}

///|
fn parse_constructor(
  parent : String,
  line : StringView,
  attributes : Array[String],
) -> ApiItem? {
  let signature = normalize_signature(line)
  if signature.length() == 0 {
    None
  } else {
    let name = extract_name(signature[:])
    if name.length() == 0 {
      None
    } else {
      Some({
        kind: "constructor",
        name: "\{parent}.\{name}",
        signature: apply_attributes(signature, attributes),
      })
    }
  }
}

///|
fn parse_closing_derives(parent : String, line : StringView) -> Array[ApiItem] {
  let items : Array[ApiItem] = []
  match derive_body(line) {
    Some(body) =>
      for trait_name in split_top_level_commas(body) {
        let trait_name = trait_name.trim()
        if trait_name.length() > 0 {
          let signature = "derive(\{normalize_signature(trait_name)})"
          items.push({
            kind: "derive",
            name: "\{parent}.\{normalize_signature(trait_name)}",
            signature,
          })
        }
      }
    None => ()
  }
  items
}

///|
fn derive_body(line : StringView) -> StringView? {
  match line.find("derive(") {
    Some(start) => {
      let body_start = start + 7
      let mut depth = 1
      for i in body_start.. None
  }
}

///|
fn strip_inline_derive(text : StringView) -> StringView {
  match text.find(" derive(") {
    Some(index) => text.view(start_offset=0, end_offset=index).trim()
    None => text.trim()
  }
}

///|
fn split_top_level_commas(text : StringView) -> Array[StringView] {
  let parts : Array[StringView] = []
  let mut start = 0
  let mut round_depth = 0
  let mut square_depth = 0
  for i in 0.. ApiItem? {
  if line.has_prefix("fn ") {
    Some(make_trait_method(parent, line.view(start_offset=3), attributes))
  } else if line.has_prefix("fn[") {
    Some(make_trait_method(parent, line.view(start_offset=2), attributes))
  } else if line.has_prefix("async fn ") || line.has_prefix("async fn[") {
    Some(make_trait_method(parent, line, attributes))
  } else {
    None
  }
}

///|
fn make_trait_method(
  parent : String,
  body : StringView,
  attributes : Array[String],
) -> ApiItem {
  let signature = normalize_signature(body)
  let name = extract_name(signature[:])
  {
    kind: "trait-method",
    name: "\{parent}.\{name}",
    signature: apply_attributes(signature, attributes),
  }
}

///|
fn opens_public_container(kind : String, line : StringView) -> Bool {
  is_public_container_kind(kind) &&
  has_open_brace(line) &&
  !has_close_brace(line)
}

///|
fn is_public_container_kind(kind : String) -> Bool {
  kind == "struct" || kind == "enum" || kind == "trait" || kind == "suberror"
}

///|
fn has_open_brace(line : StringView) -> Bool {
  match line.find("{") {
    Some(_) => true
    None => false
  }
}

///|
fn has_close_brace(line : StringView) -> Bool {
  match line.find("}") {
    Some(_) => true
    None => false
  }
}

///|
fn public_body(line : StringView) -> StringView {
  if line.has_prefix("pub(all) ") {
    line.view(start_offset=9).trim()
  } else if line.has_prefix("pub(open) ") {
    line.view(start_offset=10).trim()
  } else if line.has_prefix("pub ") {
    line.view(start_offset=4).trim()
  } else {
    ""
  }
}

///|
fn public_visibility(line : StringView) -> String {
  if line.has_prefix("pub(all) ") {
    "pub(all)"
  } else if line.has_prefix("pub(open) ") {
    "pub(open)"
  } else {
    "pub"
  }
}

///|
fn apply_visibility(signature : String, visibility : String) -> String {
  if visibility == "pub" {
    signature
  } else {
    "\{visibility} \{signature}"
  }
}

///|
fn apply_attributes(signature : String, attributes : Array[String]) -> String {
  if attributes.is_empty() {
    return signature
  }
  let out = StringBuilder()
  for attribute in attributes {
    if !out.is_empty() {
      out.write(" ")
    }
    out.write(attribute)
  }
  out.write(" ")
  out.write(signature)
  out.to_string()
}

///|
fn is_public_line(line : StringView) -> Bool {
  line.has_prefix("pub ") ||
  line.has_prefix("pub(all) ") ||
  line.has_prefix("pub(open) ")
}

///|
fn normalize_signature(text : StringView) -> String {
  let signature = normalize_space(text.trim())
  if signature.length() > 0 &&
    signature.unsafe_get(signature.length() - 1).unsafe_to_char() == '{' {
    signature[:signature.length() - 1].trim().to_owned()
  } else {
    signature
  }
}

///|
fn extract_name(signature : StringView) -> String {
  let signature = trim_callable_prefix(signature)
  let mut end = 0
  while end < signature.length() {
    let ch = signature.unsafe_get(end).unsafe_to_char()
    if ch == ':' &&
      end + 1 < signature.length() &&
      signature.unsafe_get(end + 1).unsafe_to_char() == ':' {
      end += 2
      continue
    }
    if is_name_boundary(ch) {
      break
    }
    end += 1
  }
  signature.view(start_offset=0, end_offset=end).to_owned()
}

///|
fn trim_callable_prefix(signature : StringView) -> StringView {
  let signature = signature.trim()
  if signature.has_prefix("async fn ") {
    trim_leading_generic_params(signature.view(start_offset=9))
  } else if signature.has_prefix("async fn[") {
    trim_leading_generic_params(signature.view(start_offset=8))
  } else if signature.has_prefix("fn ") {
    trim_leading_generic_params(signature.view(start_offset=3))
  } else if signature.has_prefix("fn[") {
    trim_leading_generic_params(signature.view(start_offset=2))
  } else {
    trim_leading_generic_params(signature)
  }
}

///|
fn trim_leading_generic_params(text : StringView) -> StringView {
  let text = text.trim()
  if !text.has_prefix("[") {
    return text
  }
  let mut depth = 0
  for i in 0.. String {
  let head = match line.find(":") {
    Some(index) => line.view(start_offset=0, end_offset=index).trim()
    None => line.trim()
  }
  let head = if head.has_prefix("mut ") {
    head.view(start_offset=4).trim()
  } else {
    head
  }
  extract_name(head)
}

///|
fn extract_using_name(signature : StringView) -> String {
  match brace_body(signature) {
    Some(body) =>
      match body.find(" as ") {
        Some(index) => extract_name(body.view(start_offset=index + 4).trim())
        None => extract_name(using_export_body(body))
      }
    None => extract_name(signature)
  }
}

///|
fn using_export_body(body : StringView) -> StringView {
  let body = body.trim()
  if body.has_prefix("type ") {
    body.view(start_offset=5).trim()
  } else if body.has_prefix("trait ") {
    body.view(start_offset=6).trim()
  } else if body.has_prefix("struct ") {
    body.view(start_offset=7).trim()
  } else if body.has_prefix("enum ") {
    body.view(start_offset=5).trim()
  } else if body.has_prefix("suberror ") {
    body.view(start_offset=9).trim()
  } else {
    body
  }
}

///|
fn brace_body(text : StringView) -> StringView? {
  match text.find("{") {
    Some(start) =>
      match text.find("}") {
        Some(end) =>
          if end > start {
            Some(text.view(start_offset=start + 1, end_offset=end).trim())
          } else {
            None
          }
        None => None
      }
    None => None
  }
}

///|
fn is_name_boundary(ch : Char) -> Bool {
  ch == '(' ||
  ch == ':' ||
  ch == '[' ||
  ch == '{' ||
  ch == '=' ||
  is_ascii_space(ch.to_int())
}

///|
fn strip_comment(line : StringView) -> StringView {
  let mut in_string = false
  let mut escaped = false
  let mut i = 0
  while i < line.length() {
    let ch = line.unsafe_get(i).unsafe_to_char()
    if in_string {
      if escaped {
        escaped = false
      } else if ch == '\\' {
        escaped = true
      } else if ch == '"' {
        in_string = false
      }
    } else if ch == '"' {
      in_string = true
    } else if ch == '/' &&
      i + 1 < line.length() &&
      line.unsafe_get(i + 1).unsafe_to_char() == '/' {
      return line.view(start_offset=0, end_offset=i)
    }
    i += 1
  }
  line
}

///|
fn normalize_space(text : StringView) -> String {
  let out = StringBuilder(size_hint=text.length())
  let mut saw_space = false
  let mut in_string = false
  let mut escaped = false
  for ch in text {
    if in_string {
      out.write_char(ch)
      if escaped {
        escaped = false
      } else if ch == '\\' {
        escaped = true
      } else if ch == '"' {
        in_string = false
      }
    } else if ch == '"' {
      if saw_space && !out.is_empty() {
        out.write(" ")
      }
      out.write_char(ch)
      saw_space = false
      in_string = true
    } else if is_ascii_space(ch.to_int()) {
      saw_space = true
    } else {
      if saw_space && !out.is_empty() {
        out.write(" ")
      }
      out.write_char(ch)
      saw_space = false
    }
  }
  out.to_string()
}

///|
fn split_lines(text : StringView) -> Array[StringView] {
  let lines : Array[StringView] = []
  let mut start = 0
  for i in 0.. Bool {
  code == ' '.to_int() || code == '\t'.to_int() || code == '\r'.to_int()
}