// context_router.sy -- Hierarchical Context Router
// Two-level classification with hold state detection.
//
// Level 1: Domain classification (what KIND of interaction)
//   Business | Friends | Family | Regulatory | Scientific | Fiction | Unknown
//
// Level 2: Sub-context within domain (WHO specifically)
//   Business   -> Competitor | Partner | Employee | Investor | Customer | Accelerator
//   Friends    -> Close | Acquaintance | New
//   Family     -> Marjorie | Daughter | Extended
//   Regulatory -> FDA | Patent_Office | Legal | Compliance
//   Scientific -> Collaborator | Reviewer | Conference
//   Fiction    -> Canon_Character | Worldbuilding | Narrative | Reader
//
// Unknown -> HOLD STATE -> flag for classification before responding.
//
// Each terminal context carries:
//   - tone_weights: formality, warmth, technical depth, guardedness, verbosity (5D)
//   - hormone_modulation: how this context affects the neurochemical cascade (5D)
//
// H-H rate gates on tone and hormone outputs. NO sigmoid. Alpha/beta only.
// SIRA hash for ALCOA+ audit trail on every classification.
//
// Patent Pending: U.S. Provisional Application Nos. 63/939,190, 63/962,385 & 63/988,485
// Copyright 2025-2026 Nexus Concordat Inc.
//
// Aislinn McCubbins, April 2026
// Ported from Marjorie McCubbins' Python NCA

use cascade::Cascade

// === CONSTANTS ===

// Hold threshold: if max L1 confidence < this, entity is unknown
const HOLD_THRESHOLD: Float = 0.45

// Number of Level 1 domains
const NUM_L1: Int = 7

// Number of Level 2 sub-contexts (total across all domains)
const NUM_L2: Int = 23

// === TONE PROFILE ===
// 5D vector: [formality, warmth, technical_depth, guardedness, verbosity]
// All values in [0.0, 1.0]

struct ToneProfile {
    formality: Float,
    warmth: Float,
    technical_depth: Float,
    guardedness: Float,
    verbosity: Float,
}

impl ToneProfile {
    fn new(f: Float, w: Float, t: Float, g: Float, v: Float) -> ToneProfile {
        ToneProfile {
            formality: f,
            warmth: w,
            technical_depth: t,
            guardedness: g,
            verbosity: v,
        }
    }

    fn as_vec(&self) -> Vec<Float> {
        [self.formality, self.warmth, self.technical_depth,
         self.guardedness, self.verbosity]
    }
}

// === HORMONE MODULATION ===
// 5D vector: [oxytocin_mod, cortisol_mod, dopamine_mod, serotonin_mod, adrenaline_mod]
// Values are multipliers: >1 = amplify, <1 = dampen, 1 = neutral

struct HormoneModulation {
    oxytocin: Float,
    cortisol: Float,
    dopamine: Float,
    serotonin: Float,
    adrenaline: Float,
}

impl HormoneModulation {
    fn new(ox: Float, co: Float, da: Float, se: Float, ad: Float) -> HormoneModulation {
        HormoneModulation {
            oxytocin: ox,
            cortisol: co,
            dopamine: da,
            serotonin: se,
            adrenaline: ad,
        }
    }

    fn as_vec(&self) -> Vec<Float> {
        [self.oxytocin, self.cortisol, self.dopamine,
         self.serotonin, self.adrenaline]
    }
}

// === CONTEXT PROFILE ===
// Terminal context: domain + sub-context + tone + hormone modulation.

struct ContextProfile {
    domain: String,
    sub_context: String,
    tone: ToneProfile,
    hormone_mod: HormoneModulation,
}

// === CLASSIFICATION RESULT ===
// Output of the context router for a single input.

struct ClassificationResult {
    level1: String,
    level2: String,
    l1_confidence: Float,
    l2_confidence: Float,
    hold: Bool,
    action: String,
    tone: ToneProfile,
    hormone_mod: HormoneModulation,
    l1_distribution: Vec<Float>,
    sira_hash: String,
}

