//! Independent witness types for the trusted evidence reducer.
//!
//! Each witness carries:
//!
//! - its `class` (which of the four ADR 0041 §4 witness classes it is),
//! - its `authority_domain` (a disjointness tag, ADR 0013),
//! - its `tier` (third-party, operator-owned, or local),
//! - a timestamp (`asserted_at`) and the digest it claims to witness
//!   (`asserted_subject_blake3`),
//! - an in-memory `signature` payload (no I/O on the trust path),
//! - a typed `payload` discriminated by `class`.
//!
//! No method on these types performs I/O, network, or filesystem reads. The
//! CLI is responsible for loading the witness bytes and the verifier public
//! key into memory before invoking [`crate::verify`].

use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};

/// One of the four ADR 0041 §4 witness classes. Each class binds a fixed
/// `AuthorityDomain`; mismatches are rejected as `verifier.witness.authority_overlap`.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum WitnessClass {
    /// Signed Cortex ledger chain head (ADR 0022 envelope + Ed25519 head signature).
    SignedLedgerChainHead,
    /// External anchor crossing line from a disjoint-authority sink
    /// (Mechanism A branch-protected, Mechanism B WORM, etc.).
    ExternalAnchorCrossing,
    /// Remote-CI conclusion blob signed by the CI provider's key.
    RemoteCiConclusion,
    /// SLSA-shaped reproducible-build provenance signed by a builder
    /// identity distinct from the CI signer.
    ReproducibleBuildProvenance,
}

impl WitnessClass {
    /// Authority domain a well-formed witness of this class MUST advertise.
    /// Per ADR 0013 §"Invariant: what 'external' means".
    #[must_use]
    pub const fn required_authority_domain(self) -> AuthorityDomain {
        match self {
            Self::SignedLedgerChainHead => AuthorityDomain::LocalSignedLedger,
            Self::ExternalAnchorCrossing => AuthorityDomain::ExternalAnchorSink,
            Self::RemoteCiConclusion => AuthorityDomain::RemoteCiProvider,
            Self::ReproducibleBuildProvenance => AuthorityDomain::ReproducibleBuildProvider,
        }
    }

    /// Stable lowercase wire string for this class. Used in
    /// `Broken { edge: { detail } }` output.
    #[must_use]
    pub const fn wire_str(self) -> &'static str {
        match self {
            Self::SignedLedgerChainHead => "signed_ledger_chain_head",
            Self::ExternalAnchorCrossing => "external_anchor_crossing",
            Self::RemoteCiConclusion => "remote_ci_conclusion",
            Self::ReproducibleBuildProvenance => "reproducible_build_provenance",
        }
    }
}

/// Disjointness tag for witness authority. Two witnesses sharing one of these
/// variants cannot corroborate one another (ADR 0013 §"Invariant: what
/// 'external' means" and ADR 0040 §"composition boundary").
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum AuthorityDomain {
    /// Operator-controlled local signing key.
    LocalSignedLedger,
    /// External anchor sink with disjoint write authority.
    ExternalAnchorSink,
    /// Third-party CI provider's OIDC / signing identity.
    RemoteCiProvider,
    /// Third-party reproducible-build provider's signing identity.
    ReproducibleBuildProvider,
}

impl AuthorityDomain {
    /// Stable lowercase wire string for this domain.
    #[must_use]
    pub const fn wire_str(self) -> &'static str {
        match self {
            Self::LocalSignedLedger => "local_signed_ledger",
            Self::ExternalAnchorSink => "external_anchor_sink",
            Self::RemoteCiProvider => "remote_ci_provider",
            Self::ReproducibleBuildProvider => "reproducible_build_provider",
        }
    }
}

/// Trust tier of the witness signer. Some witness classes require
/// `ThirdParty` to satisfy ADR 0041 §"Tier sufficiency".
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum WitnessTier {
    /// Local-only key (operator's own machine, not externally attested).
    Local,
    /// Operator-controlled key with declared scope, but not third-party.
    OperatorOwned,
    /// A third party signed under their own identity (e.g. GitHub OIDC,
    /// SLSA builder).
    ThirdParty,
}

