//! `wai_meter` — the native sink-metering path for the non-world cargo
//! classes (haptics, 4D splats, scores, linear film). Brackets the CPU energy
//! a sink spends reconstructing a signal / clip / mixdown / frame sequence,
//! with no elevated privileges (the processor power the reader `macmon`
//! reports), against an idle baseline, and emits a signed receipt carrying
//! that figure and its class. The 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_meter <file.whap | file.w4ds | file.wscr | file.wflm | file.wrel>
//!             [receipt_out.json [report_out.json]] [--no-report] [--previous-report <report.json>]
//!
//! World classes (worlds / state-feeds / avatars) are metered by
//! `wai_world_meter`. A linear film embeds a sealed world but reconstructs a
//! *frame sequence* (sample the world at each frame tick + the camera pose),
//! so it is metered here with the other reconstruct classes.
//! It is a bracket with stated bounds (SoC-wide power → the marginal
//! delta over an idle machine ÷ the reconstructions the window paid for; see
//! `wai::energy_burst`); it never fabricates a number — run it on a quiet box
//! for a clean 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_class::{CounterFiltering, EnergyProvenance};
use wai::energy_figure::{meter_report, EnergyFigures};
use wai::energy_report::BracketValues;
use wai::film::{Film, Reel};
use wai::haptic::{HapticReceipt, HapticSignal};
use wai::score::{Score, ScoreReceipt};
use wai::splat4d::{Splat4dClipData, Splat4dReceipt};

const SIGNER_SEED: u8 = 0x6d; // 'm' for meter
// A demo signer whose seed is published: a label, never origin-bound (jwp-receipts §7.1, §7.8).
const SIGNER_ID: &str = "did:example:wai-meter";

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_meter <file.whap | file.w4ds | file.wscr | file.wflm | file.wrel> \
             [receipt_out.json [report_out.json]] [--no-report] [--previous-report <report.json>]"
        );
        std::process::exit(2);
    };
    let out = Out { args: &report_args };
    let bytes = std::fs::read(path).expect("read file");
    let signer = SigningKey::from_bytes(&[SIGNER_SEED; 32]);

    if path.ends_with(".whap") {
        let sig = HapticSignal::from_bytes(&bytes).expect("parse whap");
        let work = sig.contract.duration * sig.contract.channels as u64;
        eprintln!("metering {path} (haptic: {} samples × {} ch)", sig.contract.duration, sig.contract.channels);
        let (uj, class, values) = meter("reconstruct", &sig, work, "samples×ch", |s| {
            for c in 0..s.channels.len() {
                std::hint::black_box(s.reconstruct_channel(c));
            }
        });
        {
            let r = HapticReceipt::build(&signer, SIGNER_ID, sig.signal_hash(), 0, sig.channel_hashes(), work, uj, None);
            let r = match class {
                Some(c) => r.with_energy_class(&signer, c).expect("a usable figure is a valid label"),
                None => r,
            };
            assert!(r.verify(), "emitted receipt must verify");
            out.emit(&r, &r.to_json(), r.energy_provenance.as_ref(), work, values, &signer);
        }
    } else if path.ends_with(".w4ds") {
        let clip = Splat4dClipData::from_bytes(&bytes).expect("parse w4ds");
        let work = clip.contract.n_frames * clip.contract.n_gaussians as u64;
        eprintln!("metering {path} (4D splat: {} frames × {} Gaussians)", clip.contract.n_frames, clip.contract.n_gaussians);
        let (uj, class, values) = meter("reconstruct", &clip, work, "frames×gaussians", |c| {
            for f in 0..c.contract.n_frames {
                std::hint::black_box(c.frame_hash(f));
            }
        });
        {
            let r = Splat4dReceipt::build(&signer, SIGNER_ID, clip.clip_hash(), 0, clip.frame_hashes(), work, uj, None);
            let r = match class {
                Some(c) => r.with_energy_class(&signer, c).expect("a usable figure is a valid label"),
                None => r,
            };
            assert!(r.verify(), "emitted receipt must verify");
            out.emit(&r, &r.to_json(), r.energy_provenance.as_ref(), work, values, &signer);
        }
    } else if path.ends_with(".wscr") {
        let score = Score::from_bytes(&bytes).expect("parse wscr");
        let work = score.contract.duration * score.notes.len() as u64;
        eprintln!("metering {path} (score: {} notes → {} samples)", score.notes.len(), score.contract.duration);
        let (uj, class, values) = meter("reconstruct", &score, work, "samples×notes", |s| {
            std::hint::black_box(s.mixdown());
        });
        {
            let r = ScoreReceipt::build(&signer, SIGNER_ID, score.score_hash(), 0, score.segment_hashes(), work, uj, None);
            let r = match class {
                Some(c) => r.with_energy_class(&signer, c).expect("a usable figure is a valid label"),
                None => r,
            };
            assert!(r.verify(), "emitted receipt must verify");
            out.emit(&r, &r.to_json(), r.energy_provenance.as_ref(), work, values, &signer);
        }
    } else if path.ends_with(".wflm") {
        let film = Film::from_bytes(&bytes).expect("parse wflm");
        let work = film.contract.frames;
        eprintln!("metering {path} (film: {} frames × ticks_per_frame {})", film.contract.frames, film.contract.ticks_per_frame);
        let (uj, class, values) = meter("reconstruct", &film, work, "frames", |f| {
            std::hint::black_box(f.frame_hashes().expect("frame hashes"));
        });
        {
            let r = film.receipt(&signer, SIGNER_ID, 0, uj, None).expect("receipt");
            let r = match class {
                Some(c) => r.with_energy_class(&signer, c).expect("a usable figure is a valid label"),
                None => r,
            };
            assert!(r.verify(), "emitted receipt must verify");
            out.emit(&r, &r.to_json(), r.energy_provenance.as_ref(), work, values, &signer);
        }
    } else if path.ends_with(".wrel") {
        let reel = Reel::from_bytes(&bytes).expect("parse wrel");
        let work = reel.total_frames();
        eprintln!("metering {path} (reel: {} shots → {} frames)", reel.shots.len(), work);
        let (uj, class, values) = meter("reconstruct", &reel, work, "frames", |r| {
            std::hint::black_box(r.frame_hashes().expect("frame hashes"));
        });
        {
            let r = reel.receipt(&signer, SIGNER_ID, 0, uj, None).expect("receipt");
            let r = match class {
                Some(c) => r.with_energy_class(&signer, c).expect("a usable figure is a valid label"),
                None => r,
            };
            assert!(r.verify(), "emitted receipt must verify");
            out.emit(&r, &r.to_json(), r.energy_provenance.as_ref(), work, values, &signer);
        }
    } else {
        eprintln!("unrecognized file (want .whap, .w4ds, .wscr, .wflm, or .wrel); world classes → wai_world_meter");
        std::process::exit(2);
    }
}

