//! The deterministic evaluation of a title (`extensions/staged-measurement.md` §3–§6,
//! §9, Appendix C): every figure CI may assert, computed the same on every machine.

use serde_json::{json, Value};

use crate::codecs::{capability_media, Media};
use crate::content_dedup::{totals_checked, DedupTotals};
use crate::measure_framing::{count_object, Counted, REGISTERED};
use crate::output_tree::{out_digest, Presentation};
use crate::quality::{self, ImageQuality, PcmQuality, Rgb8, TensorQuality};
use crate::staged::{Samples, UnitBuf};

use std::collections::BTreeMap;

use super::decode::{decode_base, layer_info, replay, DecodedBase, FinalUnit};
use super::title::{Obj, Title};
use super::MeasureError;

fn invalid(s: impl Into<String>) -> MeasureError {
    MeasureError::Invalid(s.into())
}

fn hex(b: &[u8]) -> String {
    b.iter().map(|x| format!("{x:02x}")).collect()
}

/// What a title's units hold, which fixes the quality arithmetic (§4).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum UnitClass {
    /// Pictures and frames, RGB8 (§4.2).
    Rgb8,
    /// PCM, `i16` (§4.3).
    Pcm16,
    /// An integer tensor: a splat's attribute planes (§4.4).
    Tensor,
}

impl UnitClass {
    /// The class of a base capability's units.
    pub fn of(capability: &str) -> Option<UnitClass> {
        match capability_media(capability)? {
            Media::Image | Media::Video => Some(UnitClass::Rgb8),
            Media::Audio => Some(UnitClass::Pcm16),
            Media::Volumetric => Some(UnitClass::Tensor),
            _ => None,
        }
    }
    pub fn token(self) -> &'static str {
        match self {
            UnitClass::Rgb8 => "rgb8",
            UnitClass::Pcm16 => "pcm16",
            UnitClass::Tensor => "tensor-i64",
        }
    }
}

/// A quality figure of one of §4's kinds.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum QualityKind {
    Rgb8(ImageQuality),
    Pcm16(PcmQuality),
    Tensor(TensorQuality),
}

impl QualityKind {
    /// The figure as a record writes it: counts and similarities as numbers, every sum
    /// of squares as a decimal string (§4.3).
    pub fn to_json(&self) -> Value {
        match self {
            QualityKind::Rgb8(q) => json!({"kind": "rgb8", "n_samples": q.n_samples, "sse_rgb": q.sse_rgb.to_string(),
                "sse_y": q.sse_y.to_string(), "ssim_y_q32": q.ssim_y_q32, "windows": q.windows}),
            QualityKind::Pcm16(q) => json!({"kind": "pcm16", "n_samples": q.n_samples, "sse": q.sse.to_string(), "ref_energy": q.ref_energy.to_string()}),
            QualityKind::Tensor(q) => json!({"kind": "tensor-i64", "n": q.n, "sse": q.sse.to_string(), "max_abs": q.max_abs}),
        }
    }
}

fn opt_json(q: Option<QualityKind>) -> Value {
    q.map(|q| q.to_json()).unwrap_or(Value::Null)
}

/// A quality figure pooled over units added one at a time (§4.5), of the title's kind.
/// A unit whose shape differs from its reference's is not added: a stage below an up2
/// or rate layer has no figure against the final shape.
#[derive(Debug, Clone, Copy)]
struct Pool {
    class: UnitClass,
    image: quality::ImagePool,
    pcm: Option<PcmQuality>,
    tensor: Option<TensorQuality>,
}

impl Pool {
    fn new(class: UnitClass) -> Pool {
        Pool { class, image: quality::ImagePool::default(), pcm: None, tensor: None }
    }

