///|
/// A `#aya.*` attribute recovered from source text.
///
/// `moonbitlang/parser` hands us attributes with `raw` filled in but `parsed`
/// left as `None` for user-defined namespaces, so we read `raw` ourselves.
/// `Attribute::name()` drops the namespace (`#aya.id` and `#morm.id` both
/// report `"id"`), which is not good enough to tell our attributes apart.
pub struct Attr {
  name : String
  args : Map[String, String]
} derive(Debug, Eq)

///|
pub(all) suberror AttrError {
  /// The attribute payload could not be read.
  Malformed(raw~ : String, reason~ : String)
} derive(Debug)

///|
/// Parse one raw attribute such as `#aya.entity(table="users", alias="u")`.
///
/// Returns `None` when the attribute belongs to another namespace, so callers
/// can ignore `#deprecated`, `#morm.entity`, and friends without special cases.
pub fn parse_attr(raw : String) -> Attr? raise AttrError {
  let text = raw.trim()
  guard text.has_prefix("#" + NAMESPACE + ".") else { None }
  let body = text[NAMESPACE.length() + 2:]
  match body.find("(") {
    None => Some({ name: body.trim().to_owned(), args: Map([]) })
    Some(open) => {
      guard body.has_suffix(")") else {
        raise Malformed(raw~, reason="unterminated argument list")
      }
      Some({
        name: body[:open].trim().to_owned(),
        args: parse_args(body[open + 1:body.length() - 1], raw),
      })
    }
  }
}

///|
/// The attribute namespace this generator claims.
const NAMESPACE = "aya"

///|
/// Parse `k="v", k2="v2"` into a map.
///
/// Scanned character by character rather than split on commas, because the
/// values that matter most here are SQL fragments: `default="coalesce(a, b)"`
/// has a comma inside the quotes, and splitting on it would lose half the
/// expression without saying so.
fn parse_args(
  inner : StringView,
  raw : String,
) -> Map[String, String] raise AttrError {
  let args = Map([])
  let cs = []
  for c in inner {
    cs.push(c)
  }
  let n = cs.length()
  let mut i = 0
  fn skip_spaces() {
    while i < n && cs[i].is_whitespace() {
      i = i + 1
    }
  }

  while i < n {
    skip_spaces()
    if i >= n {
      break
    }
    let key_start = i
    while i < n && cs[i] != '=' && !cs[i].is_whitespace() {
      i = i + 1
    }
    let key = take(cs, key_start, i)
    if key.is_empty() {
      raise Malformed(raw~, reason="expected an argument name")
    }
    skip_spaces()
    guard i < n && cs[i] == '=' else {
      raise Malformed(raw~, reason="expected `=` after `\{key}`")
    }
    i = i + 1
    skip_spaces()
    guard i < n && cs[i] == '"' else {
      raise Malformed(raw~, reason="value of `\{key}` must be a quoted string")
    }
    i = i + 1
    let value = StringBuilder()
    let mut closed = false
    while i < n {
      let c = cs[i]
      if c == '"' {
        closed = true
        i = i + 1
        break
      }
      // A backslash escape is passed through with its backslash removed, so
      // `default="\"n/a\""` reaches the emitter as `"n/a"`.
      if c == '\\' && i + 1 < n {
        value.write_char(cs[i + 1])
        i = i + 2
      } else {
        value.write_char(c)
        i = i + 1
      }
    }
    guard closed else {
      raise Malformed(raw~, reason="unterminated value for `\{key}`")
    }
    args[key] = value.to_string()
    skip_spaces()
    if i < n {
      guard cs[i] == ',' else {
        raise Malformed(raw~, reason="expected `,` after `\{key}`")
      }
      i = i + 1
    }
  }
  args
}

///|
/// The characters in `[start, end)` as a string.
fn take(cs : Array[Char], start : Int, end : Int) -> String {
  let buf = StringBuilder()
  for i in start.. String? {
  self.args.get(key)
}