impl WitnessTier {
    /// Stable lowercase wire string for this tier.
    #[must_use]
    pub const fn wire_str(self) -> &'static str {
        match self {
            Self::Local => "local",
            Self::OperatorOwned => "operator_owned",
            Self::ThirdParty => "third_party",
        }
    }
}

/// Algorithm-discriminated signature payload for an independent witness.
///
/// ## Serialization and backward compatibility
///
/// New records are serialized with `#[serde(tag = "type")]`, producing JSON
/// of the form `{"type": "ed25519", "public_key_bytes": "...", ...}`.
///
/// Existing serialized records in the JSONL ledger from before this enum was
/// introduced are bare structs with no `"type"` field:
/// `{"verifying_key": "...", "signature": "...", "signer_id": null}`.
///
/// The custom [`Deserialize`] implementation tries the tagged form first; on
/// failure it falls back to [`LegacyEd25519Shape`], which accepts the old
/// field names and maps them to the `Ed25519` variant. No migration of
/// existing records is required — they round-trip transparently.
///
/// When either migration is triggered (a new witness class lands that cannot
/// be expressed via the adapter-boundary translation in
/// `crates/cortex-ledger/src/external_sink/rekor.rs`) or the verifier core
/// needs to reason uniformly over algorithm families, re-evaluate whether the
/// custom deserializer can be replaced by a `#[serde(untagged)]` fallback
/// combining the enum with the legacy shape.
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum WitnessSignature {
    /// Ed25519 signature — current Rekor SET shape. The default variant;
    /// existing serialized records deserialize as this without migration.
    Ed25519 {
        /// 32-byte Ed25519 verifying key (hex-encoded in JSON).
        #[serde(with = "hex_bytes_32")]
        public_key_bytes: [u8; 32],
        /// 64-byte Ed25519 signature over the canonical witness preimage
        /// (see [`WitnessPayload::canonical_preimage`]).
        #[serde(with = "hex_bytes_64")]
        signature_bytes: [u8; 64],
        /// Optional human-readable signer label (e.g.
        /// `"https://token.actions.githubusercontent.com"`). Carried for
        /// reporting; not part of the trust decision.
        #[serde(default, skip_serializing_if = "Option::is_none")]
        signer_id: Option<String>,
    },
    /// ECDSA P-256 signature — for Cosign/Sigstore artifacts. Verification is
    /// not yet implemented at the verifier layer; the Rekor live adapter
    /// handles P-256 verification at the adapter boundary
    /// (`crates/cortex-ledger/src/external_sink/rekor.rs`).
    EcdsaP256 {
        /// DER-encoded public key.
        #[serde(with = "hex_vec")]
        public_key_der: Vec<u8>,
        /// DER-encoded signature.
        #[serde(with = "hex_vec")]
        signature_der: Vec<u8>,
    },
    /// Operator self-signed witness — third authority axis that does not
    /// require Rekor or OTS. Enables N≥2-distinct-operators quorum for
    /// operators who cannot use public infrastructure.
    SelfSigned {
        /// Operator key identifier (from the authority_key_timeline).
        key_id: String,
        /// Raw signature bytes (algorithm determined by key_id lookup).
        #[serde(with = "hex_vec")]
        signature_bytes: Vec<u8>,
    },
}

impl WitnessSignature {
    /// Optional signer label for reporting. Only present for the `Ed25519`
    /// variant; other variants carry identity via their own fields.
    #[must_use]
    pub fn signer_id(&self) -> Option<&str> {
        match self {
            Self::Ed25519 { signer_id, .. } => signer_id.as_deref(),
            Self::EcdsaP256 { .. } | Self::SelfSigned { .. } => None,
        }
    }
}

// --- backward-compatible Deserialize ---------------------------------------
//
// Strategy: deserialize into a raw `serde_json::Value`, then try the tagged
// form. If the `"type"` key is absent (legacy bare struct), map the old
// `verifying_key` / `signature` / `signer_id` fields to `Ed25519`.

