//! `commonware-codec` `Write + Read + EncodeSize` impls for the consensus-critical
//! protobuf types — `MessageData`, every body variant in the `MessageData.body`
//! oneof (26 of them), and the nested types they reach (`CommitMeta`, the
//! `LinkAddBody.target` / `LinkRemoveBody.target` inner oneofs).
//!
//! These impls coexist with the buffa-generated traits on the same types; the
//! orphan rule does not apply because `makechain-proto` owns the generated code
//! via `include!(OUT_DIR)`. PR 1 of the `commonware-codec` migration only adds
//! the impls — nothing in the consensus path consumes them yet.
//!
//! ## Wire format conventions
//!
//! Encodings follow the spec in `.claude/plans/commonware-codec-migration.md` §3
//! and the in-repo precedent in `crates/makechain-state/src/values.rs`:
//!
//! - All enums are encoded as a single `u8` discriminant. buffa generates
//!   `EnumValue<E>` (open enum: `Known(E)` or `Unknown(i32)`). We extract the
//!   discriminant via `.to_i32()` and write it through [`write_enum_u8`], which
//!   asserts the value is in `0..=255` before truncating to `u8`. Values
//!   outside that range panic in debug and return early in release — this is
//!   a defense-in-depth guard against silently corrupting the hash invariant
//!   if a future validator change allows an out-of-range discriminant to
//!   reach the encoder. Reads are total: any `u8` round-trips back into a
//!   valid `EnumValue::Unknown(0..=255)`.
//! - `u32` is 4 bytes BE; `u64` is 8 bytes BE; `bool` is 1 byte. (Matches
//!   commonware-codec's primitive impls, which use `bytes::BufMut::put_u32`/
//!   `put_u64` — both big-endian — to avoid host-endian ambiguity.)
//! - Fixed-size byte fields (32-byte hashes, 20-byte addresses, 32-byte keys)
//!   are encoded as raw bytes with no length prefix. buffa models these as
//!   `Vec<u8>`; we panic on `Write` if the runtime length is wrong, matching
//!   the established `write_address` pattern in `values.rs`.
//! - Variable-length `Vec<u8>` and `String` use commonware-codec's varint
//!   length prefix. `String` is just length-prefixed UTF-8.
//! - `Option<[u8; N]>` is a 1-byte presence tag plus N bytes when present.
//!   Used for fields that buffa models as a `Vec<u8>` but where empty bytes
//!   means "absent" (e.g. `RefUpdateBody.old_hash` "empty = new ref").
//! - Field order within each body is by proto tag number, not Rust declaration
//!   order (in practice buffa emits them in tag order, so they coincide here).
//!
//! ## `MessageData.body` oneof dispatch
//!
//! The outer `MessageData.body` is a buffa `Option<message_data::Body>`. The
//! body discriminant is a single byte: `0` for `None`, otherwise the proto
//! field number of the active variant (`5..=30`). Decoders reject any other
//! discriminant. A `const _: () = assert!(...)` invariant in this module pins
//! `0` as reserved for `None` and `5..=30` as the variant range; if a future
//! proto edit reuses tag `0..=4` for a body variant the build fails loudly.
//!
//! ## `LinkAddBody.target` / `LinkRemoveBody.target` inner oneofs
//!
//! These inner oneofs are buffa `Option<link_{add,remove}_body::Target>`.
//! 1-byte target tag: `1` = `target_project_id` (raw 32 bytes), `2` =
//! `target_owner_address` (raw 20 bytes). The plan does not define a `None`
//! encoding for these inner oneofs; we use `0` as a "no target" tag so that
//! the round-trip is total (buffa models `target` as
//! `Option<link_add_body::Target>`).

use buffa::UnknownFields;
use bytes::{Buf, BufMut};
use commonware_codec::{EncodeSize, Error as CodecError, Read, ReadExt, Write};

use crate::{
    AccountDataBody, CollaboratorAddBody, CollaboratorRemoveBody, CommitBundleBody, CommitMeta,
    ForkBody, KeychainAuthorizeBody, KeychainRevokeBody, LinkAddBody, LinkRemoveBody,
    MAX_CLAIM_SIGNATURE_LEN, MAX_KEYCHAIN_PUBLIC_KEY_LEN, MAX_KEYCHAIN_SIGNATURE_LEN,
    MAX_KEYCHAIN_WITNESS_LEN, MergeRequestAddBody, MergeRequestRemoveBody, MessageData,
    ProjectArchiveBody, ProjectCreateBody, ProjectMetadataBody, ProjectRemoveBody, ReactionAddBody,
    ReactionRemoveBody, RefDeleteBody, RefUpdateBody, SignerAddBody, SignerRemoveBody,
    StorageClaimBody, UsernameCreateBody, UsernameUpdateBody, VerificationAddBody,
    VerificationRemoveBody, link_add_body, link_remove_body, message_data,
};

// ---------------------------------------------------------------------------
// Fixed sizes & decode bounds
//
// All `MAX_*` constants here are decode-time *byte/element* bounds. They are
// intentionally generous — the structural validation layer in
// `crates/makechain-state/src/validation.rs` enforces the spec's per-field
// semantic limits on top of these (e.g. `MAX_REF_NAME_LEN = 254`). Codec reads
// only need to refuse pathologically large inputs without making semantic
// decisions.
// ---------------------------------------------------------------------------

const ADDRESS_LEN: usize = 20;
const HASH_LEN: usize = 32;
const KEY_LEN: usize = 32;

const MAX_NAME_LEN: usize = 256;
const MAX_DESCRIPTION_LEN: usize = 4096;
const MAX_LICENSE_LEN: usize = 256;
const MAX_VALUE_LEN: usize = 4096;
const MAX_USERNAME_LEN: usize = 256;
const MAX_TITLE_LEN: usize = 1024;
const MAX_URL_LEN: usize = 4096;
const MAX_REF_NAME_LEN: usize = 1024;
const MAX_CONTENT_DIGEST_LEN: usize = 64;
const MAX_CHAIN_ID_LEN: usize = 64;
const MAX_VERIFICATION_ADDRESS_LEN: usize = 64;
const MAX_COMMITS_PER_BUNDLE: usize = 1000;
use crate::MAX_ALLOWED_PROJECTS;
use crate::MAX_PARENT_COMMITS;

// ---------------------------------------------------------------------------
// Helpers — fixed-size byte slices stored in buffa as Vec<u8>
// ---------------------------------------------------------------------------

fn write_fixed(field: &str, expected_len: usize, bytes: &[u8], buf: &mut impl BufMut) {
    assert!(
        bytes.len() == expected_len,
        "{field} has invalid length {} (expected {expected_len})",
        bytes.len(),
    );
    buf.put_slice(bytes);
}

fn read_fixed<const N: usize>(buf: &mut impl Buf) -> Result<Vec<u8>, CodecError> {
    let bytes = <[u8; N]>::read(buf)?;
    Ok(bytes.to_vec())
}

// `Option<[u8; N]>` encoded with a 1-byte presence tag, where the buffa field is
// a `Vec<u8>` that's empty when absent. We map `Vec::is_empty()` <-> `None` on
// the wire and panic on a wrong nonzero length, matching `write_fixed`.
fn write_optional_fixed(field: &str, expected_len: usize, bytes: &[u8], buf: &mut impl BufMut) {
    if bytes.is_empty() {
        false.write(buf);
    } else {
        assert!(
            bytes.len() == expected_len,
            "{field} has invalid length {} (expected 0 or {expected_len})",
            bytes.len(),
        );
        true.write(buf);
        buf.put_slice(bytes);
    }
}

fn optional_fixed_encode_size(expected_len: usize, bytes: &[u8]) -> usize {
    if bytes.is_empty() {
        1
    } else {
        1 + expected_len
    }
}

fn read_optional_fixed<const N: usize>(buf: &mut impl Buf) -> Result<Vec<u8>, CodecError> {
    if bool::read(buf)? {
        let bytes = <[u8; N]>::read(buf)?;
        Ok(bytes.to_vec())
    } else {
        Ok(Vec::new())
    }
}

// ---------------------------------------------------------------------------
// Helpers — variable-length strings & byte vectors
// ---------------------------------------------------------------------------

fn write_string(s: &str, buf: &mut impl BufMut) {
    s.as_bytes().to_vec().write(buf);
}

