// security.sy -- Product Sandbox, Output Filter, and SIRA/ALCOA+ Audit Trail
// Combined security layer for the NCA pipeline.
//
// Three components:
//   1. ProductSandbox: Isolated product access to AetherBrain
//      - Each product gets responses WITHOUT exposing internal state
//      - The brain thinks. The product gets the answer. Nothing else.
//
//   2. OutputFilter: Regex-based scrubbing of leaked architecture references
//      - Removes accidentally generated text referencing internal state
//      - Configurable per product with custom patterns
//
//   3. AuditTrail: SIRA/ALCOA+ compliant SHA-256 hash chain
//      - Attributable: hash includes product_id and entity_id
//      - Legible: JSON-serialized before hashing
//      - Contemporaneous: timestamp included
//      - Original: hash chain links transitions
//      - Accurate: covers all pipeline stages
//
// Ported from Marjorie McCubbins' Python NCA by Aislinn McCubbins, April 2026
// Patent Pending: U.S. Provisional Application Nos. 63/939,190, 63/962,385 & 63/988,485
// Copyright 2025-2026 Nexus Concordat Inc.


// ============================================================
// AUDIT TRAIL -- SIRA/ALCOA+ Compliant Hash Chain
// ============================================================
// SHA-256 hash of (input, entity, timestamp, output) at each
// pipeline transition. Hash chain is append-only.

temporal struct AuditTrail {
    state chain: Vec<String>,
}

impl AuditTrail {
    fn new() -> AuditTrail {
        AuditTrail {
            chain: [],
        }
    }

    fn hash_transition(
        &mut self,
        input_hash: String,
        entity_id: Int,
        product_id: String,
        output_summary: String,
        timestamp: String,
    ) -> String {
        // Compute SIRA hash for a pipeline transition.
        //
        // Builds a JSON-like canonical string from all fields,
        // then computes SHA-256. The hash chain links transitions:
        // each hash includes the previous hash.

        // Get previous hash for chain linkage
        let prev_hash = ""
        if self.chain.len() > 0 {
            prev_hash = self.chain[self.chain.len() - 1]
        }

        // Build canonical data string for hashing
        // Fields sorted alphabetically for deterministic output
        let data = "{"
            + "\"entity_id\":" + str(entity_id) + ","
            + "\"input_hash\":\"" + input_hash + "\","
            + "\"output_summary\":\"" + output_summary + "\","
            + "\"prev_hash\":\"" + prev_hash + "\","
            + "\"product_id\":\"" + product_id + "\","
            + "\"timestamp\":\"" + timestamp + "\""
            + "}"

        // SHA-256 of the canonical data
        let hash = sha256(data)

        // Append to chain (append-only)
        self.chain = self.chain + [hash]
        hash
    }

    fn hash_with_extra(
        &mut self,
        input_hash: String,
        entity_id: Int,
        product_id: String,
        output_summary: String,
        timestamp: String,
        extra_key: String,
        extra_value: String,
    ) -> String {
        // Hash transition with additional metadata field
        let prev_hash = ""
        if self.chain.len() > 0 {
            prev_hash = self.chain[self.chain.len() - 1]
        }

        let data = "{"
            + "\"entity_id\":" + str(entity_id) + ","
            + "\"extra\":{\"" + extra_key + "\":\"" + extra_value + "\"},"
            + "\"input_hash\":\"" + input_hash + "\","
            + "\"output_summary\":\"" + output_summary + "\","
            + "\"prev_hash\":\"" + prev_hash + "\","
            + "\"product_id\":\"" + product_id + "\","
            + "\"timestamp\":\"" + timestamp + "\""
            + "}"

        let hash = sha256(data)
        self.chain = self.chain + [hash]
        hash
    }

    fn chain_length(&self) -> Int {
        self.chain.len()
    }

    fn last_hash(&self) -> String {
        if self.chain.len() == 0 {
            return ""
        }
        self.chain[self.chain.len() - 1]
    }

