///|
/// Machine architectures understood by the first linking milestone.
pub(all) enum LinkMachine {
  X86_64
  AArch64
}

///|
/// Human-readable machine name.
pub fn LinkMachine::name(self : LinkMachine) -> String {
  match self {
    X86_64 => "x86-64"
    AArch64 => "aarch64"
  }
}

///|
/// Symbol binding used during cross-object resolution.
pub(all) enum LinkBinding {
  Local
  Global
  Weak
  Other(UInt32)
}

///|
/// Human-readable binding name.
pub fn LinkBinding::name(self : LinkBinding) -> String {
  match self {
    Local => "LOCAL"
    Global => "GLOBAL"
    Weak => "WEAK"
    Other(value) => "binding(\{value})"
  }
}

///|
/// Symbol kind used in link maps and diagnostics.
pub(all) enum LinkSymbolType {
  NoType
  Object
  Func
  Section
  File
  Common
  Tls
  Other(UInt32)
}

///|
/// Human-readable symbol kind.
pub fn LinkSymbolType::name(self : LinkSymbolType) -> String {
  match self {
    NoType => "NOTYPE"
    Object => "OBJECT"
    Func => "FUNC"
    Section => "SECTION"
    File => "FILE"
    Common => "COMMON"
    Tls => "TLS"
    Other(value) => "type(\{value})"
  }
}

///|
/// One section in a link input.
pub(all) struct LinkSection {
  index : UInt32
  name : String
  kind : UInt32
  flags : UInt64
  address : UInt64
  size : UInt64
  alignment : UInt64
  entry_size : UInt64
}

///|
/// Whether a section occupies memory at runtime.
pub fn LinkSection::is_alloc(self : LinkSection) -> Bool {
  (self.flags & 0x2UL) != 0UL
}

///|
/// Whether a section contains executable code.
pub fn LinkSection::is_executable(self : LinkSection) -> Bool {
  (self.flags & 0x4UL) != 0UL
}

///|
/// One symbol in a link input.
pub(all) struct LinkSymbol {
  index : UInt32
  name : String
  binding : LinkBinding
  kind : LinkSymbolType
  visibility : UInt32
  section_index : UInt32
  value : UInt64
  size : UInt64
}

///|
/// Whether this symbol has no defining section.
pub fn LinkSymbol::is_undefined(self : LinkSymbol) -> Bool {
  self.section_index == 0
}

///|
/// Whether this symbol participates in cross-object name resolution.
pub fn LinkSymbol::is_global_or_weak(self : LinkSymbol) -> Bool {
  match self.binding {
    Global | Weak => true
    Local | Other(_) => false
  }
}

///|
/// Whether this symbol is a tentative common definition.
pub fn LinkSymbol::is_common(self : LinkSymbol) -> Bool {
  match self.kind {
    Common => true
    _ => self.section_index == 0xFFF2
  }
}

///|
/// Human-readable binding name.
pub fn LinkSymbol::binding_name(self : LinkSymbol) -> String {
  self.binding.name()
}

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

///|
/// One relocation in a link input. `symbol_index` is the index in the
/// flattened `LinkInput.symbols` array.
pub(all) struct LinkRelocation {
  target_section : UInt32
  offset : UInt64
  symbol_index : UInt32
  kind : UInt32
  addend : Int64
  has_addend : Bool
}

///|
/// A normalized, linker-facing view of one relocatable object.
pub(all) struct LinkInput {
  name : String
  machine : LinkMachine
  sections : Array[LinkSection]
  symbols : Array[LinkSymbol]
  relocations : Array[LinkRelocation]
}

///|
/// Return a section by index.
pub fn LinkInput::section(self : LinkInput, index : UInt32) -> LinkSection? {
  if index >= self.sections.length().reinterpret_as_uint() {
    None
  } else {
    Some(self.sections[index.reinterpret_as_int()])
  }
}

///|
/// Return the first section with the given name.
pub fn LinkInput::section_by_name(
  self : LinkInput,
  name : String,
) -> LinkSection? {
  for section in self.sections {
    if section.name == name {
      return Some(section)
    }
  }
  None
}

///|
/// Return the first symbol with the given name.
pub fn LinkInput::symbol_by_name(
  self : LinkInput,
  name : String,
) -> LinkSymbol? {
  for symbol in self.symbols {
    if symbol.name == name {
      return Some(symbol)
    }
  }
  None
}

///|
/// Return relocations that target a section.
pub fn LinkInput::relocations_for(
  self : LinkInput,
  target_section : UInt32,
) -> Array[LinkRelocation] {
  let result : Array[LinkRelocation] = []
  for relocation in self.relocations {
    if relocation.target_section == target_section {
      result.push(relocation)
    }
  }
  result
}

///|
/// A compact input description for logs and early diagnostics.
pub fn LinkInput::summary(self : LinkInput) -> String {
  self.name +
  ": " +
  self.machine.name() +
  ", " +
  self.sections.length().to_string() +
  " sections, " +
  self.symbols.length().to_string() +
  " symbols, " +
  self.relocations.length().to_string() +
  " relocations"
}