///|
pub(all) struct Claim {
  subject : ResolvedRef
  proposition : String
  author : Id
  scope : @ion.IonValue
  context : RevisionContext
}

///|
pub(all) struct Producer {
  id : Id
  version : Revision
  profile : Id
}

///|
pub(all) enum ReviewDecision {
  Accepted
  Rejected
}

///|
pub(all) enum AbsentReason {
  NotInvoked
  ExecutionError
  MissingResult
}

///|
pub(all) enum RunOutput {
  Observed(@ion.IonValue)
  Absent(AbsentReason)
}

///|
pub(all) enum ClauseObservation {
  Satisfied
  Violated
  NotApplicable
  InvalidArgument
  Unsupported
  Error
  InconclusiveObservation
}

///|
pub(all) struct ClauseResult {
  entity : Snapshot
  observation : ClauseObservation
}

///|
pub struct RunLimits {
  steps : Int32
  depth : Int32
}

///|
pub fn RunLimits::new(
  steps : Int32,
  depth : Int32,
) -> RunLimits raise InvalidInput {
  if steps <= 0 || depth <= 0 {
    raise InvalidInput("Run limits must be positive")
  }
  { steps, depth, }
}

///|
pub(all) struct RunObservation {
  value_profile : Id
  input : @ion.IonValue
  output : RunOutput
  clauses : Array[ClauseResult]
  evaluator : Producer
  target : String
  configuration : @ion.IonValue
  limits : RunLimits
  generated_case : @ion.IonValue?
  type_arguments : @ion.IonValue?
}

///|
pub(all) enum Observation {
  Run(RunObservation)
  Analysis(Producer, @ion.IonValue, @ion.IonValue)
  Review(Id, ReviewDecision, String)
}

///|
pub(all) struct Evidence {
  origin : String
  context : RevisionContext
  observation : Observation
}

///|
#derive(Eq, Debug)
pub(all) enum Polarity {
  Supporting
  Contradicting
}

///|
pub extend Polarity with Eq::{equal, not_equal}

///|
pub extend Polarity with @debug.Debug::{to_repr}

///|
#derive(Eq, Debug)
pub(all) enum EvidenceAvailability {
  Unknown
  Present
  InvalidClaim
}

///|
pub extend EvidenceAvailability with Eq::{equal, not_equal}

///|
pub extend EvidenceAvailability with @debug.Debug::{to_repr}

///|
pub(all) struct EvidenceAssessment {
  relationship_model : Snapshot
  relationship : Id
  reference : ResolvedRef
  polarity : Polarity
  evidence : Evidence
  freshness : Freshness
}

///|
pub(all) struct ClaimAssessment {
  claim : ResolvedRef
  claim_freshness : Freshness
  availability : EvidenceAvailability
  observations : Array[EvidenceAssessment]
  inspection : Inspection
}

///|
/// Currency is an exact revision comparison, never a proof. An observation's
/// payload and kind survive even when stale, inconclusive or contradicting.
pub fn assess_claim(
  model : Model,
  reference : ResolvedRef,
  requested : RevisionContext,
) -> ClaimAssessment {
  let inspection = inspect(model)
  let invalid : ClaimAssessment = {
    claim: reference,
    claim_freshness: Inconclusive,
    availability: InvalidClaim,
    observations: [],
    inspection,
  }
  if inspection.has_errors() {
    return invalid
  }
  let entity = match resolve(model, reference) {
    Ok(e) => e
    Err(_) => return invalid
  }
  guard entity.content is Claim(claim) else { return invalid }
  guard find_model(model, reference.model) is Some(claim_owner) else {
    return invalid
  }
  let required = required_context(model, claim_owner, claim, entity.source_refs)
  let observations : Array[EvidenceAssessment] = []
  for (link_owner, _) in model_closure(model).0 {
    for link in usable_relationships(link_owner) {
      if (link.predicate == Supports || link.predicate == Contradicts) &&
        link.target is Resolved(target) &&
        target == reference {
        if resolve(model, link.subject) is Ok(evidence_entity) &&
          evidence_entity.content is Evidence(evidence) {
          observations.push({
            relationship_model: link_owner.identity,
            relationship: link.id,
            reference: link.subject,
            polarity: if link.predicate == Supports {
              Supporting
            } else {
              Contradicting
            },
            evidence,
            freshness: compare_context(
              evidence.context,
              requested,
              observation_context(
                model, required, link_owner, link, evidence_entity, evidence,
              ),
            ),
          })
        }
      }
    }
  }
  {
    claim: reference,
    claim_freshness: compare_context(claim.context, requested, required),
    availability: if observations.is_empty() {
      Unknown
    } else {
      Present
    },
    observations,
    inspection,
  }
}