///|
/// Trait for values that can be decoded from a `RonValue`.
pub(open) trait FromRon {
  fn from_ron(RonValue, RonPath, RonOptions) -> Self raise RonDecodeError
}

///|
/// Trait for values that can be encoded as a `RonValue`.
pub(open) trait ToRon {
  fn to_ron(Self, RonOptions) -> RonValue
}

///|
/// Decode one RON document into a typed value.
pub fn[T : FromRon] decode(
  source : StringView,
  max_nesting_depth~ : Int32 = 128,
) -> T raise RonDecodeError {
  decode_with_options(
    source,
    options=RonOptions::default().with_max_nesting_depth(max_nesting_depth),
  )
}

///|
/// Decode one RON document using explicit extension and depth options.
pub fn[T : FromRon] decode_with_options(
  source : StringView,
  options~ : RonOptions = RonOptions::default(),
) -> T raise RonDecodeError {
  let document = parse_document(source, options~) catch {
    error => raise Syntax(error)
  }
  let resolved = options.with_extensions(document.extensions)
  FromRon::from_ron(document.value, Root, resolved)
}

///|
/// Decode an already parsed RON value.
pub fn[T : FromRon] from_value(value : RonValue) -> T raise RonDecodeError {
  from_value_with_options(value, RonOptions::default())
}

///|
/// Decode an already parsed RON value using explicit options.
pub fn[T : FromRon] from_value_with_options(
  value : RonValue,
  options : RonOptions,
) -> T raise RonDecodeError {
  FromRon::from_ron(value, Root, options)
}

///|
/// Encode a typed value as a `RonValue`.
pub fn[T : ToRon] to_value(value : T) -> RonValue {
  to_value_with_options(value, RonOptions::default())
}

///|
/// Encode a typed value as a `RonValue` using explicit options.
pub fn[T : ToRon] to_value_with_options(
  value : T,
  options : RonOptions,
) -> RonValue {
  ToRon::to_ron(value, options)
}

///|
/// Encode a typed value as canonical RON text.
pub fn[T : ToRon] encode(value : T) -> String {
  encode_with_options(value, options=RonOptions::default())
}

///|
/// Encode a typed value with an optional RON extension header.
pub fn[T : ToRon] encode_with_options(
  value : T,
  options~ : RonOptions = RonOptions::default(),
) -> String {
  let body = to_string(ToRon::to_ron(value, options))
  let attribute = options.extensions.to_attribute()
  if attribute.length() == 0 {
    body
  } else {
    attribute + "\n" + body
  }
}

///|
/// Return the display name of a dynamic RON value.
pub fn value_type_name(value : RonValue) -> String {
  match value {
    Unit => "unit"
    Bool(_) => "bool"
    Number(_) => "number"
    Char(_) => "char"
    String(_) => "string"
    Bytes(_) => "bytes"
    Byte(_) => "byte"
    Option(_) => "option"
    Sequence(_) => "sequence"
    Tuple(_) | TupleStruct(_, _) => "tuple"
    Map(_) => "map"
    Struct(_, _) => "struct"
    Enum(_, _, _) | EnumTuple(_, _, _) => "enum"
    Range(_) => "range"
    Identifier(_) => "identifier"
  }
}

///|
pub fn struct_fields(
  value : RonValue,
  path : RonPath,
) -> Array[(String, RonValue)] raise RonDecodeError {
  match value {
    Struct(_, fields) => fields
    _ => raise TypeMismatch(path, "struct", value_type_name(value))
  }
}

///|
pub fn tuple_fields(
  value : RonValue,
  path : RonPath,
) -> Array[RonValue] raise RonDecodeError {
  match value {
    Tuple(values) | TupleStruct(_, values) => values
    _ => raise TypeMismatch(path, "tuple", value_type_name(value))
  }
}

///|
pub fn field_value(
  fields : Array[(String, RonValue)],
  name : String,
  path : RonPath,
) -> RonValue raise RonDecodeError {
  for i = 0; i < fields.length(); i = i + 1 {
    let (field_name, value) = fields[i]
    if field_name == name {
      return value
    }
  }
  raise MissingField(path, name)
}

///|
pub fn[T : FromRon] field(
  fields : Array[(String, RonValue)],
  name : String,
  path : RonPath,
  options : RonOptions,
) -> T raise RonDecodeError {
  FromRon::from_ron(
    field_value(fields, name, path),
    path.add_key(name),
    options,
  )
}

///|
pub fn[T : FromRon] positional(
  values : Array[RonValue],
  index : Int32,
  path : RonPath,
  options : RonOptions,
) -> T raise RonDecodeError {
  match values.get(index) {
    Some(value) => FromRon::from_ron(value, path.add_index(index), options)
    None => raise UnexpectedTupleLength(path, index + 1, values.length())
  }
}

///|
/// Decode a newtype payload, honoring `unwrap_newtypes`.
pub fn[T : FromRon] newtype(
  value : RonValue,
  path : RonPath,
  options : RonOptions,
) -> T raise RonDecodeError {
  let inner = match value {
    Tuple(values) if values.length() == 1 => values[0]
    _ if options.extensions.unwrap_newtypes => value
    _ => raise TypeMismatch(path, "newtype tuple", value_type_name(value))
  }
  FromRon::from_ron(inner, path, options)
}

///|
/// Decode an enum newtype payload, honoring `unwrap_variant_newtypes`.
pub fn[T : FromRon] variant_newtype(
  value : RonValue,
  variant : String,
  path : RonPath,
  options : RonOptions,
) -> T raise RonDecodeError {
  if options.extensions.unwrap_variant_newtypes {
    match value {
      Struct(name, fields) if name == variant =>
        FromRon::from_ron(Struct("", fields), path, options)
      TupleStruct(name, values) if name == variant && values.length() == 1 =>
        FromRon::from_ron(values[0], path, options)
      EnumTuple(_, name, values) if name == variant && values.length() == 1 =>
        FromRon::from_ron(values[0], path, options)
      _ =>
        raise TypeMismatch(
          path,
          "unwrapped variant " + variant,
          value_type_name(value),
        )
    }
  } else {
    match value {
      TupleStruct(name, values) if name == variant && values.length() == 1 =>
        FromRon::from_ron(values[0], path, options)
      EnumTuple(_, name, values) if name == variant && values.length() == 1 =>
        FromRon::from_ron(values[0], path, options)
      Tuple(values) if values.length() == 1 =>
        FromRon::from_ron(values[0], path, options)
      _ =>
        raise TypeMismatch(
          path,
          "variant " + variant + " with one payload",
          value_type_name(value),
        )
    }
  }
}

///|
/// Require a named struct when `explicit_struct_names` is enabled.
pub fn require_struct_name(
  value : RonValue,
  expected : String,
  path : RonPath,
  options : RonOptions,
) -> Unit raise RonDecodeError {
  match value {
    Struct(name, _) if name == expected => ()
    Struct("", _) if !options.extensions.explicit_struct_names => ()
    Struct("", _) if options.extensions.explicit_struct_names =>
      raise InvalidValue(path, "expected explicit struct name \{expected}")
    Struct(name, _) =>
      raise InvalidValue(path, "expected struct \{expected}, found \{name}")
    _ => raise TypeMismatch(path, "struct " + expected, value_type_name(value))
  }
}