///|
/// Minimum-cell and privacy policy for released aggregates.
pub struct PrivacyPolicy {
  minimum_group_size : Int
  maximum_group_count : Int
  noise_scale : Double
  suppress_small_cells : Bool
  seed : UInt64
}

///|
/// Privacy-safe aggregate output.
pub struct SafeAggregate {
  key : Int
  count : Int
  released_value : Double
  suppressed : Bool
  noisy : Bool
}

///|
/// K-anonymity and cell-size audit.
pub struct KAnonymityAudit {
  groups : Int
  small_groups : Int
  minimum_count : Int
  suppressed_fraction : Double
  passes : Bool
}

///|
/// Creates a privacy policy with validated limits.
pub fn privacy_policy(
  minimum_group_size? : Int = 5,
  maximum_group_count? : Int = 10000,
  noise_scale? : Double = 0.0,
  suppress_small_cells? : Bool = true,
  seed? : UInt64 = 20260819,
) -> PrivacyPolicy {
  {
    minimum_group_size: minimum_group_size.max(1),
    maximum_group_count: maximum_group_count.max(1),
    noise_scale: noise_scale.max(0.0),
    suppress_small_cells,
    seed,
  }
}

///|
/// Applies count suppression to grouped numeric values.
pub fn suppress_small_groups(
  keys : Array[Int],
  counts : Array[Int],
  values : Array[Double],
  policy : PrivacyPolicy,
) -> Array[SafeAggregate] {
  let n = keys.length().min(counts.length()).min(values.length())
  let result : Array[SafeAggregate] = Array::new(
    capacity=n.min(policy.maximum_group_count),
  )
  let rng = RandomState::new(policy.seed)
  for i in 0.. 0.0,
    })
  }
  result
}

///|
fn privacy_laplace(uniform : Double) -> Double {
  let centered = clamp(uniform, 1.0e-12, 1.0 - 1.0e-12) - 0.5
  if centered >= 0.0 {
    -@math.ln(1.0 - 2.0 * centered)
  } else {
    @math.ln(1.0 + 2.0 * centered)
  }
}

///|
/// Audits group sizes against a k-anonymity threshold.
pub fn audit_k_anonymity(
  counts : Array[Int],
  policy : PrivacyPolicy,
) -> KAnonymityAudit {
  let mut small = 0
  let mut minimum = if counts.length() == 0 { 0 } else { counts[0] }
  for count in counts {
    if count < policy.minimum_group_size {
      small += 1
    }
    if count < minimum {
      minimum = count
    }
  }
  {
    groups: counts.length(),
    small_groups: small,
    minimum_count: minimum,
    suppressed_fraction: if counts.length() == 0 {
      0.0
    } else {
      small.to_double() / counts.length().to_double()
    },
    passes: small == 0 && counts.length() <= policy.maximum_group_count,
  }
}

///|
/// Caps an aggregate contribution before release.
pub fn cap_contribution(
  value : Double,
  lower : Double,
  upper : Double,
) -> Double {
  clamp(value, lower.min(upper), upper.max(lower))
}

///|
/// Applies contribution caps to a numeric vector.
pub fn cap_contributions(
  values : Array[Double],
  lower : Double,
  upper : Double,
) -> Array[Double] {
  let result = Array::new(capacity=values.length())
  for value in values {
    result.push(cap_contribution(value, lower, upper))
  }
  result
}

///|
/// Computes a bounded mean for privacy-safe release.
pub fn bounded_mean(
  values : Array[Double],
  lower : Double,
  upper : Double,
) -> Double {
  mean_or(cap_contributions(values, lower, upper), 0.0)
}

///|
/// Computes the maximum single-row influence on a bounded mean.
pub fn bounded_mean_sensitivity(
  sample_size : Int,
  lower : Double,
  upper : Double,
) -> Double {
  if sample_size <= 0 {
    0.0
  } else {
    (upper - lower).abs() / sample_size.to_double()
  }
}

///|
/// Returns a compact privacy audit vector.
pub fn privacy_summary(
  audit : KAnonymityAudit,
  policy : PrivacyPolicy,
) -> Array[Double] {
  [
    audit.groups.to_double(),
    audit.small_groups.to_double(),
    audit.minimum_count.to_double(),
    audit.suppressed_fraction,
    policy.minimum_group_size.to_double(),
    policy.noise_scale,
    if audit.passes {
      1.0
    } else {
      0.0
    },
  ]
}