//! Ontology classification (ADR-028 / D21).
//!
//! Four types govern how an ontology is shared and verified across
//! actors. Each type carries different governance and sync expectations:
//!
//! - **Type 1 (Protocol)** — mandatorily decentralized; ontoref-core is the
//!   first extracted. Every actor MUST be able to operate without relying on a
//!   central authority for the ontology's content.
//! - **Type 2a (Shared-Domain Federable)** — shared across projects but
//!   federable: a maintainer publishes the canonical version while forks may
//!   diverge with their own ids.
//! - **Type 2b (Shared-Domain Mandatorily Decentralized)** — shared across
//!   projects and mandatorily decentralized: no canonical maintainer; the
//!   address itself is the authority.
//! - **Type 3 (Project-Specific)** — opt-in per project; each project's
//!   self-description lives here.
//! - **Type 4 (Private)** — tier-0; never leaves the actor's local environment.

use serde::{Deserialize, Serialize};

/// Classification of an ontology — governs sharing and verification
/// semantics across actors.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum OntologyType {
    /// Protocol-level, mandatorily decentralized.
    Type1Protocol,
    /// Shared-domain, federable (canonical + forks).
    Type2aFederable,
    /// Shared-domain, mandatorily decentralized (no canonical authority).
    Type2bMandatoryDecentralized,
    /// Project-specific, opt-in.
    Type3ProjectSpecific,
    /// Private, tier-0 — never shared.
    Type4Private,
}

impl OntologyType {
    /// Short discriminator suitable for NCL/JSON serialization.
    pub fn tag(&self) -> &'static str {
        match self {
            Self::Type1Protocol => "Type1Protocol",
            Self::Type2aFederable => "Type2aFederable",
            Self::Type2bMandatoryDecentralized => "Type2bMandatoryDecentralized",
            Self::Type3ProjectSpecific => "Type3ProjectSpecific",
            Self::Type4Private => "Type4Private",
        }
    }

    /// Parse the discriminator produced by [`Self::tag`].
    pub fn parse(tag: &str) -> Option<Self> {
        match tag {
            "Type1Protocol" => Some(Self::Type1Protocol),
            "Type2aFederable" => Some(Self::Type2aFederable),
            "Type2bMandatoryDecentralized" => Some(Self::Type2bMandatoryDecentralized),
            "Type3ProjectSpecific" => Some(Self::Type3ProjectSpecific),
            "Type4Private" => Some(Self::Type4Private),
            _ => None,
        }
    }

    /// Whether this type forbids any centralised authority.
    pub fn must_be_decentralized(&self) -> bool {
        matches!(
            self,
            Self::Type1Protocol | Self::Type2bMandatoryDecentralized
        )
    }

    /// Whether instances of this type are allowed to leave the actor.
    pub fn shareable(&self) -> bool {
        !matches!(self, Self::Type4Private)
    }
}

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

    #[test]
    fn tag_roundtrips() {
        for variant in [
            OntologyType::Type1Protocol,
            OntologyType::Type2aFederable,
            OntologyType::Type2bMandatoryDecentralized,
            OntologyType::Type3ProjectSpecific,
            OntologyType::Type4Private,
        ] {
            let tag = variant.tag();
            assert_eq!(OntologyType::parse(tag), Some(variant));
        }
    }

    #[test]
    fn type1_and_type2b_are_decentralized() {
        assert!(OntologyType::Type1Protocol.must_be_decentralized());
        assert!(OntologyType::Type2bMandatoryDecentralized.must_be_decentralized());
        assert!(!OntologyType::Type2aFederable.must_be_decentralized());
        assert!(!OntologyType::Type3ProjectSpecific.must_be_decentralized());
        assert!(!OntologyType::Type4Private.must_be_decentralized());
    }

    #[test]
    fn private_is_not_shareable() {
        assert!(!OntologyType::Type4Private.shareable());
        assert!(OntologyType::Type3ProjectSpecific.shareable());
    }
}
