// mooncassette 基准:把「量化指标」测出来,而不是估计。
//
// 运行:
//   moon run examples/benchmarks --target js
//   moon run examples/benchmarks --target wasm-gc
//
// 输出分成两类,**刻意分开**:
//
//   规模指标 —— 确定性的,只取决于数据本身(体积、每条记录的开销)。
//               换一台机器结果一样,因此可以在 CI 中断言。
//   时间指标 —— 取决于机器与后端。只有同一台机器上的前后对比才有意义,
//               跨机器比较毫无价值。
//
// 为什么必须分开:把时间写成断言,等于把「CI 机器今天有多忙」变成测试结果;
// 而规模指标是**代码的性质**,不是环境的性质。

///|
/// 累加器。
///
/// 两个用途:
///
/// - **防止计算被优化掉**:没有它,纯计算的基准可能被编译器整个删掉,
///   于是测出来的是「什么都不做」的耗时——一个漂亮但毫无意义的数字;
/// - **记录失败次数**:静默失败是基准最坏的结局,数字很漂亮,但测的是一个
///   恒等于空操作的路径。凡会抛错的被测代码都在闭包内捕获并计数。
///
/// 顶层不能写 `let mut`,因此可变状态放进 struct 实例。
struct Accumulator {
  mut checksum : Int
  mut failures : Int
}

///|
let accumulator : Accumulator = { checksum: 0, failures: 0 }

///|
/// 一行时间指标。
struct Row {
  name : String
  us_per_op : Double
  iterations : Int
}

///|
/// 测一段代码的平均耗时(微秒)。
///
/// 先空跑一轮:首次调用包含惰性初始化,计入平均值会明显失真。
fn measure(name : String, iterations : Int, op : () -> Unit) -> Row {
  op()
  let start = @bench.monotonic_clock_start()
  for i = 0; i < iterations; i = i + 1 {
    op()
  }
  let elapsed = @bench.monotonic_clock_end(start)
  { name, us_per_op: elapsed / iterations.to_double(), iterations }
}

///|
/// 把微秒数格式化成人能读的形式。全程整数运算,不依赖浮点格式化。
fn format_us(us : Double) -> String {
  if us >= 1000.0 {
    let tenths = (us / 100.0).to_int()
    (tenths / 10).to_string() + "." + (tenths % 10).to_string() + " ms"
  } else if us >= 1.0 {
    us.to_int().to_string() + " us"
  } else {
    // 亚微秒量级:用十分之一微秒为单位表示,保留一位小数。
    let tenths = (us * 10.0).to_int()
    (tenths / 10).to_string() + "." + (tenths % 10).to_string() + " us"
  }
}

///|
fn format_bytes(bytes : Int) -> String {
  if bytes < 1024 {
    bytes.to_string() + " B"
  } else if bytes < 1024 * 1024 {
    let tenths = bytes * 10 / 1024
    (tenths / 10).to_string() + "." + (tenths % 10).to_string() + " KiB"
  } else {
    let tenths = bytes * 10 / (1024 * 1024)
    (tenths / 10).to_string() + "." + (tenths % 10).to_string() + " MiB"
  }
}

///|
fn pad_right(text : String, width : Int) -> String {
  let mut out = text
  while out.length() < width {
    out = out + " "
  }
  out
}

///|
fn print_timings(rows : Array[Row]) -> Unit {
  for row in rows {
    let per_op = row.us_per_op
    let ops_per_sec = if per_op > 0.0 {
      (1000000.0 / per_op).to_int()
    } else {
      0
    }
    println(
      "  " +
      pad_right(row.name, 32) +
      pad_right(format_us(per_op) + "/op", 14) +
      pad_right(ops_per_sec.to_string() + " ops/s", 18) +
      "n=" +
      row.iterations.to_string(),
    )
  }
}

///|
/// 造一个约 1 KB 的请求体:5 条消息,内容有代表性长度。
fn synth_request_body(index : Int) -> Json {
  let body : Map[String, Json] = Map([])
  body.set("model", Json::string("gpt-4o"))
  body.set("temperature", Json::number(0.7))
  let messages : Array[Json] = []
  for m = 0; m < 5; m = m + 1 {
    let message : Map[String, Json] = Map([])
    message.set(
      "role",
      Json::string(if m % 2 == 0 { "user" } else { "assistant" }),
    )
    message.set(
      "content",
      Json::string(
        "第 " +
        index.to_string() +
        " 组对话的第 " +
        m.to_string() +
        " 段内容:这里放一段有代表性的长度,用于让请求体接近真实使用中的规模。",
      ),
    )
    messages.push(Json::object(message))
  }
  body.set("messages", Json::array(messages))
  Json::object(body)
}

