///|
pub(all) enum RuleParseError {
  ExpectedObject(String)
  MissingField(String)
  ExpectedString(String)
  MultipleOperatorKeys(String)
  UnknownOperator(String)
  InvalidVariable(String)
  InvalidLiteral(String)
  InvalidArguments(String)
} derive(Eq, Debug)

///|
pub fn RuleParseError::message(self : RuleParseError) -> String {
  match self {
    ExpectedObject(path) => "expected an object at \{path}"
    MissingField(field) => "missing required field \{field}"
    ExpectedString(path) => "expected a string at \{path}"
    MultipleOperatorKeys(path) => "expected exactly one operator key at \{path}"
    UnknownOperator(name) => "unknown operator \{name}"
    InvalidVariable(path) => "invalid variable expression at \{path}"
    InvalidLiteral(path) => "invalid object literal at \{path}"
    InvalidArguments(path) => "expected an argument array at \{path}"
  }
}

///|
fn parse_operator(name : String) -> Result[Operator, RuleParseError] {
  match name {
    "and" => Ok(And)
    "or" => Ok(Or)
    "not" => Ok(Not)
    "==" => Ok(Equal)
    "!=" => Ok(NotEqual)
    ">" => Ok(GreaterThan)
    ">=" => Ok(GreaterOrEqual)
    "<" => Ok(LessThan)
    "<=" => Ok(LessOrEqual)
    "in" => Ok(In)
    "contains" => Ok(Contains)
    "starts_with" => Ok(StartsWith)
    "ends_with" => Ok(EndsWith)
    name => Err(UnknownOperator(name))
  }
}

///|
fn required_string(
  fields : Map[String, Json],
  name : String,
) -> Result[String, RuleParseError] {
  match fields.get(name) {
    None => Err(MissingField(name))
    Some(String(value)) => Ok(value)
    Some(_) => Err(ExpectedString(name))
  }
}

///|
pub fn parse_rule_document(json : Json) -> Result[RuleDocument, RuleParseError] {
  guard json is Object(fields) else { return Err(ExpectedObject("$")) }
  let id = match required_string(fields, "id") {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  let description = match required_string(fields, "description") {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  let condition_json = match fields.get("condition") {
    Some(value) => value
    None => return Err(MissingField("condition"))
  }
  let condition = match
    parse_expr(condition_json, RulePath::root().field("condition")) {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  Ok({ id, description, condition })
}

///|
fn parse_expr(json : Json, path : RulePath) -> Result[Expr, RuleParseError] {
  match json {
    Object(fields) => {
      if fields.length() != 1 {
        return Err(MultipleOperatorKeys(path.to_string()))
      }
      let (name, argument) = match fields.iter().next() {
        Some(entry) => entry
        None => return Err(MultipleOperatorKeys(path.to_string()))
      }
      let argument_path = path.field(name)
      match name {
        "var" =>
          match argument {
            String(data_path) => Ok(Variable(data_path))
            _ => Err(InvalidVariable(argument_path.to_string()))
          }
        "literal" =>
          match argument {
            Object(_) => Ok(Literal(argument))
            _ => Err(InvalidLiteral(argument_path.to_string()))
          }
        _ => {
          let operator = match parse_operator(name) {
            Ok(value) => value
            Err(error) => return Err(error)
          }
          match argument {
            Array(values) =>
              parse_expr_array(values, argument_path).map(fn(arguments) {
                Operation(operator, arguments)
              })
            _ => Err(InvalidArguments(argument_path.to_string()))
          }
        }
      }
    }
    value => Ok(Literal(value))
  }
}

///|
fn parse_expr_array(
  values : Array[Json],
  path : RulePath,
) -> Result[Array[Expr], RuleParseError] {
  let expressions : Array[Expr] = []
  for index, value in values {
    let expression = match parse_expr(value, path.index(index)) {
      Ok(parsed) => parsed
      Err(error) => return Err(error)
    }
    expressions.push(expression)
  }
  Ok(expressions)
}