///|
/// Utility rule for turning causal estimates into actions.
pub struct DecisionRule {
  treatment_cost : Double
  treatment_benefit : Double
  harm_cost : Double
  minimum_net_benefit : Double
  threshold : Double
}

///|
/// Decision audit for a treatment policy.
pub struct DecisionAudit {
  selected : Int
  total : Int
  selected_fraction : Double
  expected_net_benefit : Double
  false_positive_cost : Double
  false_negative_cost : Double
  passes : Bool
}

///|
/// Policy decision for one unit.
pub struct UnitDecision {
  index : Int
  effect : Double
  net_benefit : Double
  treat : Bool
  confidence : Double
}

///|
/// Creates a decision rule from utility parameters.
pub fn decision_rule(
  treatment_cost : Double,
  treatment_benefit : Double,
  harm_cost? : Double = 0.0,
  minimum_net_benefit? : Double = 0.0,
) -> DecisionRule {
  let threshold = if treatment_benefit + harm_cost == 0.0 {
    1.0e300
  } else {
    (treatment_cost + harm_cost) / (treatment_benefit + harm_cost)
  }
  {
    treatment_cost,
    treatment_benefit,
    harm_cost,
    minimum_net_benefit,
    threshold,
  }
}

///|
/// Converts an effect estimate into expected net benefit.
pub fn expected_net_benefit(effect : Double, rule : DecisionRule) -> Double {
  let harm = if effect < 0.0 { rule.harm_cost * effect.abs() } else { 0.0 }
  effect * rule.treatment_benefit - rule.treatment_cost - harm
}

///|
/// Decides treatment from an estimated individual effect.
pub fn decide_unit(
  index : Int,
  effect : Double,
  standard_error : Double,
  rule : DecisionRule,
) -> UnitDecision {
  let net_benefit = expected_net_benefit(effect, rule)
  let treat = net_benefit >= rule.minimum_net_benefit
  let confidence = if standard_error == 0.0 {
    1.0
  } else {
    clamp(effect.abs() / standard_error.abs() / 3.0, 0.0, 1.0)
  }
  { index, effect, net_benefit, treat, confidence }
}

///|
/// Applies a decision rule to a vector of effects.
pub fn decide_policy(
  effects : Array[Double],
  standard_errors : Array[Double],
  rule : DecisionRule,
) -> Array[UnitDecision] {
  let result : Array[UnitDecision] = Array::new(capacity=effects.length())
  for i in 0.. DecisionAudit {
  let n = decisions.length().min(treatment.length()).min(outcomes.length())
  let mut selected = 0
  let mut net_benefit = 0.0
  let mut false_positive = 0.0
  let mut false_negative = 0.0
  for i in 0.. 0.0 {
      false_negative += outcomes[i]
    }
  }
  {
    selected,
    total: n,
    selected_fraction: if n == 0 {
      0.0
    } else {
      selected.to_double() / n.to_double()
    },
    expected_net_benefit: if n == 0 {
      0.0
    } else {
      net_benefit / n.to_double()
    },
    false_positive_cost: false_positive,
    false_negative_cost: false_negative,
    passes: n > 0 &&
    net_benefit >= rule.minimum_net_benefit * selected.to_double(),
  }
}

///|
/// Computes net benefit for a range of treatment thresholds.
pub fn decision_curve(
  effects : Array[Double],
  rule : DecisionRule,
  thresholds : Array[Double],
) -> Array[Array[Double]] {
  let result : Array[Array[Double]] = Array::new(capacity=thresholds.length())
  for threshold in thresholds {
    let mut selected = 0
    let mut value = 0.0
    for effect in effects {
      if effect >= threshold {
        selected += 1
        value += expected_net_benefit(effect, rule)
      }
    }
    result.push([
      threshold,
      selected.to_double(),
      if effects.length() == 0 {
        0.0
      } else {
        value / effects.length().to_double()
      },
    ])
  }
  result
}

///|
/// Returns a compact decision summary vector.
pub fn decision_summary(audit : DecisionAudit) -> Array[Double] {
  [
    audit.selected.to_double(),
    audit.total.to_double(),
    audit.selected_fraction,
    audit.expected_net_benefit,
    audit.false_positive_cost,
    audit.false_negative_cost,
    if audit.passes {
      1.0
    } else {
      0.0
    },
  ]
}