///|
/// A documented estimand definition for an analysis registry.
pub struct EstimandDefinition {
  name : String
  description : String
  treatment : String
  outcome : String
  population : String
  identification : String
  required_diagnostics : Array[String]
  version : String
}

///|
/// Registry of named estimands used by a project.
pub struct EstimandRegistry {
  definitions : Array[EstimandDefinition]
  active : String
  version : String
}

///|
/// Validation result for an estimand registry.
pub struct EstimandAudit {
  registered : Int
  duplicate_names : Int
  missing_fields : Int
  active_found : Bool
  passes : Bool
}

///|
/// Creates a named estimand definition.
pub fn estimand_definition(
  name : String,
  description : String,
  treatment : String,
  outcome : String,
  population? : String = "all",
  identification? : String = "backdoor",
  required_diagnostics? : Array[String] = [],
  version? : String = "1",
) -> EstimandDefinition {
  {
    name,
    description,
    treatment,
    outcome,
    population,
    identification,
    required_diagnostics,
    version,
  }
}

///|
/// Creates an empty registry.
pub fn estimand_registry(version? : String = "1") -> EstimandRegistry {
  { definitions: [], active: "", version }
}

///|
/// Registers or replaces an estimand by name.
pub fn register_estimand(
  registry : EstimandRegistry,
  definition : EstimandDefinition,
  activate? : Bool = false,
) -> EstimandRegistry {
  let definitions = registry.definitions.copy()
  let mut found = -1
  for i in 0..= 0 {
    definitions[found] = definition
  } else {
    definitions.push(definition)
  }
  {
    definitions,
    active: if activate || registry.active == "" {
      definition.name
    } else {
      registry.active
    },
    version: registry.version,
  }
}

///|
/// Looks up an estimand by name.
pub fn find_estimand(
  registry : EstimandRegistry,
  name : String,
) -> EstimandDefinition? {
  for definition in registry.definitions {
    if definition.name == name {
      return Some(definition)
    }
  }
  None
}

///|
/// Sets the active estimand when the name exists.
pub fn activate_estimand(
  registry : EstimandRegistry,
  name : String,
) -> EstimandRegistry {
  if find_estimand(registry, name) is Some(_) {
    {
      definitions: registry.definitions,
      active: name,
      version: registry.version,
    }
  } else {
    registry
  }
}

///|
/// Counts duplicate estimand names.
pub fn duplicate_estimand_names(registry : EstimandRegistry) -> Int {
  let mut duplicates = 0
  for i in 0.. EstimandAudit {
  let mut missing = 0
  for definition in registry.definitions {
    if definition.name == "" {
      missing += 1
    }
    if definition.treatment == "" {
      missing += 1
    }
    if definition.outcome == "" {
      missing += 1
    }
    if definition.identification == "" {
      missing += 1
    }
  }
  let active_found = registry.active == "" ||
    find_estimand(registry, registry.active) is Some(_)
  let duplicates = duplicate_estimand_names(registry)
  {
    registered: registry.definitions.length(),
    duplicate_names: duplicates,
    missing_fields: missing,
    active_found,
    passes: duplicates == 0 &&
    missing == 0 &&
    active_found &&
    registry.definitions.length() > 0,
  }
}

///|
/// Returns the active estimand, if configured.
pub fn active_estimand(registry : EstimandRegistry) -> EstimandDefinition? {
  if registry.active == "" {
    None
  } else {
    find_estimand(registry, registry.active)
  }
}

///|
/// Returns all registry names in declaration order.
pub fn estimand_names(registry : EstimandRegistry) -> Array[String] {
  let result : Array[String] = Array::new(
    capacity=registry.definitions.length(),
  )
  for definition in registry.definitions {
    result.push(definition.name)
  }
  result
}

///|
/// Checks whether a required diagnostic is registered for an estimand.
pub fn requires_diagnostic(
  definition : EstimandDefinition,
  diagnostic : String,
) -> Bool {
  definition.required_diagnostics.contains(diagnostic)
}

///|
/// Adds a required diagnostic without duplicating it.
pub fn add_required_diagnostic(
  definition : EstimandDefinition,
  diagnostic : String,
) -> EstimandDefinition {
  let diagnostics = definition.required_diagnostics.copy()
  if !diagnostics.contains(diagnostic) {
    diagnostics.push(diagnostic)
  }
  {
    name: definition.name,
    description: definition.description,
    treatment: definition.treatment,
    outcome: definition.outcome,
    population: definition.population,
    identification: definition.identification,
    required_diagnostics: diagnostics,
    version: definition.version,
  }
}

///|
/// Computes a registry fingerprint from definition metadata.
pub fn estimand_registry_fingerprint(registry : EstimandRegistry) -> UInt64 {
  let rows : Array[Array[Double]] = Array::new()
  for definition in registry.definitions {
    rows.push([
      definition.name.length().to_double(),
      definition.treatment.length().to_double(),
      definition.outcome.length().to_double(),
      definition.required_diagnostics.length().to_double(),
      definition.version.length().to_double(),
    ])
  }
  matrix_checksum(rows)
}

///|
/// Creates a compact registry summary vector.
pub fn estimand_registry_summary(registry : EstimandRegistry) -> Array[Double] {
  let audit = audit_estimand_registry(registry)
  [
    audit.registered.to_double(),
    audit.duplicate_names.to_double(),
    audit.missing_fields.to_double(),
    if audit.active_found {
      1.0
    } else {
      0.0
    },
    if audit.passes {
      1.0
    } else {
      0.0
    },
    estimand_registry_fingerprint(registry).to_double(),
  ]
}