//! M1 — `compute_interface_hash` semantics.
//!
//! Pins down the determinism / order-independence / sensitivity
//! properties the macro emission and downstream consumers depend on.

use maat_sdk_types::events::EventKind;
use maat_sdk_types::plugin_id::CanonicalId;
use maat_sdk_types::plugin_manifest::{
    compute_interface_hash, CapabilityDeclaration, HandlerDeclaration,
    PhaseDeclaration, PluginDependency, StructuralNeeds, TypeDeclaration,
    WireFrameDeclaration,
};
use maat_sdk_types::priority::Priority;
use maat_sdk_types::trait_data::{PipelineRole, TraitSet};
use maat_sdk_types::types::{EntityTypeId, PhaseId};

const PHASE_ID: PhaseId = PhaseId::new("test:phase");

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

fn ty(name: &str, sig: u8) -> TypeDeclaration {
    TypeDeclaration {
        type_name: name.to_string(),
        type_id: EntityTypeId::from_raw(0xABCD),
        trait_set: TraitSet::empty(),
        trait_data_signature: h(sig),
    }
}

fn hdlr(name: &str, sig: u8) -> HandlerDeclaration {
    HandlerDeclaration {
        name: name.to_string(),
        event_kind: EventKind::Moved,
        role: PipelineRole::React,
        priority: Priority::BASE,
        signature: h(sig),
    }
}

fn cap(name: &str, sig: u8) -> CapabilityDeclaration {
    CapabilityDeclaration {
        name: name.to_string(),
        signature: h(sig),
    }
}

fn phase(sig: u8) -> PhaseDeclaration {
    PhaseDeclaration {
        phase_id: PHASE_ID,
        signature: h(sig),
    }
}

fn frame(name: &str, sig: u8) -> WireFrameDeclaration {
    WireFrameDeclaration {
        frame_name: name.to_string(),
        signature: h(sig),
    }
}

fn dep(canonical: &str, iface_hash: u8) -> PluginDependency {
    PluginDependency {
        canonical_id: CanonicalId::new(canonical).unwrap(),
        required: true,
        linked_against: (h(0x99), h(iface_hash)),
        needs: StructuralNeeds::default(),
    }
}

#[test]
fn interface_hash_is_deterministic() {
    // Same inputs across two independent calls must produce the same hash.
    let types = vec![ty("Foo", 0x11), ty("Bar", 0x22)];
    let handlers = vec![hdlr("a:move", 0x33)];
    let caps = vec![cap("audio", 0x44)];
    let phases = vec![phase(0x55)];
    let frames = vec![frame("frame:x", 0x66)];
    let deps = vec![dep("github.com/foo/bar", 0x77)];

    let a = compute_interface_hash(&types, &handlers, &caps, &phases, &frames, &deps, &[]);
    let b = compute_interface_hash(&types, &handlers, &caps, &phases, &frames, &deps, &[]);
    assert_eq!(a, b, "compute_interface_hash must be deterministic");
}

#[test]
fn interface_hash_differs_on_added_handler() {
    let types = vec![ty("Foo", 0x11)];
    let phases: Vec<PhaseDeclaration> = vec![];
    let frames: Vec<WireFrameDeclaration> = vec![];
    let caps: Vec<CapabilityDeclaration> = vec![];
    let deps: Vec<PluginDependency> = vec![];

    let h1 = vec![hdlr("a:move", 0x33)];
    let h2 = vec![hdlr("a:move", 0x33), hdlr("a:turn", 0x44)];

    let a = compute_interface_hash(&types, &h1, &caps, &phases, &frames, &deps, &[]);
    let b = compute_interface_hash(&types, &h2, &caps, &phases, &frames, &deps, &[]);
    assert_ne!(
        a, b,
        "adding a handler must change the interface_hash"
    );
}

#[test]
fn interface_hash_order_independent() {
    let types_a = vec![ty("Alpha", 0x11), ty("Beta", 0x22), ty("Gamma", 0x33)];
    let types_b = vec![ty("Gamma", 0x33), ty("Alpha", 0x11), ty("Beta", 0x22)];
    let handlers_a = vec![hdlr("a", 0x10), hdlr("b", 0x20), hdlr("c", 0x30)];
    let handlers_b = vec![hdlr("c", 0x30), hdlr("a", 0x10), hdlr("b", 0x20)];
    let caps: Vec<CapabilityDeclaration> = vec![];
    let phases: Vec<PhaseDeclaration> = vec![];
    let frames: Vec<WireFrameDeclaration> = vec![];
    let deps: Vec<PluginDependency> = vec![];

    let a = compute_interface_hash(&types_a, &handlers_a, &caps, &phases, &frames, &deps, &[]);
    let b = compute_interface_hash(&types_b, &handlers_b, &caps, &phases, &frames, &deps, &[]);
    assert_eq!(
        a, b,
        "interface_hash must be independent of declaration insertion order"
    );
}

#[test]
fn interface_hash_includes_dep_hash() {
    let types: Vec<TypeDeclaration> = vec![];
    let handlers: Vec<HandlerDeclaration> = vec![];
    let caps: Vec<CapabilityDeclaration> = vec![];
    let phases: Vec<PhaseDeclaration> = vec![];
    let frames: Vec<WireFrameDeclaration> = vec![];

    let deps_a = vec![dep("github.com/foo/bar", 0xAA)];
    let deps_b = vec![dep("github.com/foo/bar", 0xBB)];

    let a = compute_interface_hash(&types, &handlers, &caps, &phases, &frames, &deps_a, &[]);
    let b = compute_interface_hash(&types, &handlers, &caps, &phases, &frames, &deps_b, &[]);
    assert_ne!(
        a, b,
        "dependency interface_hash must flow into the plugin's hash"
    );
}

#[test]
fn interface_hash_for_empty_plugin_is_nonzero() {
    let hash = compute_interface_hash(&[], &[], &[], &[], &[], &[], &[]);
    assert_ne!(
        hash,
        [0u8; 32],
        "empty plugin should still produce a non-zero hash via block separators"
    );
}

#[test]
fn interface_hash_block_position_matters() {
    // A capability and a wire-frame with the same name + signature
    // should produce different plugin hashes. The block-separator byte
    // is what distinguishes them.
    let same_name = "common";
    let same_sig = 0x42;

    let with_cap = compute_interface_hash(
        &[],
        &[],
        &[cap(same_name, same_sig)],
        &[],
        &[],
        &[],
        &[],
    );
    let with_frame = compute_interface_hash(
        &[],
        &[],
        &[],
        &[],
        &[frame(same_name, same_sig)],
        &[],
        &[],
    );
    assert_ne!(
        with_cap, with_frame,
        "identical name/sig in different blocks must differ"
    );
}
