///|
/// A finite top-level attribute domain. None removes the attribute; Some
/// uses MoonPolicy's typed value. Domains do not enumerate unknown values.
pub(all) struct ReleaseAttributeDomain {
  source : AttributeSource
  key : String
  values : Array[@moon_policy.AttributeValue?]
} derive(Debug)

///|
pub(all) struct ReleaseSamples {
  requests : Array[@moon_policy.Request]
  complete : Bool
  candidate_count : Int
  diagnostics : Array[String]
} derive(Debug)

///|
fn domain_source_name(source : AttributeSource) -> String {
  match source {
    PrincipalAttribute => "subject"
    ResourceAttribute => "resource"
    RequestAttribute => "context"
  }
}

///|
fn apply_release_attribute(
  request : @moon_policy.Request,
  domain : ReleaseAttributeDomain,
  value : @moon_policy.AttributeValue?,
) -> @moon_policy.Request {
  // Copy all top-level maps so applying another variant cannot mutate an
  // earlier request or the caller's seed. Nested values are never modified.
  let subject_attributes = request.subject_attributes.copy()
  let resource_attributes = request.resource_attributes.copy()
  let context = request.context.copy()
  let attributes = match domain.source {
    PrincipalAttribute => subject_attributes
    ResourceAttribute => resource_attributes
    RequestAttribute => context
  }
  match value {
    Some(item) => attributes.set(domain.key, item)
    None => attributes.remove(domain.key)
  }
  { ..request, subject_attributes, resource_attributes, context, }
}

///|
/// Generates the product of declared domains for each seed, preserving seed
/// and domain order. Overflow or invalid configuration returns no samples.
/// Exact duplicate requests are removed after generation.
pub fn generate_release_samples(
  seeds : Array[@moon_policy.Request],
  domains : Array[ReleaseAttributeDomain],
  limit : Int,
) -> ReleaseSamples {
  let diagnostics : Array[String] = []
  if limit < 1 || limit > 100000 {
    diagnostics.push("Sample limit must be between 1 and 100000")
  }
  if seeds.length() == 0 || seeds.length() > 100000 {
    diagnostics.push("Need 1..100000 seed requests")
  }
  if domains.length() > 32 {
    diagnostics.push("At most 32 attribute domains are supported")
  }
  let seen_domains : Map[(String, String), Bool] = Map([])
  let normalized : Array[ReleaseAttributeDomain] = []
  for domain in domains {
    let key = (domain_source_name(domain.source), domain.key)
    if domain.key == "" ||
      domain.key.contains(".") ||
      (domain.source != RequestAttribute && domain.key == "id") {
      diagnostics.push("Domain key must be a non-reserved top-level attribute")
    }
    if seen_domains.get(key) is Some(_) {
      diagnostics.push("Duplicate attribute domain")
    }
    seen_domains.set(key, true)
    if domain.values.length() == 0 || domain.values.length() > 64 {
      diagnostics.push("Each domain needs 1..64 explicit values")
    }
    let values : Array[@moon_policy.AttributeValue?] = []
    for value in domain.values {
      if !values.contains(value) {
        values.push(value)
      }
    }
    normalized.push({ ..domain, values, })
  }
  if diagnostics.length() > 0 {
    return { requests: [], complete: false, candidate_count: 0, diagnostics, }
  }
  let mut candidate_count = seeds.length()
  for domain in normalized {
    // Division before multiplication avoids integer overflow.
    if candidate_count > limit / domain.values.length() {
      diagnostics.push("Attribute product exceeds configured sample limit")
      return { requests: [], complete: false, candidate_count: 0, diagnostics, }
    }
    candidate_count *= domain.values.length()
  }
  if candidate_count > limit {
    diagnostics.push("Seed count exceeds configured sample limit")
    return { requests: [], complete: false, candidate_count: 0, diagnostics, }
  }
  let requests : Array[@moon_policy.Request] = []
  let seen_requests : Map[String, Bool] = Map([])
  for seed in seeds {
    let mut variants = [seed]
    for domain in normalized {
      let next : Array[@moon_policy.Request] = []
      for variant in variants {
        for value in domain.values {
          next.push(apply_release_attribute(variant, domain, value))
        }
      }
      variants = next
    }
    for request in variants {
      let key = release_request_key(request)
      if seen_requests.get(key) is None {
        seen_requests.set(key, true)
        requests.push(request)
      }
    }
  }
  { requests, complete: true, candidate_count, diagnostics, }
}