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

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

///|
fn json_to_flag_value(json : Json) -> FlagValue? {
  match json {
    True => Some(BoolValue(true))
    False => Some(BoolValue(false))
    String(s) => Some(StringValue(s))
    Number(d, ..) =>
      if d == d.to_int().to_double() {
        Some(IntValue(d.to_int()))
      } else {
        Some(DoubleValue(d))
      }
    _ => None
  }
}

///|
fn parse_flag_definition(
  key : String,
  obj : Map[String, Json],
) -> FlagDefinition? {
  let enabled = match obj.get("enabled") {
    None => Some(true)
    Some(True) => Some(true)
    Some(False) => Some(false)
    _ => None
  }
  let rollout_percentage = match obj.get("rollout_percentage") {
    None => Some(None)
    Some(Number(d, ..)) =>
      if d == d.to_int().to_double() && d >= 0.0 && d <= 10000.0 {
        Some(Some(d.to_int()))
      } else {
        None
      }
    _ => None
  }
  let target = match (obj.get("target_attr"), obj.get("target_value")) {
    (None, None) => Some((None, None))
    (Some(String(attr)), Some(target_json)) =>
      match json_to_flag_value(target_json) {
        Some(target_value) => Some((Some(attr), Some(target_value)))
        None => None
      }
    _ => None
  }
  match (obj.get("value"), enabled, rollout_percentage, target) {
    (
      Some(value_json),
      Some(enabled),
      Some(rollout_percentage),
      Some((target_attr, target_value)),
    ) =>
      match json_to_flag_value(value_json) {
        Some(value) =>
          Some({
            key,
            value,
            enabled,
            rollout_percentage,
            target_attr,
            target_value,
          })
        None => None
      }
    _ => None
  }
}

///|
pub fn parse_json_provider(text : String) -> JsonProvider? {
  try {
    let json = @json.parse(text)
    match json {
      Object(root_map) => {
        let flags_map : Map[String, FlagDefinition] = Map([])
        let flags_json = match root_map.get("flags") {
          Some(Object(m)) => Some(m)
          Some(_) => None
          _ => Some(root_map)
        }
        match flags_json {
          Some(m) => {
            let mut valid = true
            for k, v in m {
              match v {
                Object(obj) =>
                  match parse_flag_definition(k, obj) {
                    Some(def) => flags_map[k] = def
                    None => valid = false
                  }
                _ => valid = false
              }
            }
            if valid {
              Some({ flags: flags_map })
            } else {
              None
            }
          }
          None => None
        }
      }
      _ => None
    }
  } catch {
    _ => None
  }
}