/// Run the §3 protocol on `item` (`energy_burst`), print the report §6
/// requires, and return what the receipt seals: `(joules_micro, Some(class))`
/// for a usable figure (`ModelBased`: the samples are a reported power),
/// `(0, None)` — the unmetered marker — otherwise;
/// and the bracket's values for the measurement report.
fn meter<T, W>(verb: &str, item: &T, work: u64, unit: &str, one: W) -> (u64, Option<EnergyProvenance>, Option<BracketValues>)
where
    T: Clone + Send + 'static,
    W: Fn(&T) + Send + Sync + 'static,
{
    let Some(b) = measure_with_macmon(verb, item, one) else {
        eprintln!(
            "macmon unavailable or too few samples; sealing UNMETERED (joules_micro carries the unmetered marker 0). \
             Install `brew install macmon` (a non-sudo power reader) for a ModelBased figure."
        );
        return (0, None, None);
    };
    let values = b.values();
    println!("== wai_meter — CPU energy bracket (the power macmon reports, no sudo): a figure only if the gate below seals it ==");
    print!("{}", b.report(verb));
    if let Some(per) = b.per_unit_uj() {
        println!("→ per {verb}   : {per:.2} µJ   (work {work} {unit})");
    }
    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), values)
        }
        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, values)
        }
    }
}

/// Where a sealed receipt and its report go.
struct Out<'a> {
    args: &'a meter_report::ReportArgs,
}

impl Out<'_> {
    /// Write the receipt when a path was given, and its report by default.
    fn emit<R: EnergyFigures>(&self, r: &R, json: &str, class: Option<&EnergyProvenance>, work: u64, values: Option<BracketValues>, signer: &SigningKey) {
        let receipt_out = self.args.positional.get(2).map(String::as_str);
        if let Some(out) = receipt_out {
            std::fs::write(out, json).expect("write receipt");
            let tag = class.map_or("UNMETERED", |c| c.label());
            println!("receipt           : {out}  (verified, joules_micro {tag}, work {work})");
        }
        meter_report::write(
            self.args,
            r,
            None,
            values,
            power_reader(),
            signer,
            SIGNER_ID,
            receipt_out,
            self.args.positional.get(3).map(String::as_str),
        );
    }
}
