///|
/// Benchmarks for FlatBuffers MoonBit implementation

///|
/// Benchmark: Build operations
test "bench_build" (b : @bench.T) {
  b.bench(name="simple_table", fn() {
    let builder = Builder::new()
    let name = builder.create_string("TestName")
    builder.start_object(3)
    builder.add_offset(0, name)
    builder.add_int32(1, 100, 0)
    builder.add_int16(2, 50, 0)
    let root = builder.end_object()
    builder.finish(root)
    b.keep(builder.to_bytes())
  })
  b.bench(name="string_vector", fn() {
    let builder = Builder::new()
    let s1 = builder.create_string("Item1")
    let s2 = builder.create_string("Item2")
    let s3 = builder.create_string("Item3")
    let vec = builder.create_offset_vector([s1, s2, s3])
    builder.start_object(1)
    builder.add_offset(0, vec)
    let root = builder.end_object()
    builder.finish(root)
    b.keep(builder.to_bytes())
  })
  b.bench(name="byte_vector", fn() {
    let builder = Builder::new()
    let data : FixedArray[Byte] = FixedArray::make(100, b'\x42')
    let vec = builder.create_byte_vector(data)
    builder.start_object(1)
    builder.add_offset(0, vec)
    let root = builder.end_object()
    builder.finish(root)
    b.keep(builder.to_bytes())
  })
  b.bench(name="shared_strings", fn() {
    let builder = Builder::new()
    let s1 = builder.create_shared_string("SharedString")
    let s2 = builder.create_shared_string("SharedString")
    let s3 = builder.create_shared_string("SharedString")
    let s4 = builder.create_shared_string("DifferentString")
    builder.start_object(4)
    builder.add_offset(0, s1)
    builder.add_offset(1, s2)
    builder.add_offset(2, s3)
    builder.add_offset(3, s4)
    let root = builder.end_object()
    builder.finish(root)
    b.keep(builder.to_bytes())
  })
  b.bench(name="nested_table", fn() {
    let builder = Builder::new()
    let inner_name = builder.create_string("InnerName")
    builder.start_object(2)
    builder.add_offset(0, inner_name)
    builder.add_int32(1, 50, 0)
    let inner = builder.end_object()
    let outer_name = builder.create_string("OuterName")
    builder.start_object(2)
    builder.add_offset(0, outer_name)
    builder.add_offset(1, inner)
    let root = builder.end_object()
    builder.finish(root)
    b.keep(builder.to_bytes())
  })
}

///|
fn prepare_simple_table_buffer() -> FixedArray[Byte] {
  let builder = Builder::new()
  let name = builder.create_string("TestName")
  builder.start_object(3)
  builder.add_offset(0, name)
  builder.add_int32(1, 100, 0)
  builder.add_int16(2, 50, 0)
  let root = builder.end_object()
  builder.finish(root)
  builder.to_fixed_array()
}

///|
fn prepare_byte_vector_buffer() -> FixedArray[Byte] {
  let builder = Builder::new()
  let data : FixedArray[Byte] = FixedArray::make(100, b'\x42')
  let vec = builder.create_byte_vector(data)
  builder.start_object(1)
  builder.add_offset(0, vec)
  let root = builder.end_object()
  builder.finish(root)
  builder.to_fixed_array()
}

///|
fn prepare_nested_table_buffer() -> FixedArray[Byte] {
  let builder = Builder::new()
  let inner_name = builder.create_string("InnerName")
  builder.start_object(2)
  builder.add_offset(0, inner_name)
  builder.add_int32(1, 50, 0)
  let inner = builder.end_object()
  let outer_name = builder.create_string("OuterName")
  builder.start_object(2)
  builder.add_offset(0, outer_name)
  builder.add_offset(1, inner)
  let root = builder.end_object()
  builder.finish(root)
  builder.to_fixed_array()
}

