use anyhow::{bail, Context, Result};
use base64::{engine::general_purpose, Engine as _};
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use std::io::Read;

pub const SIDECAR_MAGIC: &[u8; 4] = b"BTS1";
pub const SIDECAR_CONTAINER_VERSION: u8 = 1;
pub const SIDECAR_POINTER_VERSION: u8 = 1;
pub const SIDECAR_POINTER_LENGTH: usize = 48;
pub const SIDECAR_POINTER_SCHEME_PAIRSIGN_SAFE_LUMA_V2: u8 = 1;
pub const SIDECAR_POINTER_CARRIER_LABEL: u8 = 0x01;
pub const SIDECAR_POINTER_CARRIER_INTERGROOVE: u8 = 0x02;
pub const SIDECAR_POINTER_CARRIER_LEAD_IN_DEADWAX: u8 = 0x04;
pub const SIDECAR_SCHEME_PAIRSIGN_SAFE_LUMA_V2: &str = "pairsign-safe-luma-v2";
pub const SIDECAR_DEFAULT_SEED: u32 = 0x4b50_4752;
pub const SIDECAR_PAIR_SIGN_DELTA: i16 = 4;
pub const SIDECAR_PAIR_MAGNITUDE_DELTA: i16 = 12;
pub const SIDECAR_PAIR_MAGNITUDE_THRESHOLD: f64 = 16.0;
pub const SIDECAR_SAFE_V2_MIN_SCORE: u16 = 20;
pub const SIDECAR_TYPE_OPAQUE: u8 = 0;
pub const SIDECAR_TYPE_UTF8_TEXT: u8 = 1;
pub const SIDECAR_TYPE_IMAGE: u8 = 2;
pub const SIDECAR_TYPE_JSON: u8 = 3;
pub const SIDECAR_CODEC_RAW: u8 = 0;
pub const SIDECAR_CODEC_BROTLI: u8 = 1;
pub const SIDECAR_CODEC_ZSTD: u8 = 2;
pub const SIDECAR_CODEC_AVIF: u8 = 3;
pub const SIDECAR_RAW_LENGTH_ABSENT: u32 = u32::MAX;
pub const DISPLAY_HEADER_MAGIC: &[u8; 4] = b"BDH1";
pub const DISPLAY_HEADER_VERSION: u8 = 1;
pub const DISPLAY_HEADER_LENGTH: usize = 128;
pub const DISPLAY_HEADER_NAME: &str = "bitneedle-display-header.bin";
pub const DISPLAY_HEADER_MIME: &str = "application/vnd.bitneedle.display-header";
pub const PACKAGE_METADATA_ITEM_NAME: &str = "bitneedle-package-metadata.json";
pub const PACKAGE_METADATA_MIME: &str = "application/vnd.bitneedle.package-metadata+json";
pub const PACKAGE_PHOTO_MIME: &str = "image/avif";
pub const PACKAGE_COVER_ITEM_NAME: &str = "album-cover.avif";
pub const PACKAGE_PATTERN_SIDECAR_ITEM_NAME: &str = "bitneedle-pattern-map";
pub const PACKAGE_PATTERN_SIDECAR_MIME: &str = "application/vnd.bitneedle.pattern-map";


#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct SidecarContainerValidation {
    pub ok: bool,
    pub version: u8,
    pub flags: u8,
    pub item_count: usize,
    pub total_length: usize,
    pub items: Vec<SidecarItemValidation>,
}

#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct SidecarItemValidation {
    pub item_type: u8,
    pub item_type_name: String,
    pub codec: u8,
    pub codec_name: String,
    pub flags: u8,
    pub raw_byte_length: Option<u32>,
    pub stored_byte_length: u32,
    pub name: String,
    pub mime: String,
}

#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct SidecarDecodedItems {
    pub ok: bool,
    pub validation: SidecarContainerValidation,
    pub items: Vec<SidecarDecodedItem>,
}

#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct SidecarDecodedItem {
    pub item_type: u8,
    pub item_type_name: String,
    pub codec: u8,
    pub codec_name: String,
    pub flags: u8,
    pub raw_byte_length: Option<u32>,
    pub stored_byte_length: u32,
    pub decoded_byte_length: usize,
    pub name: String,
    pub mime: String,
    pub stored_data_base64: String,
    pub data_base64: String,
    pub text: Option<String>,
    pub json: Option<serde_json::Value>,
}

