///|
/// Returns the provider as the documented, line-oriented DSL. This is useful
/// for review diffs, fixtures, migration scripts, and small command-line
/// tools. The output is deterministic because flag keys are sorted.
pub fn Provider::to_dsl(self : Provider) -> String {
  let lines = []
  let keys = self.flag_keys()
  keys.sort()
  for key in keys {
    let flag = self.get_flag_definition(key).unwrap()
    let kind = flag.value_kind()
    let mut line = "flag " + flag.key + " " + kind + " " + flag.value.to_text()
    match (flag.target_attr, flag.target_value) {
      (Some(attr), Some(expected)) =>
        line = line + " target " + attr + " " + expected.to_text()
      _ =>
        match flag.rollout_percentage {
          Some(percentage) => line = line + " rollout " + percentage.to_string()
          None => ()
        }
    }
    if !flag.enabled {
      line = line + " disabled"
    }
    lines.push(line)
  }
  lines.join("\n")
}

///|
pub fn Provider::enabled_keys(self : Provider) -> Array[String] {
  let keys = []
  for key, flag in self.flags {
    if flag.enabled {
      keys.push(key)
    }
  }
  keys.sort()
  keys
}

///|
pub fn Provider::disabled_keys(self : Provider) -> Array[String] {
  let keys = []
  for key, flag in self.flags {
    if !flag.enabled {
      keys.push(key)
    }
  }
  keys.sort()
  keys
}

///|
pub fn Provider::keys_with_prefix(
  self : Provider,
  prefix : String,
) -> Array[String] {
  let keys = []
  for key in self.flags.keys() {
    if key.has_prefix(prefix) {
      keys.push(key)
    }
  }
  keys.sort()
  keys
}

///|
pub fn Provider::select(self : Provider, keys : Array[String]) -> Provider {
  let next : Map[String, FlagDefinition] = Map([])
  for key in keys {
    match self.flags.get(key) {
      Some(flag) => next[key] = flag
      None => ()
    }
  }
  { flags: next }
}

///|
pub fn Provider::without_disabled(self : Provider) -> Provider {
  self.select(self.enabled_keys())
}

///|
pub fn Provider::count_value_kind(self : Provider, kind : String) -> Int {
  let mut count = 0
  for _, flag in self.flags {
    if flag.value_kind() == kind {
      count += 1
    }
  }
  count
}

///|
pub(all) struct ProviderDiff {
  added : Array[String]
  removed : Array[String]
  changed : Array[String]
  unchanged : Array[String]
} derive(Debug, Eq)

///|
pub fn diff_providers(left : Provider, right : Provider) -> ProviderDiff {
  let added = []
  let removed = []
  let changed = []
  let unchanged = []
  for key in left.flag_keys() {
    match right.flags.get(key) {
      None => removed.push(key)
      Some(flag) =>
        match left.flags.get(key) {
          Some(previous) =>
            if flag_equal(previous, flag) {
              unchanged.push(key)
            } else {
              changed.push(key)
            }
          None => changed.push(key)
        }
    }
  }
  for key in right.flag_keys() {
    if !left.contains(key) {
      added.push(key)
    }
  }
  added.sort()
  removed.sort()
  changed.sort()
  unchanged.sort()
  { added, removed, changed, unchanged }
}

///|
fn flag_equal(left : FlagDefinition, right : FlagDefinition) -> Bool {
  left.key == right.key &&
  left.value == right.value &&
  left.enabled == right.enabled &&
  left.rollout_percentage == right.rollout_percentage &&
  left.target_attr == right.target_attr &&
  left.target_value == right.target_value
}

///|
pub fn ProviderDiff::is_compatible(self : ProviderDiff) -> Bool {
  self.removed.length() == 0
}

///|
pub fn ProviderDiff::summary(self : ProviderDiff) -> String {
  "added=" +
  self.added.length().to_string() +
  ", removed=" +
  self.removed.length().to_string() +
  ", changed=" +
  self.changed.length().to_string() +
  ", unchanged=" +
  self.unchanged.length().to_string()
}