///|
/// `mctl model mongo`: a `.api` `type` block as the document a collection stores,
/// and a typed repository over a collection port — no driver in sight.
///
/// The spec below is the shape a Mongo user actually has: an `id`, an optional
/// field, a list, and a nested type. The generator turns each block into a `Doc`
/// (derived both ways, so a value round-trips without a hand-written field walker)
/// and a `Repo` whose five calls are the five a driver serves.
///
///     moon run src/examples/15-mongo
#coverage.skip
fn main raise {
  let source =
    #|service catalog {
    #|  @handler ping
    #|  get /ping
    #|}
    #|
    #|type Product {
    #|  id: int64 `json:"id"`
    #|  name: string `json:"name"`
    #|  price: float64 `json:"price"`
    #|  tags: []string `json:"tags"`
    #|  note: string `json:"note,optional"`
    #|  maker: Maker `json:"maker"`
    #|}
    #|
    #|type Maker {
    #|  id: int64 `json:"id"`
    #|  name: string `json:"name"`
    #|}
  let spec = @moonctl.parse(source)
  let model = @moonctl.generate_mongo(spec)
  println("document model: " + model.length().to_string() + " bytes")
  println("file: " + @moonctl.mongo_file_name(spec.service))
  println("")
  // The port, then the document, then the repository: the three things a reader
  // needs to see.
  for
    anchor in [
      "pub(all) struct Collection {", "pub(all) struct ProductDoc {", "pub(all) struct ProductRepo {",
      "pub fn ProductRepo::find(", "pub fn ProductRepo::delete(",
    ] {
    println("---- " + anchor)
    println(excerpt(model, anchor, 8))
  }
  // A nested type is nested as a document, not as the wire type: the derived
  // conversions only line up if the field is a document too.
  println("---- nested")
  println(
    "  maker : MakerDoc is in the output: " +
    contains(model, "  maker : MakerDoc\n").to_string(),
  )
  println("")
  println("the spec declares " + spec.types.length().to_string() + " documents")
}

///|
/// The first index in `hay` where `needle` starts, or `-1`.
fn at_of(hay : String, needle : String) -> Int32 {
  let hn = hay.length()
  let nn = needle.length()
  if nn == 0 {
    return 0
  }
  for i = 0; i + nn <= hn; i = i + 1 {
    let mut same = true
    for j = 0; j < nn; j = j + 1 {
      if hay[i + j] != needle[j] {
        same = false
        break
      }
    }
    if same {
      return i
    }
  }
  -1
}

///|
/// Whether `hay` contains `needle`.
fn contains(hay : String, needle : String) -> Bool {
  at_of(hay, needle) >= 0
}

///|
/// The `lines` lines of `text` that follow the first occurrence of `anchor`.
fn excerpt(text : String, anchor : String, lines : Int32) -> String {
  let at = at_of(text, anchor)
  if at < 0 {
    return anchor + ": not found"
  }
  let mut end = at
  let mut seen = 0
  while end < text.length() && seen < lines {
    if text[end] == '\n' {
      seen = seen + 1
    }
    end = end + 1
  }
  text[at:end].to_owned()
}