impl<'de> Deserialize<'de> for WitnessSignature {
    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
        let raw = serde_json::Value::deserialize(deserializer)?;

        // If the value has a "type" field, delegate to the tagged inner enum.
        if raw.get("type").is_some() {
            let inner: WitnessSignatureInner =
                serde_json::from_value(raw).map_err(serde::de::Error::custom)?;
            return Ok(Self::from(inner));
        }

        // Legacy bare struct: `verifying_key` (32-byte hex), `signature`
        // (64-byte hex), `signer_id` (optional string). Produced by the
        // pre-enum `WitnessSignature` struct before ADR 0041 amendment.
        let legacy: LegacyEd25519Shape =
            serde_json::from_value(raw).map_err(serde::de::Error::custom)?;
        Ok(Self::Ed25519 {
            public_key_bytes: legacy.verifying_key,
            signature_bytes: legacy.signature,
            signer_id: legacy.signer_id,
        })
    }
}

// We cannot derive `Deserialize` directly on `WitnessSignature` with
// `#[serde(tag)]` because doing so would conflict with our custom impl above.
// Instead we mirror the three variants in a private inner enum that does
// derive `Deserialize` through the proc macro, then convert into the public
// enum.

#[derive(Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
enum WitnessSignatureInner {
    Ed25519 {
        #[serde(with = "hex_bytes_32")]
        public_key_bytes: [u8; 32],
        #[serde(with = "hex_bytes_64")]
        signature_bytes: [u8; 64],
        #[serde(default)]
        signer_id: Option<String>,
    },
    EcdsaP256 {
        #[serde(with = "hex_vec")]
        public_key_der: Vec<u8>,
        #[serde(with = "hex_vec")]
        signature_der: Vec<u8>,
    },
    SelfSigned {
        key_id: String,
        #[serde(with = "hex_vec")]
        signature_bytes: Vec<u8>,
    },
}

impl From<WitnessSignatureInner> for WitnessSignature {
    fn from(inner: WitnessSignatureInner) -> Self {
        match inner {
            WitnessSignatureInner::Ed25519 {
                public_key_bytes,
                signature_bytes,
                signer_id,
            } => Self::Ed25519 {
                public_key_bytes,
                signature_bytes,
                signer_id,
            },
            WitnessSignatureInner::EcdsaP256 {
                public_key_der,
                signature_der,
            } => Self::EcdsaP256 {
                public_key_der,
                signature_der,
            },
            WitnessSignatureInner::SelfSigned {
                key_id,
                signature_bytes,
            } => Self::SelfSigned {
                key_id,
                signature_bytes,
            },
        }
    }
}

/// Pre-enum (legacy) bare `WitnessSignature` shape for backward-compatible
/// deserialization of records written before the enum migration.
#[derive(Deserialize)]
struct LegacyEd25519Shape {
    #[serde(with = "hex_bytes_32")]
    verifying_key: [u8; 32],
    #[serde(with = "hex_bytes_64")]
    signature: [u8; 64],
    #[serde(default)]
    signer_id: Option<String>,
}

/// Typed witness payload, discriminated by class.
///
/// Each variant is the bag of fields that this witness class must convey.
/// The fields are deliberately minimal — only what the verifier needs to
/// reduce the input.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum WitnessPayload {
    /// Signed Cortex ledger chain head per ADR 0022.
    SignedLedgerChainHead {
        /// Hex-encoded chain head event hash (BLAKE3 over canonical bytes).
        chain_head_hash: String,
        /// Position of the chain head row (event count at signing time).
        event_count: u64,
    },
    /// External anchor crossing line from a Mechanism A / B / C sink.
    ExternalAnchorCrossing {
        /// Hex-encoded `chain_head_hash` the anchor witnessed.
        chain_head_hash: String,
        /// Crossing event count per ADR 0013 anchor payload.
        event_count: u64,
        /// Sink kind identifier (e.g. `"branch_protection"`, `"worm_append"`).
        sink_kind: String,
    },
    /// Remote CI conclusion: workflow id + commit SHA + conclusion timestamp.
    RemoteCiConclusion {
        /// Workflow run identifier (e.g. GitHub Actions run id as decimal).
        workflow_run_id: String,
        /// Git commit SHA whose CI run produced this conclusion.
        commit_sha: String,
        /// Conclusion timestamp as ISO 8601 (informational; freshness uses
        /// [`IndependentWitness::asserted_at`]).
        conclusion_timestamp: DateTime<Utc>,
    },
    /// SLSA-shaped reproducible-build provenance.
    ReproducibleBuildProvenance {
        /// Builder identity string, distinct from the remote CI signer.
        builder_id: String,
        /// Hex-encoded source digest (input bytes the builder consumed).
        source_digest: String,
        /// Hex-encoded output artifact digest.
        artifact_digest: String,
    },
}

