// Copyright 2026 Leo Cheng
// SPDX-License-Identifier: Apache-2.0

///|
/// Everything this package does, in one run: `moon run src/examples/tour`.
///
/// A format is a promise about bytes, and a promise is checked by looking at
/// them, so every section prints what it wrote as hexadecimal next to what
/// came back.
#coverage.skip
fn main raise {
  section("one value, one message")
  show("Int 1", 1)
  show("Int64 1", 1L)
  show("String \"ab\"", "ab")
  show("Double 1.5", 1.5)
  show("Bool true", true)
  show("Bytes b\"ab\"", @utf8.encode("ab"))

  section("containers")
  show("Array [1, 2, 3]", [1, 2, 3])
  show("Map {\"a\": 1}", { "a": 1 })
  let empty : Array[Int32] = []
  show("empty Array", empty)
  show_value("nothing at all", @moonfory.Value::Null)

  section("a struct, written down once")
  let point = @moonfory.StructMeta::new("Point", 1, [
    @moonfory.FieldMeta::new("x", @moonfory.TypeId::Int32),
    @moonfory.FieldMeta::new("y", @moonfory.TypeId::Int32),
  ])
  let written = @moonfory.Value::Struct(point, [
    ("x", @moonfory.Value::Int(1L)),
    ("y", @moonfory.Value::Int(2L)),
  ])
  show_value("Point(1, 2)", written, schemas=[point])

  section("a value that is written more than once")
  let twice = [@moonfory.shared(7, "once"), @moonfory.shared(7, "once")]
  try {
    let tracked = @moonfory.dumps(twice, track_ref=true)
    let back : Array[@moonfory.Shared[String]] = @moonfory.loads(
      tracked,
      track_ref=true,
    )
    let values = back.map(fn(one) { one.value })
    println("  " + pad(@base16.encode(tracked), 40) + "  " + join(values))
  } catch {
    failure => println("  refused  " + failure.to_string())
  } noraise {
    _ => ()
  }

  section("a row, read by field")
  let schema = @moonfory.RowSchema::new([
    { name: "id", ty: @moonfory.RowType::Int64, nullable: false, },
    { name: "name", ty: @moonfory.RowType::String, nullable: false, },
    { name: "age", ty: @moonfory.RowType::Int32, nullable: true, },
  ])
  let row = @moonfory.Row::new(schema)
  row.set(0, @moonfory.RowValue::Int64(7L))
  row.set(1, @moonfory.RowValue::String("ada"))
  row.set(2, @moonfory.RowValue::Null)
  try {
    let row_bytes = row.finish()
    let cur = @moonfory.RowCursor::new(row_bytes[:], schema)
    println(
      "  " +
      pad("id, name, age", 18) +
      "  " +
      pad(@base16.encode(row_bytes), 40) +
      "  " +
      "\{@debug.Debug::to_repr(cur.get_field(1))}",
    )
  } catch {
    failure => println("  refused  " + failure.to_string())
  } noraise {
    _ => ()
  }

  section("the time types, through moondate")
  show("Date 19000d", @moonfory.date_of_days(19_000L))
  show("Moment 1.7e9", @moonfory.moment_of_parts(1_700_000_000L, 0))
  show("Span 3.5s", @moonfory.span_of_parts(3L, 500_000_000))

  section("a message written by another language")
  for
    line in [
      ("   1", "01ff07fe01"),
      ("  -1", "01ff0701"),
      (" \"世界\"", "01ff1511164e4c75"),
      (" [None, 1]", "01ff16020a07fdff02"),
      (" {\"a\": 1}", "01ff180100011507046102"),
    ] {
    let (label, text) = line
    println("  " + pad(label, 12) + "  " + pad(text, 26) + "  " + read(text))
  }

  section("a message that is refused")
  for
    line in [
      ("truncated", "01ff040100"),
      ("two values", "01ff070201ff0702"),
      ("no xlang flag", "00ff0702"),
    ] {
    let (label, text) = line
    println("  " + pad(label, 12) + "  " + pad(text, 26) + "  " + read(text))
  }
}

///|
fn[T : @moonfory.Codec + Debug] show(
  label : String,
  value : T,
  schemas~ : Array[@moonfory.StructMeta] = [],
) -> Unit {
  try {
    let bytes = @moonfory.dumps(value)
    let back : T = @moonfory.loads(bytes, schemas~)
    println(
      "  " +
      pad(label, 18) +
      "  " +
      pad(@base16.encode(bytes), 40) +
      "  " +
      "\{@debug.Debug::to_repr(back)}",
    )
  } catch {
    failure =>
      println("  " + pad(label, 18) + "  refused  " + failure.to_string())
  } noraise {
    _ => ()
  }
}

///|
/// The same, for a `Value`: printed with its own `Show`, which says
/// `Struct(100, [x: Int(1)])` rather than the whole schema the derived `Debug`
/// would print, and is what a reader of this tour wants to see.
fn show_value(
  label : String,
  value : @moonfory.Value,
  schemas~ : Array[@moonfory.StructMeta] = [],
) -> Unit {
  try {
    let bytes = @moonfory.dumps(value)
    let back : @moonfory.Value = @moonfory.loads(bytes, schemas~)
    println(
      "  " +
      pad(label, 18) +
      "  " +
      pad(@base16.encode(bytes), 40) +
      "  " +
      back.to_string(),
    )
  } catch {
    failure =>
      println("  " + pad(label, 18) + "  refused  " + failure.to_string())
  } noraise {
    _ => ()
  }
}

///|
fn read(text : String) -> String {
  try {
    let value : @moonfory.Value = @moonfory.loads(@base16.decode(text))
    value.to_string()
  } catch {
    failure => "refused  " + failure.to_string()
  } noraise {
    text => text
  }
}

///|
fn join(texts : Array[String]) -> String {
  let out = StringBuilder()
  for i in 0.. 0 {
      out.write_char(',')
    }
    out.write_char('"')
    for c in texts[i].to_array() {
      out.write_char(c)
    }
    out.write_char('"')
  }
  out.to_string()
}

///|
fn section(title : String) -> Unit {
  println("")
  println(title)
}

///|
fn pad(text : String, width : Int32) -> String {
  let out = StringBuilder()
  for c in text.to_array() {
    out.write_char(c)
  }
  let spaces = width - text.length()
  for _ in 0..