    /// Add `test` against the reference `r`, whose shape is `shape`.
    fn add(&mut self, test: &UnitBuf, r: &[u8], shape: (u32, u32, u32)) -> Result<(), MeasureError> {
        if test.shape != shape || test.samples.byte_len() != r.len() as u64 {
            return Ok(());
        }
        let q = |e: quality::QualityError| invalid(format!("quality: {e}"));
        match (self.class, &test.samples) {
            (UnitClass::Rgb8, Samples::U8(t)) => {
                let (h, w) = (shape.0 as usize, shape.1 as usize);
                self.image.add(Rgb8 { w, h, px: r }, Rgb8 { w, h, px: t }).map_err(q)?;
            }
            (UnitClass::Pcm16, Samples::I16(t)) => {
                let rs: Vec<i16> = r.chunks_exact(2).map(|c| i16::from_le_bytes([c[0], c[1]])).collect();
                let x = quality::pcm16(&rs, t).map_err(q)?;
                let acc = self.pcm.get_or_insert(PcmQuality { n_samples: 0, sse: 0, ref_energy: 0 });
                acc.n_samples += x.n_samples;
                acc.sse += x.sse;
                acc.ref_energy += x.ref_energy;
            }
            (UnitClass::Tensor, Samples::U8(t)) => {
                let rs: Vec<i64> = r.iter().map(|&x| x as i64).collect();
                let ts: Vec<i64> = t.iter().map(|&x| x as i64).collect();
                let x = quality::tensor_i64(&rs, &ts).map_err(q)?;
                let acc = self.tensor.get_or_insert(TensorQuality { n: 0, sse: 0, max_abs: 0 });
                acc.n += x.n;
                acc.sse = acc.sse.checked_add(x.sse).ok_or_else(|| invalid("quality: a sum passes 128 bits"))?;
                acc.max_abs = acc.max_abs.max(x.max_abs);
            }
            _ => return Err(invalid("a unit holds samples of another kind than the title's")),
        }
        Ok(())
    }

    fn finish(&self) -> Option<QualityKind> {
        match self.class {
            UnitClass::Rgb8 => self.image.finish().map(QualityKind::Rgb8),
            UnitClass::Pcm16 => self.pcm.map(QualityKind::Pcm16),
            UnitClass::Tensor => self.tensor.map(QualityKind::Tensor),
        }
    }
}

/// One unit's figure against one reference.
fn single(class: UnitClass, test: &UnitBuf, r: &[u8], shape: (u32, u32, u32)) -> Result<Option<QualityKind>, MeasureError> {
    let mut p = Pool::new(class);
    p.add(test, r, shape)?;
    Ok(p.finish())
}

/// One object of the title, counted (§3).
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CountedObject {
    pub path: String,
    pub content_hash: [u8; 32],
    pub capability: String,
    pub base: usize,
    /// 0 for a base.
    pub layer: u8,
    pub from: u64,
    pub count: u64,
    pub counted: Counted,
}

impl CountedObject {
    fn to_json(&self) -> Value {
        json!({
            "file": self.path, "object": hex(&self.content_hash), "capability": self.capability,
            "base": self.base, "layer": self.layer, "units": [self.from, self.count],
            "counted": {"header": self.counted.header, "manifest": self.counted.manifest, "payload": self.counted.payload, "total": self.counted.total},
        })
    }
}

fn count(o: &Obj) -> Result<Counted, MeasureError> {
    count_object(&o.bytes).map_err(|e| invalid(format!("{}: {e}", o.path)))
}

/// One presentation of the full delivery, with its quality.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PresentationResult {
    pub p: Presentation,
    pub shape: (u32, u32, u32),
    pub vs_source: Option<QualityKind>,
    pub vs_final: Option<QualityKind>,
}

/// A rate–quality point of the layering overhead (§6.3).
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct OverheadPoint {
    pub label: String,
    pub counted: u64,
    pub vs_source: Option<QualityKind>,
    /// Each unit's final output digest, in unit order.
    pub out_digests: Vec<[u8; 32]>,
}

/// The prefix of `layers` layer counts (§3.5, §4.1).
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PrefixResult {
    pub layers: u16,
    /// The counted bytes of the objects of this stage.
    pub stage_counted: u64,
    /// The counted bytes of the prefix: every object of this stage and below.
    pub counted: u64,
    /// Per base: (stage counted, prefix counted).
    pub per_base: Vec<(u64, u64)>,
    /// Units that reach this stage.
    pub units: u64,
    pub vs_source: Option<QualityKind>,
    pub vs_final: Option<QualityKind>,
}

