//! Criterion micro-bench for the parity-critical content-addressing surface
//! (`PLAN/08_PERFORMANCE_AND_MEMORY_MODEL.md` §6.3 — "SHA-256 canonical-JSON
//! session hashing").
//!
//! Measures the two hot operations the runtime performs once per appended
//! session-DAG node:
//!   - `canonical_json` — the character-faithful `JSON.stringify` encoder, and
//!   - `content_hash`   — `hex(sha256(canonical_json(node)))[..32]`.
//!
//! The representative node is the cross-language golden from the parity test in
//! `canonical.rs`: a `{parent, turn{role, blocks[text, tool_call]}, createdAt}`
//! shape (text block with multibyte unicode + a tool_call with a mixed-number
//! input map). A larger synthetic node (many blocks) bounds the per-node cost as
//! a session DAG grows. Per §6.3 the load-bearing test is the byte-exact fixture
//! (already in `canonical.rs`); this bench is the speed companion to it.

use criterion::{Criterion, criterion_group, criterion_main};
use indusagi_core::{canonical_json, content_hash};
use serde_json::{Value, json};
use std::hint::black_box;

/// The representative session node — the exact golden value the parity test
/// hashes (`sha256(...)[..32] == 49a843a2b9dfc7f04a4fe5be52b90bfa`).
fn golden_node() -> Value {
    json!({
        "parent": "abc123",
        "turn": {
            "role": "assistant",
            "blocks": [
                { "kind": "text", "text": "Hello 世界 😀" },
                {
                    "kind": "tool_call",
                    "id": "call_0",
                    "name": "bash",
                    "input": { "cmd": "ls", "n": 42, "ratio": 0.5, "big": 1e21, "tiny": 1e-7, "neg": -3.25 }
                }
            ]
        },
        "createdAt": 1718000000000i64
    })
}

/// A larger node: an assistant turn with many text blocks + tool calls, the
/// shape a long agent turn produces. Bounds the per-node cost at the high end.
fn large_node() -> Value {
    let mut blocks: Vec<Value> = Vec::new();
    for i in 0..40 {
        blocks.push(json!({
            "kind": "text",
            "text": format!("Step {i}: reading file and reasoning about the change 世界 — line {i} of the streamed answer.")
        }));
        blocks.push(json!({
            "kind": "tool_call",
            "id": format!("call_{i}"),
            "name": "edit",
            "input": {
                "path": format!("src/module_{i}.rs"),
                "old": format!("fn old_{i}() {{ /* … */ }}"),
                "new": format!("fn new_{i}() {{ /* refactored */ }}"),
                "n": i,
                "ratio": 0.5
            }
        }));
    }
    json!({
        "parent": "deadbeefcafef00d",
        "turn": { "role": "assistant", "blocks": blocks },
        "createdAt": 1718000000123i64
    })
}

fn bench_canonical_and_hash(c: &mut Criterion) {
    let golden = golden_node();
    let large = large_node();

    // canonical_json (the JSON.stringify-faithful encoder) on the rep node.
    c.bench_function("canonical_json/golden_node", |b| {
        b.iter(|| black_box(canonical_json(black_box(&golden))));
    });

    // The full content-addressing path: canonical_json + sha256 + hex truncate.
    c.bench_function("content_hash/golden_node", |b| {
        b.iter(|| black_box(content_hash(black_box(&golden))));
    });

    // Same path on a large multi-block turn — bounds the high end.
    c.bench_function("content_hash/large_node", |b| {
        b.iter(|| black_box(content_hash(black_box(&large))));
    });
}

criterion_group!(benches, bench_canonical_and_hash);
criterion_main!(benches);