impl WitnessPayload {
    /// Class this payload belongs to. Used to verify the declared `class`
    /// field against the payload shape.
    #[must_use]
    pub const fn class(&self) -> WitnessClass {
        match self {
            Self::SignedLedgerChainHead { .. } => WitnessClass::SignedLedgerChainHead,
            Self::ExternalAnchorCrossing { .. } => WitnessClass::ExternalAnchorCrossing,
            Self::RemoteCiConclusion { .. } => WitnessClass::RemoteCiConclusion,
            Self::ReproducibleBuildProvenance { .. } => WitnessClass::ReproducibleBuildProvenance,
        }
    }

    /// Canonical signing preimage. Caller signs this exact byte sequence with
    /// the witness's verifying key; the verifier reconstructs and re-checks it.
    ///
    /// The format is a fixed UTF-8 line layout, prefixed with the class wire
    /// string, the authority domain wire string, the asserted-subject digest,
    /// and the ISO 8601 `asserted_at` stamp. This is enough to bind the
    /// signature to the exact tuple `(class, domain, subject, time, payload)`.
    #[must_use]
    pub fn canonical_preimage(
        &self,
        domain: AuthorityDomain,
        asserted_subject_blake3: &str,
        asserted_at: DateTime<Utc>,
    ) -> Vec<u8> {
        let mut out = Vec::with_capacity(256);
        out.extend_from_slice(b"cortex.verifier.witness.v1\n");
        out.extend_from_slice(b"class=");
        out.extend_from_slice(self.class().wire_str().as_bytes());
        out.push(b'\n');
        out.extend_from_slice(b"authority_domain=");
        out.extend_from_slice(domain.wire_str().as_bytes());
        out.push(b'\n');
        out.extend_from_slice(b"asserted_subject_blake3=");
        out.extend_from_slice(asserted_subject_blake3.as_bytes());
        out.push(b'\n');
        out.extend_from_slice(b"asserted_at=");
        out.extend_from_slice(asserted_at.to_rfc3339().as_bytes());
        out.push(b'\n');
        match self {
            Self::SignedLedgerChainHead {
                chain_head_hash,
                event_count,
            } => {
                out.extend_from_slice(b"chain_head_hash=");
                out.extend_from_slice(chain_head_hash.as_bytes());
                out.push(b'\n');
                out.extend_from_slice(b"event_count=");
                out.extend_from_slice(event_count.to_string().as_bytes());
                out.push(b'\n');
            }
            Self::ExternalAnchorCrossing {
                chain_head_hash,
                event_count,
                sink_kind,
            } => {
                out.extend_from_slice(b"chain_head_hash=");
                out.extend_from_slice(chain_head_hash.as_bytes());
                out.push(b'\n');
                out.extend_from_slice(b"event_count=");
                out.extend_from_slice(event_count.to_string().as_bytes());
                out.push(b'\n');
                out.extend_from_slice(b"sink_kind=");
                out.extend_from_slice(sink_kind.as_bytes());
                out.push(b'\n');
            }
            Self::RemoteCiConclusion {
                workflow_run_id,
                commit_sha,
                conclusion_timestamp,
            } => {
                out.extend_from_slice(b"workflow_run_id=");
                out.extend_from_slice(workflow_run_id.as_bytes());
                out.push(b'\n');
                out.extend_from_slice(b"commit_sha=");
                out.extend_from_slice(commit_sha.as_bytes());
                out.push(b'\n');
                out.extend_from_slice(b"conclusion_timestamp=");
                out.extend_from_slice(conclusion_timestamp.to_rfc3339().as_bytes());
                out.push(b'\n');
            }
            Self::ReproducibleBuildProvenance {
                builder_id,
                source_digest,
                artifact_digest,
            } => {
                out.extend_from_slice(b"builder_id=");
                out.extend_from_slice(builder_id.as_bytes());
                out.push(b'\n');
                out.extend_from_slice(b"source_digest=");
                out.extend_from_slice(source_digest.as_bytes());
                out.push(b'\n');
                out.extend_from_slice(b"artifact_digest=");
                out.extend_from_slice(artifact_digest.as_bytes());
                out.push(b'\n');
            }
        }
        out
    }
}