#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct SidecarDecodeResult {
    pub ok: bool,
    pub descriptor: serde_json::Value,
    pub validation: SidecarContainerValidation,
    pub bts1_byte_length: usize,
    pub sha256: String,
    pub carrier_pixels: usize,
    pub carrier_pairs: usize,
    pub capacity_bytes: usize,
}

#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct SidecarCapacity {
    pub scheme: String,
    pub carriers: Vec<String>,
    pub carrier_pixels: usize,
    pub carrier_pairs: usize,
    pub capacity_bits: usize,
    pub capacity_bytes: usize,
    pub bits_per_pair: f64,
    pub two_bit_pairs: usize,
}

#[derive(Debug, Clone)]
pub struct SidecarHeaderPointer {
    pub scheme: String,
    pub carriers: Vec<SidecarCarrier>,
    pub seed: u32,
    pub length: usize,
    pub sha256: String,
    pub sha256_bytes: [u8; 32],
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum SidecarCarrier {
    Label,
    Intergroove,
    LeadInDeadwax,
}

impl SidecarCarrier {
    pub fn name(self) -> &'static str {
        sidecar_carrier_name(self)
    }
}

pub fn sha256_digest_bytes(bytes: &[u8]) -> [u8; 32] {
    let digest = Sha256::digest(bytes);
    let mut out = [0u8; 32];
    out.copy_from_slice(&digest);
    out
}

pub fn sha256_base64url(bytes: &[u8]) -> String {
    general_purpose::URL_SAFE_NO_PAD.encode(sha256_digest_bytes(bytes))
}

pub fn decode_base64_text(value: &str, label: &str) -> Result<Vec<u8>> {
    let trimmed = value.trim();
    general_purpose::URL_SAFE_NO_PAD
        .decode(trimmed)
        .or_else(|_| general_purpose::URL_SAFE.decode(trimmed))
        .or_else(|_| general_purpose::STANDARD.decode(trimmed))
        .with_context(|| format!("{label} is not valid base64"))
}

pub fn sidecar_type_name(item_type: u8) -> String {
    match item_type {
        SIDECAR_TYPE_OPAQUE => "opaque".to_string(),
        SIDECAR_TYPE_UTF8_TEXT => "utf8Text".to_string(),
        SIDECAR_TYPE_IMAGE => "image".to_string(),
        SIDECAR_TYPE_JSON => "json".to_string(),
        value => format!("private:{value}"),
    }
}

pub fn sidecar_codec_name(codec: u8) -> String {
    match codec {
        SIDECAR_CODEC_RAW => "raw".to_string(),
        SIDECAR_CODEC_BROTLI => "brotli".to_string(),
        SIDECAR_CODEC_ZSTD => "zstd".to_string(),
        SIDECAR_CODEC_AVIF => "avif".to_string(),
        value => format!("private:{value}"),
    }
}

fn parse_sidecar_registry_value(
    value: &serde_json::Value,
    label: &str,
    names: &[(&str, u8)],
) -> Result<u8> {
    if let Some(number) = value.as_u64() {
        return u8::try_from(number).with_context(|| format!("{label} exceeds u8 range"));
    }
    let Some(raw) = value.as_str() else {
        bail!("{label} must be a string or integer");
    };
    let normalized = raw.trim().to_ascii_lowercase().replace([' ', '-', '_'], "");
    for (name, code) in names {
        if normalized == *name {
            return Ok(*code);
        }
    }
    bail!("unknown {label}: {raw}");
}

pub fn parse_sidecar_item_type(value: &serde_json::Value) -> Result<u8> {
    parse_sidecar_registry_value(
        value,
        "sidecar item type",
        &[
            ("opaque", SIDECAR_TYPE_OPAQUE),
            ("bytes", SIDECAR_TYPE_OPAQUE),
            ("binary", SIDECAR_TYPE_OPAQUE),
            ("utf8text", SIDECAR_TYPE_UTF8_TEXT),
            ("text", SIDECAR_TYPE_UTF8_TEXT),
            ("utf8", SIDECAR_TYPE_UTF8_TEXT),
            ("image", SIDECAR_TYPE_IMAGE),
            ("photo", SIDECAR_TYPE_IMAGE),
            ("json", SIDECAR_TYPE_JSON),
        ],
    )
}

