//! The `codecs` parameter (http-delivery §4): the grammar of a WAI element, the union
//! a variant declares (exhaustive), the floor every object of it offers, and support.
//! The registry decides kinds: [`crate::codecs::registry::capability_kind`] and
//! [`crate::codecs::registry::is_candidate`].

use super::head::{parse_head, Head, HeadParse, Version};
use super::RangeRefusal;
use crate::codecs::registry::{capability_kind, is_candidate, is_dispatchable, Kind};
use crate::container::{companion_indices, refinement_indices, ManifestV2, RefForm, Refines};

/// The container an element names: `wai1` (every object is a `WAI1`) or `wai2` (some
/// object is a `WAI2`).
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum Prefix {
    Wai1,
    Wai2,
}

impl Prefix {
    /// `wai1` or `wai2`.
    pub fn label(self) -> &'static str {
        match self {
            Prefix::Wai1 => "wai1",
            Prefix::Wai2 => "wai2",
        }
    }
}

/// One `alt`: a selectable capability and the companion or refinement capabilities
/// applied with it, each without its leading `wai.`.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Alt {
    pub cap: String,
    pub plus: Vec<String>,
}

/// A WAI element.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Element {
    pub prefix: Prefix,
    pub alts: Vec<Alt>,
}

/// Why a `codecs` value, or a head against one, is refused (§4).
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum CodecsError {
    Malformed(String),
    NotMedia(String),
    Candidate(String),
    NoCommonCapability,
    Undeclared(String),
    /// A layer object (§10.4) whose `refines.base` names no object of the sequence.
    LayerUnbound(String),
}

impl CodecsError {
    /// `codecs_malformed`, `codecs_not_media`, `codecs_candidate`,
    /// `codecs_no_common_capability` or `codecs_undeclared`.
    pub fn code(&self) -> &'static str {
        match self {
            CodecsError::Malformed(_) => "codecs_malformed",
            CodecsError::NotMedia(_) => "codecs_not_media",
            CodecsError::Candidate(_) => "codecs_candidate",
            CodecsError::NoCommonCapability => "codecs_no_common_capability",
            CodecsError::Undeclared(_) => "codecs_undeclared",
            CodecsError::LayerUnbound(_) => "codecs_layer_unbound",
        }
    }
}

impl std::fmt::Display for CodecsError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            CodecsError::Malformed(s) | CodecsError::NotMedia(s) | CodecsError::Candidate(s) | CodecsError::Undeclared(s)
            | CodecsError::LayerUnbound(s) => {
                write!(f, "{}: {s}", self.code())
            }
            CodecsError::NoCommonCapability => f.write_str(self.code()),
        }
    }
}

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

/// Whether `cap` (without `wai.`) matches the `cap` rule.
fn cap_matches(cap: &str) -> bool {
    let mut parts = cap.split('.');
    let first = parts.next().unwrap_or("");
    let lcalnum = |b: u8| b.is_ascii_lowercase() || b.is_ascii_digit();
    let mut rest = 0;
    let ok_rest = parts.all(|p| {
        rest += 1;
        !p.is_empty() && p.bytes().all(|b| lcalnum(b) || b == b'_')
    });
    !first.is_empty() && first.bytes().all(lcalnum) && rest >= 1 && ok_rest
}

/// What a capability may be in an element, by its registered kind: an error for a
/// record, a derivation or a candidate; otherwise whether it is applied with another
/// (companion-only or refinement-only). A capability this revision does not register
/// may stand either way.
fn applied_kind(cap: &str) -> Result<Option<bool>, CodecsError> {
    let full = format!("wai.{cap}");
    match capability_kind(&full) {
        Some(Kind::Derivation | Kind::Record) => Err(CodecsError::NotMedia(full)),
        _ if is_candidate(&full) => Err(CodecsError::Candidate(full)),
        Some(Kind::Companion | Kind::Refinement) => Ok(Some(true)),
        Some(Kind::Payload) => Ok(Some(false)),
        None => Ok(None),
    }
}

/// A `cap` in its place: leading an alt (`first`), or after `+`.
fn check_cap(cap: &str, first: bool) -> Result<(), CodecsError> {
    if !cap_matches(cap) {
        return Err(CodecsError::Malformed(format!("{cap:?} is not a capability without its wai. prefix")));
    }
    match (first, applied_kind(cap)?) {
        (true, Some(true)) => Err(CodecsError::Malformed(format!("wai.{cap} is applied with another and cannot lead an alt"))),
        (false, Some(false)) => Err(CodecsError::Malformed(format!("wai.{cap} is standalone media and cannot follow +"))),
        _ => Ok(()),
    }
}

fn check_alt(alt: &Alt) -> Result<(), CodecsError> {
    check_cap(&alt.cap, true)?;
    alt.plus.iter().try_for_each(|p| check_cap(p, false))
}

