//! Platform decode capabilities ([platform-decode](../../extensions/platform-decode.md)):
//! what a sink asks a web platform about an entry it could decode through the platform's
//! own decoders — Media Capabilities `decodingInfo()` and WebCodecs `isConfigSupported()` —
//! read from the entry's payload, and how the answers enter browser-sink's capability
//! report.
//!
//! - [`query`]: the configuration a sink asks about for an entry (§2): the codec string,
//!   the size, the frame rate and the bit rate, read from the payload, never from what an
//!   encoder declared.
//! - [`report_answer`]: the answer in browser-sink §3's shape (§3): `supported` from
//!   WebCodecs, `smooth` and `power_efficient` from Media Capabilities only once WebCodecs
//!   said supported. Hints, never an energy figure and never evidence of hardware.
//! - [`HardwareAcceleration`]: WebCodecs' preference, a hint the user agent may ignore.
//!
//! The order the answers feed is SPEC §4's energy order, applied inside browser-sink's
//! routing (browser-sink §4.7, `crate::energy_order`): an answer counts only for an object
//! the page routes to the user agent's decoder. Nothing here calls a platform: the answers
//! are inputs, and the query's vectors (`wai/platform-decode-conformance/`) run on every
//! target.

use crate::codecs;

// ---------------------------------------------------------------- the query (§2)

/// A frame rate, `num / den` frames a second.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Rate {
    pub num: u32,
    pub den: u32,
}

/// The configuration a sink asks the platform about for one entry (§2).
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Query {
    /// The codecs parameter (RFC 6381) and WebCodecs codec string.
    pub codec: String,
    /// The picture size: the largest frame's, after cropping where the bitstream crops.
    pub width: u32,
    pub height: u32,
    /// The coded size WebCodecs' `codedWidth`/`codedHeight` take.
    pub coded_width: u32,
    pub coded_height: u32,
    /// The frame rate the payload states, or the presentation rate the caller gave where
    /// it states none; `None` when neither is known.
    pub framerate: Option<Rate>,
    /// The mean bit rate over the payload, bits a second, rounded up; `None` without a rate.
    pub bitrate: Option<u64>,
    /// Luma bit depth.
    pub bit_depth: u8,
}

impl Query {
    /// The `MediaDecodingConfiguration` for `decodingInfo()` (Media Capabilities §2.1),
    /// with `type` set to `decoding_type` (`"file"`, `"media-source"` or `"webrtc"`).
    /// `None` without a frame rate: `framerate` and `bitrate` are required members, and a
    /// sink does not invent them (§2).
    pub fn decoding_configuration(&self, decoding_type: &str) -> Option<serde_json::Value> {
        let rate = self.framerate?;
        let framerate = f64::from(rate.num) / f64::from(rate.den);
        Some(serde_json::json!({
            "type": decoding_type,
            "video": {
                "contentType": format!("video/mp4; codecs=\"{}\"", self.codec),
                "width": self.width,
                "height": self.height,
                "bitrate": self.bitrate?,
                "framerate": framerate,
            }
        }))
    }

    /// The `VideoDecoderConfig` for `VideoDecoder.isConfigSupported()` (WebCodecs §7.6).
    pub fn decoder_config(&self, hardware_acceleration: HardwareAcceleration) -> serde_json::Value {
        serde_json::json!({
            "codec": self.codec,
            "codedWidth": self.coded_width,
            "codedHeight": self.coded_height,
            "hardwareAcceleration": hardware_acceleration.as_str(),
        })
    }
}

/// Why no query is formed for an entry.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum QueryError {
    /// §2's table does not map this capability to a platform codec string.
    NotMapped(String),
    /// The payload does not hold what the query is read from.
    Malformed(String),
}

impl std::fmt::Display for QueryError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::NotMapped(c) => write!(f, "{c}: no platform codec string is mapped for it"),
            Self::Malformed(w) => write!(f, "the payload: {w}"),
        }
    }
}

impl std::error::Error for QueryError {}

fn malformed<T>(why: impl Into<String>) -> Result<T, QueryError> {
    Err(QueryError::Malformed(why.into()))
}

/// The capabilities §2 maps to a platform codec string.
pub const MAPPED: [&str; 3] = [codecs::CAP_VIDEO_AV1, codecs::CAP_VIDEO_AV1_LOSSLESS, codecs::CAP_VIDEO_HEVC];

