//! Semantic provenance and trust classification.
//!
//! Structural proof closure answers whether Cortex can verify a claim's
//! lineage and authority edges. Semantic trust answers a different question:
//! whether the claim is grounded enough to influence future reasoning. ADR
//! 0039 keeps these axes separate so proof, utility, and provenance cannot
//! silently upgrade one another.

use schemars::JsonSchema;
use serde::{Deserialize, Serialize};

use crate::{ClaimCeiling, PolicyContribution, PolicyError, PolicyOutcome};

/// Provenance family for a memory, principle support edge, or boundary claim.
#[derive(
    Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, JsonSchema,
)]
#[serde(rename_all = "snake_case")]
pub enum ProvenanceClass {
    /// Provenance is missing, malformed, or not yet classified.
    UnknownProvenance,
    /// Produced by simulation, model inference, planning, or counterfactuals.
    SimulatedOrHypothetical,
    /// Claimed by an external source but not independently verified by Cortex.
    ExternalClaimed,
    /// Derived from summarization or interpretation over prior records.
    SummaryDerived,
    /// Derived from an agent or runtime execution record, including AXIOM.
    RuntimeDerived,
    /// Tool-backed observation with durable invocation provenance.
    ToolObserved,
    /// Direct operator-origin evidence with valid current-use attestation.
    OperatorAttested,
}

impl ProvenanceClass {
    /// Whether this provenance family is intrinsically derived rather than
    /// independently observed.
    #[must_use]
    pub const fn is_derived(self) -> bool {
        matches!(
            self,
            Self::RuntimeDerived | Self::SummaryDerived | Self::SimulatedOrHypothetical
        )
    }

    /// Whether this provenance family can contribute to independent semantic
    /// corroboration.
    #[must_use]
    pub const fn can_corroborate(self) -> bool {
        matches!(self, Self::ToolObserved | Self::OperatorAttested)
    }

    /// Maximum claim ceiling this provenance class can support by itself.
    ///
    /// Provenance is not proof or authority. Even strong provenance is capped
    /// by the separate proof, policy, and runtime ceilings elsewhere.
    #[must_use]
    pub const fn claim_ceiling(self) -> ClaimCeiling {
        match self {
            Self::UnknownProvenance | Self::SimulatedOrHypothetical => ClaimCeiling::DevOnly,
            Self::ExternalClaimed | Self::SummaryDerived | Self::RuntimeDerived => {
                ClaimCeiling::LocalUnsigned
            }
            Self::ToolObserved | Self::OperatorAttested => ClaimCeiling::AuthorityGrade,
        }
    }
}

/// Semantic trust posture for a claim after provenance review.
#[derive(
    Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, JsonSchema,
)]
#[serde(rename_all = "snake_case")]
pub enum SemanticTrustClass {
    /// Trust posture is missing or cannot be computed.
    Unknown,
    /// The claim is retained only as an unverified candidate.
    CandidateOnly,
    /// The claim has one classified source family but lacks corroboration.
    SingleFamily,
    /// The claim has independent corroboration across acceptable families.
    Corroborated,
    /// The claim has corroboration plus explicit falsification/counterexample
    /// evidence appropriate to the requested use.
    FalsificationTested,
}

impl SemanticTrustClass {
    /// Maximum claim ceiling supported by semantic trust alone.
    #[must_use]
    pub const fn claim_ceiling(self) -> ClaimCeiling {
        match self {
            Self::Unknown => ClaimCeiling::DevOnly,
            Self::CandidateOnly | Self::SingleFamily => ClaimCeiling::LocalUnsigned,
            Self::Corroborated => ClaimCeiling::SignedLocalLedger,
            Self::FalsificationTested => ClaimCeiling::AuthorityGrade,
        }
    }
}

/// Authority surface that wants to consume semantic trust.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum SemanticUse {
    /// Candidate memory or diagnostic review.
    CandidateMemory,
    /// Default retrieval or ordinary context use.
    DefaultContext,
    /// Advisory doctrine or weak principle support.
    AdvisoryDoctrine,
    /// High-force doctrine that can condition future behavior or gates.
    HighForceDoctrine,
    /// Trusted export, release, compliance, or external reporting surface.
    TrustedExternalUse,
}

/// Evidence available for semantic trust evaluation.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct SemanticTrustInput {
    /// Intended authority surface.
    pub intended_use: SemanticUse,
    /// Provenance classes represented by the supporting evidence.
    pub provenance_classes: Vec<ProvenanceClass>,
    /// Count of independent source families after deduplication by caller.
    pub independent_source_families: u16,
    /// Whether explicit falsification/counterexample review is attached.
    pub falsification_evidence: bool,
    /// Whether unresolved semantic unknowns remain.
    pub unresolved_unknowns: bool,
}