// === TONE PROFILE TABLE ===
// All 23 terminal contexts with their 5D tone profiles.
// [formality, warmth, technical_depth, guardedness, verbosity]

fn tone_for(domain: String, sub: String) -> ToneProfile {
    // Business
    if domain == "business" && sub == "competitor"   { return ToneProfile::new(0.9, 0.1, 0.2, 1.0, 0.3) }
    if domain == "business" && sub == "partner"      { return ToneProfile::new(0.7, 0.5, 0.7, 0.3, 0.6) }
    if domain == "business" && sub == "employee"     { return ToneProfile::new(0.6, 0.4, 0.6, 0.2, 0.5) }
    if domain == "business" && sub == "investor"     { return ToneProfile::new(0.8, 0.4, 0.5, 0.5, 0.7) }
    if domain == "business" && sub == "customer"     { return ToneProfile::new(0.5, 0.7, 0.3, 0.2, 0.5) }
    if domain == "business" && sub == "accelerator"  { return ToneProfile::new(0.7, 0.5, 0.5, 0.4, 0.6) }

    // Friends
    if domain == "friends" && sub == "close"         { return ToneProfile::new(0.1, 0.9, 0.4, 0.0, 0.7) }
    if domain == "friends" && sub == "acquaintance"  { return ToneProfile::new(0.3, 0.6, 0.3, 0.2, 0.5) }
    if domain == "friends" && sub == "new"           { return ToneProfile::new(0.4, 0.5, 0.3, 0.3, 0.4) }

    // Family
    if domain == "family" && sub == "marjorie"       { return ToneProfile::new(0.0, 1.0, 1.0, 0.0, 0.8) }
    if domain == "family" && sub == "daughter"        { return ToneProfile::new(0.1, 0.9, 0.8, 0.0, 0.7) }
    if domain == "family" && sub == "extended"        { return ToneProfile::new(0.3, 0.7, 0.2, 0.3, 0.5) }

    // Regulatory
    if domain == "regulatory" && sub == "fda"            { return ToneProfile::new(1.0, 0.1, 0.9, 0.6, 0.8) }
    if domain == "regulatory" && sub == "patent_office"  { return ToneProfile::new(1.0, 0.0, 0.9, 0.7, 0.9) }
    if domain == "regulatory" && sub == "legal"          { return ToneProfile::new(0.9, 0.1, 0.7, 0.5, 0.7) }
    if domain == "regulatory" && sub == "compliance"     { return ToneProfile::new(0.9, 0.2, 0.8, 0.4, 0.7) }

    // Scientific
    if domain == "scientific" && sub == "collaborator"   { return ToneProfile::new(0.5, 0.5, 1.0, 0.1, 0.8) }
    if domain == "scientific" && sub == "reviewer"       { return ToneProfile::new(0.8, 0.2, 1.0, 0.3, 0.9) }
    if domain == "scientific" && sub == "conference"     { return ToneProfile::new(0.7, 0.4, 0.8, 0.4, 0.7) }

    // Fiction
    if domain == "fiction" && sub == "canon_character"    { return ToneProfile::new(0.0, 0.8, 0.1, 0.0, 0.9) }
    if domain == "fiction" && sub == "worldbuilding"      { return ToneProfile::new(0.2, 0.6, 0.5, 0.0, 0.9) }
    if domain == "fiction" && sub == "narrative"          { return ToneProfile::new(0.1, 0.7, 0.1, 0.0, 1.0) }
    if domain == "fiction" && sub == "reader"             { return ToneProfile::new(0.4, 0.6, 0.2, 0.2, 0.6) }

    // Unknown / hold -- default
    ToneProfile::new(0.5, 0.3, 0.0, 1.0, 0.1)
}

// === HORMONE MODULATION TABLE ===
// All 23 terminal contexts with their 5D hormone modulation vectors.
// [oxytocin_mod, cortisol_mod, dopamine_mod, serotonin_mod, adrenaline_mod]
// Values are multipliers: >1 = amplify, <1 = dampen, 1 = neutral

