///|
pub(all) struct FlagDefinition {
  key : String
  value : FlagValue
  enabled : Bool
  rollout_percentage : Int?
  target_attr : String?
  target_value : FlagValue?
} derive(Debug)

///|
pub fn FlagDefinition::has_target(self : FlagDefinition) -> Bool {
  match (self.target_attr, self.target_value) {
    (Some(_), Some(_)) => true
    _ => false
  }
}

///|
pub fn FlagDefinition::is_rollout(self : FlagDefinition) -> Bool {
  self.rollout_percentage is Some(_)
}

///|
pub fn FlagDefinition::value_kind(self : FlagDefinition) -> String {
  match self.value {
    BoolValue(_) => "bool"
    StringValue(_) => "string"
    IntValue(_) => "int"
    DoubleValue(_) => "double"
  }
}

///|
/// Returns configuration defects without changing the provider. The JSON
/// parser performs the same checks while the builder API clamps rollout values;
/// this method is useful for provider inspection and diagnostics.
pub fn FlagDefinition::validation_errors(
  self : FlagDefinition,
) -> Array[String] {
  let errors = []
  if self.key == "" {
    errors.push("empty_key")
  }
  match self.rollout_percentage {
    Some(value) =>
      if value < 0 || value > 10000 {
        errors.push("rollout_out_of_range")
      }
    None => ()
  }
  match (self.target_attr, self.target_value) {
    (None, None) => ()
    (Some(attr), Some(_)) =>
      if attr == "" {
        errors.push("empty_target_attribute")
      }
    (Some(_), None) => errors.push("missing_target_value")
    (None, Some(_)) => errors.push("missing_target_attribute")
  }
  errors
}

///|
pub struct Provider {
  flags : Map[String, FlagDefinition]
} derive(Debug)

///|
pub(all) struct ProviderStats {
  mut total : Int
  mut enabled : Int
  mut disabled : Int
  mut rollout : Int
  mut targeted : Int
  mut bool_count : Int
  mut string_count : Int
  mut int_count : Int
  mut double_count : Int
} derive(Debug, Eq)

///|
pub impl FeatureProvider for Provider with fn get_flag_definition(
  self : Provider,
  key : String,
) -> FlagDefinition? {
  self.flags.get(key)
}

///|
pub(all) struct BoolEvaluation {
  flag_key : String
  value : Bool
  reason : String
} derive(Debug, Eq)

///|
pub fn empty_provider() -> Provider {
  { flags: Map([]) }
}

///|
/// Inserts a fully described definition for adapters and migration tools.
/// Builder helpers remain the preferred safe path for ordinary applications.
pub fn Provider::with_definition(
  self : Provider,
  definition : FlagDefinition,
) -> Provider {
  let next = self.flags.copy()
  next[definition.key] = definition
  { flags: next }
}

///|
pub fn Provider::contains(self : Provider, key : String) -> Bool {
  self.flags.contains(key)
}

///|
pub fn Provider::flag_keys(self : Provider) -> Array[String] {
  let keys = []
  for key in self.flags.keys() {
    keys.push(key)
  }
  keys
}

///|
pub fn Provider::remove(self : Provider, key : String) -> Provider {
  let next = self.flags.copy()
  next.remove(key)
  { flags: next }
}

///|
pub fn Provider::merge(self : Provider, overlay : Provider) -> Provider {
  let next = self.flags.copy()
  next.merge_in_place(overlay.flags)
  { flags: next }
}

///|
pub fn Provider::stats(self : Provider) -> ProviderStats {
  let stats = {
    total: 0,
    enabled: 0,
    disabled: 0,
    rollout: 0,
    targeted: 0,
    bool_count: 0,
    string_count: 0,
    int_count: 0,
    double_count: 0,
  }
  for _, flag in self.flags {
    stats.total += 1
    if flag.enabled {
      stats.enabled += 1
    } else {
      stats.disabled += 1
    }
    if flag.is_rollout() {
      stats.rollout += 1
    }
    if flag.has_target() {
      stats.targeted += 1
    }
    match flag.value {
      BoolValue(_) => stats.bool_count += 1
      StringValue(_) => stats.string_count += 1
      IntValue(_) => stats.int_count += 1
      DoubleValue(_) => stats.double_count += 1
    }
  }
  stats
}

