// 离线示例:完整走一遍「录制 → 落盘 → 回放」,全程不联网、不需要密钥。
//
// 运行:
//   moon run examples/offline_demo --target js
//
// 这个例子本身就是项目的价值主张:演示用的 cassette 一旦生成,
// 后续所有验证都可以在没有网络、没有密钥的环境里重复执行。

///|
let output_path : String = "examples/demo.cassette.json"

///|
/// 第二份录制:模拟「模型换了版本」,用于演示漂移检测。
let drifted_path : String = "examples/demo.drifted.cassette.json"

///|
/// 构造一个请求(含一个会泄漏密钥的字段,用来演示脱敏)。
fn request_of(content : String) -> @core.Request {
  let body : Map[String, Json] = Map([])
  body.set("api_key", Json::string("sk-abcdefgh12345678"))
  let messages : Array[Json] = []
  let message : Map[String, Json] = Map([])
  message.set("role", Json::string("user"))
  message.set("content", Json::string(content))
  messages.push(Json::object(message))
  body.set("messages", Json::array(messages))
  // 易变字段:每次调用都不同,规范化时会被剔除,不会影响指纹命中。
  body.set("request_id", Json::string("req-" + content.length().to_string()))
  @core.Request::new("openai", "gpt-4o", Json::object(body))
}

///|
fn chat_response(
  text : String,
  input_tokens : Int,
  output_tokens : Int,
) -> @core.Response {
  let body : Map[String, Json] = Map([])
  let choices : Array[Json] = []
  let choice : Map[String, Json] = Map([])
  let message : Map[String, Json] = Map([])
  message.set("role", Json::string("assistant"))
  message.set("content", Json::string(text))
  choice.set("message", Json::object(message))
  choices.push(Json::object(choice))
  body.set("choices", Json::array(choices))
  @core.Response::ok(
    Json::object(body),
    usage=@core.Usage::new(input_tokens, output_tokens),
  )
}

///|
fn main {
  // ── 第一步:录制 ──────────────────────────────────────────────────
  // 真实项目里这里换成你的 HTTP Transport;本例用脚本化的 MockTransport,
  // 以便在没有密钥的机器上也能完整演示。
  let transport = @recorder.MockTransport::new()
  transport.on(
    request_of("你好,请介绍一下自己"),
    chat_response("你好!我是月兔助手。", 12, 9),
  )
  transport.on(
    request_of("第二个问题"),
    chat_response("这是第二个回答。", 8, 7),
  )

  let session = @mooncassette.record_session(
    @core.Cassette::new("offline-demo", recorded_at="2026-09-12T08:00:00Z"),
    transport,
  )
  let _ = session.send(request_of("你好,请介绍一下自己")) catch {
    _ => panic()
  }
  let _ = session.send(request_of("第二个问题")) catch { _ => panic() }
  let text = @mooncassette.save(session)
  @fs.write_string_to_file(output_path, text) catch {
    _ => {
      println("failed to write " + output_path)
      return
    }
  }
  let stats = session.stats()
  println("recorded : " + stats.recorded.to_string() + " interaction(s)")
  println("written  : " + output_path)
  println(
    "transport calls during recording: " + transport.call_count().to_string(),
  )

  // ── 第二步:回放 ──────────────────────────────────────────────────
  // 关键点:这个会话**没有** transport,物理上不可能发起真实调用。
  let playback = @mooncassette.replay_session(text) catch {
    error => {
      println("failed to load cassette: " + error.to_string())
      return
    }
  }
  let replayed = playback.send(request_of("你好,请介绍一下自己")) catch {
    error => {
      println("replay failed: " + error.to_string())
      return
    }
  }
  println("replayed : " + @canon.to_canonical_string(replayed.body))
  let playback_stats = playback.stats()
  println("replay hits: " + playback_stats.hits.to_string())

  // ── 第三步:漂移检测 ──────────────────────────────────────────────
  // 模拟「模型换了版本」:同一批请求,第一个问题的答案变了。
  // 注意这里的代码一行没改 —— 输出却变了,这正是漂移检测要捕获的情形。
  let revised_transport = @recorder.MockTransport::new()
  revised_transport.on(
    request_of("你好,请介绍一下自己"),
    chat_response("嗨,我是助手。", 12, 6),
  )
  revised_transport.on(
    request_of("第二个问题"),
    chat_response("这是第二个回答。", 8, 7),
  )
  let revised = @mooncassette.record_session(
    @core.Cassette::new("offline-demo-v2", recorded_at="2026-09-12T09:00:00Z"),
    revised_transport,
  )
  let _ = revised.send(request_of("你好,请介绍一下自己")) catch {
    _ => panic()
  }
  let _ = revised.send(request_of("第二个问题")) catch { _ => panic() }

  // 把 v2 也落盘,这样 CLI 的 `diff` 可以直接用仓库里的两个文件演示。
  @fs.write_string_to_file(drifted_path, @mooncassette.save(revised)) catch {
    _ => println("failed to write " + drifted_path)
  }

  let report = @mooncassette.compare_recordings(
    session.cassette(),
    revised.cassette(),
  )
  println("")
  println("drift report (v1 -> v2):")
  for line in report.lines() {
    println("  " + line)
  }
  println("  " + report.summary())
  println("ok")
}