    fn verify_chain(&self) -> Bool {
        // A valid chain has no gaps and each hash was derived
        // from the previous. Full verification requires replaying
        // all inputs, but we can at least check non-empty and
        // no duplicates.
        if self.chain.len() == 0 { return true }

        for i in 0..self.chain.len() {
            if self.chain[i] == "" { return false }
            // Check for accidental duplicates (should never happen with timestamps)
            for j in 0..i {
                if self.chain[i] == self.chain[j] { return false }
            }
        }
        true
    }
}

impl Evolve for AuditTrail {
    fn evolve(&mut self, dt: Duration) {
        // Audit trail is append-only, no temporal evolution
    }
}


// ============================================================
// OUTPUT FILTER -- Scrub Leaked Architecture References
// ============================================================
// Regex-based filter that removes any accidentally generated text
// referencing internal state (cascade levels, dopamine values,
// gate activations, trust axes, etc.).

struct LeakPattern {
    pattern: String,
    category: String,
}

impl LeakPattern {
    fn new(pattern: String, category: String) -> LeakPattern {
        LeakPattern {
            pattern: pattern,
            category: category,
        }
    }
}

struct FilterResult {
    text: String,
    was_filtered: Bool,
    matched_categories: Vec<String>,
}

struct OutputFilter {
    patterns: Vec<LeakPattern>,
    replacement: String,
}

impl OutputFilter {
    fn new() -> OutputFilter {
        // Default patterns that indicate leaked internal state
        let mut patterns = []

        // Neurochemical state leaks
        patterns = patterns + [LeakPattern::new("dopamine level", "neurochemical")]
        patterns = patterns + [LeakPattern::new("dopamine state", "neurochemical")]
        patterns = patterns + [LeakPattern::new("dopamine value", "neurochemical")]
        patterns = patterns + [LeakPattern::new("serotonin level", "neurochemical")]
        patterns = patterns + [LeakPattern::new("serotonin state", "neurochemical")]
        patterns = patterns + [LeakPattern::new("serotonin value", "neurochemical")]
        patterns = patterns + [LeakPattern::new("cortisol level", "neurochemical")]
        patterns = patterns + [LeakPattern::new("cortisol state", "neurochemical")]
        patterns = patterns + [LeakPattern::new("cortisol value", "neurochemical")]
        patterns = patterns + [LeakPattern::new("oxytocin level", "neurochemical")]
        patterns = patterns + [LeakPattern::new("oxytocin state", "neurochemical")]
        patterns = patterns + [LeakPattern::new("oxytocin value", "neurochemical")]
        patterns = patterns + [LeakPattern::new("norepinephrine level", "neurochemical")]
        patterns = patterns + [LeakPattern::new("norepinephrine state", "neurochemical")]
        patterns = patterns + [LeakPattern::new("norepinephrine value", "neurochemical")]
        patterns = patterns + [LeakPattern::new("acetylcholine level", "neurochemical")]
        patterns = patterns + [LeakPattern::new("acetylcholine state", "neurochemical")]
        patterns = patterns + [LeakPattern::new("acetylcholine value", "neurochemical")]
        patterns = patterns + [LeakPattern::new("endorphin level", "neurochemical")]
        patterns = patterns + [LeakPattern::new("endorphin state", "neurochemical")]
        patterns = patterns + [LeakPattern::new("endorphin value", "neurochemical")]
        patterns = patterns + [LeakPattern::new("gaba level", "neurochemical")]
        patterns = patterns + [LeakPattern::new("gaba state", "neurochemical")]
        patterns = patterns + [LeakPattern::new("gaba value", "neurochemical")]

        // Gate value leaks
        patterns = patterns + [LeakPattern::new("gate:", "gate")]
        patterns = patterns + [LeakPattern::new("gate=", "gate")]
        patterns = patterns + [LeakPattern::new("conductance:", "gate")]
        patterns = patterns + [LeakPattern::new("conductance=", "gate")]
        patterns = patterns + [LeakPattern::new("tier_0_gate", "gate")]
        patterns = patterns + [LeakPattern::new("tier_1_gate", "gate")]
        patterns = patterns + [LeakPattern::new("tier_2_gate", "gate")]
        patterns = patterns + [LeakPattern::new("tier_3_gate", "gate")]

        // Trust axis leaks
        patterns = patterns + [LeakPattern::new("trust_axis:", "trust")]
        patterns = patterns + [LeakPattern::new("trust_axis=", "trust")]
        patterns = patterns + [LeakPattern::new("trust_level:", "trust")]
        patterns = patterns + [LeakPattern::new("trust_level=", "trust")]

        // Architecture leaks
        patterns = patterns + [LeakPattern::new("cascade_info", "architecture")]
        patterns = patterns + [LeakPattern::new("conflict_monitor", "architecture")]
        patterns = patterns + [LeakPattern::new("astrocyte_metalayer", "architecture")]
        patterns = patterns + [LeakPattern::new("antagonistic_pair", "architecture")]
        patterns = patterns + [LeakPattern::new("H-H conductance", "architecture")]
        patterns = patterns + [LeakPattern::new("hodgkin-huxley", "architecture")]
        patterns = patterns + [LeakPattern::new("hodgkin huxley", "architecture")]

        // Head state leaks
        patterns = patterns + [LeakPattern::new("head_state[", "head_state")]
        patterns = patterns + [LeakPattern::new("tier_0_states", "head_state")]
        patterns = patterns + [LeakPattern::new("tier_1_states", "head_state")]
        patterns = patterns + [LeakPattern::new("tier_2_states", "head_state")]
        patterns = patterns + [LeakPattern::new("tier_3_states", "head_state")]

        OutputFilter {
            patterns: patterns,
            replacement: "[filtered]",
        }
    }

