use serde::{Deserialize, Serialize};

macro_rules! icon_ids {
    ($($variant:ident),+ $(,)?) => {
        #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
        #[repr(u8)]
        pub enum IconId {
            $($variant,)+
        }

        impl IconId {
            pub const ALL: &'static [IconId] = &[$(IconId::$variant,)+];

            pub const COUNT: usize = Self::ALL.len();
        }
    };
}

icon_ids! {
    Point,
    Line,
    CenterpointArc,
    TangentArc,
    ThreePointArc,
    Circle,
    PerimeterCircle,
    CornerRectangle,
    CenterRectangle,
    ThreePointCornerRectangle,
    ThreePointCenterRectangle,
    Parallelogram,
    SmartDimension,
    Coincident,
    Horizontal,
    Vertical,
    Parallel,
    Perpendicular,
    Tangent,
    Equal,
    Concentric,
    Midpoint,
    Symmetric,
    Fix,
    ExtrudedBossBase,
    ExtrudedCut,
    TreeFeature,
    TreePlane,
    TreeSketch,
    TreeOrigin,
    TabTree,
    TabProperties,
    TabConfiguration,
    TabDimensionExpert,
    TabDisplay,
    ZoomToFit,
    ZoomToArea,
    PreviousView,
    SectionView,
    ViewOrientation,
    DisplayStyle,
    HideShowItems,
    EditAppearance,
    ViewSettings,
    Check,
    Cross,
}

#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct IconTile(u8);

impl IconTile {
    #[must_use]
    pub const fn index(self) -> u8 {
        self.0
    }

    #[must_use]
    pub fn as_usize(self) -> usize {
        usize::from(self.0)
    }
}

impl IconId {
    #[must_use]
    pub const fn tile(self) -> IconTile {
        IconTile(self as u8)
    }
}

#[cfg(test)]
mod tests {
    use super::IconId;

    #[test]
    fn tile_index_and_usize_agree() {
        IconId::ALL.iter().for_each(|icon| {
            let tile = icon.tile();
            assert_eq!(usize::from(tile.index()), tile.as_usize());
        });
    }

    #[test]
    fn tile_is_injective_and_dense_over_all() {
        let mut indices: Vec<usize> = IconId::ALL
            .iter()
            .map(|icon| icon.tile().as_usize())
            .collect();
        indices.sort_unstable();
        assert_eq!(indices.len(), IconId::COUNT);
        let dense = indices.iter().enumerate().all(|(i, &v)| v == i);
        assert!(
            dense,
            "tile indices over ALL must be exactly 0..COUNT with no gaps or duplicates",
        );
    }

    #[test]
    fn all_is_ordered_by_discriminant() {
        IconId::ALL.iter().enumerate().for_each(|(i, icon)| {
            assert_eq!(
                usize::from(icon.tile().index()),
                i,
                "ALL must list variants in discriminant order so the tile matches the slot",
            );
        });
    }
}