pub fn parse_sidecar_codec(value: &serde_json::Value) -> Result<u8> {
    parse_sidecar_registry_value(
        value,
        "sidecar codec",
        &[
            ("raw", SIDECAR_CODEC_RAW),
            ("none", SIDECAR_CODEC_RAW),
            ("brotli", SIDECAR_CODEC_BROTLI),
            ("br", SIDECAR_CODEC_BROTLI),
            ("zstd", SIDECAR_CODEC_ZSTD),
            ("zstandard", SIDECAR_CODEC_ZSTD),
            ("avif", SIDECAR_CODEC_AVIF),
        ],
    )
}

pub fn validate_sidecar_registry_ranges(item_type: u8, codec: u8) -> Result<()> {
    if (4..=31).contains(&item_type) {
        bail!("sidecar item type {item_type} is reserved");
    }
    if (4..=31).contains(&codec) {
        bail!("sidecar codec {codec} is reserved");
    }
    Ok(())
}

pub fn validate_sidecar_name(value: &str, label: &str) -> Result<()> {
    if value.chars().any(|ch| {
        let code = ch as u32;
        code <= 0x1f || code == 0x7f
    }) {
        bail!("sidecar {label} must not contain control characters");
    }
    Ok(())
}

pub fn looks_like_avif(bytes: &[u8]) -> bool {
    if bytes.len() < 16 || &bytes[4..8] != b"ftyp" {
        return false;
    }
    bytes[8..]
        .chunks(4)
        .any(|chunk| chunk == b"avif" || chunk == b"avis")
}

pub fn validate_sidecar_item_payload(
    item_type: u8,
    codec: u8,
    raw_byte_length: u32,
    stored: &[u8],
    mime: &str,
) -> Result<()> {
    validate_sidecar_registry_ranges(item_type, codec)?;
    if codec == SIDECAR_CODEC_AVIF && item_type != SIDECAR_TYPE_IMAGE {
        bail!("AVIF sidecar codec is only valid for image items");
    }
    if item_type == SIDECAR_TYPE_IMAGE && codec != SIDECAR_CODEC_AVIF {
        bail!("image sidecar items must use AVIF in this version");
    }
    if matches!(item_type, SIDECAR_TYPE_UTF8_TEXT | SIDECAR_TYPE_JSON)
        && codec == SIDECAR_CODEC_AVIF
    {
        bail!("text and JSON sidecar items cannot use AVIF");
    }
    if codec == SIDECAR_CODEC_RAW && raw_byte_length != stored.len() as u32 {
        bail!("raw sidecar item rawByteLength must equal stored length");
    }
    if item_type == SIDECAR_TYPE_UTF8_TEXT && codec == SIDECAR_CODEC_RAW {
        std::str::from_utf8(stored).context("raw UTF-8 text sidecar item is not valid UTF-8")?;
    }
    if item_type == SIDECAR_TYPE_JSON && codec == SIDECAR_CODEC_RAW {
        serde_json::from_slice::<serde_json::Value>(stored)
            .context("raw JSON sidecar item is not valid JSON")?;
    }
    if item_type == SIDECAR_TYPE_IMAGE && codec == SIDECAR_CODEC_AVIF {
        if !looks_like_avif(stored) {
            bail!("AVIF sidecar image does not look like an AVIF file");
        }
        if !mime.is_empty() && !mime.eq_ignore_ascii_case("image/avif") {
            bail!("AVIF sidecar image MIME type must be image/avif");
        }
    }
    Ok(())
}

pub fn default_sidecar_mime(item_type: u8, codec: u8) -> &'static str {
    match (item_type, codec) {
        (SIDECAR_TYPE_UTF8_TEXT, _) => "text/plain;charset=utf-8",
        (SIDECAR_TYPE_JSON, _) => "application/json",
        (SIDECAR_TYPE_IMAGE, SIDECAR_CODEC_AVIF) => "image/avif",
        _ => "",
    }
}

fn read_u16be(bytes: &[u8], offset: usize, label: &str) -> Result<u16> {
    if offset + 2 > bytes.len() {
        bail!("{label} is truncated");
    }
    Ok(u16::from_be_bytes(
        bytes[offset..offset + 2].try_into().expect("slice length"),
    ))
}

fn read_u32be(bytes: &[u8], offset: usize, label: &str) -> Result<u32> {
    if offset + 4 > bytes.len() {
        bail!("{label} is truncated");
    }
    Ok(u32::from_be_bytes(
        bytes[offset..offset + 4].try_into().expect("slice length"),
    ))
}