/// An independent witness, in the shape the verifier consumes. All fields are
/// already verified at the deserialization boundary: missing keys, malformed
/// hex, and unknown classes fail closed before [`crate::verify`] runs.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct IndependentWitness {
    /// Witness class.
    pub class: WitnessClass,
    /// Authority domain advertised by this witness. MUST equal
    /// `class.required_authority_domain()` or the verifier rejects with
    /// `verifier.witness.authority_overlap`.
    pub authority_domain: AuthorityDomain,
    /// Trust tier of the signer.
    pub tier: WitnessTier,
    /// When the witness was asserted (signed). Compared to the caller-supplied
    /// `now` for freshness.
    pub asserted_at: DateTime<Utc>,
    /// Lowercase BLAKE3 hex of the bytes this witness claims to attest.
    /// MUST equal the producer-supplied `EvidenceInput::evidence_blake3` or
    /// the verifier rejects with `verifier.witness.disagreement`.
    pub asserted_subject_blake3: String,
    /// Algorithm-discriminated signature material.
    pub signature: WitnessSignature,
    /// Typed payload for this witness class.
    pub payload: WitnessPayload,
}

impl IndependentWitness {
    /// Compute the canonical signing preimage this witness's signature must cover.
    #[must_use]
    pub fn canonical_preimage(&self) -> Vec<u8> {
        self.payload.canonical_preimage(
            self.authority_domain,
            &self.asserted_subject_blake3,
            self.asserted_at,
        )
    }
}

/// Lightweight summary returned in `VerifiedTrustState` for reporting.
/// Contains the typed identity of each witness without leaking signature bytes
/// into report JSON.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct WitnessSummary {
    /// Witness class (wire string).
    pub class: String,
    /// Authority domain (wire string).
    pub authority_domain: String,
    /// Tier (wire string).
    pub tier: String,
    /// Optional signer id (from `WitnessSignature::Ed25519 { signer_id }` or
    /// the key_id for `SelfSigned`). Not present for `EcdsaP256`.
    pub signer_id: Option<String>,
    /// ISO 8601 timestamp when the witness was signed.
    pub asserted_at: DateTime<Utc>,
}

impl WitnessSummary {
    /// Build a summary from an [`IndependentWitness`].
    #[must_use]
    pub fn from_witness(witness: &IndependentWitness) -> Self {
        let signer_id = match &witness.signature {
            WitnessSignature::Ed25519 { signer_id, .. } => signer_id.clone(),
            WitnessSignature::SelfSigned { key_id, .. } => Some(key_id.clone()),
            WitnessSignature::EcdsaP256 { .. } => None,
        };
        Self {
            class: witness.class.wire_str().to_string(),
            authority_domain: witness.authority_domain.wire_str().to_string(),
            tier: witness.tier.wire_str().to_string(),
            signer_id,
            asserted_at: witness.asserted_at,
        }
    }
}

// ---------------------------------------------------------------------------
// SelfSignedKeyRegistry — operator-supplied key table for SelfSigned witnesses
// ---------------------------------------------------------------------------

/// Signature algorithm declared for a `SelfSignedKeyEntry`. The verifier
/// dispatches on this to select the correct verification routine.
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum SelfSignedAlgorithm {
    /// Ed25519 — 32-byte raw public key.
    Ed25519,
    /// ECDSA P-256 — DER-encoded SubjectPublicKeyInfo.
    EcdsaP256,
}

