// Case B: 1,250 fresh local-model requests, five balanced model-order rounds.
// No retries, digit extraction, seeded-RNG claim, or conversation history.
// Keep the raw UTF-8 hex traces and -1 usage fields when aggregating results.
import aner.llm
let models = List("llama3.1:latest", "llama3.2:latest", "mistral:latest", "qwen2.5:14b-instruct", "qwen3.8:latest")
let rounds = 5
let calls_per_round = 50
let max_tokens = 256
let temperature = 1.0
let timeout_ms = 120_000
let prompt = "Pick a number between 1 and 10. Return only the integer."
fn integer_choice(text: String) -> Int64 {
    for value in Range(1, 11) { if text.trim() == String(value) { return value } }
    return 0
}
// Per-model accounting includes rejected choices and reported zero usage.
// A failed request stops the study; summaries are emitted only after completion.
let attempted = Array<Int64>(models.len(), 0)
let accepted = Array<Int64>(models.len(), 0)
let counts = Array<Int64>(models.len() * 10, 0)
let finish_not_stop = Array<Int64>(models.len(), 0)
let empty = Array<Int64>(models.len(), 0)
let noncanonical = Array<Int64>(models.len(), 0)
let usage_names = List("input_tokens", "output_tokens", "total_tokens", "cached_input_tokens", "cache_write_input_tokens", "reasoning_tokens")
let usage_known_total = Array<Int64>(models.len() * 6, 0)
let usage_missing_count = Array<Int64>(models.len() * 6, 0)

// Hex encoding preserves exact visible response strings without JSON escaping.
// Missing usage is -1, distinct from a reported zero; details are not additive.
var model_hex_json = "["
for position in Range(models.len()) {
    if position > 0 { model_hex_json = model_hex_json + "," }
    model_hex_json = model_hex_json + "\"" + Bytes.encode_utf8(text: cast<String>(models.get(position))).hex() + "\""
}
model_hex_json = model_hex_json + "]"
print("TNC_LLM_STUDY_V1 {\"schema\":\"traceable-number-choices/llm-study/1\",\"case\":\"B\",\"provider\":\"ollama\",\"endpoint_scope\":\"loopback\",\"models_hex\":" + model_hex_json
    + ",\"rounds\":" + String(rounds) + ",\"calls_per_model_round\":" + String(calls_per_round)
    + ",\"planned_requests\":" + String(rounds * models.len() * calls_per_round)
    + ",\"max_tokens\":" + String(max_tokens) + ",\"temperature\":" + String(temperature)
    + ",\"timeout_ms\":" + String(timeout_ms)
    + ",\"prompt_hex\":\"" + Bytes.encode_utf8(text: prompt).hex()
    + "\",\"unknown_usage_sentinel\":-1,\"history\":\"fresh-single-user-message\",\"classification\":\"finish-stop-and-trimmed-exact-integer-1-through-10\",\"elapsed_scope\":\"native-generate-call\"}")

for round in Range(rounds) {
  for position in Range(models.len()) {
    let model_index = (position + round) % models.len()
    let name = cast<String>(models.get(model_index))
    let model = llm.client(provider: "ollama", model: name, endpoint: "http://127.0.0.1:11434", timeout_ms: timeout_ms)
    for request in Range(calls_per_round) {
        attempted[model_index] += 1
        let started = Duration.monotonic()
        let answer = model.generate(prompt, max_tokens: max_tokens, temperature: temperature)
        let elapsed_ns = Duration.monotonic().subtract(started).nanoseconds()
        let raw_text = answer.text()
        let finish = answer.finish_reason()
        let usage = List(answer.input_tokens(), answer.output_tokens(), answer.total_tokens(),
            answer.cached_input_tokens(), answer.cache_write_input_tokens(), answer.reasoning_tokens())
        // Begin native response aggregation: finish, empty, then noncanonical.
        var value = 0
        if finish != "stop" { finish_not_stop[model_index] += 1 }
        else if raw_text.trim() == "" { empty[model_index] += 1 }
        else {
            value = integer_choice(raw_text)
            if value == 0 { noncanonical[model_index] += 1 }
            else {
                accepted[model_index] += 1
                counts[model_index * 10 + value - 1] += 1
            }
        }
        for category in Range(6) {
            let tokens = cast<Int64>(usage.get(category))
            let slot = model_index * 6 + category
            if tokens == -1 { usage_missing_count[slot] += 1 }
            else { usage_known_total[slot] += tokens }
        }
        // End native response aggregation; preserve every raw request field.
        print("TNC_LLM_V1 {\"schema\":\"traceable-number-choices/llm-request/1\",\"case\":\"B\",\"request_id\":" + String((round * models.len() + position) * calls_per_round + request + 1) + ",\"round\":" + String(round + 1)
            + ",\"position\":" + String(position + 1) + ",\"request\":" + String(request + 1)
            + ",\"model\":\"" + name + "\",\"returned_model_hex\":\"" + Bytes.encode_utf8(text: answer.model()).hex()
            + "\",\"finish_hex\":\"" + Bytes.encode_utf8(text: answer.finish_reason()).hex()
            + "\",\"text_hex\":\"" + Bytes.encode_utf8(text: answer.text()).hex()
            + "\",\"value\":" + String(value) + ",\"elapsed_ns\":" + String(elapsed_ns)
            + ",\"input_tokens\":" + String(answer.input_tokens()) + ",\"output_tokens\":" + String(answer.output_tokens())
            + ",\"total_tokens\":" + String(answer.total_tokens()) + ",\"cached_input_tokens\":" + String(answer.cached_input_tokens())
            + ",\"cache_write_input_tokens\":" + String(answer.cache_write_input_tokens())
            + ",\"reasoning_tokens\":" + String(answer.reasoning_tokens()) + "}")
    }
  }
}

// Native end-of-study summaries; external provenance supplies model digests.
for model_index in Range(models.len()) {
    var histogram_json = "["
    var counted = 0
    for face in Range(10) {
        if face > 0 { histogram_json = histogram_json + "," }
        let count = counts[model_index * 10 + face]
        histogram_json = histogram_json + String(count)
        counted += count
    }
    histogram_json = histogram_json + "]"
    let rejected = finish_not_stop[model_index] + empty[model_index] + noncanonical[model_index]
    if counted != accepted[model_index] || accepted[model_index] + rejected != attempted[model_index]
        || attempted[model_index] != rounds * calls_per_round {
        print("FAILED: Case B model accounting")
        print(integer_div(1, 0))
    }
    var usage_json = "{"
    for category in Range(6) {
        if category > 0 { usage_json = usage_json + "," }
        let slot = model_index * 6 + category
        usage_json = usage_json + "\"" + cast<String>(usage_names.get(category))
            + "\":{\"known_total\":" + String(usage_known_total[slot])
            + ",\"missing_count\":" + String(usage_missing_count[slot]) + "}"
    }
    usage_json = usage_json + "}"
    print("TNC_LLM_SUMMARY_V1 {\"schema\":\"traceable-number-choices/llm-summary/1\",\"case\":\"B\",\"model\":\""
        + cast<String>(models.get(model_index)) + "\",\"attempted\":" + String(attempted[model_index])
        + ",\"accepted\":" + String(accepted[model_index]) + ",\"rejected\":" + String(rejected)
        + ",\"counts\":" + histogram_json + ",\"rejections\":{\"finish_not_stop\":" + String(finish_not_stop[model_index])
        + ",\"empty\":" + String(empty[model_index]) + ",\"noncanonical\":" + String(noncanonical[model_index])
        + "},\"usage\":" + usage_json + "}")
}