/// Everything §12.1 lets CI assert about a title.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Evaluation {
    pub title: String,
    pub content_id: [u8; 32],
    pub framing: &'static str,
    pub unit_us: u64,
    pub class: UnitClass,
    pub bases: Vec<(DecodedBase, Option<u64>, u64)>,
    pub objects: Vec<CountedObject>,
    pub bfpf_min: Option<u64>,
    pub prefixes: Vec<PrefixResult>,
    /// Decoder entries in first-use order: capability, the pin it verified, and the
    /// content hash of the envelope it decoded (staged-measurement Appendix C).
    pub decoders: Vec<(String, Option<String>, [u8; 32])>,
    pub presentations: Vec<PresentationResult>,
    /// The output tree's root and leaf count (Appendix C).
    pub output: Option<crate::output_tree::OutputCommitment>,
    pub overhead: Vec<OverheadPoint>,
    pub dedup: DedupTotals,
}

/// Each unit's slice of the source, at its final shape, in global unit order.
fn split_source<'a>(source: &'a [u8], finals: &[FinalUnit]) -> Result<Vec<&'a [u8]>, MeasureError> {
    let mut at = 0usize;
    let mut out = Vec::new();
    for f in finals {
        let n = f.buf.samples.byte_len() as usize;
        out.push(source.get(at..at + n).ok_or_else(|| invalid("the source is shorter than the units at their final stage"))?);
        at += n;
    }
    if at != source.len() {
        return Err(invalid(format!("the source holds {} bytes; the units at their final stage hold {at}", source.len())));
    }
    Ok(out)
}

/// One stage a unit reached, as the first replay records it: no output, its digest.
struct StageRecord {
    unit: u64,
    layers: u16,
    capability: String,
    pin: Option<String>,
    object: [u8; 32],
    digest: [u8; 32],
    shape: (u32, u32, u32),
}

