//! Version model — `PluginRef`, `VersionMatch`, comparison helpers.
//!
//! - `content_hash` is strict identity. Same hash → same source.
//! - `interface_hash` is the wire/contract hash. Same interface_hash →
//!   substitution allowed (with caution; behaviour may differ).

use crate::plugin_id::CanonicalId;

/// 32-byte blake3 digest. Plain alias to keep `[u8; 32]` from leaking
/// into every signature.
pub type Blake3 = [u8; 32];

/// Reference to a specific installed plugin version.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct PluginRef {
    pub canonical_id: CanonicalId,
    pub content_hash: Blake3,
    pub interface_hash: Blake3,
}

impl PluginRef {
    pub fn new(canonical_id: CanonicalId, content_hash: Blake3, interface_hash: Blake3) -> Self {
        Self {
            canonical_id,
            content_hash,
            interface_hash,
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum VersionMatch {
    /// Same `content_hash`. Mechanically identical.
    Identical,
    /// Same `interface_hash`, different `content_hash`. Wire-compatible
    /// but behaviour may differ — bug fixes, perf, internal refactors.
    InterfaceCompatible,
    /// Different `interface_hash`. Cannot substitute without migration.
    Incompatible,
}

/// Resolution order: Identical → InterfaceCompatible → Incompatible.
/// `canonical_id` mismatch always yields `Incompatible`.
pub fn compare_versions(left: &PluginRef, right: &PluginRef) -> VersionMatch {
    if left.canonical_id != right.canonical_id {
        return VersionMatch::Incompatible;
    }
    if left.content_hash == right.content_hash {
        return VersionMatch::Identical;
    }
    if left.interface_hash == right.interface_hash {
        return VersionMatch::InterfaceCompatible;
    }
    VersionMatch::Incompatible
}

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

    fn cid(s: &str) -> CanonicalId {
        CanonicalId::new(s).unwrap()
    }

    fn h(byte: u8) -> Blake3 {
        [byte; 32]
    }

    #[test]
    fn version_match_identical_for_same_content_hash() {
        let a = PluginRef::new(cid("github.com/foo/bar"), h(0x11), h(0x22));
        let b = PluginRef::new(cid("github.com/foo/bar"), h(0x11), h(0x33)); // diff iface still wins on content
        assert_eq!(compare_versions(&a, &b), VersionMatch::Identical);
    }

    #[test]
    fn version_match_interface_compatible_for_same_interface_different_content() {
        let a = PluginRef::new(cid("github.com/foo/bar"), h(0x11), h(0x22));
        let b = PluginRef::new(cid("github.com/foo/bar"), h(0x99), h(0x22));
        assert_eq!(
            compare_versions(&a, &b),
            VersionMatch::InterfaceCompatible
        );
    }

    #[test]
    fn version_match_incompatible_for_different_interface_hash() {
        let a = PluginRef::new(cid("github.com/foo/bar"), h(0x11), h(0x22));
        let b = PluginRef::new(cid("github.com/foo/bar"), h(0x99), h(0x88));
        assert_eq!(compare_versions(&a, &b), VersionMatch::Incompatible);
    }

    #[test]
    fn different_canonical_id_is_incompatible() {
        let a = PluginRef::new(cid("github.com/foo/bar"), h(0x11), h(0x22));
        let b = PluginRef::new(cid("github.com/baz/qux"), h(0x11), h(0x22));
        assert_eq!(compare_versions(&a, &b), VersionMatch::Incompatible);
    }
}
