///|
/// Integer byte order for addressed word reads and reference scans.
pub(all) enum ByteOrder {
  LittleEndian
  BigEndian
} derive(Eq, Debug)

///|
/// Read one unsigned 1..4 byte integer from consecutive occupied addresses.
/// The result uses Int64 so every 32-bit bit pattern remains nonnegative.
pub fn read_uint(
  image : @model.FirmwareImage,
  address : Int64,
  width : Int,
  order? : ByteOrder = LittleEndian,
) -> Int64 raise @model.FirmwareError {
  if width < 1 || width > 4 {
    raise @model.FirmwareError(
      @model.diagnostic(
        InvalidOption,
        "integer width must be between 1 and 4 bytes",
      ),
    )
  }
  ignore(@model.data_range(address, width))
  let mut value = 0L
  for index in 0.. value
      None =>
        raise @model.FirmwareError(
          @model.diagnostic(
            InvalidRange,
            "integer read crosses an unmapped address",
            address=byte_address,
          ),
        )
    }
    value = value * 256L + byte.to_int().to_int64()
  }
  value
}

///|
/// Encode one unsigned 1..4 byte integer with explicit byte order.
pub fn encode_uint(
  value : Int64,
  width : Int,
  order? : ByteOrder = LittleEndian,
) -> Bytes raise @model.FirmwareError {
  if width < 1 || width > 4 || value < 0L || value >= 1L << (width * 8) {
    raise @model.FirmwareError(
      @model.diagnostic(
        InvalidOption,
        "unsigned integer does not fit requested width",
      ),
    )
  }
  Bytes::makei(width, index => {
    let shift = if order == LittleEndian {
      index * 8
    } else {
      (width - 1 - index) * 8
    }
    ((value >> shift) & 255L).to_byte()
  })
}

///|
/// Return an image copy with encoded bytes applied under an explicit overlap
/// policy. The source image remains unchanged if encoding or insertion fails.
pub fn write_uint(
  image : @model.FirmwareImage,
  address : Int64,
  value : Int64,
  width : Int,
  order? : ByteOrder = LittleEndian,
  policy? : @model.OverlapPolicy = Overwrite,
) -> @model.FirmwareImage raise @model.FirmwareError {
  let bytes = encode_uint(value, width, order~)
  let result = image.copy()
  result.memory.insert(address, bytes, policy~)
  { ..result, metadata: @model.Metadata::new(Unknown), }
}

///|
/// Find occupied words equal to an unsigned value. Candidates are checked at
/// a caller-selected alignment and cannot cross gaps. Results are sorted.
pub fn find_uint(
  image : @model.FirmwareImage,
  value : Int64,
  width : Int,
  order? : ByteOrder = LittleEndian,
  alignment? : Int = 1,
  max_matches? : Int = 100000,
) -> Array[Int64] raise @model.FirmwareError {
  let needle = encode_uint(value, width, order~)
  if alignment < 1 ||
    alignment > 1024 * 1024 ||
    (alignment & (alignment - 1)) != 0 ||
    max_matches < 0 ||
    max_matches > 1000000 {
    raise @model.FirmwareError(
      @model.diagnostic(
        InvalidOption,
        "word search alignment or match limit is invalid",
      ),
    )
  }
  let result = []
  for segment in image.memory.segments() {
    let last = segment.data.length() - width
    if last >= 0 {
      for offset in 0..<=last {
        let address = segment.start + offset.to_int64()
        if address % alignment.to_int64() == 0L &&
          segment.data[offset:offset + width] == needle {
          if result.length() >= max_matches {
            raise @model.FirmwareError(
              @model.diagnostic(ResourceLimit, "word match limit exceeded"),
            )
          }
          result.push(address)
        }
      }
    }
  }
  result
}

///|
/// Minimal Cortex-M vector information at an explicit vector-table base.
pub struct CortexMVectorTable {
  base_address : Int64
  initial_stack_pointer : Int64
  reset_vector : Int64
  reset_address : Int64
  thumb : Bool
  stack_aligned : Bool
  reset_mapped : Bool
} derive(Eq, Debug)

///|
/// Address from which the first two Cortex-M vectors were decoded.
pub fn CortexMVectorTable::base(self : CortexMVectorTable) -> Int64 {
  self.base_address
}

///|
/// Initial main stack pointer stored in vector slot zero.
pub fn CortexMVectorTable::stack_pointer(self : CortexMVectorTable) -> Int64 {
  self.initial_stack_pointer
}

///|
/// Raw reset vector, including the architectural Thumb-state bit.
pub fn CortexMVectorTable::raw_reset_vector(self : CortexMVectorTable) -> Int64 {
  self.reset_vector
}

///|
/// Reset handler address with the Thumb-state bit cleared.
pub fn CortexMVectorTable::reset_handler(self : CortexMVectorTable) -> Int64 {
  self.reset_address
}

///|
/// Whether the raw reset vector selects Thumb state.
pub fn CortexMVectorTable::has_thumb_bit(self : CortexMVectorTable) -> Bool {
  self.thumb
}

///|
/// Whether the initial stack pointer meets the requested alignment.
pub fn CortexMVectorTable::is_stack_aligned(self : CortexMVectorTable) -> Bool {
  self.stack_aligned
}

///|
/// Whether the normalized reset handler has a byte in the sparse image.
pub fn CortexMVectorTable::is_reset_mapped(self : CortexMVectorTable) -> Bool {
  self.reset_mapped
}

///|
/// Decode the first two little-endian Cortex-M vectors without assuming that
/// the image starts at address zero. This inspects structure only; device-
/// specific RAM and executable-region checks belong in target layout policy.
pub fn inspect_cortex_m_vectors(
  image : @model.FirmwareImage,
  base_address : Int64,
  stack_alignment? : Int = 8,
) -> CortexMVectorTable raise @model.FirmwareError {
  if stack_alignment < 1 ||
    stack_alignment > 1024 ||
    (stack_alignment & (stack_alignment - 1)) != 0 {
    raise @model.FirmwareError(
      @model.diagnostic(
        InvalidOption,
        "stack alignment must be a power of two up to 1024",
      ),
    )
  }
  let stack = read_uint(image, base_address, 4)
  let reset = read_uint(image, base_address + 4L, 4)
  let target = reset & 0xFFFFFFFEL
  {
    base_address,
    initial_stack_pointer: stack,
    reset_vector: reset,
    reset_address: target,
    thumb: (reset & 1L) != 0L,
    stack_aligned: stack % stack_alignment.to_int64() == 0L,
    reset_mapped: image.memory.contains(target),
  }
}