/// The query for an entry of `capability` whose payload is `payload` (§2). For a payload
/// that states no frame rate (an HEVC byte stream), `presentation_rate` is the rate the
/// sink will present at, and without one the query has no rate.
pub fn query(capability: &str, payload: &[u8], presentation_rate: Option<Rate>) -> Result<Query, QueryError> {
    match capability {
        codecs::CAP_VIDEO_AV1 | codecs::CAP_VIDEO_AV1_LOSSLESS => av1_query(payload),
        codecs::CAP_VIDEO_HEVC => hevc_query(payload, presentation_rate),
        other => Err(QueryError::NotMapped(other.to_string())),
    }
}

fn mean_bitrate(bytes: usize, frames: u64, rate: Rate) -> Option<u64> {
    if frames == 0 || rate.den == 0 || rate.num == 0 {
        return None;
    }
    let num = (bytes as u128) * 8 * u128::from(rate.num);
    let den = u128::from(frames) * u128::from(rate.den);
    u64::try_from(num.div_ceil(den)).ok()
}

// AV1 (SPEC §5's framing, AV1 §5, AV1-ISOBMFF §5)

/// A bit reader, most significant bit first.
struct Bits<'a> {
    b: &'a [u8],
    at: usize,
}

impl Bits<'_> {
    fn f(&mut self, n: u32) -> Result<u32, QueryError> {
        let mut v = 0u32;
        for _ in 0..n {
            let Some(byte) = self.b.get(self.at >> 3) else {
                return malformed("a header runs past its unit");
            };
            v = (v << 1) | u32::from((byte >> (7 - (self.at & 7))) & 1);
            self.at += 1;
        }
        Ok(v)
    }

    fn flag(&mut self) -> Result<bool, QueryError> {
        Ok(self.f(1)? == 1)
    }

    /// `uvlc()` (AV1 §4.10.3).
    fn uvlc(&mut self) -> Result<u32, QueryError> {
        let mut lz = 0u32;
        while !self.flag()? {
            lz += 1;
            if lz > 32 {
                return malformed("a uvlc() past 32 leading zeros");
            }
        }
        if lz >= 32 {
            return Ok(u32::MAX);
        }
        Ok(self.f(lz)? + ((1u32 << lz) - 1))
    }

    /// `ue(v)` (H.265 §9.2).
    fn ue(&mut self) -> Result<u32, QueryError> {
        let mut lz = 0u32;
        while !self.flag()? {
            lz += 1;
            if lz > 31 {
                return malformed("an exp-Golomb code past 31 leading zeros");
            }
        }
        Ok(self.f(lz)? + ((1u32 << lz) - 1))
    }
}

/// `leb128()` (AV1 §4.10.5).
fn leb128(b: &[u8]) -> Result<(u64, usize), QueryError> {
    let mut v = 0u64;
    for i in 0..8 {
        let Some(&byte) = b.get(i) else { return malformed("an OBU size runs past the end") };
        v |= u64::from(byte & 0x7f) << (7 * i);
        if byte & 0x80 == 0 {
            return Ok((v, i + 1));
        }
    }
    malformed("an OBU size longer than 8 bytes")
}

/// The fields of an AV1 sequence header (AV1 §5.5) that the codecs parameter carries.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Av1Sequence {
    pub seq_profile: u8,
    pub seq_level_idx_0: u8,
    pub seq_tier_0: u8,
    pub bit_depth: u8,
    pub mono_chrome: bool,
    pub subsampling_x: bool,
    pub subsampling_y: bool,
    pub chroma_sample_position: u8,
    /// `color_primaries`, `transfer_characteristics`, `matrix_coefficients` when
    /// `color_description_present_flag` is 1.
    pub color_description: Option<(u8, u8, u8)>,
    pub color_range: bool,
}