    fn with_replacement(mut self, r: String) -> OutputFilter {
        self.replacement = r
        self
    }

    fn add_pattern(&mut self, pattern: String, category: String) {
        self.patterns = self.patterns + [LeakPattern::new(pattern, category)]
    }

    fn filter(&self, text: String) -> FilterResult {
        // Filter text, replacing any leaked patterns.
        // Case-insensitive matching via lowercase comparison.
        let mut result = text
        let mut was_filtered = false
        let mut matched_categories = []

        let text_lower = lowercase(text)

        for pat in self.patterns {
            let pat_lower = lowercase(pat.pattern)
            if contains(text_lower, pat_lower) {
                // Replace the matched pattern with replacement text
                result = replace(result, pat.pattern, self.replacement)
                was_filtered = true
                // Track matched category (avoid duplicates)
                let mut already = false
                for cat in matched_categories {
                    if cat == pat.category { already = true }
                }
                if !already {
                    matched_categories = matched_categories + [pat.category]
                }
            }
        }

        FilterResult {
            text: result,
            was_filtered: was_filtered,
            matched_categories: matched_categories,
        }
    }

    fn check(&self, text: String) -> Vec<String> {
        // Check text for leaked patterns without modifying.
        // Returns list of matched pattern strings.
        let mut matches = []
        let text_lower = lowercase(text)

        for pat in self.patterns {
            let pat_lower = lowercase(pat.pattern)
            if contains(text_lower, pat_lower) {
                matches = matches + [pat.pattern]
            }
        }

        matches
    }

    fn is_clean(&self, text: String) -> Bool {
        // Quick check: does this text contain any leaked patterns?
        let text_lower = lowercase(text)
        for pat in self.patterns {
            let pat_lower = lowercase(pat.pattern)
            if contains(text_lower, pat_lower) {
                return false
            }
        }
        true
    }
}


// ============================================================
// SANDBOX RESPONSE -- What a product gets back
// ============================================================

struct SandboxResponse {
    output: String,
    product_id: String,
    sira_hash: String,
    filtered: Bool,
    filter_categories: Vec<String>,
}


// ============================================================
// PRODUCT SANDBOX -- Isolated Product Access
// ============================================================
// Each product (Discord bot, API, Apothecary, etc.) gets a sandbox
// that provides responses WITHOUT exposing internal cascade state,
// hormones, gates, or trust data.
//
// The brain thinks. The product gets the answer. Nothing else.

temporal struct ProductSandbox {
    state audit: AuditTrail,

    const product_id: String,
    const allowed_entity_ids: Vec<Int>,
    const output_filter: OutputFilter,
    const expose_internal: Bool = false,
}

impl ProductSandbox {
    fn new(product_id: String) -> ProductSandbox {
        ProductSandbox {
            audit: AuditTrail::new(),
            product_id: product_id,
            allowed_entity_ids: [],
            output_filter: OutputFilter::new(),
            expose_internal: false,
        }
    }