///|
pub fn Provider::definitions(self : Provider) -> Array[FlagDefinition] {
  let definitions = []
  for _, flag in self.flags {
    definitions.push(flag)
  }
  definitions
}

///|
pub fn Provider::with_value(
  self : Provider,
  key : String,
  value : FlagValue,
  enabled? : Bool = true,
) -> Provider {
  let next = self.flags.copy()
  next[key] = {
    key,
    value,
    enabled,
    rollout_percentage: None,
    target_attr: None,
    target_value: None,
  }
  { flags: next }
}

///|
pub fn Provider::with_value_rollout(
  self : Provider,
  key : String,
  value : FlagValue,
  percentage~ : Int,
  enabled? : Bool = true,
) -> Provider {
  let next = self.flags.copy()
  next[key] = {
    key,
    value,
    enabled,
    rollout_percentage: Some(clamp_percentage(percentage)),
    target_attr: None,
    target_value: None,
  }
  { flags: next }
}

///|
pub fn Provider::with_value_target(
  self : Provider,
  key : String,
  value : FlagValue,
  attr~ : String,
  equals~ : FlagValue,
  enabled? : Bool = true,
) -> Provider {
  let next = self.flags.copy()
  next[key] = {
    key,
    value,
    enabled,
    rollout_percentage: None,
    target_attr: Some(attr),
    target_value: Some(equals),
  }
  { flags: next }
}

///|
pub fn Provider::with_bool(
  self : Provider,
  key : String,
  value : Bool,
  enabled? : Bool = true,
) -> Provider {
  let next = self.flags.copy()
  next[key] = {
    key,
    value: BoolValue(value),
    enabled,
    rollout_percentage: None,
    target_attr: None,
    target_value: None,
  }
  { flags: next }
}

///|
pub fn Provider::with_bool_rollout(
  self : Provider,
  key : String,
  value : Bool,
  percentage~ : Int,
  enabled? : Bool = true,
) -> Provider {
  let next = self.flags.copy()
  next[key] = {
    key,
    value: BoolValue(value),
    enabled,
    rollout_percentage: Some(clamp_percentage(percentage)),
    target_attr: None,
    target_value: None,
  }
  { flags: next }
}

///|
pub fn Provider::with_bool_target(
  self : Provider,
  key : String,
  value : Bool,
  attr~ : String,
  equals~ : FlagValue,
  enabled? : Bool = true,
) -> Provider {
  let next = self.flags.copy()
  next[key] = {
    key,
    value: BoolValue(value),
    enabled,
    rollout_percentage: None,
    target_attr: Some(attr),
    target_value: Some(equals),
  }
  { flags: next }
}

///|
pub fn Provider::with_string(
  self : Provider,
  key : String,
  value : String,
  enabled? : Bool = true,
) -> Provider {
  let next = self.flags.copy()
  next[key] = {
    key,
    value: StringValue(value),
    enabled,
    rollout_percentage: None,
    target_attr: None,
    target_value: None,
  }
  { flags: next }
}

///|
pub fn Provider::with_string_rollout(
  self : Provider,
  key : String,
  value : String,
  percentage~ : Int,
  enabled? : Bool = true,
) -> Provider {
  let next = self.flags.copy()
  next[key] = {
    key,
    value: StringValue(value),
    enabled,
    rollout_percentage: Some(clamp_percentage(percentage)),
    target_attr: None,
    target_value: None,
  }
  { flags: next }
}

///|
pub fn Provider::with_string_target(
  self : Provider,
  key : String,
  value : String,
  attr~ : String,
  equals~ : FlagValue,
  enabled? : Bool = true,
) -> Provider {
  let next = self.flags.copy()
  next[key] = {
    key,
    value: StringValue(value),
    enabled,
    rollout_percentage: None,
    target_attr: Some(attr),
    target_value: Some(equals),
  }
  { flags: next }
}

