//! The head of an object (http-delivery §2, §5.1): for `WAI1`, the magic, `man_len`,
//! the manifest and `payload_len`; for `WAI2`, the magic, `man_len`, the manifest,
//! `n_renditions` and the rendition table (SPEC §2, §7.1). The head gives every
//! component's byte range and, through each entry's `sha256`, its digest.

use super::{RangeRefusal, DOMAIN_LEN};
use crate::container::{read_manifest, read_manifest_v2, Manifest, ManifestV2};

/// Which container an object is.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Version {
    Wai1,
    Wai2,
}

/// One component's byte range in the object: [start, start + len).
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct ComponentRange {
    pub index: usize,
    pub start: u64,
    pub len: u64,
}

/// A parsed head. `bytes` is the head itself, [0, head_len).
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Head {
    pub version: Version,
    pub head_len: u64,
    pub envelope_len: u64,
    pub manifest: std::ops::Range<u64>,
    pub components: Vec<ComponentRange>,
    pub bytes: Vec<u8>,
}

/// What [`parse_head`] found: the head, or how many bytes from offset 0 it needs.
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum HeadParse {
    Need(u64),
    Head(Head),
}

/// `covered_len` (http-delivery §2): the head rounded up so that, with the content
/// hash's domain prefix, it ends on a BLAKE3 chunk boundary, and never past the object.
pub fn covered_len(head_len: u64, envelope_len: u64) -> u64 {
    let x = DOMAIN_LEN + head_len;
    envelope_len.min(x.div_ceil(1024) * 1024 - DOMAIN_LEN)
}

fn u32_at(b: &[u8], at: usize) -> u64 {
    u64::from(u32::from_le_bytes(b[at..at + 4].try_into().expect("4 bytes")))
}

/// Parse the head at the start of `prefix`, an object of `envelope_len` bytes, and
/// apply §5.1: the object is exactly one envelope. Returns `Need(n)` while `prefix`
/// holds fewer than the `n` bytes the head needs. A `man_len` past SPEC §2's reader
/// limit ([`crate::container::MANIFEST_MAX_LEN`]) is refused from the first eight
/// bytes, so `n` never asks for a manifest longer than that.
pub fn parse_head(prefix: &[u8], envelope_len: u64) -> Result<HeadParse, RangeRefusal> {
    let have = prefix.len() as u64;
    if have < 8 {
        return Ok(HeadParse::Need(8));
    }
    let version = match &prefix[..4] {
        b"WAI1" => Version::Wai1,
        b"WAI2" => Version::Wai2,
        _ => return Err(RangeRefusal::HeadMalformed("the magic is neither WAI1 nor WAI2".into())),
    };
    let man_len = u32_at(prefix, 4);
    crate::container::check_manifest_len(man_len).map_err(RangeRefusal::HeadMalformed)?;
    let manifest = 8..8 + man_len;
    let (head_len, components) = match version {
        Version::Wai1 => {
            let head_len = 12 + man_len;
            if have < head_len {
                return Ok(HeadParse::Need(head_len));
            }
            let payload_len = u32_at(prefix, (8 + man_len) as usize);
            (head_len, vec![ComponentRange { index: 0, start: head_len, len: payload_len }])
        }
        Version::Wai2 => {
            if have < 10 + man_len {
                return Ok(HeadParse::Need(10 + man_len));
            }
            let at = (8 + man_len) as usize;
            let n = u64::from(u16::from_le_bytes([prefix[at], prefix[at + 1]]));
            let head_len = 10 + man_len + 8 * n;
            if have < head_len {
                return Ok(HeadParse::Need(head_len));
            }
            let table = at + 2;
            let mut expected = 0u64;
            let mut components = Vec::with_capacity(n as usize);
            for i in 0..n as usize {
                let off = u32_at(prefix, table + 8 * i);
                let len = u32_at(prefix, table + 8 * i + 4);
                if off != expected {
                    return Err(RangeRefusal::HeadMalformed(format!(
                        "rendition {i}'s offset {off} is not contiguous (expected {expected})")));
                }
                components.push(ComponentRange { index: i, start: head_len + off, len });
                expected += len;
            }
            (head_len, components)
        }
    };
    let total = head_len + components.iter().map(|c| c.len).sum::<u64>();
    if total != envelope_len {
        return Err(RangeRefusal::ObjectLengthMismatch { expected: total, got: envelope_len });
    }
    let bytes = prefix[..head_len as usize].to_vec();
    let head = Head { version, head_len, envelope_len, manifest, components, bytes };
    // The manifest parses, names no member twice, and (WAI2) has one entry per row.
    match head.version {
        Version::Wai1 => {
            head.manifest_v1()?;
        }
        Version::Wai2 => {
            let m = head.manifest_v2()?;
            if m.renditions.len() != head.components.len() {
                return Err(RangeRefusal::HeadMalformed(format!(
                    "the table has {} rows and the manifest {} renditions",
                    head.components.len(), m.renditions.len())));
            }
        }
    }
    Ok(HeadParse::Head(head))
}

impl Head {
    fn manifest_bytes(&self) -> &[u8] {
        &self.bytes[self.manifest.start as usize..self.manifest.end as usize]
    }

    /// The `WAI1` manifest.
    pub fn manifest_v1(&self) -> Result<Manifest, RangeRefusal> {
        if self.version != Version::Wai1 {
            return Err(RangeRefusal::HeadMalformed("not a WAI1 head".into()));
        }
        read_manifest(self.manifest_bytes()).map_err(|e| RangeRefusal::HeadMalformed(e.to_string()))
    }

    /// The `WAI2` manifest.
    pub fn manifest_v2(&self) -> Result<ManifestV2, RangeRefusal> {
        if self.version != Version::Wai2 {
            return Err(RangeRefusal::HeadMalformed("not a WAI2 head".into()));
        }
        read_manifest_v2(self.manifest_bytes()).map_err(|e| RangeRefusal::HeadMalformed(e.to_string()))
    }

    /// The component at `index`.
    pub fn component(&self, index: usize) -> Option<ComponentRange> {
        self.components.get(index).copied()
    }

    /// This object's `covered_len`.
    pub fn covered_len(&self) -> u64 {
        covered_len(self.head_len, self.envelope_len)
    }

    /// Whether the object is a layer (http-delivery §2, §10.4): a `WAI1` whose capability
    /// is refinement-only (SPEC §5; staged-delivery §3). A layer goes to staged delivery
    /// §4, never to SPEC §4's dispatch. A payload capability that carries `refines` is an
    /// ordinary object: SPEC §4 dispatches it, and `refines` is not read there.
    pub fn is_layer(&self) -> bool {
        self.version == Version::Wai1
            && self.manifest_v1().is_ok_and(|m| crate::codecs::is_refinement_only(&m.model_requirement.capability))
    }
}