fn string_encode_size(s: &str) -> usize {
    s.as_bytes().to_vec().encode_size()
}

fn read_string(buf: &mut impl Buf, max_len: usize) -> Result<String, CodecError> {
    let bytes = <Vec<u8> as Read>::read_cfg(buf, &((0..=max_len).into(), ()))?;
    String::from_utf8(bytes).map_err(|_| CodecError::Invalid("String", "invalid UTF-8"))
}

fn write_bytes(bytes: &[u8], buf: &mut impl BufMut) {
    bytes.to_vec().write(buf);
}

fn bytes_encode_size(bytes: &[u8]) -> usize {
    bytes.to_vec().encode_size()
}

fn read_bytes(buf: &mut impl Buf, max_len: usize) -> Result<Vec<u8>, CodecError> {
    <Vec<u8> as Read>::read_cfg(buf, &((0..=max_len).into(), ()))
}

// `Vec<[u8; 32]>` — 32-byte items with a varint count prefix. Each item is raw
// 32 bytes (no per-item length prefix). buffa models these as `Vec<Vec<u8>>`,
// so we panic on `write` if any item is the wrong length.
fn write_hash_list(field: &str, items: &[Vec<u8>], buf: &mut impl BufMut) {
    let typed: Vec<[u8; HASH_LEN]> = items
        .iter()
        .map(|item| {
            <[u8; HASH_LEN]>::try_from(item.as_slice()).unwrap_or_else(|_| {
                panic!(
                    "{field} item has invalid length {} (expected {HASH_LEN})",
                    item.len()
                )
            })
        })
        .collect();
    typed.write(buf);
}

fn hash_list_encode_size(items: &[Vec<u8>]) -> usize {
    // Encoding equals: varint(count) + count*HASH_LEN.
    // Mirror via a typed Vec<[u8;32]> so we go through the same code path as
    // Write.
    let typed: Vec<[u8; HASH_LEN]> = items
        .iter()
        .map(|item| <[u8; HASH_LEN]>::try_from(item.as_slice()).unwrap_or([0u8; HASH_LEN]))
        .collect();
    typed.encode_size()
}

fn read_hash_list(buf: &mut impl Buf, max_count: usize) -> Result<Vec<Vec<u8>>, CodecError> {
    let typed = <Vec<[u8; HASH_LEN]> as Read>::read_cfg(buf, &((0..=max_count).into(), ()))?;
    Ok(typed.into_iter().map(|item| item.to_vec()).collect())
}

// `repeated bytes` — a varint count prefix then each item length-prefixed
// (varint len + raw bytes). buffa models these as `Vec<Vec<u8>>`. Bounded on
// read by a max element count and a max per-element length (DoS guard).
fn write_bytes_list(items: &[Vec<u8>], buf: &mut impl BufMut) {
    items.to_vec().write(buf);
}

fn bytes_list_encode_size(items: &[Vec<u8>]) -> usize {
    items.to_vec().encode_size()
}

fn read_bytes_list(
    buf: &mut impl Buf,
    max_count: usize,
    max_item_len: usize,
) -> Result<Vec<Vec<u8>>, CodecError> {
    <Vec<Vec<u8>> as Read>::read_cfg(
        buf,
        &((0..=max_count).into(), ((0..=max_item_len).into(), ())),
    )
}

// ---------------------------------------------------------------------------
// Helpers — enum discriminant write
//
// All protobuf enums in `MessageData` (and every body type) are encoded as a
// single `u8` on the wire — the canonical hash input. buffa models open enums
// as `EnumValue<E>` where the inner discriminant is `i32` (`Known(_)` exposes
// the proto-assigned value; `Unknown(i32)` carries any wire value).
//
// `EnumValue::Unknown(i32)` can hold ANY 32-bit value, including values >= 256
// or negative. A raw `as u8` cast would silently truncate (`257 as u8 == 1`),
// producing a bytes-identical encoding for two distinct in-memory values and
// silently corrupting the BLAKE3 chain hash invariant.
//
// `write_enum_u8` asserts the discriminant is in `0..=255` at encode time:
// - debug builds panic (loud, immediate signal during testing)
// - release builds skip the write and return early (defense-in-depth — the
//   resulting bytes will fail to decode, preventing the corrupted hash from
//   being committed)
//
// Validation upstream (`crates/makechain-state/src/validation.rs`) enforces
// that all known proto enum values fit in `0..=255` today; this helper is the
// defense-in-depth backstop for future drift.
// ---------------------------------------------------------------------------

/// Write an `EnumValue<E>` as a single `u8`.
///
/// Asserts `0 <= value <= 255`. See module docs for the rationale.
fn write_enum_u8<E: ::buffa::Enumeration>(field: &::buffa::EnumValue<E>, buf: &mut impl BufMut) {
    let raw = field.to_i32();
    debug_assert!(
        (0..=255).contains(&raw),
        "enum discriminant {raw} out of u8 range (0..=255); EnumValue::Unknown({raw}) would \
         silently truncate and corrupt the chain hash invariant"
    );
    // Release build: refuse to write an out-of-range discriminant.
    // `try_from` covers the range check; on failure the encoded stream is
    // shorter than expected and downstream decode will reject it.
    let Ok(byte) = u8::try_from(raw) else { return };
    byte.write(buf);
}

// ---------------------------------------------------------------------------
// MessageData
// ---------------------------------------------------------------------------

// I2: compile-time invariant — `MessageData.body` discriminants `0..=4` are
// reserved (0 = None; 1..=4 unused), and `5..=30` map 1:1 to proto field
// numbers for the 26 oneof variants. If a future proto edit reuses tag
// `0..=4` for a body variant, this assert fails the build before the codec
// can silently corrupt a hash.
const _: () = {
    const RESERVED_NONE: u8 = 0;
    const FIRST_VARIANT: u8 = 5;
    const LAST_VARIANT: u8 = 30;
    assert!(
        RESERVED_NONE < FIRST_VARIANT,
        "MessageData.body: discriminant 0 must be reserved for None"
    );
    assert!(
        LAST_VARIANT >= FIRST_VARIANT,
        "MessageData.body: variant range must be non-empty"
    );
    assert!(
        (LAST_VARIANT - FIRST_VARIANT + 1) == 26,
        "MessageData.body: expected exactly 26 variant tags (5..=30)"
    );
};