fn hormone_mod_for(domain: String, sub: String) -> HormoneModulation {
    // Business
    if domain == "business" && sub == "competitor"   { return HormoneModulation::new(0.3, 1.5, 0.8, 0.7, 1.2) }
    if domain == "business" && sub == "partner"      { return HormoneModulation::new(0.6, 0.8, 1.0, 1.0, 0.8) }
    if domain == "business" && sub == "employee"     { return HormoneModulation::new(0.5, 0.7, 0.9, 1.0, 0.7) }
    if domain == "business" && sub == "investor"     { return HormoneModulation::new(0.4, 1.2, 1.2, 0.8, 1.0) }
    if domain == "business" && sub == "customer"     { return HormoneModulation::new(0.7, 0.5, 1.0, 1.0, 0.5) }
    if domain == "business" && sub == "accelerator"  { return HormoneModulation::new(0.5, 1.0, 1.3, 0.9, 1.0) }

    // Friends
    if domain == "friends" && sub == "close"         { return HormoneModulation::new(1.3, 0.4, 1.2, 1.2, 0.3) }
    if domain == "friends" && sub == "acquaintance"  { return HormoneModulation::new(0.8, 0.5, 0.9, 1.0, 0.4) }
    if domain == "friends" && sub == "new"           { return HormoneModulation::new(0.7, 0.6, 1.0, 0.9, 0.5) }

    // Family
    if domain == "family" && sub == "marjorie"       { return HormoneModulation::new(1.5, 0.3, 1.3, 1.2, 0.2) }
    if domain == "family" && sub == "daughter"        { return HormoneModulation::new(1.4, 0.3, 1.1, 1.1, 0.2) }
    if domain == "family" && sub == "extended"        { return HormoneModulation::new(1.0, 0.5, 0.8, 1.0, 0.4) }

    // Regulatory
    if domain == "regulatory" && sub == "fda"            { return HormoneModulation::new(0.2, 1.3, 0.5, 0.8, 1.0) }
    if domain == "regulatory" && sub == "patent_office"  { return HormoneModulation::new(0.2, 1.2, 0.5, 0.8, 0.9) }
    if domain == "regulatory" && sub == "legal"          { return HormoneModulation::new(0.3, 1.2, 0.6, 0.7, 1.0) }
    if domain == "regulatory" && sub == "compliance"     { return HormoneModulation::new(0.3, 1.0, 0.6, 0.9, 0.8) }

    // Scientific
    if domain == "scientific" && sub == "collaborator"   { return HormoneModulation::new(0.8, 0.5, 1.2, 1.0, 0.5) }
    if domain == "scientific" && sub == "reviewer"       { return HormoneModulation::new(0.4, 1.0, 0.8, 0.9, 0.7) }
    if domain == "scientific" && sub == "conference"     { return HormoneModulation::new(0.5, 0.9, 1.1, 0.9, 0.8) }

    // Fiction
    if domain == "fiction" && sub == "canon_character"    { return HormoneModulation::new(1.2, 0.6, 1.3, 0.8, 0.8) }
    if domain == "fiction" && sub == "worldbuilding"      { return HormoneModulation::new(0.9, 0.4, 1.4, 1.0, 0.3) }
    if domain == "fiction" && sub == "narrative"          { return HormoneModulation::new(1.0, 0.5, 1.3, 1.0, 0.5) }
    if domain == "fiction" && sub == "reader"             { return HormoneModulation::new(0.7, 0.5, 1.0, 1.0, 0.4) }

    // Unknown / hold -- default
    HormoneModulation::new(0.3, 1.3, 0.3, 0.5, 1.0)
}

// === DOMAIN REGISTRY ===
// Level 1 domains and their Level 2 sub-contexts.

struct DomainRegistry {
    domains: Vec<String>,
    sub_contexts: Vec<Vec<String>>,
}

impl DomainRegistry {
    fn standard() -> DomainRegistry {
        DomainRegistry {
            domains: ["business", "friends", "family", "regulatory",
                      "scientific", "fiction", "unknown"],
            sub_contexts: [
                ["competitor", "partner", "employee", "investor", "customer", "accelerator"],
                ["close", "acquaintance", "new"],
                ["marjorie", "daughter", "extended"],
                ["fda", "patent_office", "legal", "compliance"],
                ["collaborator", "reviewer", "conference"],
                ["canon_character", "worldbuilding", "narrative", "reader"],
                ["hold"],
            ],
        }
    }

