///|
/// Scope components are interned once, never repeated as full JSON map keys.
priv struct Scope {
  kind : Int32
  parent : Int32
  component : String
  path : ReadOnlyArray[String]
  encoded_path : ReadOnlyArray[String]
  json_body : Int64
  md_body : Int64
  text_body : Int64
}

///|
priv struct ScopePool {
  scopes : Array[Scope]
  indexes : Map[(Int32, String), Int32]
}

///|
fn new_scope_pool() -> ScopePool {
  {
    scopes: [
      {
        kind: 1,
        parent: -2,
        component: "",
        path: [],
        encoded_path: [],
        json_body: 0L,
        md_body: 0L,
        text_body: 0L,
      },
    ],
    indexes: Map([]),
  }
}

///|
fn ScopePool::intern(
  self : ScopePool,
  parent : Int32,
  component : String,
) -> Int32 {
  let key = (parent, component)
  if self.indexes.get(key) is Some(index) {
    return index
  }
  let path : Array[String] = []
  let encoded : Array[String] = []
  if parent >= 0 {
    for part in self.scopes[parent].path {
      path.push(part)
    }
    for part in self.scopes[parent].encoded_path {
      encoded.push(part)
    }
  }
  path.push(component)
  let encoded_component = Json(component).stringify()
  encoded.push(encoded_component)
  let previous = if parent > 0 { Some(self.scopes[parent]) } else { None }
  let index = self.scopes.length()
  self.scopes.push({
    kind: if parent < 0 {
      0
    } else {
      1
    },
    parent,
    component,
    path: ReadOnlyArray::from_array(path),
    encoded_path: ReadOnlyArray::from_array(encoded),
    json_body: previous.map(s => s.json_body + 1L).unwrap_or(0L) +
    encoded_component.length().to_int64(),
    md_body: previous.map(s => s.md_body + 1L).unwrap_or(0L) +
    safe_units(encoded_component),
    text_body: previous.map(s => s.text_body + 1L).unwrap_or(0L) +
    terminal_units(encoded_component),
  })
  self.indexes[key] = index
  index
}

///|
fn ScopePool::remap(
  self : ScopePool,
  scopes : ReadOnlyArray[Scope],
) -> Array[Int32] {
  let mapping : Array[Int32] = [0]
  for i = 1; i < scopes.length(); i = i + 1 {
    let scope = scopes[i]
    let parent = if scope.kind == 0 { -1 } else { mapping[scope.parent] }
    mapping.push(self.intern(parent, scope.component))
  }
  mapping
}

///|
fn compare_scope(a : Scope, b : Scope) -> Int32 {
  if a.kind != b.kind {
    return a.kind.compare(b.kind)
  }
  let common = if a.encoded_path.length() < b.encoded_path.length() {
    a.encoded_path.length()
  } else {
    b.encoded_path.length()
  }
  for i = 0; i < common; i = i + 1 {
    let x = a.encoded_path[i]
    let y = b.encoded_path[i]
    if !physical_equal(x, y) {
      let order = x.lexical_compare(y)
      if order != 0 {
        return order
      }
    }
  }
  // In JSON arrays a comma precedes a closing bracket: longer prefixes first.
  b.encoded_path.length().compare(a.encoded_path.length())
}

///|
fn ScopePool::ranks(self : ScopePool) -> Array[Int32] {
  let order : Array[Int32] = []
  for i = 0; i < self.scopes.length(); i = i + 1 {
    order.push(i)
  }
  order.sort_by((a, b) => compare_scope(self.scopes[a], self.scopes[b]))
  let ranks = Array::make(self.scopes.length(), 0)
  for i = 0; i < order.length(); i = i + 1 {
    ranks[order[i]] = i
  }
  ranks
}

///|
fn Scope::id_units(
  self : Scope,
  encoded_name : String,
  format : Int32,
) -> Int64 {
  let prefix = if self.kind == 0 { "[\"class\"," } else { "[\"suite\",[" }
  let suffix = if self.kind == 0 { "," } else { "]," }
  match format {
    1 =>
      safe_units(prefix) +
      self.md_body +
      safe_units(suffix) +
      safe_units(encoded_name) +
      safe_units("]")
    2 =>
      terminal_units(prefix) +
      self.text_body +
      terminal_units(suffix) +
      terminal_units(encoded_name) +
      1L
    _ =>
      prefix.length().to_int64() +
      self.json_body +
      suffix.length().to_int64() +
      encoded_name.length().to_int64() +
      1L
  }
}