pub fn validate_sidecar_container(bytes: &[u8]) -> Result<SidecarContainerValidation> {
    if bytes.len() < 12 {
        bail!("sidecar container is too short");
    }
    if &bytes[..4] != SIDECAR_MAGIC {
        bail!("sidecar container magic is unsupported");
    }
    let version = bytes[4];
    if version != SIDECAR_CONTAINER_VERSION {
        bail!("unsupported sidecar container version {version}");
    }
    let flags = bytes[5];
    if flags != 0 {
        bail!("sidecar container flags must be 0 in this version");
    }
    let item_count = read_u16be(bytes, 6, "sidecar item count")? as usize;
    let total_length = read_u32be(bytes, 8, "sidecar total length")? as usize;
    if total_length != bytes.len() {
        bail!("sidecar total length does not match byte stream length");
    }

    let mut offset = 12usize;
    let mut items = Vec::with_capacity(item_count);
    for _ in 0..item_count {
        if offset + 16 > bytes.len() {
            bail!("sidecar item header is truncated");
        }
        let item_type = bytes[offset];
        let codec = bytes[offset + 1];
        let item_flags = bytes[offset + 2];
        let reserved = bytes[offset + 3];
        if item_flags != 0 {
            bail!("sidecar item flags must be 0 in this version");
        }
        if reserved != 0 {
            bail!("sidecar item reserved byte must be 0");
        }
        let raw_byte_length = read_u32be(bytes, offset + 4, "sidecar item raw length")?;
        let stored_byte_length = read_u32be(bytes, offset + 8, "sidecar item stored length")?;
        let name_len = read_u16be(bytes, offset + 12, "sidecar item name length")? as usize;
        let mime_len = read_u16be(bytes, offset + 14, "sidecar item MIME length")? as usize;
        offset += 16;
        let item_end = offset
            .checked_add(name_len)
            .and_then(|value| value.checked_add(mime_len))
            .and_then(|value| value.checked_add(stored_byte_length as usize))
            .context("sidecar item length overflow")?;
        if item_end > bytes.len() {
            bail!("sidecar item payload is truncated");
        }
        let name = std::str::from_utf8(&bytes[offset..offset + name_len])
            .context("sidecar item name is not valid UTF-8")?
            .to_string();
        offset += name_len;
        let mime = std::str::from_utf8(&bytes[offset..offset + mime_len])
            .context("sidecar item MIME type is not valid ASCII/UTF-8")?
            .to_string();
        offset += mime_len;
        let stored = &bytes[offset..offset + stored_byte_length as usize];
        offset += stored_byte_length as usize;

        validate_sidecar_name(&name, "item name")?;
        validate_sidecar_name(&mime, "MIME type")?;
        validate_sidecar_item_payload(item_type, codec, raw_byte_length, stored, &mime)?;
        items.push(SidecarItemValidation {
            item_type,
            item_type_name: sidecar_type_name(item_type),
            codec,
            codec_name: sidecar_codec_name(codec),
            flags: item_flags,
            raw_byte_length: (raw_byte_length != SIDECAR_RAW_LENGTH_ABSENT)
                .then_some(raw_byte_length),
            stored_byte_length,
            name,
            mime,
        });
    }
    if offset != bytes.len() {
        bail!("sidecar container has trailing bytes");
    }
    Ok(SidecarContainerValidation {
        ok: true,
        version,
        flags,
        item_count,
        total_length,
        items,
    })
}

fn decode_sidecar_item_payload(
    item_type: u8,
    codec: u8,
    raw_byte_length: u32,
    stored: &[u8],
) -> Result<Vec<u8>> {
    let decoded = match codec {
        SIDECAR_CODEC_RAW | SIDECAR_CODEC_AVIF => stored.to_vec(),
        SIDECAR_CODEC_BROTLI => {
            let mut reader = brotli::Decompressor::new(stored, 4096);
            let mut out = Vec::new();
            reader
                .read_to_end(&mut out)
                .context("failed to decompress Brotli sidecar item")?;
            out
        }
        SIDECAR_CODEC_ZSTD => bail!("zstd sidecar item extraction is not implemented"),
        _ => stored.to_vec(),
    };
    if raw_byte_length != SIDECAR_RAW_LENGTH_ABSENT && decoded.len() != raw_byte_length as usize {
        bail!(
            "decoded sidecar item length {} does not match declared raw length {}",
            decoded.len(),
            raw_byte_length
        );
    }
    if item_type == SIDECAR_TYPE_UTF8_TEXT {
        std::str::from_utf8(&decoded)
            .context("decoded UTF-8 text sidecar item is not valid UTF-8")?;
    }
    if item_type == SIDECAR_TYPE_JSON {
        serde_json::from_slice::<serde_json::Value>(&decoded)
            .context("decoded JSON sidecar item is not valid JSON")?;
    }
    Ok(decoded)
}