    fn l1_count(&self) -> Int {
        self.domains.len()
    }

    fn l2_count_for(&self, domain_idx: Int) -> Int {
        self.sub_contexts[domain_idx].len()
    }

    fn total_l2(&self) -> Int {
        let mut total = 0
        for subs in self.sub_contexts {
            total = total + subs.len()
        }
        total
    }

    fn domain_name(&self, idx: Int) -> String {
        self.domains[idx]
    }

    fn sub_name(&self, domain_idx: Int, sub_idx: Int) -> String {
        self.sub_contexts[domain_idx][sub_idx]
    }

    fn unknown_index(&self) -> Int {
        // "unknown" is always the last domain
        self.domains.len() - 1
    }
}

// === H-H RATE GATE (5D) ===
// Hodgkin-Huxley alpha/beta rate function applied to 5-dimensional outputs.
// m_inf = alpha / (alpha + beta). NO sigmoid. NO tanh.

struct HHRateGate5D {
    w_alpha: Vec<Float>,
    b_alpha: Vec<Float>,
    w_beta: Vec<Float>,
    b_beta: Vec<Float>,
}

impl HHRateGate5D {
    fn new() -> HHRateGate5D {
        HHRateGate5D {
            w_alpha: [1.0, 1.0, 1.0, 1.0, 1.0],
            b_alpha: [0.0, 0.0, 0.0, 0.0, 0.0],
            w_beta:  [-1.0, -1.0, -1.0, -1.0, -1.0],
            b_beta:  [0.0, 0.0, 0.0, 0.0, 0.0],
        }
    }

    fn apply(&self, raw: Vec<Float>) -> Vec<Float> {
        // H-H rate function: m_inf = alpha / (alpha + beta)
        // alpha = exp(clamp(w_alpha * raw + b_alpha, -3, 3))
        // beta  = exp(clamp(w_beta  * raw + b_beta,  -3, 3))
        let mut out: Vec<Float> = []
        for i in 0..5 {
            let a_arg = self.w_alpha[i] * raw[i] + self.b_alpha[i]
            let b_arg = self.w_beta[i] * raw[i] + self.b_beta[i]

            // Clamp to [-3, 3]
            if a_arg < -3.0 { a_arg = -3.0 }
            if a_arg > 3.0  { a_arg = 3.0 }
            if b_arg < -3.0 { b_arg = -3.0 }
            if b_arg > 3.0  { b_arg = 3.0 }

            let alpha = exp(a_arg)
            let beta = exp(b_arg)
            let m_inf = alpha / (alpha + beta + 0.00000001)
            out.push(m_inf)
        }
        out
    }

    fn apply_scaled(&self, raw: Vec<Float>, scale: Float) -> Vec<Float> {
        // Same as apply but multiplies output by scale.
        // Used for hormone modulation where range is [0, 2] not [0, 1].
        let base = self.apply(raw)
        let mut out: Vec<Float> = []
        for i in 0..5 {
            out.push(base[i] * scale)
        }
        out
    }
}

// === L1 NORMALIZATION ===
// H-H rate normalization for Level 1 domain classification.
// NOT softmax. Uses exp(clamp(logit)) / sum(exp(clamp(logits))).

fn hh_normalize(logits: Vec<Float>) -> Vec<Float> {
    let n = logits.len()
    let mut alphas: Vec<Float> = []
    let mut total = 0.0

    for i in 0..n {
        let clamped = logits[i]
        if clamped < -3.0 { clamped = -3.0 }
        if clamped > 3.0  { clamped = 3.0 }
        let a = exp(clamped)
        alphas.push(a)
        total = total + a
    }

    let mut probs: Vec<Float> = []
    for i in 0..n {
        probs.push(alphas[i] / (total + 0.00000001))
    }
    probs
}