impl Av1Sequence {
    /// Read a sequence header OBU's payload through `color_config()` (AV1 §5.5.1, §5.5.2).
    pub fn parse(payload: &[u8]) -> Result<Av1Sequence, QueryError> {
        let mut r = Bits { b: payload, at: 0 };
        let seq_profile = r.f(3)? as u8;
        if seq_profile > 2 {
            return malformed(format!("seq_profile {seq_profile}"));
        }
        let _still_picture = r.flag()?;
        let reduced = r.flag()?;
        let (seq_level_idx_0, seq_tier_0);
        if reduced {
            seq_level_idx_0 = r.f(5)? as u8;
            seq_tier_0 = 0;
        } else {
            let timing_info_present = r.flag()?;
            let mut decoder_model_info_present = false;
            let mut buffer_delay_length = 0;
            if timing_info_present {
                r.f(32)?;
                r.f(32)?;
                if r.flag()? {
                    r.uvlc()?;
                }
                decoder_model_info_present = r.flag()?;
                if decoder_model_info_present {
                    buffer_delay_length = r.f(5)? + 1;
                    r.f(32)?;
                    r.f(5)?;
                    r.f(5)?;
                }
            }
            let initial_display_delay_present = r.flag()?;
            let count = r.f(5)? + 1;
            let (mut lvl0, mut tier0) = (0u8, 0u8);
            for i in 0..count {
                r.f(12)?;
                let lvl = r.f(5)? as u8;
                let tier = if lvl > 7 { r.f(1)? as u8 } else { 0 };
                if decoder_model_info_present && r.flag()? {
                    r.f(buffer_delay_length)?;
                    r.f(buffer_delay_length)?;
                    r.f(1)?;
                }
                if initial_display_delay_present && r.flag()? {
                    r.f(4)?;
                }
                if i == 0 {
                    (lvl0, tier0) = (lvl, tier);
                }
            }
            seq_level_idx_0 = lvl0;
            seq_tier_0 = tier0;
        }
        let wbits = r.f(4)? + 1;
        let hbits = r.f(4)? + 1;
        r.f(wbits)?;
        r.f(hbits)?;
        if !reduced && r.flag()? {
            r.f(4)?;
            r.f(3)?;
        }
        r.f(3)?; // use_128x128_superblock, enable_filter_intra, enable_intra_edge_filter
        if !reduced {
            r.f(4)?; // interintra compound, masked compound, warped motion, dual filter
            let order_hint = r.flag()?;
            if order_hint {
                r.f(2)?; // jnt_comp, ref_frame_mvs
            }
            let force_sct = if r.flag()? { 2 } else { r.f(1)? };
            if force_sct > 0 && !r.flag()? {
                r.f(1)?;
            }
            if order_hint {
                r.f(3)?;
            }
        }
        r.f(3)?; // superres, cdef, restoration
        // color_config()
        let high_bitdepth = r.flag()?;
        let twelve_bit = seq_profile == 2 && high_bitdepth && r.flag()?;
        let bit_depth = match (high_bitdepth, twelve_bit) {
            (false, _) => 8,
            (true, false) => 10,
            (true, true) => 12,
        };
        let mono_chrome = seq_profile != 1 && r.flag()?;
        let color_description = if r.flag()? { Some((r.f(8)? as u8, r.f(8)? as u8, r.f(8)? as u8)) } else { None };
        // CP_BT_709, TC_SRGB, MC_IDENTITY.
        let srgb = color_description == Some((1, 13, 0));
        let (sx, sy, csp, color_range);
        if mono_chrome {
            color_range = r.flag()?;
            (sx, sy, csp) = (true, true, 0);
        } else if srgb {
            (sx, sy, csp, color_range) = (false, false, 0, true);
        } else {
            color_range = r.flag()?;
            let (x, y) = if seq_profile == 0 {
                (true, true)
            } else if seq_profile == 1 {
                (false, false)
            } else if bit_depth == 12 {
                let x = r.flag()?;
                (x, if x { r.flag()? } else { false })
            } else {
                (true, false)
            };
            (sx, sy) = (x, y);
            csp = if x && y { r.f(2)? as u8 } else { 0 };
        }
        Ok(Av1Sequence {
            seq_profile,
            seq_level_idx_0,
            seq_tier_0,
            bit_depth,
            mono_chrome,
            subsampling_x: sx,
            subsampling_y: sy,
            chroma_sample_position: csp,
            color_description,
            color_range,
        })
    }

    /// The codecs parameter string (AV1-ISOBMFF §5): every field, then the trailing part
    /// omitted where it is exactly the default `.0.110.01.01.01.0`. Without a colour
    /// description the primaries, transfer and matrix are written as the defaults, 01.
    pub fn codec_string(&self) -> String {
        let head = format!("av01.{}.{:02}{}.{:02}", self.seq_profile, self.seq_level_idx_0, if self.seq_tier_0 == 1 { 'H' } else { 'M' }, self.bit_depth);
        let (cp, tc, mc) = self.color_description.unwrap_or((1, 1, 1));
        let third = if self.subsampling_x && self.subsampling_y { self.chroma_sample_position } else { 0 };
        let tail = format!(
            ".{}.{}{}{}.{cp:02}.{tc:02}.{mc:02}.{}",
            u8::from(self.mono_chrome),
            u8::from(self.subsampling_x),
            u8::from(self.subsampling_y),
            third,
            u8::from(self.color_range)
        );
        if tail == ".0.110.01.01.01.0" { head } else { head + &tail }
    }
}

