///|
/// One undefined reference to a symbol.
pub(all) struct SymbolReference {
  object_index : UInt32
  object_name : String
  symbol_index : UInt32
}

///|
/// One symbol selected for the link.
pub(all) struct ResolvedSymbol {
  name : String
  binding : LinkBinding
  kind : LinkSymbolType
  object_index : UInt32
  object_name : String
  symbol_index : UInt32
  value : UInt64
  size : UInt64
}

///|
/// Human-readable binding name for a resolved symbol.
pub fn ResolvedSymbol::binding_name(self : ResolvedSymbol) -> String {
  self.binding.name()
}

///|
/// Human-readable symbol kind for a resolved symbol.
pub fn ResolvedSymbol::kind_name(self : ResolvedSymbol) -> String {
  self.kind.name()
}

///|
/// A symbol referenced but not defined by any input object.
pub(all) struct UnresolvedSymbol {
  name : String
  references : Array[SymbolReference]
}

///|
/// Two or more strong definitions of the same global symbol.
pub(all) struct DuplicateDefinition {
  name : String
  objects : Array[String]
}

///|
/// The result of resolving global and weak symbols across input objects.
pub(all) struct LinkResolution {
  symbols : Array[ResolvedSymbol]
  unresolved : Array[UnresolvedSymbol]
  duplicates : Array[DuplicateDefinition]
  object_count : Int32
  input_symbol_count : Int32
}

///|
/// Whether the resolution has no conflicts or missing definitions.
pub fn LinkResolution::is_ok(self : LinkResolution) -> Bool {
  self.unresolved.is_empty() && self.duplicates.is_empty()
}

///|
/// A deterministic one-line summary.
pub fn LinkResolution::summary(self : LinkResolution) -> String {
  self.object_count.to_string() +
  " objects, " +
  self.symbols.length().to_string() +
  " resolved, " +
  self.unresolved.length().to_string() +
  " unresolved, " +
  self.duplicates.length().to_string() +
  " duplicate definitions"
}

///|
/// Render a deterministic link map suitable for terminal output and CI logs.
pub fn LinkResolution::to_link_map(self : LinkResolution) -> String {
  let builder = StringBuilder()
  builder.write_string("Link map\n")
  builder.write_string("objects: ")
  builder.write_string(self.object_count.to_string())
  builder.write_string("\ninput symbols: ")
  builder.write_string(self.input_symbol_count.to_string())
  builder.write_string("\n\nresolved:\n")
  if self.symbols.is_empty() {
    builder.write_string("  \n")
  } else {
    for symbol in self.symbols {
      builder.write_string("  ")
      builder.write_string(symbol.name)
      builder.write_string(" <- ")
      builder.write_string(symbol.object_name)
      builder.write_string(" [")
      builder.write_string(symbol.binding_name())
      builder.write_string(" ")
      builder.write_string(symbol.kind_name())
      builder.write_string(" value=0x")
      builder.write_string(symbol.value.to_string(radix=16))
      builder.write_string(" size=")
      builder.write_string(symbol.size.to_string())
      builder.write_string("]\n")
    }
  }
  builder.write_string("\nduplicate definitions:\n")
  if self.duplicates.is_empty() {
    builder.write_string("  \n")
  } else {
    for duplicate in self.duplicates {
      builder.write_string("  ")
      builder.write_string(duplicate.name)
      builder.write_string(": ")
      for index in 0.. 0 {
          builder.write_string(", ")
        }
        builder.write_string(duplicate.objects[index])
      }
      builder.write_string("\n")
    }
  }
  builder.write_string("\nunresolved:\n")
  if self.unresolved.is_empty() {
    builder.write_string("  \n")
  } else {
    for unresolved in self.unresolved {
      builder.write_string("  ")
      builder.write_string(unresolved.name)
      builder.write_string(" <- ")
      for index in 0.. 0 {
          builder.write_string(", ")
        }
        builder.write_string(unresolved.references[index].object_name)
      }
      builder.write_string("\n")
    }
  }
  builder.to_string()
}

///|
fn add_definition(
  definitions : Map[String, Array[DefinitionCandidate]],
  name : String,
  candidate : DefinitionCandidate,
) -> Unit {
  let entries = match definitions.get(name) {
    Some(value) => value
    None => {
      let created : Array[DefinitionCandidate] = []
      definitions.set(name, created)
      created
    }
  }
  entries.push(candidate)
}

///|
fn add_reference(
  references : Map[String, Array[SymbolReference]],
  name : String,
  reference : SymbolReference,
) -> Unit {
  let entries = match references.get(name) {
    Some(value) => value
    None => {
      let created : Array[SymbolReference] = []
      references.set(name, created)
      created
    }
  }
  entries.push(reference)
}

///|
priv struct DefinitionCandidate {
  object_index : UInt32
  symbol : LinkSymbol
}

///|
fn resolved_from(
  candidate : DefinitionCandidate,
  inputs : Array[LinkInput],
) -> ResolvedSymbol {
  let symbol = candidate.symbol
  {
    name: symbol.name,
    binding: symbol.binding,
    kind: symbol.kind,
    object_index: candidate.object_index,
    object_name: inputs[candidate.object_index.reinterpret_as_int()].name,
    symbol_index: symbol.index,
    value: symbol.value,
    size: symbol.size,
  }
}

///|
/// Resolve global and weak definitions across a set of input objects.
///
/// Resolution order is caller order: inputs and symbols are visited in the
/// order supplied. A strong definition wins over weak and common definitions;
/// two strong definitions are reported as a duplicate instead of being silently
/// chosen. Local symbols never participate in cross-object resolution.
pub fn resolve_objects(inputs : Array[LinkInput]) -> LinkResolution {
  let names : Array[String] = []
  let definitions : Map[String, Array[DefinitionCandidate]] = Map([])
  let references : Map[String, Array[SymbolReference]] = Map([])
  let mut input_symbol_count = 0
  for object_index in 0.. value
      None => []
    }
    let references_for_name = match references.get(name) {
      Some(value) => value
      None => []
    }
    let strong : Array[DefinitionCandidate] = []
    let weak : Array[DefinitionCandidate] = []
    for candidate in candidates {
      match candidate.symbol.binding {
        Global =>
          if candidate.symbol.is_common() {
            weak.push(candidate)
          } else {
            strong.push(candidate)
          }
        Weak => weak.push(candidate)
        Local | Other(_) => ()
      }
    }
    if strong.length() > 1 {
      let objects : Array[String] = []
      for candidate in strong {
        objects.push(inputs[candidate.object_index.reinterpret_as_int()].name)
      }
      duplicates.push({ name, objects, })
    } else if strong.length() == 1 {
      resolved.push(resolved_from(strong[0], inputs))
    } else if weak.length() > 0 {
      resolved.push(resolved_from(weak[0], inputs))
    } else if references_for_name.length() > 0 {
      unresolved.push({ name, references: references_for_name, })
    }
  }
  {
    symbols: resolved,
    unresolved,
    duplicates,
    object_count: inputs.length(),
    input_symbol_count,
  }
}