pub fn decode_sidecar_container_items(bytes: &[u8]) -> Result<SidecarDecodedItems> {
    let validation = validate_sidecar_container(bytes)?;
    let item_count = read_u16be(bytes, 6, "sidecar item count")? as usize;
    let mut offset = 12usize;
    let mut items = Vec::with_capacity(item_count);
    for _ in 0..item_count {
        if offset + 16 > bytes.len() {
            bail!("sidecar item header is truncated");
        }
        let item_type = bytes[offset];
        let codec = bytes[offset + 1];
        let flags = bytes[offset + 2];
        let raw_byte_length = read_u32be(bytes, offset + 4, "sidecar item raw length")?;
        let stored_byte_length = read_u32be(bytes, offset + 8, "sidecar item stored length")?;
        let name_len = read_u16be(bytes, offset + 12, "sidecar item name length")? as usize;
        let mime_len = read_u16be(bytes, offset + 14, "sidecar item MIME length")? as usize;
        offset += 16;
        let item_end = offset
            .checked_add(name_len)
            .and_then(|value| value.checked_add(mime_len))
            .and_then(|value| value.checked_add(stored_byte_length as usize))
            .context("sidecar item length overflow")?;
        if item_end > bytes.len() {
            bail!("sidecar item payload is truncated");
        }
        let name = std::str::from_utf8(&bytes[offset..offset + name_len])
            .context("sidecar item name is not valid UTF-8")?
            .to_string();
        offset += name_len;
        let mime = std::str::from_utf8(&bytes[offset..offset + mime_len])
            .context("sidecar item MIME type is not valid ASCII/UTF-8")?
            .to_string();
        offset += mime_len;
        let stored = &bytes[offset..offset + stored_byte_length as usize];
        offset += stored_byte_length as usize;
        let decoded = decode_sidecar_item_payload(item_type, codec, raw_byte_length, stored)?;
        let text = if item_type == SIDECAR_TYPE_UTF8_TEXT {
            Some(
                std::str::from_utf8(&decoded)
                    .context("decoded UTF-8 text sidecar item is not valid UTF-8")?
                    .to_string(),
            )
        } else {
            None
        };
        let json = if item_type == SIDECAR_TYPE_JSON {
            Some(
                serde_json::from_slice::<serde_json::Value>(&decoded)
                    .context("decoded JSON sidecar item is not valid JSON")?,
            )
        } else {
            None
        };
        items.push(SidecarDecodedItem {
            item_type,
            item_type_name: sidecar_type_name(item_type),
            codec,
            codec_name: sidecar_codec_name(codec),
            flags,
            raw_byte_length: (raw_byte_length != SIDECAR_RAW_LENGTH_ABSENT)
                .then_some(raw_byte_length),
            stored_byte_length,
            decoded_byte_length: decoded.len(),
            name,
            mime,
            stored_data_base64: general_purpose::STANDARD.encode(stored),
            data_base64: general_purpose::STANDARD.encode(&decoded),
            text,
            json,
        });
    }
    if offset != bytes.len() {
        bail!("sidecar container has trailing bytes");
    }
    Ok(SidecarDecodedItems {
        ok: true,
        validation,
        items,
    })
}

pub fn parse_sidecar_carrier(raw: &str) -> Result<SidecarCarrier> {
    match raw
        .trim()
        .to_ascii_lowercase()
        .replace([' ', '-', '_'], "")
        .as_str()
    {
        "label" => Ok(SidecarCarrier::Label),
        "intergroove" | "intragroove" | "groove" => Ok(SidecarCarrier::Intergroove),
        "leadindeadwax" | "leaddeadwax" | "leadin" | "leadout" | "deadwax" | "runout" => {
            Ok(SidecarCarrier::LeadInDeadwax)
        }
        _ => bail!("unknown sidecar carrier: {raw}"),
    }
}