/// SPEC §5's framing of `wai.video.av1`: `n`, `h`, `w`, `fps_num`, `fps_den`, then each
/// temporal unit with its length. Returns the five values and the first temporal unit.
fn av1_framing(p: &[u8]) -> Result<([u32; 5], &[u8]), QueryError> {
    if p.len() < 24 {
        return malformed("shorter than the AV1 framing's header and one unit length");
    }
    let w = |i: usize| u32::from_le_bytes([p[4 * i], p[4 * i + 1], p[4 * i + 2], p[4 * i + 3]]);
    let head = [w(0), w(1), w(2), w(3), w(4)];
    let len = w(5) as usize;
    let Some(tu) = p.get(24..24 + len) else { return malformed("the first temporal unit runs past the payload") };
    Ok((head, tu))
}

fn av1_query(p: &[u8]) -> Result<Query, QueryError> {
    let ([n, h, w, fps_num, fps_den], tu) = av1_framing(p)?;
    if n == 0 || h == 0 || w == 0 {
        return malformed("an AV1 payload of no frames, or of a zero size");
    }
    // The first temporal unit's sequence header OBU (AV1 §5.3).
    let mut at = 0usize;
    let seq = loop {
        let Some(&header) = tu.get(at) else { return malformed("the first temporal unit holds no sequence header") };
        let obu_type = (header >> 3) & 0x0f;
        let ext = header & 0x04 != 0;
        if header & 0x02 == 0 {
            return malformed("an OBU without its size field");
        }
        let hl = if ext { 2 } else { 1 };
        let (size, sl) = leb128(tu.get(at + hl..).unwrap_or(&[]))?;
        let start = at + hl + sl;
        let Some(body) = usize::try_from(size).ok().and_then(|s| tu.get(start..start.checked_add(s)?)) else {
            return malformed("an OBU runs past its temporal unit");
        };
        if obu_type == 1 {
            break Av1Sequence::parse(body)?;
        }
        at = start + body.len();
    };
    let rate = (fps_num > 0 && fps_den > 0).then_some(Rate { num: fps_num, den: fps_den });
    Ok(Query {
        codec: seq.codec_string(),
        width: w,
        height: h,
        coded_width: w,
        coded_height: h,
        framerate: rate,
        bitrate: rate.and_then(|r| mean_bitrate(p.len(), u64::from(n), r)),
        bit_depth: seq.bit_depth,
    })
}

// HEVC (H.265 Annex B, §7.3.2.2, §7.3.3; ISO/IEC 14496-15 Annex E.3)

/// The NAL units of an Annex B byte stream (H.265 Annex B), each without its start code.
fn annex_b_units(s: &[u8]) -> Vec<&[u8]> {
    let mut starts = Vec::new();
    let mut i = 0;
    while i + 3 <= s.len() {
        if s[i] == 0 && s[i + 1] == 0 && s[i + 2] == 1 {
            starts.push(i + 3);
            i += 3;
        } else {
            i += 1;
        }
    }
    let mut out = Vec::with_capacity(starts.len());
    for (k, &st) in starts.iter().enumerate() {
        let mut end = starts.get(k + 1).map_or(s.len(), |&n| n - 3);
        // The zero_byte before a four-byte start code, and trailing_zero_8bits.
        while end > st && s[end - 1] == 0 {
            end -= 1;
        }
        out.push(&s[st..end]);
    }
    out
}

/// A NAL unit's RBSP: the two-byte header dropped, and each emulation-prevention byte
/// (`0x000003`) removed (H.265 §7.3.1.1).
fn rbsp(nal: &[u8]) -> Vec<u8> {
    let mut out = Vec::with_capacity(nal.len());
    let mut zeros = 0;
    for &b in nal.iter().skip(2) {
        if zeros >= 2 && b == 3 {
            zeros = 0;
            continue;
        }
        zeros = if b == 0 { zeros + 1 } else { 0 };
        out.push(b);
    }
    out
}