/// Evaluate a title under its limits. A title that states the code it must be refused
/// with is evaluated all the same; [`check_refusal`] compares.
pub fn evaluate(t: &Title) -> Result<Evaluation, MeasureError> {
    let decoded: Vec<DecodedBase> = t.bases.iter().map(|b| decode_base(b, &t.limits)).collect::<Result<_, _>>()?;
    let class = UnitClass::of(&decoded[0].info.capability).ok_or_else(|| invalid("the base's media has no quality kind"))?;
    if decoded.iter().any(|d| UnitClass::of(&d.info.capability) != Some(class)) {
        return Err(invalid("the bases' units are of different kinds"));
    }
    // §3: every object counted.
    let mut objects = Vec::new();
    for (i, (b, d)) in t.bases.iter().zip(&decoded).enumerate() {
        objects.push(CountedObject {
            path: b.obj.path.clone(),
            content_hash: d.info.content_hash,
            capability: d.info.capability.clone(),
            base: i,
            layer: 0,
            from: d.info.first_unit,
            count: d.units.len() as u64,
            counted: count(&b.obj)?,
        });
    }
    for o in &t.layers {
        let l = layer_info(&o.bytes, &decoded, &o.path)?;
        objects.push(CountedObject {
            path: o.path.clone(),
            content_hash: crate::jwp_hash::content_hash(&o.bytes),
            capability: l.capability,
            base: l.base,
            layer: l.layer,
            from: l.from,
            count: l.count,
            counted: count(o)?,
        });
    }
    let named: Vec<(&str, &[u8])> = t.layers.iter().map(|o| (o.path.as_str(), o.bytes.as_slice())).collect();

    // The first replay: every stage's digest, and each unit's final output.
    let mut records: Vec<StageRecord> = Vec::new();
    let finals = replay(&decoded, &named, &t.limits, &mut |e| {
        records.push(StageRecord {
            unit: e.unit,
            layers: e.layers,
            capability: e.capability.to_string(),
            pin: e.pin.map(str::to_string),
            object: e.object,
            digest: out_digest(&e.buf.samples.canonical_bytes()),
            shape: e.buf.shape,
        });
        Ok(())
    })?;
    let source: Option<Vec<&[u8]>> = t.source.as_ref().map(|s| split_source(&s.bytes, &finals)).transpose()?;
    let index: BTreeMap<u64, usize> = finals.iter().enumerate().map(|(i, f)| (f.unit, i)).collect();
    let final_bytes: Vec<Vec<u8>> = finals.iter().map(|f| f.buf.samples.canonical_bytes()).collect();

    // Decoder entries, in first use as presentations run by (layers, unit).
    records.sort_by_key(|r| (r.layers, r.unit));
    let mut decoders: Vec<(String, Option<String>, [u8; 32])> = Vec::new();
    for r in &records {
        let key = (r.capability.clone(), r.pin.clone(), r.object);
        if !decoders.contains(&key) {
            decoders.push(key);
        }
    }
    records.sort_by_key(|r| (r.unit, r.layers));
    let top = objects.iter().map(|o| o.layer).max().unwrap_or(0) as usize;

    // The second replay: each stage's quality against the source and the final output,
    // and the prefixes' pools; a unit's final output also stands for every prefix above
    // its last stage.
    let mut by_stage: BTreeMap<(u64, u16), (Option<QualityKind>, Option<QualityKind>)> = BTreeMap::new();
    let mut src_pools: Vec<Pool> = vec![Pool::new(class); top + 1];
    let mut fin_pools: Vec<Pool> = vec![Pool::new(class); top + 1];
    replay(&decoded, &named, &t.limits, &mut |e| {
        let i = index[&e.unit];
        let shape = finals[i].buf.shape;
        let k = e.layers as usize - 1;
        let vs_source = match &source {
            Some(src) => {
                src_pools[k].add(e.buf, src[i], shape)?;
                single(class, e.buf, src[i], shape)?
            }
            None => None,
        };
        fin_pools[k].add(e.buf, &final_bytes[i], shape)?;
        by_stage.insert((e.unit, e.layers), (vs_source, single(class, e.buf, &final_bytes[i], shape)?));
        Ok(())
    })?;
    for (i, f) in finals.iter().enumerate() {
        for k in f.layers as usize..=top {
            if let Some(src) = &source {
                src_pools[k].add(&f.buf, src[i], f.buf.shape)?;
            }
            fin_pools[k].add(&f.buf, &final_bytes[i], f.buf.shape)?;
        }
    }
    let mut presentations = Vec::new();
    for r in &records {
        let decoder = decoders.iter().position(|d| d.0 == r.capability && d.1 == r.pin && d.2 == r.object).expect("listed") as u16;
        let (vs_source, vs_final) = by_stage.remove(&(r.unit, r.layers)).ok_or_else(|| invalid("the two replays disagree"))?;
        presentations.push(PresentationResult {
            p: Presentation { unit: r.unit, layers: r.layers, decoder, out_digest: r.digest },
            shape: r.shape,
            vs_source,
            vs_final,
        });
    }
    let ps: Vec<Presentation> = presentations.iter().map(|r| r.p).collect();
    let output = crate::output_tree::commit(&ps).map_err(|e| invalid(e.to_string()))?;

    // §3.5 and §4.1 per prefix.
    let mut prefixes = Vec::new();
    let mut cumulative = vec![0u64; decoded.len()];
    for k in 0..=top {
        let mut per_base = Vec::new();
        for (b, cum) in cumulative.iter_mut().enumerate() {
            let s: u64 = objects.iter().filter(|o| o.base == b && o.layer as usize == k).map(|o| o.counted.total).sum();
            *cum += s;
            per_base.push((s, *cum));
        }
        prefixes.push(PrefixResult {
            layers: k as u16 + 1,
            stage_counted: per_base.iter().map(|p| p.0).sum(),
            counted: per_base.iter().map(|p| p.1).sum(),
            per_base,
            units: finals.iter().filter(|f| f.layers as usize > k).count() as u64,
            vs_source: src_pools[k].finish(),
            vs_final: fin_pools[k].finish(),
        });
    }

    // §5.1: the base that holds unit 0, the one object it needs.
    let bfpf_min = objects.iter().find(|o| o.layer == 0 && o.from == 0 && o.count >= 1).map(|o| o.counted.total);

    // §6: the staged point, then each single-layer reference.
    let mut overhead = Vec::new();
    if let Some(src) = &source {
        let mut staged = Pool::new(class);
        for (i, f) in finals.iter().enumerate() {
            staged.add(&f.buf, src[i], f.buf.shape)?;
        }
        overhead.push(OverheadPoint {
            label: "staged".into(),
            counted: objects.iter().map(|o| o.counted.total).sum(),
            vs_source: staged.finish(),
            out_digests: final_bytes.iter().map(|b| out_digest(b)).collect(),
        });
        for r in &t.references {
            let named: Vec<(&str, &[u8])> = r.layers.iter().map(|o| (o.path.as_str(), o.bytes.as_slice())).collect();
            let rf = replay(&decoded, &named, &t.limits, &mut |_| Ok(()))?;
            let mut counted: u64 = objects.iter().filter(|o| o.layer == 0).map(|o| o.counted.total).sum();
            for o in &r.layers {
                counted += count(o)?.total;
            }
            let mut pool = Pool::new(class);
            for (i, f) in rf.iter().enumerate() {
                pool.add(&f.buf, src[i], finals[i].buf.shape)?;
            }
            overhead.push(OverheadPoint {
                label: r.label.clone(),
                counted,
                vs_source: pool.finish(),
                out_digests: rf.iter().map(|f| out_digest(&f.buf.samples.canonical_bytes())).collect(),
            });
        }
    }

    // §9: every object the title references, a reference's bases counted again.
    let mut items: Vec<([u8; 32], u64)> = objects.iter().map(|o| (o.content_hash, o.counted.total)).collect();
    for r in &t.references {
        items.extend(objects.iter().filter(|o| o.layer == 0).map(|o| (o.content_hash, o.counted.total)));
        for o in &r.layers {
            items.push((crate::jwp_hash::content_hash(&o.bytes), count(o)?.total));
        }
    }
    let dedup = totals_checked(items).map_err(|(h, a, b)| invalid(format!("object {} has two lengths, {a} and {b}", hex(&h))))?;

    let bases = decoded.into_iter().zip(&t.bases).map(|(d, b)| (d, b.capture_us, b.set.iter().map(|s| s.bytes.len() as u64).sum())).collect();
    Ok(Evaluation {
        title: t.name.clone(),
        content_id: t.content_id(),
        framing: REGISTERED,
        unit_us: t.unit_us,
        class,
        bases,
        objects,
        bfpf_min,
        prefixes,
        decoders,
        presentations,
        output,
        overhead,
        dedup,
    })
}