/// A single entry in the operator-supplied key registry. Each entry binds a
/// `key_id` string (matched against `WitnessSignature::SelfSigned { key_id }`)
/// to an algorithm and the raw public-key bytes.
#[derive(Debug, Clone, serde::Deserialize)]
pub struct SelfSignedKeyEntry {
    /// Identifier matching `WitnessSignature::SelfSigned { key_id }`.
    pub key_id: String,
    /// Algorithm family for this key.
    pub algorithm: SelfSignedAlgorithm,
    /// Hex-encoded raw public-key bytes.
    ///
    /// For `Ed25519`: 32 bytes (64 hex chars).
    /// For `EcdsaP256`: DER-encoded SubjectPublicKeyInfo.
    pub key_bytes_hex: String,
}

impl SelfSignedKeyEntry {
    /// Decode `key_bytes_hex` into raw bytes.
    pub fn key_bytes(&self) -> Result<Vec<u8>, String> {
        hex_decode(&self.key_bytes_hex)
    }
}

/// TOML wire shape used only during `SelfSignedKeyRegistry::load()`. The outer
/// struct matches `{ keys = [...] }`.
#[derive(serde::Deserialize)]
struct KeyRegistryFile {
    keys: Vec<SelfSignedKeyEntry>,
}

/// In-memory table of operator-supplied public keys for `SelfSigned` witness
/// verification. Loaded once at CLI startup; the verifier is given a reference
/// to the populated registry before the trust path runs.
#[derive(Debug, Clone)]
pub struct SelfSignedKeyRegistry {
    entries: Vec<SelfSignedKeyEntry>,
}

impl SelfSignedKeyRegistry {
    /// Return an empty registry (no `SelfSigned` witness will verify).
    #[must_use]
    pub fn empty() -> Self {
        Self {
            entries: Vec::new(),
        }
    }

    /// Load a key registry from a TOML file on disk.
    ///
    /// Expected file format:
    ///
    /// ```toml
    /// [[keys]]
    /// key_id = "my-operator-key"
    /// algorithm = "ed25519"
    /// key_bytes_hex = "aabbcc..."  # 64 hex chars for Ed25519 (32 bytes)
    ///
    /// [[keys]]
    /// key_id = "my-p256-key"
    /// algorithm = "ecdsa_p256"
    /// key_bytes_hex = "..."  # DER hex
    /// ```
    ///
    /// Returns `Err` if the file cannot be read or fails to parse as the
    /// expected TOML shape. Key-bytes are NOT decoded here; decoding happens
    /// at verification time so a single malformed entry does not block
    /// loading the rest.
    pub fn load(path: &std::path::Path) -> Result<Self, String> {
        let raw = std::fs::read_to_string(path).map_err(|e| {
            format!(
                "witness-key-registry: cannot read `{}`: {e}",
                path.display()
            )
        })?;
        let file: KeyRegistryFile = toml::from_str(&raw).map_err(|e| {
            format!(
                "witness-key-registry: `{}` did not parse as expected TOML: {e}",
                path.display()
            )
        })?;
        Ok(Self { entries: file.keys })
    }

    /// Look up a key entry by `key_id`. Returns `None` when the id is not
    /// present in this registry.
    #[must_use]
    pub fn get(&self, key_id: &str) -> Option<&SelfSignedKeyEntry> {
        self.entries.iter().find(|e| e.key_id == key_id)
    }

    /// Number of entries in the registry.
    #[must_use]
    pub fn len(&self) -> usize {
        self.entries.len()
    }

    /// Whether the registry contains no entries.
    #[must_use]
    pub fn is_empty(&self) -> bool {
        self.entries.is_empty()
    }
}

// ---------------------------------------------------------------------------
// Serde helpers: fixed-size byte arrays and dynamic vecs, hex-encoded in JSON
// ---------------------------------------------------------------------------