/// Parse one WAI element (§4.1). A `cap` twice among an element's alts, or twice in
/// one alt, is malformed.
pub fn parse_element(s: &str) -> Result<Element, CodecsError> {
    let malformed = || CodecsError::Malformed(s.to_string());
    let (prefix, rest) = s.split_once('.').ok_or_else(malformed)?;
    let prefix = match prefix {
        "wai1" => Prefix::Wai1,
        "wai2" => Prefix::Wai2,
        _ => return Err(malformed()),
    };
    let mut alts: Vec<Alt> = Vec::new();
    for a in rest.split('|') {
        let mut caps = a.split('+');
        let cap = caps.next().unwrap_or("").to_string();
        let alt = Alt { cap, plus: caps.map(str::to_string).collect() };
        check_alt(&alt)?;
        let repeated_plus = alt.plus.iter().enumerate().any(|(i, p)| alt.plus[..i].contains(p) || *p == alt.cap);
        if repeated_plus || alts.iter().any(|x| x.cap == alt.cap) {
            return Err(CodecsError::Malformed(format!("{s}: a capability named twice")));
        }
        alts.push(alt);
    }
    Ok(Element { prefix, alts })
}

/// Whether an element of a `codecs` value is a WAI element: it begins with `wai` in
/// any ASCII case. Such an element that is not a well-formed `wai1.` or `wai2.` element
/// is malformed, never classical.
fn is_wai_element(e: &str) -> bool {
    e.as_bytes().get(..3).is_some_and(|b| b.eq_ignore_ascii_case(b"wai"))
}

/// Parse a `codecs` value: one entry per `,`-separated element, `None` for a
/// classical element (§4.1). Spaces and tabs around an element are not part of it; an
/// empty element is malformed.
pub fn parse(value: &str) -> Result<Vec<Option<Element>>, CodecsError> {
    value
        .split(',')
        .map(|e| e.trim_matches(|c| c == ' ' || c == '\t'))
        .map(|e| {
            if e.is_empty() {
                Err(CodecsError::Malformed(format!("{value:?}: an empty element")))
            } else if is_wai_element(e) {
                parse_element(e).map(Some)
            } else {
                Ok(None)
            }
        })
        .collect()
}

/// The element as text.
pub fn render(e: &Element) -> String {
    let alts: Vec<String> = e.alts.iter().map(|a| std::iter::once(a.cap.as_str()).chain(a.plus.iter().map(String::as_str)).collect::<Vec<_>>().join("+")).collect();
    format!("{}.{}", e.prefix.label(), alts.join("|"))
}

fn short(cap: &str) -> String {
    cap.strip_prefix("wai.").unwrap_or(cap).to_string()
}

fn push_unique(v: &mut Vec<String>, s: String) {
    if !v.contains(&s) {
        v.push(s);
    }
}

/// The alts of one `WAI2` manifest: one per distinct selectable capability (SPEC §7.1:
/// never a companion, a refinement or a capability SPEC §4 does not dispatch) in table
/// order, each with the companion capabilities bound to a rendition of it and the
/// refinement capabilities whose base is one, in table order.
fn alts_v2(m: &ManifestV2) -> Vec<Alt> {
    let e = &m.renditions;
    let mut alts: Vec<Alt> = Vec::new();
    for (i, r) in e.iter().enumerate() {
        if !r.is_selectable() {
            continue;
        }
        let cap = short(&r.capability);
        let pos = match alts.iter().position(|a| a.cap == cap) {
            Some(p) => p,
            None => {
                alts.push(Alt { cap, plus: Vec::new() });
                alts.len() - 1
            }
        };
        let mut bound: Vec<usize> = companion_indices(e, i);
        bound.extend(refinement_indices(e, i));
        bound.sort_unstable();
        for j in bound {
            push_unique(&mut alts[pos].plus, short(&e[j].capability));
        }
    }
    // `+` capabilities in table order across the renditions of one capability.
    for a in &mut alts {
        a.plus.sort_by_key(|p| e.iter().position(|r| short(&r.capability) == *p));
    }
    alts
}

