//! Identifies each sync committee subnet by an integer identifier.
use std::{
    collections::HashSet,
    fmt::{self, Display},
    ops::{Deref, DerefMut},
    sync::LazyLock,
};

use safe_arith::{ArithError, SafeArith};
use serde::{Deserialize, Serialize};
use typenum::Unsigned;

use crate::core::{EthSpec, consts::altair::SYNC_COMMITTEE_SUBNET_COUNT};

static SYNC_SUBNET_ID_TO_STRING: LazyLock<Vec<String>> = LazyLock::new(|| {
    let mut v = Vec::with_capacity(SYNC_COMMITTEE_SUBNET_COUNT as usize);

    for i in 0..SYNC_COMMITTEE_SUBNET_COUNT {
        v.push(i.to_string());
    }
    v
});

#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(transparent)]
pub struct SyncSubnetId(#[serde(with = "serde_utils::quoted_u64")] u64);

pub fn sync_subnet_id_to_string(i: u64) -> &'static str {
    if i < SYNC_COMMITTEE_SUBNET_COUNT {
        SYNC_SUBNET_ID_TO_STRING
            .get(i as usize)
            .expect("index below SYNC_COMMITTEE_SUBNET_COUNT")
    } else {
        "sync subnet id out of range"
    }
}

impl SyncSubnetId {
    pub fn new(id: u64) -> Self {
        id.into()
    }

    /// Compute required subnets to subscribe to given the sync committee indices.
    pub fn compute_subnets_for_sync_committee<E: EthSpec>(
        sync_committee_indices: &[u64],
    ) -> Result<HashSet<Self>, ArithError> {
        let subcommittee_size = E::SyncSubcommitteeSize::to_u64();

        sync_committee_indices
            .iter()
            .map(|index| index.safe_div(subcommittee_size).map(Self::new))
            .collect()
    }
}

impl Display for SyncSubnetId {
    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
        write!(f, "{}", self.0)
    }
}

impl Deref for SyncSubnetId {
    type Target = u64;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

impl DerefMut for SyncSubnetId {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.0
    }
}

impl From<u64> for SyncSubnetId {
    fn from(x: u64) -> Self {
        Self(x)
    }
}

impl From<SyncSubnetId> for u64 {
    fn from(from: SyncSubnetId) -> u64 {
        from.0
    }
}

impl From<&SyncSubnetId> for u64 {
    fn from(from: &SyncSubnetId) -> u64 {
        from.0
    }
}

impl AsRef<str> for SyncSubnetId {
    fn as_ref(&self) -> &str {
        sync_subnet_id_to_string(self.0)
    }
}