///|
/// Benchmark: Read operations
test "bench_read" (b : @bench.T) {
  let simple_bytes = prepare_simple_table_buffer()
  // Read all fields
  b.bench(name="table_all_fields", fn() {
    let buf = ByteBuffer::new(simple_bytes)
    let table = Table::get_root(buf)
    let name = table.get_string(0, "")
    let hp = table.get_int32(1, 0)
    let mana = table.get_int16(2, 0)
    b.keep((name, hp, mana))
  })
  // Partial read: only one int field (FlatBuffers strength)
  b.bench(name="table_one_field", fn() {
    let buf = ByteBuffer::new(simple_bytes)
    let table = Table::get_root(buf)
    let hp = table.get_int32(1, 0)
    b.keep(hp)
  })
  let byte_vec_bytes = prepare_byte_vector_buffer()
  // Read all 100 bytes one by one
  b.bench(name="vec_all_100", fn() {
    let buf = ByteBuffer::new(byte_vec_bytes)
    let table = Table::get_root(buf)
    let len = table.get_vector_length(0)
    let mut sum = 0
    for i = 0; i < len; i = i + 1 {
      sum = sum + table.get_vector_byte(0, i).to_int()
    }
    b.keep(sum)
  })
  // Partial read: only first 10 bytes (FlatBuffers strength)
  b.bench(name="vec_first_10", fn() {
    let buf = ByteBuffer::new(byte_vec_bytes)
    let table = Table::get_root(buf)
    let mut sum = 0
    for i = 0; i < 10; i = i + 1 {
      sum = sum + table.get_vector_byte(0, i).to_int()
    }
    b.keep(sum)
  })
  // Partial read: only first element
  b.bench(name="vec_first_1", fn() {
    let buf = ByteBuffer::new(byte_vec_bytes)
    let table = Table::get_root(buf)
    let first = table.get_vector_byte(0, 0)
    b.keep(first)
  })
  // Bulk read all (optimized)
  b.bench(name="vec_bulk_all", fn() {
    let buf = ByteBuffer::new(byte_vec_bytes)
    let table = Table::get_root(buf)
    let bytes = table.get_vector_bytes_slice(0)
    let mut sum = 0
    for b in bytes {
      sum = sum + b.to_int()
    }
    b.keep(sum)
  })
  let nested_bytes = prepare_nested_table_buffer()
  b.bench(name="nested_table", fn() {
    let buf = ByteBuffer::new(nested_bytes)
    let table = Table::get_root(buf)
    let outer_name = table.get_string(0, "")
    let inner_table = table.get_table(1)
    match inner_table {
      Some(inner) => {
        let inner_name = inner.get_string(0, "")
        let inner_val = inner.get_int32(1, 0)
        b.keep((outer_name, inner_name, inner_val))
      }
      None => b.keep(("", "", 0))
    }
  })
}

///|
/// Benchmark: Verifier
test "bench_verify" (b : @bench.T) {
  let bytes = prepare_simple_table_buffer()
  b.bench(name="verify_buffer", fn() {
    let buf = ByteBuffer::new(bytes)
    b.keep(buf.verify())
  })
}

///|
/// Benchmark: JSON conversion
test "bench_json" (b : @bench.T) {
  let bytes = prepare_simple_table_buffer()
  b.bench(name="json_conversion", fn() {
    let buf = ByteBuffer::new(bytes)
    let table = Table::get_root(buf)
    let json = JsonObjectBuilder::new()
      .add_string("name", table.get_string(0, ""))
      .add_int("hp", table.get_int32(1, 0))
      .add_int("mana", table.get_int16(2, 0))
      .to_json_string()
    b.keep(json)
  })
}

///|
fn get_person_schema() -> TableSchema {
  TableSchema::new("Person", [
    FieldDef::new("name", 0, FieldType::string()),
    FieldDef::with_default_int("age", 1, FieldType::int32(), 0),
    FieldDef::with_default_int("score", 2, FieldType::int16(), 0),
  ])
}

///|
fn prepare_object_api_buffer(schema : TableSchema) -> FixedArray[Byte] {
  let builder = Builder::new()
  let obj = ObjectBuilder::new(builder, schema).set_string("name", "TestPerson")
  obj.start()
  ignore(obj.add_int32("age", 30))
  ignore(obj.add_int16("score", 95))
  obj.finish_buffer()
  builder.to_fixed_array()
}

///|
/// Benchmark: Object API
test "bench_object_api" (b : @bench.T) {
  let schema = get_person_schema()
  b.bench(name="object_build", fn() {
    let builder = Builder::new()
    let obj = ObjectBuilder::new(builder, schema).set_string(
      "name", "TestPerson",
    )
    obj.start()
    ignore(obj.add_int32("age", 30))
    ignore(obj.add_int16("score", 95))
    obj.finish_buffer()
    b.keep(builder.to_bytes())
  })
  let bytes = prepare_object_api_buffer(schema)
  b.bench(name="object_read", fn() {
    let buf = ByteBuffer::new(bytes)
    let table = Table::get_root(buf)
    let reader = ObjectReader::new(table, schema)
    let name = reader.get_string("name")
    let age = reader.get_int32("age")
    let score = reader.get_int16("score")
    b.keep((name, age, score))
  })
}

///|
/// Benchmark: Code generation
test "bench_codegen" (b : @bench.T) {
  let schema_text =
    #|table Monster {
    #|  name:string;
    #|  hp:short = 100;
    #|  mana:short = 50;
    #|}
  b.bench(name="schema_parse", fn() {
    let schema = parse_schema(schema_text)
    b.keep(schema.tables.length())
  })
  let schema = parse_schema(schema_text)
  b.bench(name="code_generate", fn() {
    let code = generate_moonbit(schema)
    b.keep(code.length())
  })
}