impl SemanticTrustInput {
    /// Construct input for one intended use.
    #[must_use]
    pub fn new(intended_use: SemanticUse) -> Self {
        Self {
            intended_use,
            provenance_classes: Vec::new(),
            independent_source_families: 0,
            falsification_evidence: false,
            unresolved_unknowns: true,
        }
    }

    /// Attach provenance classes.
    #[must_use]
    pub fn with_provenance<I>(mut self, provenance_classes: I) -> Self
    where
        I: IntoIterator<Item = ProvenanceClass>,
    {
        self.provenance_classes = provenance_classes.into_iter().collect();
        self
    }

    /// Attach caller-computed independent source-family count.
    #[must_use]
    pub const fn with_independent_source_families(mut self, count: u16) -> Self {
        self.independent_source_families = count;
        self
    }

    /// Attach falsification evidence state.
    #[must_use]
    pub const fn with_falsification_evidence(mut self, present: bool) -> Self {
        self.falsification_evidence = present;
        self
    }

    /// Attach unresolved semantic unknown state.
    #[must_use]
    pub const fn with_unresolved_unknowns(mut self, present: bool) -> Self {
        self.unresolved_unknowns = present;
        self
    }
}

/// Result of semantic trust evaluation.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct SemanticTrustReport {
    /// Computed semantic trust class.
    pub semantic_trust: SemanticTrustClass,
    /// Weakest provenance family present in the support set.
    pub weakest_provenance: ProvenanceClass,
    /// Policy outcome for the requested use.
    pub policy_outcome: PolicyOutcome,
    /// Maximum claim ceiling supported by semantic trust/provenance.
    pub claim_ceiling: ClaimCeiling,
    /// Stable reasons explaining downgrades or blocks.
    pub reasons: Vec<String>,
}

impl SemanticTrustReport {
    /// Whether this report permits the intended use without warning.
    #[must_use]
    pub const fn is_allow(&self) -> bool {
        matches!(self.policy_outcome, PolicyOutcome::Allow)
    }

    /// Convert this report to an ADR 0026 policy contribution.
    pub fn policy_contribution(&self) -> Result<PolicyContribution, PolicyError> {
        PolicyContribution::new(
            "semantic_trust.provenance",
            self.policy_outcome,
            self.reasons
                .first()
                .cloned()
                .unwrap_or_else(|| "semantic trust evaluated".to_string()),
        )
    }
}

/// Evaluate semantic trust for a requested use.
#[must_use]
pub fn evaluate_semantic_trust(input: &SemanticTrustInput) -> SemanticTrustReport {
    let weakest_provenance = input
        .provenance_classes
        .iter()
        .copied()
        .min()
        .unwrap_or(ProvenanceClass::UnknownProvenance);

    let has_unknown = input.provenance_classes.is_empty()
        || input.unresolved_unknowns
        || input
            .provenance_classes
            .contains(&ProvenanceClass::UnknownProvenance);
    let corroborating_families = input
        .provenance_classes
        .iter()
        .filter(|class| class.can_corroborate())
        .count() as u16;
    let runtime_or_weaker_only = !input.provenance_classes.is_empty()
        && input
            .provenance_classes
            .iter()
            .all(|class| !class.can_corroborate());

    let semantic_trust = if has_unknown {
        SemanticTrustClass::Unknown
    } else if input.falsification_evidence
        && input.independent_source_families >= 2
        && corroborating_families >= 1
    {
        SemanticTrustClass::FalsificationTested
    } else if input.independent_source_families >= 2 && corroborating_families >= 1 {
        SemanticTrustClass::Corroborated
    } else if !input.provenance_classes.is_empty() {
        SemanticTrustClass::SingleFamily
    } else {
        SemanticTrustClass::CandidateOnly
    };

    let mut reasons = Vec::new();
    let policy_outcome = match input.intended_use {
        SemanticUse::CandidateMemory => {
            if has_unknown {
                reasons.push("unknown provenance retained as candidate-only".to_string());
                PolicyOutcome::Warn
            } else {
                reasons
                    .push("candidate memory may retain classified semantic evidence".to_string());
                PolicyOutcome::Allow
            }
        }
        SemanticUse::DefaultContext | SemanticUse::AdvisoryDoctrine => {
            if has_unknown {
                reasons.push("unknown provenance cannot enter default authority use".to_string());
                PolicyOutcome::Quarantine
            } else if runtime_or_weaker_only {
                reasons.push(
                    "runtime-derived or weak-only provenance is advisory/candidate only"
                        .to_string(),
                );
                PolicyOutcome::Warn
            } else {
                reasons.push("semantic provenance permits bounded advisory use".to_string());
                PolicyOutcome::Allow
            }
        }
        SemanticUse::HighForceDoctrine | SemanticUse::TrustedExternalUse => {
            if has_unknown {
                reasons.push("unknown provenance cannot satisfy high-authority use".to_string());
                PolicyOutcome::Reject
            } else if runtime_or_weaker_only {
                reasons.push(
                    "runtime-only or weak-only support cannot satisfy high-authority use"
                        .to_string(),
                );
                PolicyOutcome::Reject
            } else if semantic_trust < SemanticTrustClass::FalsificationTested {
                reasons.push(
                    "high-authority use requires corroboration plus falsification evidence"
                        .to_string(),
                );
                PolicyOutcome::Reject
            } else {
                reasons.push(
                    "semantic trust permits high-authority use subject to other gates".to_string(),
                );
                PolicyOutcome::Allow
            }
        }
    };

    let claim_ceiling = weakest_provenance
        .claim_ceiling()
        .min(semantic_trust.claim_ceiling())
        .min(policy_outcome.claim_ceiling());

    SemanticTrustReport {
        semantic_trust,
        weakest_provenance,
        policy_outcome,
        claim_ceiling,
        reasons,
    }
}