/// The fields of an HEVC sequence parameter set that the codecs parameter and the query
/// carry.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct HevcSps {
    pub profile_space: u8,
    pub tier: u8,
    pub profile_idc: u8,
    /// `general_profile_compatibility_flag[j]` at bit `j`.
    pub compatibility: u32,
    /// The 48 bits from `general_progressive_source_flag` on, as six bytes.
    pub constraints: [u8; 6],
    pub level_idc: u8,
    pub chroma_format_idc: u32,
    pub width: u32,
    pub height: u32,
    pub cropped_width: u32,
    pub cropped_height: u32,
    pub bit_depth: u8,
}

impl HevcSps {
    /// Read an SPS NAL unit (type 33), header included, through `bit_depth_luma_minus8`.
    pub fn parse(nal: &[u8]) -> Result<HevcSps, QueryError> {
        if nal.len() < 3 || (nal[0] >> 1) & 0x3f != 33 {
            return malformed("not an SPS NAL unit");
        }
        let b = rbsp(nal);
        let mut r = Bits { b: &b, at: 0 };
        r.f(4)?; // sps_video_parameter_set_id
        let max_sub_layers_minus1 = r.f(3)?;
        r.f(1)?; // sps_temporal_id_nesting_flag
        // profile_tier_level(1, sps_max_sub_layers_minus1), H.265 §7.3.3
        let profile_space = r.f(2)? as u8;
        let tier = r.f(1)? as u8;
        let profile_idc = r.f(5)? as u8;
        let mut compatibility = 0u32;
        for j in 0..32 {
            compatibility |= r.f(1)? << j;
        }
        let mut constraints = [0u8; 6];
        for c in &mut constraints {
            *c = r.f(8)? as u8;
        }
        let level_idc = r.f(8)? as u8;
        let mut present = Vec::new();
        for _ in 0..max_sub_layers_minus1 {
            present.push((r.flag()?, r.flag()?));
        }
        if max_sub_layers_minus1 > 0 {
            for _ in max_sub_layers_minus1..8 {
                r.f(2)?;
            }
        }
        for (profile, level) in present {
            if profile {
                for _ in 0..11 {
                    r.f(8)?; // 88 bits
                }
            }
            if level {
                r.f(8)?;
            }
        }
        r.ue()?; // sps_seq_parameter_set_id
        let chroma_format_idc = r.ue()?;
        if chroma_format_idc > 3 {
            return malformed(format!("chroma_format_idc {chroma_format_idc}"));
        }
        let separate = chroma_format_idc == 3 && r.flag()?;
        let width = r.ue()?;
        let height = r.ue()?;
        let (mut cw, mut ch) = (width, height);
        if r.flag()? {
            let (l, rr, t, bt) = (r.ue()?, r.ue()?, r.ue()?, r.ue()?);
            // SubWidthC and SubHeightC (H.265 Table 6-1); 1 for 4:4:4 and for separate planes.
            let (sw, sh) = match (chroma_format_idc, separate) {
                (1, _) => (2, 2),
                (2, _) => (2, 1),
                _ => (1, 1),
            };
            cw = width.checked_sub(sw * (l + rr)).ok_or(QueryError::Malformed("a conformance window wider than the picture".into()))?;
            ch = height.checked_sub(sh * (t + bt)).ok_or(QueryError::Malformed("a conformance window taller than the picture".into()))?;
        }
        let bit_depth = r.ue()? + 8;
        if bit_depth > 16 {
            return malformed(format!("bit depth {bit_depth}"));
        }
        Ok(HevcSps { profile_space, tier, profile_idc, compatibility, constraints, level_idc, chroma_format_idc, width, height, cropped_width: cw, cropped_height: ch, bit_depth: bit_depth as u8 })
    }

    /// The codecs parameter (ISO/IEC 14496-15 Annex E.3, as WebCodecs' HEVC registration
    /// cites it): `hev1.`, the profile space as nothing, `A`, `B` or `C` followed by the
    /// profile in decimal; the compatibility flags as one hexadecimal value with flag `j`
    /// at bit `j`; `L` or `H` and the level in decimal; then each constraint byte in
    /// hexadecimal, trailing zero bytes omitted. `hev1` because an Annex B stream carries
    /// its parameter sets in band.
    pub fn codec_string(&self) -> String {
        let space = ["", "A", "B", "C"][usize::from(self.profile_space & 3)];
        let mut s = format!("hev1.{space}{}.{:X}.{}{}", self.profile_idc, self.compatibility, if self.tier == 1 { 'H' } else { 'L' }, self.level_idc);
        let last = self.constraints.iter().rposition(|&c| c != 0);
        if let Some(k) = last {
            for c in &self.constraints[..=k] {
                s.push_str(&format!(".{c:X}"));
            }
        }
        s
    }
}