pub fn sidecar_carrier_name(carrier: SidecarCarrier) -> &'static str {
    match carrier {
        SidecarCarrier::Label => "label",
        SidecarCarrier::Intergroove => "intergroove",
        SidecarCarrier::LeadInDeadwax => "leadInDeadwax",
    }
}

pub fn normalize_sidecar_carriers(raw: Option<&[String]>) -> Result<Vec<SidecarCarrier>> {
    let mut carriers = Vec::new();
    if let Some(raw) = raw {
        for value in raw {
            let carrier = parse_sidecar_carrier(value)?;
            if !carriers.contains(&carrier) {
                carriers.push(carrier);
            }
        }
    } else {
        carriers.push(SidecarCarrier::Label);
        carriers.push(SidecarCarrier::Intergroove);
    }
    if carriers.is_empty() {
        bail!("sidecar carriers must not be empty");
    }
    Ok(carriers)
}

pub fn default_sidecar_carriers() -> Vec<SidecarCarrier> {
    vec![SidecarCarrier::Label, SidecarCarrier::Intergroove]
}

pub fn normalize_sidecar_scheme(raw: Option<&str>) -> Result<String> {
    let Some(raw) = raw else {
        return Ok(SIDECAR_SCHEME_PAIRSIGN_SAFE_LUMA_V2.to_string());
    };
    let normalized = raw.trim().to_ascii_lowercase();
    match normalized.as_str() {
        SIDECAR_SCHEME_PAIRSIGN_SAFE_LUMA_V2 => Ok(normalized),
        _ => bail!("unsupported sidecar carrier scheme {raw}"),
    }
}

pub fn sidecar_pointer_scheme_id(scheme: &str) -> Result<u8> {
    match normalize_sidecar_scheme(Some(scheme))?.as_str() {
        SIDECAR_SCHEME_PAIRSIGN_SAFE_LUMA_V2 => Ok(SIDECAR_POINTER_SCHEME_PAIRSIGN_SAFE_LUMA_V2),
        _ => unreachable!("scheme normalized"),
    }
}

pub fn sidecar_pointer_scheme_name(scheme_id: u8) -> Result<String> {
    match scheme_id {
        SIDECAR_POINTER_SCHEME_PAIRSIGN_SAFE_LUMA_V2 => {
            Ok(SIDECAR_SCHEME_PAIRSIGN_SAFE_LUMA_V2.to_string())
        }
        _ => bail!("unsupported sidecar pointer scheme id {scheme_id}"),
    }
}

pub fn sidecar_pointer_carrier_flags(carriers: &[SidecarCarrier]) -> u8 {
    let mut flags = 0u8;
    if carriers.contains(&SidecarCarrier::Label) {
        flags |= SIDECAR_POINTER_CARRIER_LABEL;
    }
    if carriers.contains(&SidecarCarrier::Intergroove) {
        flags |= SIDECAR_POINTER_CARRIER_INTERGROOVE;
    }
    if carriers.contains(&SidecarCarrier::LeadInDeadwax) {
        flags |= SIDECAR_POINTER_CARRIER_LEAD_IN_DEADWAX;
    }
    flags
}

pub fn sidecar_pointer_carriers(flags: u8) -> Result<Vec<SidecarCarrier>> {
    if flags
        & !(SIDECAR_POINTER_CARRIER_LABEL
            | SIDECAR_POINTER_CARRIER_INTERGROOVE
            | SIDECAR_POINTER_CARRIER_LEAD_IN_DEADWAX)
        != 0
    {
        bail!("sidecar pointer has unsupported carrier flags {flags:#04x}");
    }
    let mut carriers = Vec::new();
    if flags & SIDECAR_POINTER_CARRIER_LABEL != 0 {
        carriers.push(SidecarCarrier::Label);
    }
    if flags & SIDECAR_POINTER_CARRIER_INTERGROOVE != 0 {
        carriers.push(SidecarCarrier::Intergroove);
    }
    if flags & SIDECAR_POINTER_CARRIER_LEAD_IN_DEADWAX != 0 {
        carriers.push(SidecarCarrier::LeadInDeadwax);
    }
    if carriers.is_empty() {
        bail!("sidecar pointer has no carriers");
    }
    Ok(carriers)
}


