// Struct tags. A `.api` field says where its value comes from and what it has to
// look like in the back-tick tag goctl borrowed from Go: `json:"name"` reads it out
// of the request body, `form:"page"` off the query string, `path:"id"` out of a URL
// segment, `header:"X-Trace"` off a request header, and the options after the wire
// name — `optional`, `default=`, `options=`, `range=` — constrain it. The parser
// keeps the tag verbatim on `Field.tag`; everything that reads one is here.

///|
/// Where a field's value is bound from (← goctl's struct tags): the request body
/// (`json:`, and an untagged field), the query string (`form:`), a URL segment
/// (`path:`), or a request header (`header:`).
pub(all) enum Bind {
  Body
  Query
  Path
  Header
} derive(Eq)

///|
/// A `range=` constraint, e.g. `range=[1:120]` or `range=(0:]`. Each bound is
/// written as the spec wrote it and is empty when that end is open; `lo_inc` /
/// `hi_inc` say whether the bracket was inclusive (`[` `]`) or exclusive (`(` `)`).
pub(all) struct Range {
  lo : String
  hi : String
  lo_inc : Bool
  hi_inc : Bool
} derive(Eq)

///|
/// The binding key a tag carries and the body of its `"…"`, e.g. `path:"region"` →
/// `(Path, "region")`. When a tag names several the leftmost wins, and a field with
/// no binding tag reads out of the body.
fn tag_bind(tag : String) -> (Bind, String) {
  let mut at = -1
  let mut kind = Body
  let mut body = ""
  for
    probe in [
      ("json:\"", Body),
      ("form:\"", Query),
      ("path:\"", Path),
      ("header:\"", Header),
    ] {
    let found = index_of_str(tag, probe.0)
    if found < 0 || (at >= 0 && found >= at) {
      continue
    }
    let start = found + probe.0.length()
    let end = index_of(tag, '"', start)
    if end < 0 {
      continue
    }
    at = found
    kind = probe.1
    body = tag[start:end].to_owned()
  }
  (kind, body)
}

///|
/// A field's tag read apart: where it binds from, the wire name it binds under, and
/// the options that follow that name.
fn tag_read(f : Field) -> (Bind, String, Array[String]) {
  let (kind, body) = tag_bind(f.tag)
  let pieces = split_char(body, ',')
  let name = trim(pieces[0])
  let opts : Array[String] = []
  for i = 1; i < pieces.length(); i = i + 1 {
    let o = trim(pieces[i])
    if o != "" {
      opts.push(o)
    }
  }
  (kind, if name == "" { f.name } else { name }, opts)
}

///|
/// The value of the `key=` option in `opts`, or `None` when it carries none.
fn opt_value(opts : Array[String], key : String) -> String? {
  let head = key + "="
  for o in opts {
    if starts_with(o, head) {
      return Some(o[head.length():].to_owned())
    }
  }
  None
}

///|
/// Where this field's value is bound from: `form:` off the query string, `path:`
/// out of a URL segment, `header:` off a request header, and everything else — an
/// untagged field included — out of the request body.
pub fn Field::bind(self : Field) -> Bind {
  tag_read(self).0
}

///|
/// The name this field is carried under on the wire: the one its binding tag gives
/// (`json:"user_name"` → `user_name`, `form:"page"` → `page`, `path:"region"` →
/// `region`), with the options after it stripped, falling back to the field's own
/// name when it carries no tag.
pub fn Field::json_name(self : Field) -> String {
  tag_read(self).1
}

///|
/// The MoonBit spelling of this field: `snake_case`, because a MoonBit struct field
/// has to start lower-case — `UserName` is not one. Every generator that emits
/// MoonBit names the field with this, so the struct, the row decoder and the column
/// projection all agree; `json_name` is the wire side of the same field.
pub fn Field::mbt_name(self : Field) -> String {
  to_snake(self.name)
}

///|
/// Whether the tag marked this field `optional` (or Go's `omitempty`): it may be
/// absent, which is what keeps it out of a schema's `required` list.
pub fn Field::optional(self : Field) -> Bool {
  for o in tag_read(self).2 {
    if o == "optional" || o == "omitempty" {
      return true
    }
  }
  false
}

///|
/// The `default=` this field's tag declared, written the way the spec wrote it, or
/// `None` when it declared none.
pub fn Field::default_(self : Field) -> String? {
  opt_value(tag_read(self).2, "default")
}

///|
/// The values an `options=a|b|c` tag allows this field to take, empty when the tag
/// declared no such list.
pub fn Field::options(self : Field) -> Array[String] {
  let out : Array[String] = []
  match opt_value(tag_read(self).2, "options") {
    Some(v) =>
      for one in split_char(v, '|') {
        let t = trim(one)
        if t != "" {
          out.push(t)
        }
      }
    None => ()
  }
  out
}

///|
/// The `range=[lo:hi]` bounds this field's tag declared, or `None` when it declared
/// none. Either bound may be empty, which leaves that end open.
pub fn Field::range(self : Field) -> Range? {
  let raw = match opt_value(tag_read(self).2, "range") {
    Some(v) => trim(v)
    None => return None
  }
  let n = raw.length()
  if n < 3 {
    return None
  }
  let lo_inc = raw[0] == '['
  let hi_inc = raw[n - 1] == ']'
  if (lo_inc || raw[0] == '(') == false ||
    (hi_inc || raw[n - 1] == ')') == false {
    return None
  }
  let colon = index_of(raw, ':', 1)
  if colon < 0 {
    return None
  }
  Some({
    lo: trim(raw[1:colon].to_owned()),
    hi: trim(raw[colon + 1:n - 1].to_owned()),
    lo_inc,
    hi_inc,
  })
}