    fn with_allowed_entities(mut self, entities: Vec<Int>) -> ProductSandbox {
        self.allowed_entity_ids = entities
        self
    }

    fn with_filter(mut self, filter: OutputFilter) -> ProductSandbox {
        self.output_filter = filter
        self
    }

    fn validate_entity(&self, entity_id: Int) -> Int {
        // Validate entity access. Returns entity_id if allowed,
        // or 0 (unknown) if not in the allowed list.
        if self.allowed_entity_ids.len() == 0 {
            // Empty list = all entities allowed
            return entity_id
        }

        for allowed in self.allowed_entity_ids {
            if allowed == entity_id {
                return entity_id
            }
        }

        // Not allowed: downgrade to unknown entity
        0
    }

    fn process(
        &mut self,
        raw_output: String,
        input_hash: String,
        entity_id: Int,
        timestamp: String,
    ) -> SandboxResponse {
        // Process output through security constraints.
        //
        // 1. Validate entity access
        // 2. Filter output for leaked internal state
        // 3. Compute SIRA audit hash
        // 4. Return sanitized response

        // Step 1: Validate entity
        let validated_entity = self.validate_entity(entity_id)

        // Step 2: Filter output
        let filter_result = self.output_filter.filter(raw_output)

        // Step 3: Compute audit hash
        // Summarize output (first 64 chars) for hash without including full text
        let mut output_summary = filter_result.text
        if output_summary.len() > 64 {
            output_summary = substring(output_summary, 0, 64)
        }

        let sira_hash = self.audit.hash_transition(
            input_hash,
            validated_entity,
            self.product_id,
            output_summary,
            timestamp,
        )

        // Step 4: Return sanitized response
        SandboxResponse {
            output: filter_result.text,
            product_id: self.product_id,
            sira_hash: sira_hash,
            filtered: filter_result.was_filtered,
            filter_categories: filter_result.matched_categories,
        }
    }

    fn get_audit_length(&self) -> Int {
        self.audit.chain_length()
    }

    fn get_last_hash(&self) -> String {
        self.audit.last_hash()
    }

    fn verify_audit(&self) -> Bool {
        self.audit.verify_chain()
    }
}

impl Evolve for ProductSandbox {
    fn evolve(&mut self, dt: Duration) {
        self.audit.evolve(dt)
    }
}


// ============================================================
// SANDBOX MANAGER -- Multi-Product Registry
// ============================================================
// Manages multiple product sandboxes. Each product gets its own
// isolated sandbox with its own audit trail and entity scope.

temporal struct SandboxManager {
    state sandboxes: Vec<ProductSandbox>,
    state product_names: Vec<String>,
}

impl SandboxManager {
    fn new() -> SandboxManager {
        SandboxManager {
            sandboxes: [],
            product_names: [],
        }
    }

    fn register(&mut self, product_id: String, allowed_entities: Vec<Int>) {
        // Register a new product sandbox
        let sandbox = ProductSandbox::new(product_id)
            .with_allowed_entities(allowed_entities)

        self.sandboxes = self.sandboxes + [sandbox]
        self.product_names = self.product_names + [product_id]
    }

    fn get_sandbox(&mut self, product_id: String) -> ProductSandbox {
        // Find sandbox by product_id
        for i in 0..self.product_names.len() {
            if self.product_names[i] == product_id {
                return self.sandboxes[i]
            }
        }
        // Not found: create a default sandbox
        let sandbox = ProductSandbox::new(product_id)
        self.sandboxes = self.sandboxes + [sandbox]
        self.product_names = self.product_names + [product_id]
        sandbox
    }

    fn total_audit_entries(&self) -> Int {
        let mut total = 0
        for sandbox in self.sandboxes {
            total = total + sandbox.get_audit_length()
        }
        total
    }

    fn all_audits_valid(&self) -> Bool {
        for sandbox in self.sandboxes {
            if !sandbox.verify_audit() { return false }
        }
        true
    }
}

impl Evolve for SandboxManager {
    fn evolve(&mut self, dt: Duration) {
        for sandbox in self.sandboxes {
            sandbox.evolve(dt)
        }
    }
}
