///| Backend-independent storage layout model.
///
/// The model is built from the Solidity compiler's `storageLayout` output,
/// documented at
/// .
/// It deliberately keeps compiler-generated type ids only as references:
/// semantic equality is decided by the type descriptions they resolve to, not
/// by the ids, which change between compilations.

///|
/// A storage slot number.
///
/// Solidity slots are unbounded decimal integers, emitted as JSON strings
/// because they can exceed every fixed-width integer type. The value is
/// therefore kept as canonical decimal digits and is never routed through a
/// floating-point number.
pub struct Slot {
  priv digits : String
} derive(Eq, @debug.Debug)

///|
/// Parses a storage slot from its decimal representation.
///
/// Returns `None` if `text` is empty or contains anything other than ASCII
/// digits. Leading zeros are removed, so `Slot::parse("007")` equals
/// `Slot::parse("7")`.
pub fn Slot::parse(text : String) -> Slot? {
  if text.is_empty() {
    return None
  }
  let digits = StringBuilder()
  let mut significant = false
  for c in text {
    if !c.is_ascii_digit() {
      return None
    }
    if c != '0' {
      significant = true
    }
    if significant {
      digits.write_char(c)
    }
  }
  if significant {
    Some({ digits: digits.to_string(), })
  } else {
    Some({ digits: "0", })
  }
}

///|
/// Canonical decimal representation of a slot.
pub fn Slot::to_string(self : Slot) -> String {
  self.digits
}

///|
/// Numeric order of two slots.
///
/// Both values are canonical decimal digits without leading zeros, so the
/// shorter value is the smaller one and equal-length values compare digit by
/// digit. This is deliberately not a lexicographic string order: slot `10` is
/// greater than slot `9`.
pub impl Compare for Slot with fn compare(self : Slot, other : Slot) -> Int {
  let a = self.digits.char_length()
  let b = other.digits.char_length()
  if a < b {
    -1
  } else if a > b {
    1
  } else {
    self.digits.compare(other.digits)
  }
}

///|
/// Structural description of a storage type.
///
/// The shape carries the compiler type ids that a type references, not the
/// referenced types themselves. Storage types can refer to each other
/// (a struct may hold a mapping whose value is that same struct), so resolving
/// references is the job of a later comparison step, which can then guard
/// recursion with visited pairs.
pub(all) enum TypeShape {
  /// A value laid out directly in its slot bytes: scalars, enums, and
  /// user-defined value types. The type `label` is what distinguishes types
  /// with the same size, such as `uint128` and `int128`.
  Value
  /// A fixed-size array. The fields are the base element type id and the
  /// lossless decimal element count.
  StaticArray(String, String)
  /// A dynamic array. The field is the base element type id.
  DynamicArray(String)
  /// A mapping. The fields are the key and value type ids.
  Mapping(String, String)
  /// `bytes` and `string`, whose stored size varies at run time.
  Bytes
  /// A struct. Members carry slots and offsets relative to the start of the
  /// struct, in declaration order.
  Struct(Array[StorageEntry])
} derive(Eq, @debug.Debug)

///|
/// A storage variable declaration.
pub(all) struct StorageEntry {
  /// Declared name of the variable.
  label : String
  /// Emitting contract as the artifact reports it, such as `fileA:A`. Base
  /// contract variables come first in the layout, so this is what separates
  /// an inherited variable from a new one.
  contract : String?
  /// Slot where the variable starts.
  slot : Slot
  /// Byte offset inside `slot`.
  offset : Int
  /// Compiler type id, a key into `StorageLayout.types`.
  type_id : String
} derive(Eq, @debug.Debug)

///|
/// A storage type normalized from the compiler type table.
pub(all) struct TypeEntry {
  /// Compiler-generated type id. Used only to resolve references.
  id : String
  /// Canonical type name printed by the compiler, such as `uint128` or
  /// `struct A.S`.
  label : String
  /// Bytes the type occupies, from `numberOfBytes`.
  byte_size : Int
  /// Structural description of the type.
  shape : TypeShape
} derive(Eq, @debug.Debug)

///|
/// A normalized storage layout.
pub(all) struct StorageLayout {
  /// Declared storage variables, in artifact order.
  entries : Array[StorageEntry]
  /// Referenced types, sorted by id with MoonBit's `String::compare` order so
  /// that output is deterministic.
  types : Array[TypeEntry]
} derive(Eq, @debug.Debug)

///|
/// A layout with no variables and no types, as a contract that declares no
/// transient storage has.
pub fn StorageLayout::empty() -> StorageLayout {
  { entries: [], types: [], }
}

///|
/// Finds the normalized type with compiler id `id`.
pub fn StorageLayout::find_type(
  self : StorageLayout,
  id : String,
) -> TypeEntry? {
  for entry in self.types {
    if entry.id == id {
      return Some(entry)
    }
  }
  None
}

///|
pub extend Slot with Eq::{not_equal, equal}

///|
pub extend Slot with @debug.Debug::{to_repr}

///|
pub extend Slot with Compare::{op_lt, op_le, op_ge, compare, op_gt}

///|
pub extend TypeShape with Eq::{not_equal, equal}

///|
pub extend TypeShape with @debug.Debug::{to_repr}

///|
pub extend StorageEntry with Eq::{not_equal, equal}

///|
pub extend StorageEntry with @debug.Debug::{to_repr}

///|
pub extend TypeEntry with Eq::{not_equal, equal}

///|
pub extend TypeEntry with @debug.Debug::{to_repr}

///|
pub extend StorageLayout with Eq::{not_equal, equal}

///|
pub extend StorageLayout with @debug.Debug::{to_repr}