// Ported from topojson-client src/neighbors.js, ISC.

///|
fn arc_id(i : Int) -> Int {
  if i < 0 {
    -i - 1
  } else {
    i
  }
}

///|
fn refs_flat(j : Json, out : Array[Int]) -> Unit {
  match j {
    Array(a) =>
      for v in a {
        refs_flat(v, out)
      }
    Number(n, ..) => out.push(n.to_int())
    _ => ()
  }
}

///|
fn geometry_refs(g : Json) -> Array[Int] {
  let out : Array[Int] = []
  if kind(g) == "GeometryCollection" {
    for c in array(field(g, "geometries")) {
      for i in geometry_refs(c) {
        out.push(i)
      }
    }
  } else {
    refs_flat(field(g, "arcs"), out)
  }
  out
}

///|
fn Topology::selected(
  self : Topology,
  names : Array[String],
) -> Array[Json] raise TopoError {
  names.map(n => self.lookup(n))
}

///|
fn adjacency(objects : Array[Json]) -> Array[Array[Int]] raise TopoError {
  let owners : Map[Int, Array[Int]] = Map([])
  for k = 0; k < objects.length(); k = k + 1 {
    for r in geometry_refs(objects[k]) {
      let id = arc_id(r)
      let list = owners.get(id).unwrap_or([])
      list.push(k)
      owners[id] = list
    }
  }
  let mut work = 0
  for _, group in owners {
    if group.length() > 1000 {
      raise Invalid("limit.neighbor.owners")
    }
    work += group.length() * group.length()
    if work > 2000000 {
      raise Invalid("limit.neighbor.pairs")
    }
  }
  let out = Array::makei(objects.length(), _ => [])
  for _, group in owners {
    for j = 0; j < group.length(); j = j + 1 {
      for k = j + 1; k < group.length(); k = k + 1 {
        let a = group[j]
        let b = group[k]
        if !out[a].contains(b) {
          out[a].push(b)
        }
        if !out[b].contains(a) {
          out[b].push(a)
        }
      }
    }
  }
  for row in out {
    row.sort()
  }
  out
}

///|
/// Neighbors are based on shared arc identity, not coincident coordinates.
pub fn Topology::neighbors(
  self : Topology,
  names : Array[String],
) -> Array[Array[Int]] raise TopoError {
  adjacency(self.selected(names))
}

///|
/// Each direct member of a GeometryCollection is one neighbor-graph node.
pub fn Topology::collection_neighbors(
  self : Topology,
  name : String,
) -> Array[Array[Int]] raise TopoError {
  let g = self.lookup(name)
  if kind(g) != "GeometryCollection" {
    raise Invalid("collection.required")
  }
  adjacency(array(field(g, "geometries")))
}