/// The element describing one object that is not a layer (§4.2): its union. A `WAI1`
/// lists its capability and its fallback where SPEC §4 dispatches them; an object that
/// lists none is not media (`codecs_not_media`). A layer has no element of its own: it
/// joins its base's (§4.2, [`union_of`]).
pub fn element_of(head: &Head) -> Result<Element, CodecsError> {
    let bad = |e: super::RangeRefusal| CodecsError::Malformed(e.to_string());
    if head.is_layer() {
        return Err(CodecsError::Malformed("a layer is described with its base".into()));
    }
    let (prefix, alts) = match head.version {
        Version::Wai1 => {
            let m = head.manifest_v1().map_err(bad)?;
            let mut alts: Vec<Alt> = Vec::new();
            for c in [Some(m.model_requirement.capability.as_str()), m.model_requirement.fallback.as_deref()].into_iter().flatten() {
                if is_dispatchable(c) && !alts.iter().any(|a| a.cap == short(c)) {
                    alts.push(Alt { cap: short(c), plus: Vec::new() });
                }
            }
            (Prefix::Wai1, alts)
        }
        Version::Wai2 => (Prefix::Wai2, alts_v2(&head.manifest_v2().map_err(bad)?)),
    };
    if alts.is_empty() {
        return Err(CodecsError::NotMedia("no capability SPEC §4 dispatches".into()));
    }
    for a in &alts {
        check_alt(a)?;
    }
    Ok(Element { prefix, alts })
}

/// One object of a sequence, as [`union_of`] and [`floor_of`] read it: its content
/// hash, its head, and, for a `WAI2`, the content hash of each component's exploded
/// envelope (SPEC §7.1) where one can be built: a layer may name any of these as its
/// base.
#[derive(Clone, Debug)]
pub struct SeqObject {
    pub cid: [u8; 32],
    pub head: Head,
    pub components: Vec<Option<[u8; 32]>>,
}

impl SeqObject {
    /// The object an envelope is.
    pub fn from_envelope(env: &[u8]) -> Result<SeqObject, RangeRefusal> {
        let head = match parse_head(env, env.len() as u64)? {
            HeadParse::Head(h) => h,
            HeadParse::Need(n) => return Err(RangeRefusal::HeadMalformed(format!("the head needs {n} bytes"))),
        };
        let components = match head.version {
            Version::Wai1 => Vec::new(),
            Version::Wai2 => crate::container::WaiMulti::explode_each(env)
                .map(|v| v.into_iter().map(|r| r.ok().map(|b| super::content_hash(&b))).collect())
                .unwrap_or_default(),
        };
        Ok(SeqObject { cid: super::content_hash(env), head, components })
    }
}

/// The capability through which object `b` is the base whose content hash is `base`: a
/// `WAI1`'s `capability` when its cid is `base`, or the capability of the selectable
/// component of a `WAI2` whose exploded envelope has that hash. `None` otherwise.
fn base_capability(b: &SeqObject, base: &[u8; 32]) -> Option<String> {
    if b.head.is_layer() {
        return None;
    }
    if b.cid == *base && b.head.version == Version::Wai1 {
        return b.head.manifest_v1().ok().map(|m| short(&m.model_requirement.capability));
    }
    let i = b.components.iter().position(|c| *c == Some(*base))?;
    let m = b.head.manifest_v2().ok()?;
    m.renditions.get(i).filter(|r| r.is_selectable()).map(|r| short(&r.capability))
}

/// The alts of each object of a sequence that is not a layer, with each layer's
/// capability after `+` in the alt of the base its `refines.base` names: a `WAI1`
/// object of the sequence, through its capability (staged delivery's base is never
/// decoded through a fallback), or a `WAI2` component, through the component's
/// capability. Layers join in sequence order, after the object's own capabilities. An
/// object the sequence lists more than once is one object of it, described once with
/// every layer it has, and a layer joins every object of the sequence that is its base.
fn described(objects: &[SeqObject]) -> Result<Vec<Element>, CodecsError> {
    let mut seen = std::collections::BTreeSet::new();
    let objects: Vec<&SeqObject> = objects.iter().filter(|o| seen.insert(o.cid)).collect();
    let mut els: Vec<Option<Element>> = Vec::new();
    for o in &objects {
        els.push(if o.head.is_layer() { None } else { Some(element_of(&o.head)?) });
    }
    for o in objects.iter().filter(|o| o.head.is_layer()) {
        let m = o.head.manifest_v1().map_err(|e| CodecsError::Malformed(e.to_string()))?;
        let cap = short(&m.model_requirement.capability);
        check_cap(&cap, false)?;
        let refines = m.refines.as_ref()
            .ok_or_else(|| CodecsError::Malformed(format!("the layer wai.{cap} carries no refines")))
            .and_then(|v| Refines::from_value(v, RefForm::ContentHash).map_err(|e| CodecsError::Malformed(format!("refines: {}", e.0))))?;
        let base = super::unhex32(&refines.base).ok_or_else(|| CodecsError::Malformed("refines.base".into()))?;
        let targets: Vec<(usize, String)> = objects.iter().enumerate()
            .filter(|(_, b)| !b.head.is_layer())
            .filter_map(|(k, b)| base_capability(b, &base).map(|c| (k, c)))
            .collect();
        if targets.is_empty() {
            return Err(CodecsError::LayerUnbound(format!("wai.{cap} refines {}", refines.base)));
        }
        for (k, base_cap) in targets {
            let el = els[k].as_mut().expect("a base is not a layer");
            let alt = el.alts.iter_mut().find(|a| a.cap == base_cap)
                .ok_or_else(|| CodecsError::LayerUnbound(format!("wai.{cap}: its base's capability is not dispatched")))?;
            push_unique(&mut alt.plus, cap.clone());
        }
    }
    Ok(els.into_iter().flatten().collect())
}