pub fn decode_sidecar_header_pointer(payload: &[u8]) -> Result<SidecarHeaderPointer> {
    if payload.len() != SIDECAR_POINTER_LENGTH {
        bail!("sidecar pointer has invalid length {}", payload.len());
    }
    if &payload[..4] != SIDECAR_MAGIC {
        bail!("sidecar pointer magic is unsupported");
    }
    let version = payload[4];
    if version != SIDECAR_POINTER_VERSION {
        bail!("unsupported sidecar pointer version {version}");
    }
    let scheme = sidecar_pointer_scheme_name(payload[5])?;
    let carriers = sidecar_pointer_carriers(payload[6])?;
    if payload[7] != 0 {
        bail!("sidecar pointer reserved byte must be 0");
    }
    let seed = u32::from_be_bytes(payload[8..12].try_into().expect("slice length"));
    if seed == 0 {
        bail!("sidecar seed must be nonzero");
    }
    let length = u32::from_be_bytes(payload[12..16].try_into().expect("slice length")) as usize;
    if length == 0 {
        bail!("sidecar length must be nonzero");
    }
    let mut sha256_bytes = [0u8; 32];
    sha256_bytes.copy_from_slice(&payload[16..48]);
    let sha256 = general_purpose::URL_SAFE_NO_PAD.encode(sha256_bytes);
    Ok(SidecarHeaderPointer {
        scheme,
        carriers,
        seed,
        length,
        sha256,
        sha256_bytes,
    })
}

pub fn sidecar_header_pointer_json(pointer: &SidecarHeaderPointer) -> serde_json::Value {
    serde_json::json!({
        "v": SIDECAR_POINTER_VERSION,
        "c": "BTS1",
        "s": pointer.scheme.as_str(),
        "r": pointer.carriers.iter().map(|carrier| sidecar_carrier_name(*carrier)).collect::<Vec<_>>(),
        "n": pointer.seed,
        "l": pointer.length,
        "h": pointer.sha256.as_str(),
    })
}


pub fn mulberry32_next(state: &mut u32) -> u32 {
    *state = state.wrapping_add(0x6d2b_79f5);
    let mut t = *state;
    t = (t ^ (t >> 15)).wrapping_mul(t | 1);
    t ^= t.wrapping_add((t ^ (t >> 7)).wrapping_mul(t | 61));
    t ^ (t >> 14)
}

pub fn shuffle_pairs_mulberry32(pairs: &mut [(usize, usize)], seed: u32) {
    let mut state = seed;
    for i in (1..pairs.len()).rev() {
        let j = (mulberry32_next(&mut state) as usize) % (i + 1);
        pairs.swap(i, j);
    }
}

pub use bytes2rgb::luma_rec709 as sidecar_luma_rec709;

pub fn metadata_dither(pixel_index: usize, sequence_index: usize, salt: usize) -> u8 {
    let mut value = pixel_index as u64;
    value ^= (sequence_index as u64).wrapping_mul(0x9e37_79b9_7f4a_7c15);
    value ^= (salt as u64).wrapping_mul(0xbf58_476d_1ce4_e5b9);
    value ^= value >> 30;
    value = value.wrapping_mul(0xbf58_476d_1ce4_e5b9);
    value ^= value >> 27;
    value = value.wrapping_mul(0x94d0_49bb_1331_11eb);
    value ^= value >> 31;
    (value & 0xff) as u8
}


pub fn sidecar_capacity_bytes_for_scheme(scheme: &str, carrier_pairs: usize) -> Result<usize> {
    match normalize_sidecar_scheme(Some(scheme))?.as_str() {
        SIDECAR_SCHEME_PAIRSIGN_SAFE_LUMA_V2 => Ok(carrier_pairs / 4),
        _ => unreachable!("scheme normalized"),
    }
}

pub fn sidecar_pair_bit_width_for_scheme(
    scheme: &str,
    _rgba: &[u8],
    _first_pixel: usize,
    _second_pixel: usize,
) -> Result<usize> {
    match normalize_sidecar_scheme(Some(scheme))?.as_str() {
        SIDECAR_SCHEME_PAIRSIGN_SAFE_LUMA_V2 => Ok(2),
        _ => unreachable!("scheme normalized"),
    }
}

pub fn sidecar_bit_capacity_for_pairs(
    scheme: &str,
    pairs: &[(usize, usize)],
    rgba: &[u8],
) -> Result<usize> {
    let mut bits = 0usize;
    for &(first, second) in pairs {
        bits = bits
            .checked_add(sidecar_pair_bit_width_for_scheme(
                scheme, rgba, first, second,
            )?)
            .context("sidecar pair bit capacity overflow")?;
    }
    Ok(bits)
}

