//! `wai_world_meter` — the native sink-metering path for §8 joules.
//!
//! Brackets the CPU energy a sink spends replaying a sealed
//! `wai.world.replay`, with no elevated privileges: it samples the
//! processor power the non-privileged reader `macmon` reports, brackets
//! a tight replay burst against an idle baseline, and reports the
//! **marginal** CPU energy attributable to the replay. That power is one
//! the reader reports from a channel it selects, not a counter this meter
//! reads (on macOS 27 the processor channels of IOReport's `Energy Model`
//! group stop updating for an unprivileged reader), so the figure is
//! `ModelBased`, never `OnChipCounter` (energy-measurement §3.1;
//! `wai::energy_burst::PowerSource`).
//!
//!   wai_world_meter <sealed.wwld> [receipt_out.json [report_out.json]]
//!                   [--no-report] [--previous-report <report.json>]
//!
//! Output: bracketed microjoules per replay (and per tick), the figure
//! that fills a receipt's `joules_micro` with its class. With a second
//! argument the meter also emits a full §8 `SessionReceipt` carrying
//! that figure and its class, signed and self-verified.
//!
//! This is a bracket with stated bounds: the reported CPU power is
//! SoC-wide, so the meter reports the DELTA over an otherwise-idle
//! machine (busy − idle mean power × window ÷ the replays the window paid
//! for; see `wai::energy_burst`). Run it on a quiet box for a clean number;
//! it never fabricates one. A usable figure is sealed with its acquisition
//! class, `ModelBased`; without `macmon`, or when the reported power did not
//! move (§3.1), or the §4 ratio cannot support a figure, the receipt is sealed
//! UNMETERED:
//! `joules_micro` carries the unmetered marker `0`, with no class, never a
//! figure of zero joules.
//!
//! Beside a measured figure it writes a measurement report (energy-measurement
//! §6): a `measurement` claim, signed with the receipt's key, carrying the
//! bracket's values, the build and the power reader — beside the receipt
//! (`<receipt>.report.json`), at the path given, or to standard output when no
//! receipt path is given. `--no-report` turns it off, and says so.

use ed25519_dalek::SigningKey;
use wai::energy_burst::{measure_with_macmon, power_reader};
use wai::energy_figure::meter_report;
use wai::energy_class::CounterFiltering;
use wai::world::feed::CAP_SIM_FEED0;
use wai::world::sim::{CAP_SIM_BALLISTIC0, CAP_SIM_KINEMATIC0};
use wai::world::skeletal::CAP_SIM_SKELETAL0;
use wai::world::truss::CAP_SIM_TRUSS0;
use wai::world::{
    collect_checkpoints_and_work, run_sealed_dispatch, world_hash, Ballistic0, Checkpoint,
    FeedState, Kinematic0, SessionReceipt, Skeletal0, Truss0, WorldContainer,
};

