///|
/// A source position. Occurrences distinguish repeated native object fields.
pub(all) enum InstanceStep {
  Field(String, Int32)
  Index(Int32)
  MapKey(String, Int32)
} derive(Eq, Debug)

///|
pub struct InstancePath {
  priv parts : Array[InstanceStep]
} derive(Eq, Debug)

///|
pub fn InstancePath::root() -> InstancePath {
  { parts: [], }
}

///|
fn InstancePath::child(
  self : InstancePath,
  step : InstanceStep,
) -> InstancePath {
  { parts: self.parts + [step], }
}

///|
pub fn InstancePath::field(
  self : InstancePath,
  name : String,
  occurrence~ : Int32 = 0,
) -> InstancePath raise ConfigurationError {
  guard occurrence >= 0 else {
    raise ConfigurationError("I expected a nonnegative field occurrence.")
  }
  self.child(Field(name, occurrence))
}

///|
pub fn InstancePath::index(
  self : InstancePath,
  index : Int32,
) -> InstancePath raise ConfigurationError {
  guard index >= 0 else {
    raise ConfigurationError("I expected a nonnegative array index.")
  }
  self.child(Index(index))
}

///|
pub fn InstancePath::map_key(
  self : InstancePath,
  name : String,
  occurrence~ : Int32 = 0,
) -> InstancePath raise ConfigurationError {
  guard occurrence >= 0 else {
    raise ConfigurationError("I expected a nonnegative map-key occurrence.")
  }
  self.child(MapKey(name, occurrence))
}

///|
pub fn InstancePath::steps(self : InstancePath) -> Array[InstanceStep] {
  self.parts.copy()
}

///|
pub fn InstancePath::append(
  self : InstancePath,
  relative : InstancePath,
) -> InstancePath {
  { parts: self.parts + relative.parts, }
}

///|
pub fn InstancePath::pointer(self : InstancePath) -> String {
  let out = StringBuilder()
  for part in self.parts {
    out.write_string("/")
    match part {
      Index(i) => out.write_string(i.to_string())
      Field(name, _) | MapKey(name, _) =>
        out.write_string(
          name.replace_all(old="~", new="~0").replace_all(old="/", new="~1"),
        )
    }
  }
  out.to_string()
}

///|
pub impl @json.ToJson for InstancePath with fn to_json(self) {
  Json::array(
    self.parts.map(step => {
      match step {
        Index(i) =>
          Json::object({
            "kind": Json::string("index"),
            "index": Json::number(i.to_double()),
          })
        Field(name, occurrence) | MapKey(name, occurrence) =>
          Json::object({
            "kind": Json::string(
              if step is MapKey(_, _) {
                "mapKey"
              } else {
                "field"
              },
            ),
            "name": Json::string(name),
            "occurrence": Json::number(occurrence.to_double()),
          })
      }
    }),
  )
}