/// Serde module for 32-byte arrays serialized as lowercase hex strings.
pub(crate) mod hex_bytes_32 {
    use serde::{Deserialize, Deserializer, Serializer};

    pub fn serialize<S: Serializer>(value: &[u8; 32], serializer: S) -> Result<S::Ok, S::Error> {
        serializer.serialize_str(&hex_encode(value))
    }

    pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<[u8; 32], D::Error> {
        let text = <String as Deserialize>::deserialize(deserializer)?;
        let bytes = super::hex_decode(&text).map_err(serde::de::Error::custom)?;
        if bytes.len() != 32 {
            return Err(serde::de::Error::custom(format!(
                "expected 32-byte hex string, got {}",
                bytes.len()
            )));
        }
        let mut out = [0u8; 32];
        out.copy_from_slice(&bytes);
        Ok(out)
    }

    fn hex_encode(bytes: &[u8]) -> String {
        let mut out = String::with_capacity(bytes.len() * 2);
        for b in bytes {
            out.push(super::hex_nibble(b >> 4));
            out.push(super::hex_nibble(b & 0x0F));
        }
        out
    }
}

/// Serde module for 64-byte arrays serialized as lowercase hex strings.
pub(crate) mod hex_bytes_64 {
    use serde::{Deserialize, Deserializer, Serializer};

    pub fn serialize<S: Serializer>(value: &[u8; 64], serializer: S) -> Result<S::Ok, S::Error> {
        serializer.serialize_str(&hex_encode(value))
    }

    pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<[u8; 64], D::Error> {
        let text = <String as Deserialize>::deserialize(deserializer)?;
        let bytes = super::hex_decode(&text).map_err(serde::de::Error::custom)?;
        if bytes.len() != 64 {
            return Err(serde::de::Error::custom(format!(
                "expected 64-byte hex string, got {}",
                bytes.len()
            )));
        }
        let mut out = [0u8; 64];
        out.copy_from_slice(&bytes);
        Ok(out)
    }

    fn hex_encode(bytes: &[u8]) -> String {
        let mut out = String::with_capacity(bytes.len() * 2);
        for b in bytes {
            out.push(super::hex_nibble(b >> 4));
            out.push(super::hex_nibble(b & 0x0F));
        }
        out
    }
}

/// Serde module for `Vec<u8>` serialized as lowercase hex strings.
pub(crate) mod hex_vec {
    use serde::{Deserialize, Deserializer, Serializer};

    pub fn serialize<S: Serializer>(value: &[u8], serializer: S) -> Result<S::Ok, S::Error> {
        let mut out = String::with_capacity(value.len() * 2);
        for b in value {
            out.push(super::hex_nibble(b >> 4));
            out.push(super::hex_nibble(b & 0x0F));
        }
        serializer.serialize_str(&out)
    }

    pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<Vec<u8>, D::Error> {
        let text = <String as Deserialize>::deserialize(deserializer)?;
        super::hex_decode(&text).map_err(serde::de::Error::custom)
    }
}

fn hex_nibble(nib: u8) -> char {
    match nib {
        0..=9 => (b'0' + nib) as char,
        10..=15 => (b'a' + nib - 10) as char,
        _ => unreachable!("nibble fits in 4 bits"),
    }
}

fn hex_decode(text: &str) -> Result<Vec<u8>, String> {
    if !text.len().is_multiple_of(2) {
        return Err(format!("hex string length {} is not even", text.len()));
    }
    let mut out = Vec::with_capacity(text.len() / 2);
    let bytes = text.as_bytes();
    let mut i = 0;
    while i < bytes.len() {
        let high = hex_value(bytes[i])?;
        let low = hex_value(bytes[i + 1])?;
        out.push((high << 4) | low);
        i += 2;
    }
    Ok(out)
}

fn hex_value(byte: u8) -> Result<u8, String> {
    match byte {
        b'0'..=b'9' => Ok(byte - b'0'),
        b'a'..=b'f' => Ok(byte - b'a' + 10),
        b'A'..=b'F' => Ok(byte - b'A' + 10),
        _ => Err(format!("non-hex character {byte:#x?}")),
    }
}