///|
/// 造一个 n 条记录的 cassette。
fn synth_cassette(n : Int) -> @core.Cassette {
  let interactions : Array[@core.Interaction] = []
  for i = 0; i < n; i = i + 1 {
    let request = @core.Request::new("openai", "gpt-4o", synth_request_body(i))
    let answer : Map[String, Json] = Map([])
    answer.set("id", Json::string("chatcmpl-" + i.to_string()))
    answer.set("model", Json::string("gpt-4o"))
    let choice : Map[String, Json] = Map([])
    choice.set("index", Json::number(0))
    let message : Map[String, Json] = Map([])
    message.set("role", Json::string("assistant"))
    message.set(
      "content",
      Json::string(
        "这是第 " +
        i.to_string() +
        " 条回答,长度也接近真实情况。",
      ),
    )
    choice.set("message", Json::object(message))
    choice.set("finish_reason", Json::string("stop"))
    answer.set("choices", Json::array([Json::object(choice)]))
    let response = @core.Response::ok(
      Json::object(answer),
      usage=@core.Usage::new(120, 48),
    )
    interactions.push(@core.Interaction::new(request, response))
  }
  @core.Cassette::new(
    "bench",
    recorded_at="2026-09-13T00:00:00Z",
    interactions~,
  )
}

///|
/// 造一段 n 帧的 SSE 文本,形状与 OpenAI 流式响应一致。
fn synth_sse(n : Int) -> String {
  let text = StringBuilder::new()
  for i = 0; i < n; i = i + 1 {
    text.write_string(
      "data: {\"id\":\"chatcmpl-stream\",\"object\":\"chat.completion.chunk\"," +
      "\"choices\":[{\"index\":0,\"delta\":{\"content\":\"片段 " +
      i.to_string() +
      "\"},\"finish_reason\":null}]}\n\n",
    )
  }
  text.write_string("data: [DONE]\n\n")
  text.to_string()
}

///|
/// 一份含若干密钥的 JSON,用于测脱敏开销。
///
/// 直接构造而不是解析一段字面量:`fn main` 里不能调用会抛错的函数,
/// 而且基准的输入不该把「解析」这一步混进被测开销。
fn synth_secrets() -> Json {
  let nested : Map[String, Json] = Map([])
  nested.set("password", Json::string("hunter2"))
  let root : Map[String, Json] = Map([])
  root.set("api_key", Json::string("sk-abcdefgh12345678"))
  root.set(
    "authorization",
    Json::string("Bearer eyJhbGciOiJIUzI1NiJ9.payload.sig"),
  )
  root.set("nested", Json::object(nested))
  root.set("safe", Json::string("keep me"))
  Json::object(root)
}

///|
fn print_sizes(records : Array[Int]) -> Unit {
  println("规模指标(确定性,只取决于数据本身)")
  println(
    "  " +
    pad_right("records", 10) +
    pad_right("pretty", 12) +
    pad_right("compact", 12) +
    "compact/record",
  )
  let mut last_pretty = 0
  let mut last_compact = 0
  let mut last_n = 0
  for n in records {
    let cassette = synth_cassette(n)
    let pretty = @codec.encode(cassette).length()
    let compact = @codec.encode(cassette, indent=0).length()
    println(
      "  " +
      pad_right(n.to_string(), 10) +
      pad_right(format_bytes(pretty), 12) +
      pad_right(format_bytes(compact), 12) +
      format_bytes(compact / n) +
      "/record",
    )
    last_pretty = pretty
    last_compact = compact
    last_n = n
  }
  let floor_compact = @codec.encode(synth_cassette(10), indent=0).length()
  if last_pretty > 0 {
    println(
      "  紧凑编码比 2 空格缩进省 " +
      (100 - last_compact * 100 / last_pretty).to_string() +
      "%",
    )
    println(
      "  每条记录的开销 = (compact(1000) - compact(10)) / 990 = " +
      format_bytes((last_compact - floor_compact) / (last_n - 10)) +
      "(与上面的每记录字节数一致,说明增长是线性的)",
    )
  }
}