impl Write for MessageData {
    fn write(&self, buf: &mut impl BufMut) {
        // Field 1: type (enum -> u8). buffa stores i32; cast through u8.
        write_enum_u8(&self.r#type, buf);
        // Field 2: timestamp (u64 LE)
        self.timestamp.write(buf);
        // Field 3: network (enum -> u8)
        write_enum_u8(&self.network, buf);
        // Field 4: owner_address (raw 20 bytes)
        write_fixed(
            "MessageData.owner_address",
            ADDRESS_LEN,
            &self.owner_address,
            buf,
        );
        // Field 5..=30: body (1-byte tag + variant payload, or 0 for None)
        match &self.body {
            None => 0u8.write(buf),
            Some(message_data::Body::ProjectCreate(b)) => {
                5u8.write(buf);
                b.write(buf);
            }
            Some(message_data::Body::ProjectMetadata(b)) => {
                6u8.write(buf);
                b.write(buf);
            }
            Some(message_data::Body::ProjectArchive(b)) => {
                7u8.write(buf);
                b.write(buf);
            }
            Some(message_data::Body::Fork(b)) => {
                8u8.write(buf);
                b.write(buf);
            }
            Some(message_data::Body::ProjectRemove(b)) => {
                9u8.write(buf);
                b.write(buf);
            }
            Some(message_data::Body::RefUpdate(b)) => {
                10u8.write(buf);
                b.write(buf);
            }
            Some(message_data::Body::RefDelete(b)) => {
                11u8.write(buf);
                b.write(buf);
            }
            Some(message_data::Body::CommitBundle(b)) => {
                12u8.write(buf);
                b.write(buf);
            }
            Some(message_data::Body::CollaboratorAdd(b)) => {
                13u8.write(buf);
                b.write(buf);
            }
            Some(message_data::Body::CollaboratorRemove(b)) => {
                14u8.write(buf);
                b.write(buf);
            }
            Some(message_data::Body::AccountData(b)) => {
                15u8.write(buf);
                b.write(buf);
            }
            Some(message_data::Body::VerificationAdd(b)) => {
                16u8.write(buf);
                b.write(buf);
            }
            Some(message_data::Body::VerificationRemove(b)) => {
                17u8.write(buf);
                b.write(buf);
            }
            Some(message_data::Body::StorageClaim(b)) => {
                18u8.write(buf);
                b.write(buf);
            }
            Some(message_data::Body::UsernameCreate(b)) => {
                19u8.write(buf);
                b.write(buf);
            }
            Some(message_data::Body::UsernameUpdate(b)) => {
                20u8.write(buf);
                b.write(buf);
            }
            Some(message_data::Body::LinkAdd(b)) => {
                21u8.write(buf);
                b.write(buf);
            }
            Some(message_data::Body::LinkRemove(b)) => {
                22u8.write(buf);
                b.write(buf);
            }
            Some(message_data::Body::ReactionAdd(b)) => {
                23u8.write(buf);
                b.write(buf);
            }
            Some(message_data::Body::ReactionRemove(b)) => {
                24u8.write(buf);
                b.write(buf);
            }
            Some(message_data::Body::SignerAdd(b)) => {
                25u8.write(buf);
                b.write(buf);
            }
            Some(message_data::Body::SignerRemove(b)) => {
                26u8.write(buf);
                b.write(buf);
            }
            Some(message_data::Body::MergeRequestAdd(b)) => {
                27u8.write(buf);
                b.write(buf);
            }
            Some(message_data::Body::MergeRequestRemove(b)) => {
                28u8.write(buf);
                b.write(buf);
            }
            Some(message_data::Body::KeychainAuthorize(b)) => {
                29u8.write(buf);
                b.write(buf);
            }
            Some(message_data::Body::KeychainRevoke(b)) => {
                30u8.write(buf);
                b.write(buf);
            }
        }
    }
}

impl EncodeSize for MessageData {
    fn encode_size(&self) -> usize {
        let body_size = match &self.body {
            None => 0,
            Some(message_data::Body::ProjectCreate(b)) => b.encode_size(),
            Some(message_data::Body::ProjectMetadata(b)) => b.encode_size(),
            Some(message_data::Body::ProjectArchive(b)) => b.encode_size(),
            Some(message_data::Body::Fork(b)) => b.encode_size(),
            Some(message_data::Body::ProjectRemove(b)) => b.encode_size(),
            Some(message_data::Body::RefUpdate(b)) => b.encode_size(),
            Some(message_data::Body::RefDelete(b)) => b.encode_size(),
            Some(message_data::Body::CommitBundle(b)) => b.encode_size(),
            Some(message_data::Body::CollaboratorAdd(b)) => b.encode_size(),
            Some(message_data::Body::CollaboratorRemove(b)) => b.encode_size(),
            Some(message_data::Body::AccountData(b)) => b.encode_size(),
            Some(message_data::Body::VerificationAdd(b)) => b.encode_size(),
            Some(message_data::Body::VerificationRemove(b)) => b.encode_size(),
            Some(message_data::Body::StorageClaim(b)) => b.encode_size(),
            Some(message_data::Body::UsernameCreate(b)) => b.encode_size(),
            Some(message_data::Body::UsernameUpdate(b)) => b.encode_size(),
            Some(message_data::Body::LinkAdd(b)) => b.encode_size(),
            Some(message_data::Body::LinkRemove(b)) => b.encode_size(),
            Some(message_data::Body::ReactionAdd(b)) => b.encode_size(),
            Some(message_data::Body::ReactionRemove(b)) => b.encode_size(),
            Some(message_data::Body::SignerAdd(b)) => b.encode_size(),
            Some(message_data::Body::SignerRemove(b)) => b.encode_size(),
            Some(message_data::Body::MergeRequestAdd(b)) => b.encode_size(),
            Some(message_data::Body::MergeRequestRemove(b)) => b.encode_size(),
            Some(message_data::Body::KeychainAuthorize(b)) => b.encode_size(),
            Some(message_data::Body::KeychainRevoke(b)) => b.encode_size(),
        };
        // type + timestamp(u64) + network + owner_address + body_tag + body
        1 + 8 + 1 + ADDRESS_LEN + 1 + body_size
    }
}

impl Read for MessageData {
    type Cfg = ();
    fn read_cfg(buf: &mut impl Buf, _cfg: &()) -> Result<Self, CodecError> {
        let r#type = ::buffa::EnumValue::from(i32::from(u8::read(buf)?));
        let timestamp = u64::read(buf)?;
        let network = ::buffa::EnumValue::from(i32::from(u8::read(buf)?));
        let owner_address = read_fixed::<ADDRESS_LEN>(buf)?;
        let body_tag = u8::read(buf)?;
        let body = match body_tag {
            0 => None,
            5 => Some(message_data::Body::ProjectCreate(
                ::buffa::alloc::boxed::Box::new(ProjectCreateBody::read(buf)?),
            )),
            6 => Some(message_data::Body::ProjectMetadata(
                ::buffa::alloc::boxed::Box::new(ProjectMetadataBody::read(buf)?),
            )),
            7 => Some(message_data::Body::ProjectArchive(
                ::buffa::alloc::boxed::Box::new(ProjectArchiveBody::read(buf)?),
            )),
            8 => Some(message_data::Body::Fork(::buffa::alloc::boxed::Box::new(
                ForkBody::read(buf)?,
            ))),
            9 => Some(message_data::Body::ProjectRemove(
                ::buffa::alloc::boxed::Box::new(ProjectRemoveBody::read(buf)?),
            )),
            10 => Some(message_data::Body::RefUpdate(
                ::buffa::alloc::boxed::Box::new(RefUpdateBody::read(buf)?),
            )),
            11 => Some(message_data::Body::RefDelete(
                ::buffa::alloc::boxed::Box::new(RefDeleteBody::read(buf)?),
            )),
            12 => Some(message_data::Body::CommitBundle(
                ::buffa::alloc::boxed::Box::new(CommitBundleBody::read(buf)?),
            )),
            13 => Some(message_data::Body::CollaboratorAdd(
                ::buffa::alloc::boxed::Box::new(CollaboratorAddBody::read(buf)?),
            )),
            14 => Some(message_data::Body::CollaboratorRemove(
                ::buffa::alloc::boxed::Box::new(CollaboratorRemoveBody::read(buf)?),
            )),
            15 => Some(message_data::Body::AccountData(
                ::buffa::alloc::boxed::Box::new(AccountDataBody::read(buf)?),
            )),
            16 => Some(message_data::Body::VerificationAdd(
                ::buffa::alloc::boxed::Box::new(VerificationAddBody::read(buf)?),
            )),
            17 => Some(message_data::Body::VerificationRemove(
                ::buffa::alloc::boxed::Box::new(VerificationRemoveBody::read(buf)?),
            )),
            18 => Some(message_data::Body::StorageClaim(
                ::buffa::alloc::boxed::Box::new(StorageClaimBody::read(buf)?),
            )),
            19 => Some(message_data::Body::UsernameCreate(
                ::buffa::alloc::boxed::Box::new(UsernameCreateBody::read(buf)?),
            )),
            20 => Some(message_data::Body::UsernameUpdate(
                ::buffa::alloc::boxed::Box::new(UsernameUpdateBody::read(buf)?),
            )),
            21 => Some(message_data::Body::LinkAdd(
                ::buffa::alloc::boxed::Box::new(LinkAddBody::read(buf)?),
            )),
            22 => Some(message_data::Body::LinkRemove(
                ::buffa::alloc::boxed::Box::new(LinkRemoveBody::read(buf)?),
            )),
            23 => Some(message_data::Body::ReactionAdd(
                ::buffa::alloc::boxed::Box::new(ReactionAddBody::read(buf)?),
            )),
            24 => Some(message_data::Body::ReactionRemove(
                ::buffa::alloc::boxed::Box::new(ReactionRemoveBody::read(buf)?),
            )),
            25 => Some(message_data::Body::SignerAdd(
                ::buffa::alloc::boxed::Box::new(SignerAddBody::read(buf)?),
            )),
            26 => Some(message_data::Body::SignerRemove(
                ::buffa::alloc::boxed::Box::new(SignerRemoveBody::read(buf)?),
            )),
            27 => Some(message_data::Body::MergeRequestAdd(
                ::buffa::alloc::boxed::Box::new(MergeRequestAddBody::read(buf)?),
            )),
            28 => Some(message_data::Body::MergeRequestRemove(
                ::buffa::alloc::boxed::Box::new(MergeRequestRemoveBody::read(buf)?),
            )),
            29 => Some(message_data::Body::KeychainAuthorize(
                ::buffa::alloc::boxed::Box::new(KeychainAuthorizeBody::read(buf)?),
            )),
            30 => Some(message_data::Body::KeychainRevoke(
                ::buffa::alloc::boxed::Box::new(KeychainRevokeBody::read(buf)?),
            )),
            _ => return Err(CodecError::Invalid("MessageData.body", "invalid tag")),
        };
        Ok(Self {
            r#type,
            timestamp,
            network,
            owner_address,
            body,
            __buffa_unknown_fields: UnknownFields::default(),
        })
    }
}

// ---------------------------------------------------------------------------
// ProjectCreateBody (proto tag 5)
// fields: name(1) string, visibility(2) enum, description(3) string, license(4) string
// ---------------------------------------------------------------------------

impl Write for ProjectCreateBody {
    fn write(&self, buf: &mut impl BufMut) {
        write_string(&self.name, buf);
        write_enum_u8(&self.visibility, buf);
        write_string(&self.description, buf);
        write_string(&self.license, buf);
    }
}

impl EncodeSize for ProjectCreateBody {
    fn encode_size(&self) -> usize {
        string_encode_size(&self.name)
            + 1
            + string_encode_size(&self.description)
            + string_encode_size(&self.license)
    }
}

impl Read for ProjectCreateBody {
    type Cfg = ();
    fn read_cfg(buf: &mut impl Buf, _cfg: &()) -> Result<Self, CodecError> {
        Ok(Self {
            name: read_string(buf, MAX_NAME_LEN)?,
            visibility: ::buffa::EnumValue::from(i32::from(u8::read(buf)?)),
            description: read_string(buf, MAX_DESCRIPTION_LEN)?,
            license: read_string(buf, MAX_LICENSE_LEN)?,
            __buffa_unknown_fields: UnknownFields::default(),
        })
    }
}

// ---------------------------------------------------------------------------
// ProjectMetadataBody (proto tag 6)
// fields: project_id(1) bytes[32], field(2) enum, value(3) string
// ---------------------------------------------------------------------------

impl Write for ProjectMetadataBody {
    fn write(&self, buf: &mut impl BufMut) {
        write_fixed(
            "ProjectMetadataBody.project_id",
            HASH_LEN,
            &self.project_id,
            buf,
        );
        write_enum_u8(&self.field, buf);
        write_string(&self.value, buf);
    }
}

impl EncodeSize for ProjectMetadataBody {
    fn encode_size(&self) -> usize {
        HASH_LEN + 1 + string_encode_size(&self.value)
    }
}

impl Read for ProjectMetadataBody {
    type Cfg = ();
    fn read_cfg(buf: &mut impl Buf, _cfg: &()) -> Result<Self, CodecError> {
        Ok(Self {
            project_id: read_fixed::<HASH_LEN>(buf)?,
            field: ::buffa::EnumValue::from(i32::from(u8::read(buf)?)),
            value: read_string(buf, MAX_VALUE_LEN)?,
            __buffa_unknown_fields: UnknownFields::default(),
        })
    }
}

// ---------------------------------------------------------------------------
// ProjectArchiveBody (proto tag 7)
// fields: project_id(1) bytes[32]
// ---------------------------------------------------------------------------

impl Write for ProjectArchiveBody {
    fn write(&self, buf: &mut impl BufMut) {
        write_fixed(
            "ProjectArchiveBody.project_id",
            HASH_LEN,
            &self.project_id,
            buf,
        );
    }
}

impl EncodeSize for ProjectArchiveBody {
    fn encode_size(&self) -> usize {
        HASH_LEN
    }
}

impl Read for ProjectArchiveBody {
    type Cfg = ();
    fn read_cfg(buf: &mut impl Buf, _cfg: &()) -> Result<Self, CodecError> {
        Ok(Self {
            project_id: read_fixed::<HASH_LEN>(buf)?,
            __buffa_unknown_fields: UnknownFields::default(),
        })
    }
}

// ---------------------------------------------------------------------------
// ForkBody (proto tag 8)
// fields: source_project_id(1) bytes[32], name(2) string,
// source_commit_hash(3) bytes[32], visibility(4) enum
// ---------------------------------------------------------------------------

impl Write for ForkBody {
    fn write(&self, buf: &mut impl BufMut) {
        write_fixed(
            "ForkBody.source_project_id",
            HASH_LEN,
            &self.source_project_id,
            buf,
        );
        write_string(&self.name, buf);
        write_fixed(
            "ForkBody.source_commit_hash",
            HASH_LEN,
            &self.source_commit_hash,
            buf,
        );
        write_enum_u8(&self.visibility, buf);
    }
}

impl EncodeSize for ForkBody {
    fn encode_size(&self) -> usize {
        HASH_LEN + string_encode_size(&self.name) + HASH_LEN + 1
    }
}

impl Read for ForkBody {
    type Cfg = ();
    fn read_cfg(buf: &mut impl Buf, _cfg: &()) -> Result<Self, CodecError> {
        Ok(Self {
            source_project_id: read_fixed::<HASH_LEN>(buf)?,
            name: read_string(buf, MAX_NAME_LEN)?,
            source_commit_hash: read_fixed::<HASH_LEN>(buf)?,
            visibility: ::buffa::EnumValue::from(i32::from(u8::read(buf)?)),
            __buffa_unknown_fields: UnknownFields::default(),
        })
    }
}

// ---------------------------------------------------------------------------
// ProjectRemoveBody (proto tag 9)
// fields: project_id(1) bytes[32]
// ---------------------------------------------------------------------------

impl Write for ProjectRemoveBody {
    fn write(&self, buf: &mut impl BufMut) {
        write_fixed(
            "ProjectRemoveBody.project_id",
            HASH_LEN,
            &self.project_id,
            buf,
        );
    }
}

impl EncodeSize for ProjectRemoveBody {
    fn encode_size(&self) -> usize {
        HASH_LEN
    }
}

impl Read for ProjectRemoveBody {
    type Cfg = ();
    fn read_cfg(buf: &mut impl Buf, _cfg: &()) -> Result<Self, CodecError> {
        Ok(Self {
            project_id: read_fixed::<HASH_LEN>(buf)?,
            __buffa_unknown_fields: UnknownFields::default(),
        })
    }
}

// ---------------------------------------------------------------------------
// RefUpdateBody (proto tag 10)
// fields: project_id(1) bytes[32], ref_name(2) bytes, ref_type(3) enum,
// old_hash(4) Option<[u8;32]> (empty = new ref), new_hash(5) bytes[32],
// force(6) bool, nonce(7) u64
// ---------------------------------------------------------------------------

impl Write for RefUpdateBody {
    fn write(&self, buf: &mut impl BufMut) {
        write_fixed("RefUpdateBody.project_id", HASH_LEN, &self.project_id, buf);
        write_bytes(&self.ref_name, buf);
        write_enum_u8(&self.ref_type, buf);
        write_optional_fixed("RefUpdateBody.old_hash", HASH_LEN, &self.old_hash, buf);
        write_fixed("RefUpdateBody.new_hash", HASH_LEN, &self.new_hash, buf);
        self.force.write(buf);
        self.nonce.write(buf);
    }
}

impl EncodeSize for RefUpdateBody {
    fn encode_size(&self) -> usize {
        HASH_LEN
            + bytes_encode_size(&self.ref_name)
            + 1
            + optional_fixed_encode_size(HASH_LEN, &self.old_hash)
            + HASH_LEN
            + 1
            + 8
    }
}

impl Read for RefUpdateBody {
    type Cfg = ();
    fn read_cfg(buf: &mut impl Buf, _cfg: &()) -> Result<Self, CodecError> {
        Ok(Self {
            project_id: read_fixed::<HASH_LEN>(buf)?,
            ref_name: read_bytes(buf, MAX_REF_NAME_LEN)?,
            ref_type: ::buffa::EnumValue::from(i32::from(u8::read(buf)?)),
            old_hash: read_optional_fixed::<HASH_LEN>(buf)?,
            new_hash: read_fixed::<HASH_LEN>(buf)?,
            force: bool::read(buf)?,
            nonce: u64::read(buf)?,
            __buffa_unknown_fields: UnknownFields::default(),
        })
    }
}

// ---------------------------------------------------------------------------
// RefDeleteBody (proto tag 11)
// fields: project_id(1) bytes[32], ref_name(2) bytes,
// expected_hash(3) bytes[32], nonce(4) u64
// ---------------------------------------------------------------------------

impl Write for RefDeleteBody {
    fn write(&self, buf: &mut impl BufMut) {
        write_fixed("RefDeleteBody.project_id", HASH_LEN, &self.project_id, buf);
        write_bytes(&self.ref_name, buf);
        write_optional_fixed(
            "RefDeleteBody.expected_hash",
            HASH_LEN,
            &self.expected_hash,
            buf,
        );
        self.nonce.write(buf);
    }
}

impl EncodeSize for RefDeleteBody {
    fn encode_size(&self) -> usize {
        HASH_LEN
            + bytes_encode_size(&self.ref_name)
            + optional_fixed_encode_size(HASH_LEN, &self.expected_hash)
            + 8
    }
}

impl Read for RefDeleteBody {
    type Cfg = ();
    fn read_cfg(buf: &mut impl Buf, _cfg: &()) -> Result<Self, CodecError> {
        Ok(Self {
            project_id: read_fixed::<HASH_LEN>(buf)?,
            ref_name: read_bytes(buf, MAX_REF_NAME_LEN)?,
            expected_hash: read_optional_fixed::<HASH_LEN>(buf)?,
            nonce: u64::read(buf)?,
            __buffa_unknown_fields: UnknownFields::default(),
        })
    }
}

// ---------------------------------------------------------------------------
// CommitMeta (nested in CommitBundleBody.commits)
// fields: hash(1) bytes[32], parents(2) repeated bytes[32], tree_root(3) bytes[32],
// author_timestamp(4) u64, title(5) string, message_hash(6) bytes[32],
// author_address(7) bytes[20]
// ---------------------------------------------------------------------------

impl Write for CommitMeta {
    fn write(&self, buf: &mut impl BufMut) {
        write_fixed("CommitMeta.hash", HASH_LEN, &self.hash, buf);
        write_hash_list("CommitMeta.parents", &self.parents, buf);
        write_fixed("CommitMeta.tree_root", HASH_LEN, &self.tree_root, buf);
        self.author_timestamp.write(buf);
        write_string(&self.title, buf);
        write_fixed("CommitMeta.message_hash", HASH_LEN, &self.message_hash, buf);
        write_fixed(
            "CommitMeta.author_address",
            ADDRESS_LEN,
            &self.author_address,
            buf,
        );
    }
}

impl EncodeSize for CommitMeta {
    fn encode_size(&self) -> usize {
        HASH_LEN
            + hash_list_encode_size(&self.parents)
            + HASH_LEN
            + 8
            + string_encode_size(&self.title)
            + HASH_LEN
            + ADDRESS_LEN
    }
}

impl Read for CommitMeta {
    type Cfg = ();
    fn read_cfg(buf: &mut impl Buf, _cfg: &()) -> Result<Self, CodecError> {
        Ok(Self {
            hash: read_fixed::<HASH_LEN>(buf)?,
            parents: read_hash_list(buf, MAX_PARENT_COMMITS)?,
            tree_root: read_fixed::<HASH_LEN>(buf)?,
            author_timestamp: u64::read(buf)?,
            title: read_string(buf, MAX_TITLE_LEN)?,
            message_hash: read_fixed::<HASH_LEN>(buf)?,
            author_address: read_fixed::<ADDRESS_LEN>(buf)?,
            __buffa_unknown_fields: UnknownFields::default(),
        })
    }
}

// ---------------------------------------------------------------------------
// CommitBundleBody (proto tag 12)
// fields: project_id(1) bytes[32], commits(2) repeated CommitMeta,
// content_digest(3) bytes (variable, 0..=64), url(4) string
// ---------------------------------------------------------------------------

impl Write for CommitBundleBody {
    fn write(&self, buf: &mut impl BufMut) {
        write_fixed(
            "CommitBundleBody.project_id",
            HASH_LEN,
            &self.project_id,
            buf,
        );
        self.commits.write(buf);
        write_bytes(&self.content_digest, buf);
        write_string(&self.url, buf);
    }
}

impl EncodeSize for CommitBundleBody {
    fn encode_size(&self) -> usize {
        HASH_LEN
            + self.commits.encode_size()
            + bytes_encode_size(&self.content_digest)
            + string_encode_size(&self.url)
    }
}

impl Read for CommitBundleBody {
    type Cfg = ();
    fn read_cfg(buf: &mut impl Buf, _cfg: &()) -> Result<Self, CodecError> {
        Ok(Self {
            project_id: read_fixed::<HASH_LEN>(buf)?,
            commits: <Vec<CommitMeta> as Read>::read_cfg(
                buf,
                &((0..=MAX_COMMITS_PER_BUNDLE).into(), ()),
            )?,
            content_digest: read_bytes(buf, MAX_CONTENT_DIGEST_LEN)?,
            url: read_string(buf, MAX_URL_LEN)?,
            __buffa_unknown_fields: UnknownFields::default(),
        })
    }
}

// ---------------------------------------------------------------------------
// CollaboratorAddBody (proto tag 13)
// fields: project_id(1) bytes[32], permission(2) enum, target_owner_address(3) bytes[20]
// ---------------------------------------------------------------------------

impl Write for CollaboratorAddBody {
    fn write(&self, buf: &mut impl BufMut) {
        write_fixed(
            "CollaboratorAddBody.project_id",
            HASH_LEN,
            &self.project_id,
            buf,
        );
        write_enum_u8(&self.permission, buf);
        write_fixed(
            "CollaboratorAddBody.target_owner_address",
            ADDRESS_LEN,
            &self.target_owner_address,
            buf,
        );
    }
}

impl EncodeSize for CollaboratorAddBody {
    fn encode_size(&self) -> usize {
        HASH_LEN + 1 + ADDRESS_LEN
    }
}

impl Read for CollaboratorAddBody {
    type Cfg = ();
    fn read_cfg(buf: &mut impl Buf, _cfg: &()) -> Result<Self, CodecError> {
        Ok(Self {
            project_id: read_fixed::<HASH_LEN>(buf)?,
            permission: ::buffa::EnumValue::from(i32::from(u8::read(buf)?)),
            target_owner_address: read_fixed::<ADDRESS_LEN>(buf)?,
            __buffa_unknown_fields: UnknownFields::default(),
        })
    }
}

// ---------------------------------------------------------------------------
// CollaboratorRemoveBody (proto tag 14)
// fields: project_id(1) bytes[32], target_owner_address(2) bytes[20]
// ---------------------------------------------------------------------------

impl Write for CollaboratorRemoveBody {
    fn write(&self, buf: &mut impl BufMut) {
        write_fixed(
            "CollaboratorRemoveBody.project_id",
            HASH_LEN,
            &self.project_id,
            buf,
        );
        write_fixed(
            "CollaboratorRemoveBody.target_owner_address",
            ADDRESS_LEN,
            &self.target_owner_address,
            buf,
        );
    }
}

impl EncodeSize for CollaboratorRemoveBody {
    fn encode_size(&self) -> usize {
        HASH_LEN + ADDRESS_LEN
    }
}

impl Read for CollaboratorRemoveBody {
    type Cfg = ();
    fn read_cfg(buf: &mut impl Buf, _cfg: &()) -> Result<Self, CodecError> {
        Ok(Self {
            project_id: read_fixed::<HASH_LEN>(buf)?,
            target_owner_address: read_fixed::<ADDRESS_LEN>(buf)?,
            __buffa_unknown_fields: UnknownFields::default(),
        })
    }
}

// ---------------------------------------------------------------------------
// AccountDataBody (proto tag 15)
// fields: field(1) enum, value(2) string
// ---------------------------------------------------------------------------

impl Write for AccountDataBody {
    fn write(&self, buf: &mut impl BufMut) {
        write_enum_u8(&self.field, buf);
        write_string(&self.value, buf);
    }
}

impl EncodeSize for AccountDataBody {
    fn encode_size(&self) -> usize {
        1 + string_encode_size(&self.value)
    }
}

impl Read for AccountDataBody {
    type Cfg = ();
    fn read_cfg(buf: &mut impl Buf, _cfg: &()) -> Result<Self, CodecError> {
        Ok(Self {
            field: ::buffa::EnumValue::from(i32::from(u8::read(buf)?)),
            value: read_string(buf, MAX_VALUE_LEN)?,
            __buffa_unknown_fields: UnknownFields::default(),
        })
    }
}

// ---------------------------------------------------------------------------
// VerificationAddBody (proto tag 16)
// fields: verification_type(1) enum, address(2) bytes (variable),
// claim_signature(3) bytes (variable), chain_id(4) bytes (variable)
// ---------------------------------------------------------------------------

impl Write for VerificationAddBody {
    fn write(&self, buf: &mut impl BufMut) {
        write_enum_u8(&self.verification_type, buf);
        write_bytes(&self.address, buf);
        write_bytes(&self.claim_signature, buf);
        write_bytes(&self.chain_id, buf);
    }
}

impl EncodeSize for VerificationAddBody {
    fn encode_size(&self) -> usize {
        1 + bytes_encode_size(&self.address)
            + bytes_encode_size(&self.claim_signature)
            + bytes_encode_size(&self.chain_id)
    }
}

impl Read for VerificationAddBody {
    type Cfg = ();
    fn read_cfg(buf: &mut impl Buf, _cfg: &()) -> Result<Self, CodecError> {
        Ok(Self {
            verification_type: ::buffa::EnumValue::from(i32::from(u8::read(buf)?)),
            address: read_bytes(buf, MAX_VERIFICATION_ADDRESS_LEN)?,
            claim_signature: read_bytes(buf, MAX_CLAIM_SIGNATURE_LEN)?,
            chain_id: read_bytes(buf, MAX_CHAIN_ID_LEN)?,
            __buffa_unknown_fields: UnknownFields::default(),
        })
    }
}

// ---------------------------------------------------------------------------
// VerificationRemoveBody (proto tag 17)
// fields: address(1) bytes (variable)
// ---------------------------------------------------------------------------

impl Write for VerificationRemoveBody {
    fn write(&self, buf: &mut impl BufMut) {
        write_bytes(&self.address, buf);
    }
}

impl EncodeSize for VerificationRemoveBody {
    fn encode_size(&self) -> usize {
        bytes_encode_size(&self.address)
    }
}

impl Read for VerificationRemoveBody {
    type Cfg = ();
    fn read_cfg(buf: &mut impl Buf, _cfg: &()) -> Result<Self, CodecError> {
        Ok(Self {
            address: read_bytes(buf, MAX_VERIFICATION_ADDRESS_LEN)?,
            __buffa_unknown_fields: UnknownFields::default(),
        })
    }
}

// ---------------------------------------------------------------------------
// StorageClaimBody (proto tag 18) — self-contained Tempo settlement evidence.
// fields: settlement_chain_id(1) u64, tx_index(2) u32, settlement_log_index(3) u32,
// finalization_cert(4) bytes, header_rlp(5) bytes, receipt_rlp(6) bytes,
// proof_nodes(7) repeated bytes, settlement_tx_hash(8) bytes[32] (display-only).
//
// StorageClaimBody evidence length caps (DoS guard on read) live in the crate
// root alongside the other shared length caps; imported here for the decoder.
use crate::{
    MAX_FINALIZATION_CERT_LEN, MAX_HEADER_RLP_LEN, MAX_PROOF_NODE_LEN, MAX_PROOF_NODES,
    MAX_RECEIPT_RLP_LEN,
};
// ---------------------------------------------------------------------------

impl Write for StorageClaimBody {
    fn write(&self, buf: &mut impl BufMut) {
        self.settlement_chain_id.write(buf);
        self.tx_index.write(buf);
        self.settlement_log_index.write(buf);
        write_bytes(&self.finalization_cert, buf);
        write_bytes(&self.header_rlp, buf);
        write_bytes(&self.receipt_rlp, buf);
        write_bytes_list(&self.proof_nodes, buf);
        write_fixed(
            "StorageClaimBody.settlement_tx_hash",
            HASH_LEN,
            &self.settlement_tx_hash,
            buf,
        );
    }
}

impl EncodeSize for StorageClaimBody {
    fn encode_size(&self) -> usize {
        8 + 4
            + 4
            + bytes_encode_size(&self.finalization_cert)
            + bytes_encode_size(&self.header_rlp)
            + bytes_encode_size(&self.receipt_rlp)
            + bytes_list_encode_size(&self.proof_nodes)
            + HASH_LEN
    }
}

impl Read for StorageClaimBody {
    type Cfg = ();
    fn read_cfg(buf: &mut impl Buf, _cfg: &()) -> Result<Self, CodecError> {
        Ok(Self {
            settlement_chain_id: u64::read(buf)?,
            tx_index: u32::read(buf)?,
            settlement_log_index: u32::read(buf)?,
            finalization_cert: read_bytes(buf, MAX_FINALIZATION_CERT_LEN)?,
            header_rlp: read_bytes(buf, MAX_HEADER_RLP_LEN)?,
            receipt_rlp: read_bytes(buf, MAX_RECEIPT_RLP_LEN)?,
            proof_nodes: read_bytes_list(buf, MAX_PROOF_NODES, MAX_PROOF_NODE_LEN)?,
            settlement_tx_hash: read_fixed::<HASH_LEN>(buf)?,
            __buffa_unknown_fields: UnknownFields::default(),
        })
    }
}

// ---------------------------------------------------------------------------
// UsernameCreateBody (proto tag 19)
// fields: username(1) string
// ---------------------------------------------------------------------------

impl Write for UsernameCreateBody {
    fn write(&self, buf: &mut impl BufMut) {
        write_string(&self.username, buf);
    }
}

impl EncodeSize for UsernameCreateBody {
    fn encode_size(&self) -> usize {
        string_encode_size(&self.username)
    }
}

impl Read for UsernameCreateBody {
    type Cfg = ();
    fn read_cfg(buf: &mut impl Buf, _cfg: &()) -> Result<Self, CodecError> {
        Ok(Self {
            username: read_string(buf, MAX_USERNAME_LEN)?,
            __buffa_unknown_fields: UnknownFields::default(),
        })
    }
}

// ---------------------------------------------------------------------------
// UsernameUpdateBody (proto tag 20)
// fields: username(1) string
// ---------------------------------------------------------------------------

impl Write for UsernameUpdateBody {
    fn write(&self, buf: &mut impl BufMut) {
        write_string(&self.username, buf);
    }
}

impl EncodeSize for UsernameUpdateBody {
    fn encode_size(&self) -> usize {
        string_encode_size(&self.username)
    }
}

impl Read for UsernameUpdateBody {
    type Cfg = ();
    fn read_cfg(buf: &mut impl Buf, _cfg: &()) -> Result<Self, CodecError> {
        Ok(Self {
            username: read_string(buf, MAX_USERNAME_LEN)?,
            __buffa_unknown_fields: UnknownFields::default(),
        })
    }
}

// ---------------------------------------------------------------------------
// LinkAddBody (proto tag 21)
// fields: link_type(1) enum, target oneof:
// tag 1 = target_project_id (raw 32 bytes), tag 2 = target_owner_address (raw 20 bytes),
// tag 0 = no target
// ---------------------------------------------------------------------------

impl Write for LinkAddBody {
    fn write(&self, buf: &mut impl BufMut) {
        write_enum_u8(&self.link_type, buf);
        match &self.target {
            None => 0u8.write(buf),
            Some(link_add_body::Target::TargetProjectId(project_id)) => {
                1u8.write(buf);
                write_fixed("LinkAddBody.target_project_id", HASH_LEN, project_id, buf);
            }
            Some(link_add_body::Target::TargetOwnerAddress(addr)) => {
                2u8.write(buf);
                write_fixed("LinkAddBody.target_owner_address", ADDRESS_LEN, addr, buf);
            }
        }
    }
}

impl EncodeSize for LinkAddBody {
    fn encode_size(&self) -> usize {
        let target_size = match &self.target {
            None => 0,
            Some(link_add_body::Target::TargetProjectId(_)) => HASH_LEN,
            Some(link_add_body::Target::TargetOwnerAddress(_)) => ADDRESS_LEN,
        };
        1 + 1 + target_size
    }
}

impl Read for LinkAddBody {
    type Cfg = ();
    fn read_cfg(buf: &mut impl Buf, _cfg: &()) -> Result<Self, CodecError> {
        let link_type = ::buffa::EnumValue::from(i32::from(u8::read(buf)?));
        let target = match u8::read(buf)? {
            0 => None,
            1 => Some(link_add_body::Target::TargetProjectId(read_fixed::<
                HASH_LEN,
            >(buf)?)),
            2 => Some(link_add_body::Target::TargetOwnerAddress(read_fixed::<
                ADDRESS_LEN,
            >(
                buf
            )?)),
            _ => return Err(CodecError::Invalid("LinkAddBody.target", "invalid tag")),
        };
        Ok(Self {
            link_type,
            target,
            __buffa_unknown_fields: UnknownFields::default(),
        })
    }
}

// ---------------------------------------------------------------------------
// LinkRemoveBody (proto tag 22)
// (same shape as LinkAddBody)
// ---------------------------------------------------------------------------

impl Write for LinkRemoveBody {
    fn write(&self, buf: &mut impl BufMut) {
        write_enum_u8(&self.link_type, buf);
        match &self.target {
            None => 0u8.write(buf),
            Some(link_remove_body::Target::TargetProjectId(project_id)) => {
                1u8.write(buf);
                write_fixed(
                    "LinkRemoveBody.target_project_id",
                    HASH_LEN,
                    project_id,
                    buf,
                );
            }
            Some(link_remove_body::Target::TargetOwnerAddress(addr)) => {
                2u8.write(buf);
                write_fixed(
                    "LinkRemoveBody.target_owner_address",
                    ADDRESS_LEN,
                    addr,
                    buf,
                );
            }
        }
    }
}

impl EncodeSize for LinkRemoveBody {
    fn encode_size(&self) -> usize {
        let target_size = match &self.target {
            None => 0,
            Some(link_remove_body::Target::TargetProjectId(_)) => HASH_LEN,
            Some(link_remove_body::Target::TargetOwnerAddress(_)) => ADDRESS_LEN,
        };
        1 + 1 + target_size
    }
}

impl Read for LinkRemoveBody {
    type Cfg = ();
    fn read_cfg(buf: &mut impl Buf, _cfg: &()) -> Result<Self, CodecError> {
        let link_type = ::buffa::EnumValue::from(i32::from(u8::read(buf)?));
        let target = match u8::read(buf)? {
            0 => None,
            1 => Some(link_remove_body::Target::TargetProjectId(read_fixed::<
                HASH_LEN,
            >(
                buf
            )?)),
            2 => Some(link_remove_body::Target::TargetOwnerAddress(read_fixed::<
                ADDRESS_LEN,
            >(
                buf
            )?)),
            _ => return Err(CodecError::Invalid("LinkRemoveBody.target", "invalid tag")),
        };
        Ok(Self {
            link_type,
            target,
            __buffa_unknown_fields: UnknownFields::default(),
        })
    }
}

// ---------------------------------------------------------------------------
// ReactionAddBody (proto tag 23)
// fields: reaction_type(1) enum, target_project_id(2) bytes[32],
// target_commit_hash(3) bytes[32]
// ---------------------------------------------------------------------------

impl Write for ReactionAddBody {
    fn write(&self, buf: &mut impl BufMut) {
        write_enum_u8(&self.reaction_type, buf);
        write_fixed(
            "ReactionAddBody.target_project_id",
            HASH_LEN,
            &self.target_project_id,
            buf,
        );
        write_fixed(
            "ReactionAddBody.target_commit_hash",
            HASH_LEN,
            &self.target_commit_hash,
            buf,
        );
    }
}

impl EncodeSize for ReactionAddBody {
    fn encode_size(&self) -> usize {
        1 + HASH_LEN + HASH_LEN
    }
}

impl Read for ReactionAddBody {
    type Cfg = ();
    fn read_cfg(buf: &mut impl Buf, _cfg: &()) -> Result<Self, CodecError> {
        Ok(Self {
            reaction_type: ::buffa::EnumValue::from(i32::from(u8::read(buf)?)),
            target_project_id: read_fixed::<HASH_LEN>(buf)?,
            target_commit_hash: read_fixed::<HASH_LEN>(buf)?,
            __buffa_unknown_fields: UnknownFields::default(),
        })
    }
}

// ---------------------------------------------------------------------------
// ReactionRemoveBody (proto tag 24)
// (same shape as ReactionAddBody)
// ---------------------------------------------------------------------------

impl Write for ReactionRemoveBody {
    fn write(&self, buf: &mut impl BufMut) {
        write_enum_u8(&self.reaction_type, buf);
        write_fixed(
            "ReactionRemoveBody.target_project_id",
            HASH_LEN,
            &self.target_project_id,
            buf,
        );
        write_fixed(
            "ReactionRemoveBody.target_commit_hash",
            HASH_LEN,
            &self.target_commit_hash,
            buf,
        );
    }
}

impl EncodeSize for ReactionRemoveBody {
    fn encode_size(&self) -> usize {
        1 + HASH_LEN + HASH_LEN
    }
}

impl Read for ReactionRemoveBody {
    type Cfg = ();
    fn read_cfg(buf: &mut impl Buf, _cfg: &()) -> Result<Self, CodecError> {
        Ok(Self {
            reaction_type: ::buffa::EnumValue::from(i32::from(u8::read(buf)?)),
            target_project_id: read_fixed::<HASH_LEN>(buf)?,
            target_commit_hash: read_fixed::<HASH_LEN>(buf)?,
            __buffa_unknown_fields: UnknownFields::default(),
        })
    }
}

// ---------------------------------------------------------------------------
// SignerAddBody (proto tag 25)
// fields:
// key(1) bytes[32], scope(2) u32 (KeyScope), custody_signature(3) bytes,
// allowed_projects(4) repeated bytes[32], nonce(6) u64, request_signature(7) bytes,
// valid_after(9) u64, valid_before(10) u64, request_owner_address(11) bytes[20]
// ---------------------------------------------------------------------------

impl Write for SignerAddBody {
    fn write(&self, buf: &mut impl BufMut) {
        write_fixed("SignerAddBody.key", KEY_LEN, &self.key, buf);
        self.scope.write(buf);
        write_bytes(&self.custody_signature, buf);
        write_hash_list(
            "SignerAddBody.allowed_projects",
            &self.allowed_projects,
            buf,
        );
        self.nonce.write(buf);
        write_bytes(&self.request_signature, buf);
        self.valid_after.write(buf);
        self.valid_before.write(buf);
        write_fixed(
            "SignerAddBody.request_owner_address",
            ADDRESS_LEN,
            &self.request_owner_address,
            buf,
        );
    }
}

impl EncodeSize for SignerAddBody {
    fn encode_size(&self) -> usize {
        KEY_LEN
            + 4
            + bytes_encode_size(&self.custody_signature)
            + hash_list_encode_size(&self.allowed_projects)
            + 8
            + bytes_encode_size(&self.request_signature)
            + 8
            + 8
            + ADDRESS_LEN
    }
}

impl Read for SignerAddBody {
    type Cfg = ();
    fn read_cfg(buf: &mut impl Buf, _cfg: &()) -> Result<Self, CodecError> {
        Ok(Self {
            key: read_fixed::<KEY_LEN>(buf)?,
            scope: u32::read(buf)?,
            custody_signature: read_bytes(buf, MAX_KEYCHAIN_SIGNATURE_LEN)?,
            allowed_projects: read_hash_list(buf, MAX_ALLOWED_PROJECTS)?,
            nonce: u64::read(buf)?,
            request_signature: read_bytes(buf, MAX_KEYCHAIN_SIGNATURE_LEN)?,
            valid_after: u64::read(buf)?,
            valid_before: u64::read(buf)?,
            request_owner_address: read_fixed::<ADDRESS_LEN>(buf)?,
            __buffa_unknown_fields: UnknownFields::default(),
        })
    }
}

// ---------------------------------------------------------------------------
// SignerRemoveBody (proto tag 26)
// fields:
// key(1) bytes[32], custody_signature(2) bytes, nonce(4) u64,
// valid_after(5) u64, valid_before(6) u64
// ---------------------------------------------------------------------------

impl Write for SignerRemoveBody {
    fn write(&self, buf: &mut impl BufMut) {
        write_fixed("SignerRemoveBody.key", KEY_LEN, &self.key, buf);
        write_bytes(&self.custody_signature, buf);
        self.nonce.write(buf);
        self.valid_after.write(buf);
        self.valid_before.write(buf);
    }
}

impl EncodeSize for SignerRemoveBody {
    fn encode_size(&self) -> usize {
        KEY_LEN + bytes_encode_size(&self.custody_signature) + 8 + 8 + 8
    }
}

impl Read for SignerRemoveBody {
    type Cfg = ();
    fn read_cfg(buf: &mut impl Buf, _cfg: &()) -> Result<Self, CodecError> {
        Ok(Self {
            key: read_fixed::<KEY_LEN>(buf)?,
            custody_signature: read_bytes(buf, MAX_KEYCHAIN_SIGNATURE_LEN)?,
            nonce: u64::read(buf)?,
            valid_after: u64::read(buf)?,
            valid_before: u64::read(buf)?,
            __buffa_unknown_fields: UnknownFields::default(),
        })
    }
}

// ---------------------------------------------------------------------------
// MergeRequestAddBody (proto tag 27)
// fields:
// project_id(1) bytes[32], source_project_id(2) bytes[32],
// source_ref(3) bytes (variable), source_commit_hash(4) bytes[32],
// target_ref(5) bytes (variable), title(6) string
// ---------------------------------------------------------------------------

impl Write for MergeRequestAddBody {
    fn write(&self, buf: &mut impl BufMut) {
        write_fixed(
            "MergeRequestAddBody.project_id",
            HASH_LEN,
            &self.project_id,
            buf,
        );
        write_fixed(
            "MergeRequestAddBody.source_project_id",
            HASH_LEN,
            &self.source_project_id,
            buf,
        );
        write_bytes(&self.source_ref, buf);
        write_fixed(
            "MergeRequestAddBody.source_commit_hash",
            HASH_LEN,
            &self.source_commit_hash,
            buf,
        );
        write_bytes(&self.target_ref, buf);
        write_string(&self.title, buf);
    }
}

impl EncodeSize for MergeRequestAddBody {
    fn encode_size(&self) -> usize {
        HASH_LEN
            + HASH_LEN
            + bytes_encode_size(&self.source_ref)
            + HASH_LEN
            + bytes_encode_size(&self.target_ref)
            + string_encode_size(&self.title)
    }
}

impl Read for MergeRequestAddBody {
    type Cfg = ();
    fn read_cfg(buf: &mut impl Buf, _cfg: &()) -> Result<Self, CodecError> {
        Ok(Self {
            project_id: read_fixed::<HASH_LEN>(buf)?,
            source_project_id: read_fixed::<HASH_LEN>(buf)?,
            source_ref: read_bytes(buf, MAX_REF_NAME_LEN)?,
            source_commit_hash: read_fixed::<HASH_LEN>(buf)?,
            target_ref: read_bytes(buf, MAX_REF_NAME_LEN)?,
            title: read_string(buf, MAX_TITLE_LEN)?,
            __buffa_unknown_fields: UnknownFields::default(),
        })
    }
}

// ---------------------------------------------------------------------------
// MergeRequestRemoveBody (proto tag 28)
// fields: project_id(1) bytes[32], request_id(2) bytes[32]
// ---------------------------------------------------------------------------

impl Write for MergeRequestRemoveBody {
    fn write(&self, buf: &mut impl BufMut) {
        write_fixed(
            "MergeRequestRemoveBody.project_id",
            HASH_LEN,
            &self.project_id,
            buf,
        );
        write_fixed(
            "MergeRequestRemoveBody.request_id",
            HASH_LEN,
            &self.request_id,
            buf,
        );
    }
}

impl EncodeSize for MergeRequestRemoveBody {
    fn encode_size(&self) -> usize {
        HASH_LEN + HASH_LEN
    }
}

impl Read for MergeRequestRemoveBody {
    type Cfg = ();
    fn read_cfg(buf: &mut impl Buf, _cfg: &()) -> Result<Self, CodecError> {
        Ok(Self {
            project_id: read_fixed::<HASH_LEN>(buf)?,
            request_id: read_fixed::<HASH_LEN>(buf)?,
            __buffa_unknown_fields: UnknownFields::default(),
        })
    }
}

// ---------------------------------------------------------------------------
// KeychainAuthorizeBody (proto tag 29)
// fields:
// key_id(1) bytes[20], signature_type(2) enum, public_key(3) bytes,
// admin(4) bool, expires_at(5) u64, valid_after(6) u64, valid_before(7) u64,
// nonce(8) u64, authorization_signature(9) bytes, witness(10) bytes
// ---------------------------------------------------------------------------

impl Write for KeychainAuthorizeBody {
    fn write(&self, buf: &mut impl BufMut) {
        write_fixed(
            "KeychainAuthorizeBody.key_id",
            ADDRESS_LEN,
            &self.key_id,
            buf,
        );
        write_enum_u8(&self.signature_type, buf);
        write_bytes(&self.public_key, buf);
        self.admin.write(buf);
        self.expires_at.write(buf);
        self.valid_after.write(buf);
        self.valid_before.write(buf);
        self.nonce.write(buf);
        write_bytes(&self.authorization_signature, buf);
        write_bytes(&self.witness, buf);
    }
}

impl EncodeSize for KeychainAuthorizeBody {
    fn encode_size(&self) -> usize {
        ADDRESS_LEN
            + 1
            + bytes_encode_size(&self.public_key)
            + 1
            + 8
            + 8
            + 8
            + 8
            + bytes_encode_size(&self.authorization_signature)
            + bytes_encode_size(&self.witness)
    }
}

impl Read for KeychainAuthorizeBody {
    type Cfg = ();
    fn read_cfg(buf: &mut impl Buf, _cfg: &()) -> Result<Self, CodecError> {
        Ok(Self {
            key_id: read_fixed::<ADDRESS_LEN>(buf)?,
            signature_type: ::buffa::EnumValue::from(i32::from(u8::read(buf)?)),
            public_key: read_bytes(buf, MAX_KEYCHAIN_PUBLIC_KEY_LEN)?,
            admin: bool::read(buf)?,
            expires_at: u64::read(buf)?,
            valid_after: u64::read(buf)?,
            valid_before: u64::read(buf)?,
            nonce: u64::read(buf)?,
            authorization_signature: read_bytes(buf, MAX_KEYCHAIN_SIGNATURE_LEN)?,
            witness: read_bytes(buf, MAX_KEYCHAIN_WITNESS_LEN)?,
            __buffa_unknown_fields: UnknownFields::default(),
        })
    }
}

// ---------------------------------------------------------------------------
// KeychainRevokeBody (proto tag 30)
// fields:
// key_id(1) bytes[20], valid_after(2) u64, valid_before(3) u64, nonce(4) u64,
// revocation_signature(5) bytes, witness(6) bytes
// ---------------------------------------------------------------------------

impl Write for KeychainRevokeBody {
    fn write(&self, buf: &mut impl BufMut) {
        write_fixed("KeychainRevokeBody.key_id", ADDRESS_LEN, &self.key_id, buf);
        self.valid_after.write(buf);
        self.valid_before.write(buf);
        self.nonce.write(buf);
        write_bytes(&self.revocation_signature, buf);
        write_bytes(&self.witness, buf);
    }
}

impl EncodeSize for KeychainRevokeBody {
    fn encode_size(&self) -> usize {
        ADDRESS_LEN
            + 8
            + 8
            + 8
            + bytes_encode_size(&self.revocation_signature)
            + bytes_encode_size(&self.witness)
    }
}

impl Read for KeychainRevokeBody {
    type Cfg = ();
    fn read_cfg(buf: &mut impl Buf, _cfg: &()) -> Result<Self, CodecError> {
        Ok(Self {
            key_id: read_fixed::<ADDRESS_LEN>(buf)?,
            valid_after: u64::read(buf)?,
            valid_before: u64::read(buf)?,
            nonce: u64::read(buf)?,
            revocation_signature: read_bytes(buf, MAX_KEYCHAIN_SIGNATURE_LEN)?,
            witness: read_bytes(buf, MAX_KEYCHAIN_WITNESS_LEN)?,
            __buffa_unknown_fields: UnknownFields::default(),
        })
    }
}