fn hevc_query(p: &[u8], presentation_rate: Option<Rate>) -> Result<Query, QueryError> {
    let units = annex_b_units(p);
    let sps = units.iter().find(|u| u.len() >= 2 && (u[0] >> 1) & 0x3f == 33).ok_or(QueryError::Malformed("no SPS NAL unit".into()))?;
    let sps = HevcSps::parse(sps)?;
    // A picture begins at each VCL NAL unit (types 0..=31) whose
    // first_slice_segment_in_pic_flag, the slice header's first bit, is 1.
    let pictures = units.iter().filter(|u| u.len() >= 3 && (u[0] >> 1) & 0x3f <= 31 && u[2] & 0x80 != 0).count() as u64;
    Ok(Query {
        codec: sps.codec_string(),
        width: sps.cropped_width,
        height: sps.cropped_height,
        coded_width: sps.width,
        coded_height: sps.height,
        framerate: presentation_rate,
        bitrate: presentation_rate.and_then(|r| mean_bitrate(p.len(), pictures, r)),
        bit_depth: sps.bit_depth,
    })
}

// ---------------------------------------------------------------- the answers (§3)

/// WebCodecs `HardwareAcceleration` (WebCodecs §7.9): a hint the user agent may ignore.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum HardwareAcceleration {
    NoPreference,
    PreferHardware,
    PreferSoftware,
}

impl HardwareAcceleration {
    pub fn as_str(self) -> &'static str {
        match self {
            Self::NoPreference => "no-preference",
            Self::PreferHardware => "prefer-hardware",
            Self::PreferSoftware => "prefer-software",
        }
    }

    pub fn parse(s: &str) -> Option<HardwareAcceleration> {
        [Self::NoPreference, Self::PreferHardware, Self::PreferSoftware].into_iter().find(|h| h.as_str() == s)
    }
}

/// The answer a page enters in browser-sink's capability report for one codec string
/// (browser-sink §3: `supported`, `smooth`, `power_efficient`, `probe`), from what the
/// platform said (§3): `supported` from `isConfigSupported()` for the preference the sink
/// will configure, and `smooth` and `power_efficient` from `decodingInfo()`'s answer, read
/// only where `isConfigSupported()` said supported (Media Capabilities is a further question
/// about a decoder already found, http-delivery §12.2): a Media Capabilities `supported` of
/// `false` overrides nothing. `probe` is `not_run`: this extension runs none (browser-sink
/// §4.4). One shape, the report's, carries the answers; nothing here is an energy figure.
pub fn report_answer(webcodecs_supported: bool, decoding_info: Option<(bool, bool)>) -> serde_json::Value {
    let (smooth, power_efficient) = match decoding_info {
        Some((s, p)) if webcodecs_supported => (serde_json::Value::Bool(s), serde_json::Value::Bool(p)),
        _ => (serde_json::Value::Null, serde_json::Value::Null),
    };
    serde_json::json!({ "supported": webcodecs_supported, "smooth": smooth, "power_efficient": power_efficient, "probe": "not_run" })
}

/// A query's JSON form for a caller that asks a platform: its members, with the
/// `decodingInfo()` configuration (type `media-source`, or `null` without a rate) and the
/// `isConfigSupported()` configurations with `prefer-hardware` and `no-preference`.
pub fn query_json(q: &Query) -> serde_json::Value {
    serde_json::json!({
        "codec": q.codec,
        "width": q.width,
        "height": q.height,
        "codedWidth": q.coded_width,
        "codedHeight": q.coded_height,
        "framerate": q.framerate.map(|r| serde_json::json!([r.num, r.den])),
        "bitrate": q.bitrate,
        "bitDepth": q.bit_depth,
        "decodingConfiguration": q.decoding_configuration("media-source"),
        "decoderConfigPreferHardware": q.decoder_config(HardwareAcceleration::PreferHardware),
        "decoderConfigNoPreference": q.decoder_config(HardwareAcceleration::NoPreference),
    })
}

#[cfg(test)]
mod tests;