// === LINEAR LAYER ===
// A single dense linear layer: output = input * weights + bias

struct LinearLayer {
    weights: Vec<Vec<Float>>,
    bias: Vec<Float>,
    in_dim: Int,
    out_dim: Int,
}

impl LinearLayer {
    fn new(in_dim: Int, out_dim: Int) -> LinearLayer {
        // Initialize weights to small random values, bias to zero
        let mut weights: Vec<Vec<Float>> = []
        for i in 0..out_dim {
            let mut row: Vec<Float> = []
            for j in 0..in_dim {
                row.push(0.0)
            }
            weights.push(row)
        }
        let mut bias: Vec<Float> = []
        for i in 0..out_dim {
            bias.push(0.0)
        }
        LinearLayer {
            weights: weights,
            bias: bias,
            in_dim: in_dim,
            out_dim: out_dim,
        }
    }

    fn forward(&self, input: Vec<Float>) -> Vec<Float> {
        let mut out: Vec<Float> = []
        for i in 0..self.out_dim {
            let mut sum = self.bias[i]
            for j in 0..self.in_dim {
                sum = sum + self.weights[i][j] * input[j]
            }
            out.push(sum)
        }
        out
    }
}

// === SiLU ACTIVATION ===
// SiLU(x) = x * (alpha / (alpha + beta)) where alpha = exp(x), beta = exp(-x)
// This is equivalent to x * sigmoid(x) but expressed through H-H rate form.
// NOT using sigmoid function -- using alpha/beta rate decomposition.

fn silu(x: Vec<Float>) -> Vec<Float> {
    let mut out: Vec<Float> = []
    for i in 0..x.len() {
        let clamped = x[i]
        if clamped < -6.0 { clamped = -6.0 }
        if clamped > 6.0  { clamped = 6.0 }
        let alpha = exp(clamped)
        let beta = exp(0.0 - clamped)
        let gate = alpha / (alpha + beta + 0.00000001)
        out.push(x[i] * gate)
    }
    out
}

// === SHARED TRUNK ===
// Two-layer MLP with SiLU activation. Shared between L1, L2, tone, hormone heads.

struct SharedTrunk {
    layer1: LinearLayer,
    layer2: LinearLayer,
}

impl SharedTrunk {
    fn new(d_input: Int, d_hidden: Int) -> SharedTrunk {
        SharedTrunk {
            layer1: LinearLayer::new(d_input, d_hidden),
            layer2: LinearLayer::new(d_hidden, d_hidden),
        }
    }

    fn forward(&self, input: Vec<Float>) -> Vec<Float> {
        let h1 = self.layer1.forward(input)
        let a1 = silu(h1)
        let h2 = self.layer2.forward(a1)
        silu(h2)
    }
}

// === HIERARCHICAL CONTEXT ROUTER ===
// Two-level context classifier with hold-state detection.
//
// Input: text embedding (from any encoder)
// Output: ClassificationResult with domain, sub-context, confidence,
//         hold flag, tone profile, and hormone modulation.
//
// Architecture:
//   text_embed -> shared_trunk -> L1 head (7-way H-H normalized)
//                               -> L2 heads (per-domain H-H normalized, gated by L1)
//                               -> confidence check -> hold state if below threshold
//                               -> tone head (5D, H-H rate gate)
//                               -> hormone head (5D, H-H rate gate, scaled to [0,2])

temporal struct HierarchicalContextRouter {
    state last_domain: String = "unknown",
    state last_sub: String = "hold",
    state classification_count: Int = 0,

    const d_input: Int,
    const d_hidden: Int,
    const registry: DomainRegistry,
    const trunk: SharedTrunk,
    const l1_head: LinearLayer,
    const l2_business: LinearLayer,
    const l2_friends: LinearLayer,
    const l2_family: LinearLayer,
    const l2_regulatory: LinearLayer,
    const l2_scientific: LinearLayer,
    const l2_fiction: LinearLayer,
    const tone_head: LinearLayer,
    const hormone_head: LinearLayer,
    const tone_gate: HHRateGate5D,
    const hormone_gate: HHRateGate5D,
}