///|
pub fn Provider::with_int(
  self : Provider,
  key : String,
  value : Int,
  enabled? : Bool = true,
) -> Provider {
  let next = self.flags.copy()
  next[key] = {
    key,
    value: IntValue(value),
    enabled,
    rollout_percentage: None,
    target_attr: None,
    target_value: None,
  }
  { flags: next }
}

///|
pub fn Provider::with_int_rollout(
  self : Provider,
  key : String,
  value : Int,
  percentage~ : Int,
  enabled? : Bool = true,
) -> Provider {
  let next = self.flags.copy()
  next[key] = {
    key,
    value: IntValue(value),
    enabled,
    rollout_percentage: Some(clamp_percentage(percentage)),
    target_attr: None,
    target_value: None,
  }
  { flags: next }
}

///|
pub fn Provider::with_int_target(
  self : Provider,
  key : String,
  value : Int,
  attr~ : String,
  equals~ : FlagValue,
  enabled? : Bool = true,
) -> Provider {
  let next = self.flags.copy()
  next[key] = {
    key,
    value: IntValue(value),
    enabled,
    rollout_percentage: None,
    target_attr: Some(attr),
    target_value: Some(equals),
  }
  { flags: next }
}

///|
pub fn Provider::with_double(
  self : Provider,
  key : String,
  value : Double,
  enabled? : Bool = true,
) -> Provider {
  let next = self.flags.copy()
  next[key] = {
    key,
    value: DoubleValue(value),
    enabled,
    rollout_percentage: None,
    target_attr: None,
    target_value: None,
  }
  { flags: next }
}

///|
pub fn Provider::with_double_rollout(
  self : Provider,
  key : String,
  value : Double,
  percentage~ : Int,
  enabled? : Bool = true,
) -> Provider {
  let next = self.flags.copy()
  next[key] = {
    key,
    value: DoubleValue(value),
    enabled,
    rollout_percentage: Some(clamp_percentage(percentage)),
    target_attr: None,
    target_value: None,
  }
  { flags: next }
}

///|
pub fn Provider::with_double_target(
  self : Provider,
  key : String,
  value : Double,
  attr~ : String,
  equals~ : FlagValue,
  enabled? : Bool = true,
) -> Provider {
  let next = self.flags.copy()
  next[key] = {
    key,
    value: DoubleValue(value),
    enabled,
    rollout_percentage: None,
    target_attr: Some(attr),
    target_value: Some(equals),
  }
  { flags: next }
}

///|
fn target_matches(flag : FlagDefinition, ctx : EvalContext) -> Bool {
  match (flag.target_attr, flag.target_value) {
    (Some(attr), Some(expected)) =>
      match ctx.attributes.get(attr) {
        Some(actual) => actual == expected
        None => false
      }
    _ => true
  }
}

///|
pub fn[P : FeatureProvider] evaluate_bool(
  provider : P,
  flag_key : String,
  ctx : EvalContext,
  default~ : Bool,
) -> BoolEvaluation {
  ignore(ctx)
  match provider.get_flag_definition(flag_key) {
    Some(flag) =>
      if !flag.enabled {
        { flag_key, value: default, reason: "disabled" }
      } else if !target_matches(flag, ctx) {
        { flag_key, value: default, reason: "target_miss" }
      } else {
        match flag.value {
          BoolValue(value) =>
            match flag.rollout_percentage {
              Some(percentage) =>
                if rollout_bucket(flag_key, ctx.targeting_key) < percentage {
                  { flag_key, value, reason: "rollout_match" }
                } else {
                  { flag_key, value: default, reason: "rollout_miss" }
                }
              None =>
                match (flag.target_attr, flag.target_value) {
                  (Some(_), Some(_)) =>
                    { flag_key, value, reason: "target_match" }
                  _ => { flag_key, value, reason: "static" }
                }
            }
          _ => { flag_key, value: default, reason: "type_mismatch" }
        }
      }
    None => { flag_key, value: default, reason: "default" }
  }
}