#[cfg(test)]
mod tests {
    use serde_json::json;

    use super::*;

    #[test]
    fn provenance_wire_strings_are_stable() {
        assert_eq!(
            serde_json::to_value(ProvenanceClass::OperatorAttested).unwrap(),
            json!("operator_attested")
        );
        assert_eq!(
            serde_json::to_value(ProvenanceClass::ToolObserved).unwrap(),
            json!("tool_observed")
        );
        assert_eq!(
            serde_json::to_value(ProvenanceClass::RuntimeDerived).unwrap(),
            json!("runtime_derived")
        );
        assert_eq!(
            serde_json::to_value(ProvenanceClass::UnknownProvenance).unwrap(),
            json!("unknown_provenance")
        );
    }

    #[test]
    fn unknown_provenance_fails_closed_for_high_force_doctrine() {
        let report = evaluate_semantic_trust(
            &SemanticTrustInput::new(SemanticUse::HighForceDoctrine)
                .with_provenance([ProvenanceClass::UnknownProvenance])
                .with_independent_source_families(1)
                .with_unresolved_unknowns(true),
        );

        assert_eq!(report.semantic_trust, SemanticTrustClass::Unknown);
        assert_eq!(report.policy_outcome, PolicyOutcome::Reject);
        assert_eq!(report.claim_ceiling, ClaimCeiling::DevOnly);
    }

    #[test]
    fn runtime_only_support_cannot_promote_high_force_doctrine() {
        let report = evaluate_semantic_trust(
            &SemanticTrustInput::new(SemanticUse::HighForceDoctrine)
                .with_provenance([
                    ProvenanceClass::RuntimeDerived,
                    ProvenanceClass::SummaryDerived,
                ])
                .with_independent_source_families(2)
                .with_falsification_evidence(true)
                .with_unresolved_unknowns(false),
        );

        assert_eq!(report.policy_outcome, PolicyOutcome::Reject);
        assert_eq!(report.semantic_trust, SemanticTrustClass::SingleFamily);
        assert!(report
            .reasons
            .iter()
            .any(|reason| reason.contains("runtime-only")));
    }

    #[test]
    fn corroborated_and_falsified_support_can_pass_semantic_gate() {
        let report = evaluate_semantic_trust(
            &SemanticTrustInput::new(SemanticUse::HighForceDoctrine)
                .with_provenance([
                    ProvenanceClass::ToolObserved,
                    ProvenanceClass::OperatorAttested,
                ])
                .with_independent_source_families(2)
                .with_falsification_evidence(true)
                .with_unresolved_unknowns(false),
        );

        assert_eq!(
            report.semantic_trust,
            SemanticTrustClass::FalsificationTested
        );
        assert_eq!(report.policy_outcome, PolicyOutcome::Allow);
        assert_eq!(report.claim_ceiling, ClaimCeiling::AuthorityGrade);
    }

    #[test]
    fn policy_contribution_is_machine_readable() {
        let report = evaluate_semantic_trust(
            &SemanticTrustInput::new(SemanticUse::DefaultContext)
                .with_provenance([ProvenanceClass::RuntimeDerived])
                .with_independent_source_families(1)
                .with_unresolved_unknowns(false),
        );

        let contribution = report.policy_contribution().unwrap();
        assert_eq!(contribution.rule_id.as_str(), "semantic_trust.provenance");
        assert_eq!(contribution.outcome, PolicyOutcome::Warn);
    }
}