fn main() {
    let report_args = meter_report::parse_args_or_exit(&std::env::args().collect::<Vec<_>>());
    let args = &report_args.positional;
    let Some(path) = args.get(1) else {
        eprintln!("usage: wai_world_meter <sealed.wwld> [receipt_out.json [report_out.json]] [--no-report] [--previous-report <report.json>]");
        std::process::exit(2);
    };

    let bytes = std::fs::read(path).expect("read wwld");
    let container = WorldContainer::from_bytes(&bytes).expect("parse wwld");
    if container.seal.is_none() {
        eprintln!("not a sealed wai.world.replay (no seal section)");
        std::process::exit(2);
    }
    run_sealed_dispatch(&container).expect("replay verifies");
    let final_tick = container.seal.as_ref().unwrap().final_tick;

    eprintln!("metering {path} ({final_tick} ticks/replay), no sudo — the processor power macmon reports (ModelBased)");

    // The §3 protocol (energy_burst): an idle bracket, then replays on several
    // threads released together from a start barrier, counted per replay —
    // never per batch, which would collapse the count for a heavy world — with
    // a busy bracket over them.
    let mut values = None;
    let (joules_micro, class) = match measure_with_macmon("replay", &container, |c| {
        let _ = run_sealed_dispatch(c);
    }) {
        None => {
            eprintln!(
                "macmon unavailable or too few samples; sealing UNMETERED (joules_micro carries the unmetered marker 0). \
                 Install `brew install macmon` (the non-sudo power reader this meter samples)."
            );
            (0, None)
        }
        Some(b) => {
            values = b.values();
            println!("== wai_world_meter — CPU energy bracket (the power macmon reports, no sudo): a figure only if the gate below seals it ==");
            print!("{}", b.report("replay"));
            if let Some(per_replay_uj) = b.per_unit_uj() {
                println!("→ per replay        : {per_replay_uj:.1} µJ   ({final_tick} ticks)");
                println!("→ per tick          : {:.3} µJ", per_replay_uj / final_tick as f64);
            }
            b.warn_if_unpublishable();
            // A reported power seals `ModelBased`, never `OnChipCounter` (§3.1).
            match b.figure(CounterFiltering::Unknown) {
                Some((uj, class)) => {
                    println!("joules_micro (receipt, {}): {uj}", class.label());
                    (uj, Some(class))
                }
                None => {
                    eprintln!(
                        "no usable figure obtained ({:.1}x noise: {}); sealing \
                         UNMETERED rather than a number the measurement does not support.",
                        b.ratio(),
                        b.unsealed_reason().unwrap_or("refused")
                    );
                    println!("joules_micro (receipt, UNMETERED): 0, the unmetered marker: the bracket above is no figure this receipt carries");
                    (0, None)
                }
            }
        }
    };

    // The §8 receipt carrying the bracketed energy, written when a path is
    // given; its report is written whether or not it is.
    {
        let (checkpoints, work_steps) = checkpoints_and_work(&container, final_tick);
        // A native sink signs with its own identity key; this metering
        // tool uses a fixed, clearly-labeled demo signer.
        let signer = SigningKey::from_bytes(&[0x6du8; 32]); // 'm' for meter
        let wh = world_hash(&container.contract_bytes, &container.graph_bytes);
        // A demo signer whose seed is published: a label, never origin-bound (jwp-receipts §7.1, §7.8).
        let signer_id = "did:example:wai-world-meter";
        let receipt = SessionReceipt::build(
            &signer,
            signer_id,
            wh, 0, &container.ops, &checkpoints, joules_micro, work_steps, None,
        );
        let receipt = match class {
            Some(c) => receipt.with_energy_class(&signer, c).expect("a usable figure is a valid label"),
            None => receipt,
        };
        assert!(receipt.verify(), "emitted receipt must verify");
        if let Some(out) = args.get(2) {
            std::fs::write(out, receipt.to_json()).expect("write receipt");
            let tag = receipt.energy_provenance.as_ref().map_or("UNMETERED", |c| c.label());
            println!("receipt           : {out}  (verified, joules_micro {tag}, work_steps {work_steps})");
        }
        meter_report::write(
            &report_args,
            &receipt,
            None,
            values,
            power_reader(),
            &signer,
            signer_id,
            args.get(2).map(String::as_str),
            args.get(3).map(String::as_str),
        );
    }
}

/// Dispatch `collect_checkpoints_and_work` on the contract's sim.
fn checkpoints_and_work(c: &WorldContainer, final_tick: u64) -> (Vec<Checkpoint>, u64) {
    let r = match c.contract.sim.as_str() {
        CAP_SIM_KINEMATIC0 => collect_checkpoints_and_work(&Kinematic0, c, final_tick),
        CAP_SIM_BALLISTIC0 => collect_checkpoints_and_work(&Ballistic0, c, final_tick),
        CAP_SIM_TRUSS0 => collect_checkpoints_and_work(&Truss0, c, final_tick),
        CAP_SIM_FEED0 => collect_checkpoints_and_work(&FeedState, c, final_tick),
        CAP_SIM_SKELETAL0 => collect_checkpoints_and_work(&Skeletal0, c, final_tick),
        other => {
            eprintln!("unknown sim `{other}`");
            std::process::exit(1);
        }
    };
    r.expect("collect checkpoints")
}