///|
fn main {
  println("mooncassette 基准")
  println(
    "  规模指标是确定性的;时间指标取决于机器与后端,跨机器比较没有意义。",
  )
  print_sizes([10, 100, 1000])

  println("")
  println("时间指标")
  let rows : Array[Row] = []
  let probe = @core.Request::new("openai", "gpt-4o", synth_request_body(0))
  rows.push(
    measure("fingerprint (1 KB request)", 2000, fn() {
      accumulator.checksum = accumulator.checksum +
        @fingerprint.fingerprint(probe).length()
    }),
  )
  rows.push(
    measure("canonical text (1 KB JSON)", 2000, fn() {
      accumulator.checksum = accumulator.checksum +
        @canon.to_canonical_string(probe.body).length()
    }),
  )
  let mid = synth_cassette(100)
  let mid_text = @codec.encode(mid)
  rows.push(
    measure("encode (100 records)", 100, fn() {
      accumulator.checksum = accumulator.checksum + @codec.encode(mid).length()
    }),
  )
  rows.push(
    measure("decode + verify (100 records)", 100, fn() {
      let decoded = @codec.decode(mid_text) catch {
        _ => {
          accumulator.failures = accumulator.failures + 1
          return
        }
      }
      accumulator.checksum = accumulator.checksum + decoded.length()
    }),
  )

  // 匹配:最贵的是「未命中」——它必须扫完整张表。
  let big = synth_cassette(1000)
  let hit = @core.Request::new("openai", "gpt-4o", synth_request_body(0))
  let miss = @core.Request::new("openai", "gpt-4o", synth_request_body(999999))
  let cases : Array[(@matcher.MatchPolicy, String, @core.Request)] = [
    (@matcher.MatchPolicy::Exact, "match exact hit (1000)", hit),
    (@matcher.MatchPolicy::Exact, "match exact miss (1000)", miss),
    (@matcher.MatchPolicy::FingerprintOnly, "match fp miss (1000)", miss),
    (
      @matcher.MatchPolicy::Subset(["messages"]),
      "match subset miss (1000)",
      miss,
    ),
  ]
  for entry in cases {
    let (policy, name, request) = entry
    rows.push(
      measure(name, 20, fn() {
        let found = @matcher.find_match(request, big.interactions[:], policy, 0) catch {
          _ => {
            accumulator.failures = accumulator.failures + 1
            return
          }
        }
        match found {
          Some(result) =>
            accumulator.checksum = accumulator.checksum + result.index
          None => accumulator.checksum = accumulator.checksum + 1
        }
      }),
    )
  }

  // 索引:建立一次,之后的扫描不再重算指纹。
  //
  // 这一组与上一组的差值,就是「每条记录重算一次指纹」的代价。
  let built_index = @matcher.MatchIndex::build(big.interactions[:])
  rows.push(
    measure("index build (1000)", 20, fn() {
      let built = @matcher.MatchIndex::build(big.interactions[:])
      accumulator.checksum = accumulator.checksum + built.length()
    }),
  )
  let indexed_cases : Array[(@matcher.MatchPolicy, String)] = [
    (@matcher.MatchPolicy::Exact, "match exact miss indexed (1000)"),
    (@matcher.MatchPolicy::FingerprintOnly, "match fp miss indexed (1000)"),
  ]
  for entry in indexed_cases {
    let (policy, name) = entry
    rows.push(
      measure(name, 2000, fn() {
        let found = @matcher.find_match(
          miss,
          big.interactions[:],
          policy,
          0,
          index=built_index,
        ) catch {
          _ => {
            accumulator.failures = accumulator.failures + 1
            return
          }
        }
        match found {
          Some(result) =>
            accumulator.checksum = accumulator.checksum + result.index
          None => accumulator.checksum = accumulator.checksum + 1
        }
      }),
    )
  }

  // 脱敏:一份含若干密钥的 JSON。
  let secrets = synth_secrets()
  let policy = @sanitize.SanitizePolicy::default()
  rows.push(
    measure("sanitize json (3 secrets)", 2000, fn() {
      accumulator.checksum = accumulator.checksum +
        @canon.to_canonical_string(@sanitize.sanitize_json(secrets, policy)).length()
    }),
  )

  // SSE 解析:200 帧的一整段流式响应。
  let sse = synth_sse(200)
  rows.push(
    measure("sse parse (200 frames)", 500, fn() {
      accumulator.checksum = accumulator.checksum + @stream.parse(sse).length()
    }),
  )

  // 会话回放:在 100 条记录中命中,含规范化与脱敏。
  let playback = @recorder.Session::new(mid, mode=@recorder.Mode::Playback)
  rows.push(
    measure("session replay (100 records)", 1000, fn() {
      let response = playback.send(hit) catch {
        _ => {
          accumulator.failures = accumulator.failures + 1
          return
        }
      }
      accumulator.checksum = accumulator.checksum + response.status
    }),
  )

  print_timings(rows)
  println("")
  println(
    "  被测路径的失败次数:" +
    accumulator.failures.to_string() +
    "(必须为 0,否则时间测的是空路径)",
  )
  println(
    "  结果校验值:" +
    accumulator.checksum.to_string() +
    "(防止计算被优化掉)",
  )
  println("ok")
}