//! Content-addressed hashing for definitions.
//!
//! Hash = BLAKE3 over the canonical JSON bytes of a [`Def`] (see
//! [`crate::canonical`]). The hash function takes a [`Def`] by
//! reference, so there is no `hash` field to strip — the in-memory
//! struct does not carry one.
//!
//! The single entry point at this level is [`def_hash`]. The parallel
//! entry point at module granularity is
//! [`crate::workspace::module_hash`].

use crate::ast::Def;
use crate::canonical;

/// Content hash of a single [`Def`] — the 16-hex-char (64-bit) prefix
/// of its BLAKE3 hash over canonical JSON bytes.
///
/// 64 bits is wide enough to be unique across realistic AILang
/// codebases and short enough to read at a glance in pretty-printed
/// manifests. The hash is computed over the **canonical JSON byte
/// pre-image**, not over the in-memory struct, so any change to the
/// canonical form (new fields, different `skip_serializing_if`
/// behaviour, key reordering bug) changes every hash. The Iter 13a
/// regression test below pins concrete hashes for two example
/// definitions to catch that.
///
/// # Examples
///
/// ```ignore
/// use ailang_core::{ast::*, def_hash};
///
/// let def = Def::Const(ConstDef {
///     name: "answer".into(),
///     ty: Type::int(),
///     value: Term::Lit { lit: Literal::Int { value: 42 } },
///     doc: None,
/// });
///
/// // Stable across runs: same canonical bytes -> same hash.
/// assert_eq!(def_hash(&def), def_hash(&def));
/// assert_eq!(def_hash(&def).len(), 16);
/// ```
pub fn def_hash(def: &Def) -> String {
    let bytes = canonical::to_bytes(def);
    let h = blake3::hash(&bytes);
    let hex = h.to_hex();
    hex.as_str()[..16].to_string()
}