/// One completion of a title (staged-measurement §8.1): every base decoded and every
/// layer applied under the title's limits, as [`evaluate`]'s replay applies them, and
/// nothing measured. Gives the number of (unit, stage) decodes it made — the
/// presentations a session over the title commits to. This is the work a decode time or a
/// decode figure is stated per.
pub fn completion(t: &Title) -> Result<u64, MeasureError> {
    let decoded: Vec<DecodedBase> = t.bases.iter().map(|b| decode_base(b, &t.limits)).collect::<Result<_, _>>()?;
    let named: Vec<(&str, &[u8])> = t.layers.iter().map(|o| (o.path.as_str(), o.bytes.as_slice())).collect();
    let mut stages = 0u64;
    replay(&decoded, &named, &t.limits, &mut |_| {
        stages += 1;
        Ok(())
    })?;
    Ok(stages)
}

/// A title that exists to be refused: its evaluation must fail with the code it states.
pub fn check_refusal(t: &Title) -> Result<(), MeasureError> {
    let Some(want) = &t.refused else { return Err(invalid(format!("{}: states no refusal", t.dir))) };
    match evaluate(t) {
        Err(MeasureError::Refused { code, .. }) if &code == want => Ok(()),
        Err(e) => Err(invalid(format!("{}: refused otherwise than `{want}`: {e}", t.dir))),
        Ok(_) => Err(invalid(format!("{}: evaluated, though it must be refused `{want}`", t.dir))),
    }
}

fn dedup_json(d: &DedupTotals) -> Value {
    json!({"objects": d.objects, "unique": d.unique, "referenced_bytes": d.referenced_bytes, "stored_bytes": d.stored_bytes})
}

