///|
/// Ledger verification and reporting.
///
/// The ledger is the gateway's evidence: a JSONL stream where every entry
/// commits to the previous one. `verify_ledger` recomputes the chain, so a
/// reviewer can check that a run was not edited after the fact. `report_ledger`
/// turns the same stream into the aggregate a human reads: what ran, what was
/// blocked, and why.
pub fn verify_ledger(lines : Array[String]) -> Json {
  let mut prev = ""
  let mut index = 0
  for line in lines {
    if line.trim().is_empty() {
      continue
    }
    let event = match json_parse_opt(line) {
      Some(value) => value
      None =>
        return json_obj([
          ("ok", json_bool(false)),
          ("entries", json_int(index)),
          ("reason", json_str("entry \{index} is not valid JSON")),
        ])
    }
    let recorded_prev = match string_field(event, "prev") {
      Some(value) => value
      None => ""
    }
    let recorded_hash = match string_field(event, "hash") {
      Some(value) => value
      None => ""
    }
    if recorded_prev != prev {
      return json_obj([
        ("ok", json_bool(false)),
        ("entries", json_int(index)),
        ("reason", json_str("entry \{index} does not follow the previous hash")),
      ])
    }
    let body = match json_as_object(event) {
      Some(fields) => {
        let copy : Map[String, Json] = Map([])
        for key, value in fields {
          if key != "hash" {
            copy.set(key, value)
          }
        }
        Json::object(copy)
      }
      None =>
        return json_obj([
          ("ok", json_bool(false)),
          ("entries", json_int(index)),
          ("reason", json_str("entry \{index} is not an object")),
        ])
    }
    let expected = sha256_hex(prev + "\n" + json_canonical(body))
    if expected != recorded_hash {
      return json_obj([
        ("ok", json_bool(false)),
        ("entries", json_int(index)),
        ("reason", json_str("entry \{index} hash mismatch")),
      ])
    }
    prev = recorded_hash
    index += 1
  }
  json_obj([
    ("ok", json_bool(true)),
    ("entries", json_int(index)),
    ("head", json_str(prev)),
    ("reason", json_str("chain intact")),
  ])
}

///|
fn event_reason(event : Json) -> String {
  match string_field(event, "reason") {
    Some(reason) => reason
    None => ""
  }
}

///|
fn event_tool(event : Json) -> String {
  match string_field(event, "tool") {
    Some(tool) => tool
    None => ""
  }
}

///|
fn event_call(event : Json) -> String {
  match string_field(event, "call") {
    Some(call) => call
    None => ""
  }
}

///|
fn event_decision(event : Json) -> String {
  match string_field(event, "decision") {
    Some(decision) => decision
    None => ""
  }
}

///|
fn event_rule(event : Json) -> String {
  match string_field(event, "rule") {
    Some(rule) => rule
    None => ""
  }
}

///|
fn event_seq(event : Json) -> Int32 {
  match int_field(event, "seq") {
    Some(seq) => seq
    None => 0
  }
}

///|
fn event_ts(event : Json) -> Int64 {
  match json_get(event, "ts") {
    Some(Number(n, ..)) => n.to_int64()
    _ => 0L
  }
}

///|
pub fn report_ledger(lines : Array[String]) -> Json {
  let mut allowed = 0
  let mut denied = 0
  let mut approval_requests = 0
  let mut granted = 0
  let mut rejected = 0
  let mut results = 0
  let mut result_bytes = 0
  let mut new_values = 0
  let mut entries = 0
  let mut policy_name = ""
  let mut policy_digest = ""
  let blocked : Array[Json] = []
  let timeline : Array[Json] = []
  for line in lines {
    if line.trim().is_empty() {
      continue
    }
    let event = match json_parse_opt(line) {
      Some(value) => value
      None => continue
    }
    entries += 1
    let kind = match string_field(event, "kind") {
      Some(kind) => kind
      None => ""
    }
    if kind == "session_open" {
      policy_name = match string_field(event, "policy") {
        Some(name) => name
        None => ""
      }
      policy_digest = match string_field(event, "policy_digest") {
        Some(digest) => digest
        None => ""
      }
    }
    if kind == "decision" {
      match event_decision(event) {
        "allow" => allowed += 1
        "approval" => approval_requests += 1
        "deny" => {
          denied += 1
          let evidence = match json_get(event, "evidence") {
            Some(Array(items)) => items.length()
            _ => 0
          }
          blocked.push(
            json_obj([
              ("seq", json_int(event_seq(event))),
              ("ts", json_i64(event_ts(event))),
              ("tool", json_str(event_tool(event))),
              ("call", json_str(event_call(event))),
              ("rule", json_str(event_rule(event))),
              ("reason", json_str(event_reason(event))),
              ("evidence", json_int(evidence)),
            ]),
          )
        }
        _ => ()
      }
    }
    if kind == "approval" {
      match event_decision(event) {
        "granted" => granted += 1
        "denied" | "timeout" => rejected += 1
        _ => ()
      }
    }
    if kind == "result" {
      results += 1
      result_bytes += match int_field(event, "bytes") {
        Some(bytes) => bytes
        None => 0
      }
      new_values += match int_field(event, "new_values") {
        Some(count) => count
        None => 0
      }
    }
    timeline.push(
      json_obj([
        ("seq", json_int(event_seq(event))),
        ("ts", json_i64(event_ts(event))),
        ("kind", json_str(kind)),
        ("tool", json_str(event_tool(event))),
        ("call", json_str(event_call(event))),
        ("decision", json_str(event_decision(event))),
        ("rule", json_str(event_rule(event))),
        ("reason", json_str(event_reason(event))),
      ]),
    )
  }
  json_obj([
    ("entries", json_int(entries)),
    (
      "policy",
      json_obj([
        ("name", json_str(policy_name)),
        ("digest", json_str(policy_digest)),
      ]),
    ),
    (
      "decisions",
      json_obj([
        ("allow", json_int(allowed)),
        ("deny", json_int(denied)),
        ("approval_required", json_int(approval_requests)),
        ("executed", json_int(allowed + granted)),
      ]),
    ),
    (
      "approvals",
      json_obj([
        ("granted", json_int(granted)),
        ("rejected", json_int(rejected)),
      ]),
    ),
    (
      "results",
      json_obj([
        ("count", json_int(results)),
        ("bytes", json_int(result_bytes)),
        ("new_sensitive_values", json_int(new_values)),
      ]),
    ),
    ("blocked", json_arr(blocked)),
    ("timeline", json_arr(timeline)),
    ("chain", verify_ledger(lines)),
  ])
}