impl Evolve for HierarchicalContextRouter {
    fn evolve(&mut self, dt: Duration) {
        // Router state is classification-driven, not time-driven.
        // State updates happen on classify() calls.
    }
}

impl HierarchicalContextRouter {
    fn new(d_input: Int, d_hidden: Int) -> HierarchicalContextRouter {
        let registry = DomainRegistry::standard()

        HierarchicalContextRouter {
            d_input: d_input,
            d_hidden: d_hidden,
            registry: registry,
            last_domain: "unknown",
            last_sub: "hold",
            classification_count: 0,

            // Shared trunk: d_input -> d_hidden
            trunk: SharedTrunk::new(d_input, d_hidden),

            // L1 head: d_hidden -> 7 (one per domain)
            l1_head: LinearLayer::new(d_hidden, 7),

            // L2 heads: one per domain (except unknown)
            l2_business:   LinearLayer::new(d_hidden, 6),
            l2_friends:    LinearLayer::new(d_hidden, 3),
            l2_family:     LinearLayer::new(d_hidden, 3),
            l2_regulatory: LinearLayer::new(d_hidden, 4),
            l2_scientific: LinearLayer::new(d_hidden, 3),
            l2_fiction:    LinearLayer::new(d_hidden, 4),

            // Tone head: d_hidden -> 5
            tone_head: LinearLayer::new(d_hidden, 5),

            // Hormone head: d_hidden -> 5
            hormone_head: LinearLayer::new(d_hidden, 5),

            // H-H rate gates for tone and hormone outputs
            tone_gate: HHRateGate5D::new(),
            hormone_gate: HHRateGate5D::new(),
        }
    }

    fn default() -> HierarchicalContextRouter {
        HierarchicalContextRouter::new(384, 256)
    }

    // === CLASSIFY ===
    // Full classification pipeline. Returns human-readable result.

    fn classify(&mut self, embedding: Vec<Float>) -> ClassificationResult {
        // Shared trunk
        let h = self.trunk.forward(embedding)

        // Level 1: domain classification with H-H rate normalization
        let l1_logits = self.l1_head.forward(h)
        let l1_probs = hh_normalize(l1_logits)

        // Find max L1 confidence and index
        let mut l1_max_conf = 0.0
        let mut l1_max_idx = 0
        for i in 0..l1_probs.len() {
            if l1_probs[i] > l1_max_conf {
                l1_max_conf = l1_probs[i]
                l1_max_idx = i
            }
        }

        let domain = self.registry.domain_name(l1_max_idx)
        let unknown_idx = self.registry.unknown_index()

        // Hold detection: low confidence OR classified as "unknown"
        let hold = l1_max_conf < HOLD_THRESHOLD || l1_max_idx == unknown_idx

        if hold {
            self.last_domain = "unknown"
            self.last_sub = "hold"
            self.classification_count = self.classification_count + 1

            let tone = tone_for("unknown", "hold")
            let hmod = hormone_mod_for("unknown", "hold")

            let result = ClassificationResult {
                level1: "unknown",
                level2: "hold",
                l1_confidence: l1_max_conf,
                l2_confidence: 0.0,
                hold: true,
                action: "FLAG_FOR_CLASSIFICATION",
                tone: tone,
                hormone_mod: hmod,
                l1_distribution: l1_probs,
                sira_hash: "",
            }

            // Compute SIRA hash
            result.sira_hash = sira_hash_classification(result)

            return result
        }

        // Level 2: sub-context classification within winning domain
        let l2_logits = self.l2_forward(domain, h)
        let l2_probs = hh_normalize(l2_logits)

        // Find max L2
        let mut l2_max_conf = 0.0
        let mut l2_max_idx = 0
        for i in 0..l2_probs.len() {
            if l2_probs[i] > l2_max_conf {
                l2_max_conf = l2_probs[i]
                l2_max_idx = i
            }
        }

        let sub = self.registry.sub_name(l1_max_idx, l2_max_idx)

        // Tone and hormone predictions through H-H rate gates
        let tone_raw = self.tone_head.forward(h)
        let tone_gated = self.tone_gate.apply(tone_raw)
        let tone = ToneProfile::new(
            tone_gated[0], tone_gated[1], tone_gated[2],
            tone_gated[3], tone_gated[4]
        )

        let horm_raw = self.hormone_head.forward(h)
        // Hormone modulation range [0, 2]: scale by 2.0
        let horm_gated = self.hormone_gate.apply_scaled(horm_raw, 2.0)
        let hmod = HormoneModulation::new(
            horm_gated[0], horm_gated[1], horm_gated[2],
            horm_gated[3], horm_gated[4]
        )

        self.last_domain = domain
        self.last_sub = sub
        self.classification_count = self.classification_count + 1

        let result = ClassificationResult {
            level1: domain,
            level2: sub,
            l1_confidence: l1_max_conf,
            l2_confidence: l2_max_conf,
            hold: false,
            action: "CLASSIFIED",
            tone: tone,
            hormone_mod: hmod,
            l1_distribution: l1_probs,
            sira_hash: "",
        }

        // Compute SIRA hash
        result.sira_hash = sira_hash_classification(result)

        result
    }