/// The union of a sequence of objects (§4.2): every `alt` any of them has, by first
/// appearance, each with every capability applied with it in any of them, layers
/// included. `wai2` when any object is a `WAI2`.
pub fn union_of(objects: &[SeqObject]) -> Result<Element, CodecsError> {
    let each = described(objects)?;
    let prefix = objects.iter().map(|o| if o.head.version == Version::Wai2 { Prefix::Wai2 } else { Prefix::Wai1 }).max().unwrap_or(Prefix::Wai1);
    let mut alts: Vec<Alt> = Vec::new();
    for e in each {
        for a in e.alts {
            match alts.iter_mut().find(|x| x.cap == a.cap) {
                Some(x) => {
                    for p in a.plus {
                        push_unique(&mut x.plus, p);
                    }
                }
                None => alts.push(a),
            }
        }
    }
    if alts.is_empty() {
        return Err(CodecsError::NoCommonCapability);
    }
    Ok(Element { prefix, alts })
}

/// The floor of a sequence of objects (§4.2): the `alt`s whose capability every object
/// that is not a layer has, each with the capabilities applied with it in every such
/// object, written with the union's prefix. Empty: `codecs_no_common_capability`.
pub fn floor_of(objects: &[SeqObject]) -> Result<Element, CodecsError> {
    let union = union_of(objects)?;
    let each = described(objects)?;
    let mut alts = Vec::new();
    for a in &union.alts {
        let found: Vec<&Alt> = each.iter().filter_map(|e| e.alts.iter().find(|x| x.cap == a.cap)).collect();
        if found.len() != each.len() {
            continue;
        }
        let plus = a.plus.iter().filter(|p| found.iter().all(|x| x.plus.contains(p))).cloned().collect();
        alts.push(Alt { cap: a.cap.clone(), plus });
    }
    if alts.is_empty() {
        return Err(CodecsError::NoCommonCapability);
    }
    Ok(Element { prefix: union.prefix, alts })
}

/// §7 step 3: refuse an object whose head names a capability, or a container, the
/// variant's union does not list (`codecs_undeclared`). A layer is declared when its
/// capability follows `+` in the alt of its own base's capability, as [`union_of`] puts
/// it there: `base` is the object its playlist lists it under, and a layer whose
/// `refines.base` does not name `base` (or one of its components), or that comes with no
/// base, is undeclared.
pub fn check_declared(head: &Head, union: &Element, base: Option<&SeqObject>) -> Result<(), CodecsError> {
    if head.is_layer() {
        let m = head.manifest_v1().map_err(|e| CodecsError::Malformed(e.to_string()))?;
        let cap = short(&m.model_requirement.capability);
        let undeclared = || CodecsError::Undeclared(format!("the layer wai.{cap}"));
        let refines = m.refines.as_ref()
            .and_then(|v| Refines::from_value(v, RefForm::ContentHash).ok())
            .and_then(|r| super::unhex32(&r.base))
            .ok_or_else(undeclared)?;
        let base_cap = base.and_then(|b| base_capability(b, &refines)).ok_or_else(undeclared)?;
        return if union.alts.iter().any(|a| a.cap == base_cap && a.plus.contains(&cap)) { Ok(()) } else { Err(undeclared()) };
    }
    let e = element_of(head)?;
    if e.prefix > union.prefix {
        return Err(CodecsError::Undeclared("a WAI2 object under a wai1 element".into()));
    }
    for a in &e.alts {
        let Some(u) = union.alts.iter().find(|u| u.cap == a.cap) else {
            return Err(CodecsError::Undeclared(format!("wai.{}", a.cap)));
        };
        if let Some(p) = a.plus.iter().find(|p| !u.plus.contains(p)) {
            return Err(CodecsError::Undeclared(format!("wai.{p} with wai.{}", a.cap)));
        }
    }
    Ok(())
}

/// §4.2's support over the floor: the sink reads the element's container
/// (`containers`) and has the leading capability of at least one of the floor's
/// `alt`s (`has`, given the full capability string). The capabilities after `+`
/// never affect support.
pub fn supports(floor: &Element, containers: &[Prefix], has: impl Fn(&str) -> bool) -> bool {
    let reads = match floor.prefix {
        Prefix::Wai1 => containers.contains(&Prefix::Wai1),
        Prefix::Wai2 => containers.contains(&Prefix::Wai1) && containers.contains(&Prefix::Wai2),
    };
    reads && floor.alts.iter().any(|a| has(&format!("wai.{}", a.cap)))
}