pub fn sidecar_capacity_for_pairs(
    scheme: &str,
    carriers: &[SidecarCarrier],
    pairs: &[(usize, usize)],
    rgba: &[u8],
) -> Result<SidecarCapacity> {
    let bit_capacity = sidecar_bit_capacity_for_pairs(scheme, pairs, rgba)?;
    let two_bit_pairs = pairs
        .iter()
        .filter_map(|&(first, second)| {
            sidecar_pair_bit_width_for_scheme(scheme, rgba, first, second).ok()
        })
        .filter(|width| *width == 2)
        .count();
    Ok(SidecarCapacity {
        scheme: normalize_sidecar_scheme(Some(scheme))?,
        carriers: carriers
            .iter()
            .map(|carrier| sidecar_carrier_name(*carrier).to_string())
            .collect(),
        carrier_pixels: pairs.len() * 2,
        carrier_pairs: pairs.len(),
        capacity_bits: bit_capacity,
        capacity_bytes: bit_capacity / 8,
        bits_per_pair: if pairs.is_empty() {
            0.0
        } else {
            bit_capacity as f64 / pairs.len() as f64
        },
        two_bit_pairs,
    })
}

pub fn decode_pairsign_sidecar_bytes_from_pairs(
    rgba: &[u8],
    pairs: &[(usize, usize)],
    scheme: &str,
    byte_length: usize,
) -> Result<Vec<u8>> {
    let bit_capacity = sidecar_bit_capacity_for_pairs(scheme, pairs, rgba)?;
    if byte_length.saturating_mul(8) > bit_capacity {
        bail!(
            "sidecar descriptor length {} exceeds pair-sign carrier capacity {}",
            byte_length,
            bit_capacity / 8
        );
    }
    let target_bits = byte_length
        .checked_mul(8)
        .context("sidecar decode bit length overflow")?;
    let mut out = vec![0u8; byte_length];
    let mut bit_index = 0usize;

    fn push_sidecar_decoded_bit(out: &mut [u8], bit_index: usize, bit: u8) {
        if bit != 0 {
            let byte_index = bit_index / 8;
            let shift = 7 - (bit_index % 8);
            out[byte_index] |= 1 << shift;
        }
    }

    for &(first, second) in pairs {
        if bit_index >= target_bits {
            break;
        }
        let first_luma = sidecar_luma_rec709(rgba, first);
        let second_luma = sidecar_luma_rec709(rgba, second);
        let sign_bit = if first_luma > second_luma { 1u8 } else { 0u8 };
        push_sidecar_decoded_bit(&mut out, bit_index, sign_bit);
        bit_index += 1;
        if bit_index < target_bits
            && sidecar_pair_bit_width_for_scheme(scheme, rgba, first, second)? == 2
        {
            let magnitude_bit =
                if (first_luma - second_luma).abs() >= SIDECAR_PAIR_MAGNITUDE_THRESHOLD {
                    1u8
                } else {
                    0u8
                };
            push_sidecar_decoded_bit(&mut out, bit_index, magnitude_bit);
            bit_index += 1;
        }
    }
    if bit_index < target_bits {
        bail!("sidecar carrier did not provide enough bits to decode descriptor length");
    }
    Ok(out)
}


pub fn decode_sidecar_from_pairs(
    rgba: &[u8],
    pairs: &[(usize, usize)],
    scheme: &str,
    byte_length: usize,
) -> Result<(Vec<u8>, SidecarDecodeResult)> {
    let bts1 = decode_pairsign_sidecar_bytes_from_pairs(rgba, pairs, scheme, byte_length)?;
    let validation = validate_sidecar_container(&bts1)?;
    let sha256 = sha256_base64url(&bts1);
    let capacity = sidecar_capacity_bytes_for_scheme(scheme, pairs.len())?;
    let descriptor = serde_json::json!({
        "container": "BTS1",
        "scheme": normalize_sidecar_scheme(Some(scheme))?,
        "length": byte_length,
    });
    let result = SidecarDecodeResult {
        ok: true,
        descriptor,
        validation,
        bts1_byte_length: bts1.len(),
        sha256,
        carrier_pixels: pairs.len() * 2,
        carrier_pairs: pairs.len(),
        capacity_bytes: capacity,
    };
    Ok((bts1, result))
}