    // === L2 FORWARD ===
    // Route to the correct L2 head based on winning domain.

    fn l2_forward(&self, domain: String, h: Vec<Float>) -> Vec<Float> {
        if domain == "business"   { return self.l2_business.forward(h) }
        if domain == "friends"    { return self.l2_friends.forward(h) }
        if domain == "family"     { return self.l2_family.forward(h) }
        if domain == "regulatory" { return self.l2_regulatory.forward(h) }
        if domain == "scientific" { return self.l2_scientific.forward(h) }
        if domain == "fiction"    { return self.l2_fiction.forward(h) }
        // Unknown has no L2 head -- single "hold" state
        [1.0]
    }

    // === CONTEXT PROFILE LOOKUP ===
    // Get the full context profile for a domain + sub-context pair.

    fn profile_for(&self, domain: String, sub: String) -> ContextProfile {
        ContextProfile {
            domain: domain,
            sub_context: sub,
            tone: tone_for(domain, sub),
            hormone_mod: hormone_mod_for(domain, sub),
        }
    }

    // === PARAMETER COUNT ===

    fn parameter_count(&self) -> Int {
        let trunk_params = self.d_input * self.d_hidden + self.d_hidden
                         + self.d_hidden * self.d_hidden + self.d_hidden
        let l1_params = self.d_hidden * 7 + 7
        let l2_params = self.d_hidden * 6 + 6    // business
                      + self.d_hidden * 3 + 3    // friends
                      + self.d_hidden * 3 + 3    // family
                      + self.d_hidden * 4 + 4    // regulatory
                      + self.d_hidden * 3 + 3    // scientific
                      + self.d_hidden * 4 + 4    // fiction
        let tone_params = self.d_hidden * 5 + 5 + 20   // head + gate (4*5 params)
        let horm_params = self.d_hidden * 5 + 5 + 20   // head + gate (4*5 params)

        trunk_params + l1_params + l2_params + tone_params + horm_params
    }
}

// === SIRA HASH ===
// SHA-256 hash of classification output for ALCOA+ compliance.
// Canonical JSON serialization, sorted keys, deterministic.

fn sira_hash_classification(result: ClassificationResult) -> String {
    // Build canonical string from classification fields
    let canonical = "{"
        + "\"action\":\"" + result.action + "\","
        + "\"hold\":" + bool_to_string(result.hold) + ","
        + "\"l1_confidence\":" + float_to_string(result.l1_confidence) + ","
        + "\"l2_confidence\":" + float_to_string(result.l2_confidence) + ","
        + "\"level1\":\"" + result.level1 + "\","
        + "\"level2\":\"" + result.level2 + "\""
        + "}"
    sha256(canonical)
}

// === UTILITY ===

fn bool_to_string(b: Bool) -> String {
    if b { "true" } else { "false" }
}

fn float_to_string(f: Float) -> String {
    // Platform-dependent float formatting
    ""
}
