///|
/// A reusable bundle of declarative rules for a focused incident pattern.
///
/// Packs describe an analysis vocabulary without changing the canonical event
/// model. Callers can inspect the rules, present the metadata in a UI, or run
/// the pack through `analyze_with_pack`.
pub(all) struct AnalysisPack {
  pack_id : String
  title : String
  description : String
  rules : Array[CorrelationRule]
} derive(Debug, Eq, ToJson)

///|
/// Validates the metadata and rules of a reusable analysis pack.
pub fn validate_analysis_pack(
  pack : AnalysisPack,
) -> Unit raise CorrelationRuleError {
  if pack.pack_id.trim() == "" {
    raise CorrelationRuleError::InvalidRule(
      rule_id=pack.pack_id,
      reason="analysis pack id must not be empty",
    )
  }
  if pack.title.trim() == "" {
    raise CorrelationRuleError::InvalidRule(
      rule_id=pack.pack_id,
      reason="analysis pack title must not be empty",
    )
  }
  if pack.description.trim() == "" {
    raise CorrelationRuleError::InvalidRule(
      rule_id=pack.pack_id,
      reason="analysis pack description must not be empty",
    )
  }
  validate_correlation_rules(pack.rules)
}

///|
/// Runs a reusable pack while preserving the graph builder's deterministic
/// ordering, evidence references, and diagnostics.
pub fn analyze_with_pack(
  events : Array[CanonicalEvent],
  pack : AnalysisPack,
) -> IncidentGraph raise CorrelationRuleError {
  validate_analysis_pack(pack)
  build_incident_graph(events, pack.rules)
}

///|
fn pack_pattern(category : EventCategory, event_type : String) -> EventPattern {
  {
    category: Some(category),
    event_type: Some(event_type),
    action: None,
    outcome: None,
    severity: None,
    source_system: None,
    source_component: None,
    resource_kind: None,
  }
}

///|
fn pack_rule(
  rule_id : String,
  from_category : EventCategory,
  from_type : String,
  to_category : EventCategory,
  to_type : String,
  within_seconds : Int,
  explanation : String,
) -> CorrelationRule {
  {
    rule_id,
    from: pack_pattern(from_category, from_type),
    to: pack_pattern(to_category, to_type),
    within_seconds,
    same_resource: true,
    relation_type: RelationType::MatchedRule,
    explanation,
  }
}

///|
/// Rules for changes followed by health or process failures.
pub fn config_analysis_pack() -> AnalysisPack {
  {
    pack_id: "config",
    title: "Configuration change analysis",
    description: "Relates a configuration update to a nearby health failure or process crash on the same resource.",
    rules: [
      pack_rule(
        "config-change-then-health-failure",
        EventCategory::Change,
        "config.update",
        EventCategory::Availability,
        "health.failed",
        300,
        "A health failure followed a configuration update within five minutes on the same resource.",
      ),
      pack_rule(
        "config-change-then-process-crash",
        EventCategory::Change,
        "config.update",
        EventCategory::Process,
        "process.exit",
        300,
        "A process exit followed a configuration update within five minutes on the same resource.",
      ),
    ],
  }
}

///|
/// Rules for process crashes followed by service failure or recovery.
pub fn crash_analysis_pack() -> AnalysisPack {
  {
    pack_id: "crash",
    title: "Process crash analysis",
    description: "Relates a process exit to a nearby service failure or recovery on the same resource.",
    rules: [
      pack_rule(
        "crash-then-health-failure",
        EventCategory::Process,
        "process.exit",
        EventCategory::Availability,
        "health.failed",
        120,
        "A health failure followed a process exit within two minutes on the same resource.",
      ),
      pack_rule(
        "crash-then-recovery",
        EventCategory::Process,
        "process.exit",
        EventCategory::Availability,
        "health.recovered",
        600,
        "A health recovery followed a process exit within ten minutes on the same resource.",
      ),
    ],
  }
}

///|
/// Rules for latency observations followed by timeout or request errors.
pub fn latency_analysis_pack() -> AnalysisPack {
  {
    pack_id: "latency",
    title: "Request latency analysis",
    description: "Relates a latency observation to a nearby timeout or request error on the same resource.",
    rules: [
      pack_rule(
        "latency-then-timeout",
        EventCategory::Performance,
        "request.latency",
        EventCategory::Availability,
        "request.timeout",
        60,
        "A request timeout followed a latency observation within one minute on the same resource.",
      ),
      pack_rule(
        "latency-then-error",
        EventCategory::Performance,
        "request.latency",
        EventCategory::Availability,
        "request.error",
        60,
        "A request error followed a latency observation within one minute on the same resource.",
      ),
    ],
  }
}

///|
/// Returns the built-in packs in stable display order.
pub fn built_in_analysis_packs() -> Array[AnalysisPack] {
  [config_analysis_pack(), crash_analysis_pack(), latency_analysis_pack()]
}