impl Evaluation {
    /// The deterministic block: the JSON a corpus's `goldens.json` holds for the title.
    pub fn to_json(&self) -> Value {
        json!({
            "title": self.title,
            "content_id": hex(&self.content_id),
            "framing": self.framing,
            "unit_class": self.class.token(),
            "unit_us": self.unit_us,
            "bases": self.bases.iter().map(|(d, capture, set)| json!({
                "object": hex(&d.info.content_hash), "capability": d.info.capability, "pin": d.pin,
                "tier": crate::staged::tier::token(d.info.tier), "first_unit": d.info.first_unit,
                "capture_us": capture, "units": d.units.len(), "param_set_bytes": set,
            })).collect::<Vec<_>>(),
            "objects": self.objects.iter().map(CountedObject::to_json).collect::<Vec<_>>(),
            "bfpf_min": self.bfpf_min,
            "prefixes": self.prefixes.iter().map(|p| json!({
                "layers": p.layers, "stage_counted": p.stage_counted, "counted": p.counted,
                "per_base": p.per_base.iter().map(|(s, c)| json!({"stage_counted": s, "counted": c})).collect::<Vec<_>>(),
                "units": p.units, "vs_source": opt_json(p.vs_source), "vs_final": opt_json(p.vs_final),
            })).collect::<Vec<_>>(),
            "decoders": self.decoders.iter().map(|(c, p, o)| json!({"capability": c, "pin": p, "object": hex(o)})).collect::<Vec<_>>(),
            "presentations": self.presentations.iter().map(|r| json!({
                "unit": r.p.unit, "layers": r.p.layers, "decoder": r.p.decoder, "shape": [r.shape.0, r.shape.1, r.shape.2],
                "out_sha256": hex(&r.p.out_digest), "vs_source": opt_json(r.vs_source), "vs_final": opt_json(r.vs_final),
            })).collect::<Vec<_>>(),
            "output_root": self.output.map(|c| hex(&c.root)),
            "leaf_count": self.output.map_or(0, |c| c.leaf_count),
            "overhead": self.overhead.iter().map(|o| json!({
                "label": o.label, "counted": o.counted, "vs_source": opt_json(o.vs_source),
                "out_sha256": o.out_digests.iter().map(|d| hex(d)).collect::<Vec<_>>(),
            })).collect::<Vec<_>>(),
            "dedup": dedup_json(&self.dedup),
        })
    }
}

/// Check the evaluation against the fixture tool's own reconstruction digests
/// (`expect.json`): every presentation's digest, and every reference's final digests.
pub fn check_expect(t: &Title, e: &Evaluation) -> Result<(), MeasureError> {
    let Some(x) = &t.expect else { return Ok(()) };
    if x["format"] != super::EXPECT_FORMAT {
        return Err(invalid(format!("{}: expect is not {}", t.dir, super::EXPECT_FORMAT)));
    }
    let got: Vec<Value> = e.presentations.iter().map(|r| json!({"unit": r.p.unit, "layers": r.p.layers, "out_sha256": hex(&r.p.out_digest)})).collect();
    if x["presentations"] != Value::Array(got) {
        return Err(invalid(format!("{}: the decode's presentations are not the fixture tool's reconstruction", t.dir)));
    }
    let refs: Vec<Value> = e.overhead.iter().filter(|o| o.label != "staged").map(|o| json!({"label": o.label, "out_sha256": o.out_digests.iter().map(|d| hex(d)).collect::<Vec<_>>()})).collect();
    if x["references"] != Value::Array(refs) {
        return Err(invalid(format!("{}: the references' decodes are not the fixture tool's reconstruction", t.dir)));
    }
    Ok(())
}

/// Dedup over several titles' evaluations (§9.2, across the record).
pub fn record_dedup(evals: &[(&Title, &Evaluation)]) -> Result<DedupTotals, MeasureError> {
    let mut items = Vec::new();
    for (t, e) in evals {
        items.extend(e.objects.iter().map(|o| (o.content_hash, o.counted.total)));
        for r in &t.references {
            items.extend(e.objects.iter().filter(|o| o.layer == 0).map(|o| (o.content_hash, o.counted.total)));
            for o in &r.layers {
                items.push((crate::jwp_hash::content_hash(&o.bytes), count(o)?.total));
            }
        }
    }
    totals_checked(items).map_err(|(h, a, b)| invalid(format!("object {} has two lengths, {a} and {b}", hex(&h))))
}

/// The record-wide dedup block of `goldens.json`.
pub fn record_dedup_json(d: &DedupTotals) -> Value {
    dedup_json(d)
}
