///|
pub(all) suberror VariableSnapshotError {
  InvalidIndexedCollectionElementType(String)
  InvalidIndexedCollectionLength(Int)
  InvalidIndexedCollectionIndex(index~ : Int, total_length~ : Int)
  UnorderedIndexedCollectionIndex(previous~ : Int, index~ : Int)
  InvalidEnumConstructor(String)
  InvalidEnumPayloadType(position~ : Int, type_name~ : String)
  InvalidEnumPayloadPosition(expected~ : Int, actual~ : Int)
} derive(Eq, Debug)

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

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

///|
pub struct VariableStructureSnapshot {
  priv children : Array[VariableSnapshot]
  priv truncated : Bool
} derive(Eq, Debug)

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

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

///|
fn VariableStructureSnapshot::new(
  children : Array[VariableSnapshot],
  truncated : Bool,
) -> VariableStructureSnapshot {
  { children: children.copy(), truncated, }
}

///|
pub fn VariableStructureSnapshot::children(
  self : VariableStructureSnapshot,
) -> ReadOnlyArray[VariableSnapshot] {
  ReadOnlyArray::from_array(self.children)
}

///|
pub fn VariableStructureSnapshot::is_truncated(
  self : VariableStructureSnapshot,
) -> Bool {
  self.truncated
}

///|
pub struct IndexedVariableElement {
  priv index : Int
  priv value : VariableSnapshot?
} derive(Eq, Debug)

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

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

///|
pub fn IndexedVariableElement::with_value(
  index : Int,
  value : VariableSnapshot,
) -> IndexedVariableElement {
  { index, value: Some(value), }
}

///|
pub fn IndexedVariableElement::placeholder(
  index : Int,
) -> IndexedVariableElement {
  { index, value: None, }
}

///|
pub fn IndexedVariableElement::index(self : IndexedVariableElement) -> Int {
  self.index
}

///|
pub fn IndexedVariableElement::value(
  self : IndexedVariableElement,
) -> VariableSnapshot? {
  self.value
}

///|
/// The source-level MoonBit collection represented by an indexed preview.
pub(all) enum IndexedCollectionKind {
  FixedArrayCollection
  ArrayCollection
} derive(Eq, Debug)

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

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

///|
pub fn IndexedCollectionKind::source_name(
  self : IndexedCollectionKind,
) -> String {
  match self {
    FixedArrayCollection => "FixedArray"
    ArrayCollection => "Array"
  }
}

///|
pub struct IndexedCollectionSnapshot {
  priv kind : IndexedCollectionKind
  priv element_type_name : String
  priv total_length : Int
  priv preview_elements : Array[IndexedVariableElement]
} derive(Eq, Debug)

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

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

///|
pub fn IndexedCollectionSnapshot::new(
  kind : IndexedCollectionKind,
  element_type_name : String,
  total_length : Int,
  preview_elements : Array[IndexedVariableElement],
) -> IndexedCollectionSnapshot raise VariableSnapshotError {
  guard element_type_name.trim() != "" else {
    raise InvalidIndexedCollectionElementType(element_type_name)
  }
  guard total_length >= 0 else {
    raise InvalidIndexedCollectionLength(total_length)
  }
  let mut previous_index : Int? = None
  for element in preview_elements {
    let index = element.index()
    guard index >= 0 && index < total_length else {
      raise InvalidIndexedCollectionIndex(index~, total_length~)
    }
    match previous_index {
      Some(previous) if index <= previous =>
        raise UnorderedIndexedCollectionIndex(previous~, index~)
      _ => previous_index = Some(index)
    }
  }
  {
    kind,
    element_type_name,
    total_length,
    preview_elements: preview_elements.copy(),
  }
}

///|
pub fn IndexedCollectionSnapshot::kind(
  self : IndexedCollectionSnapshot,
) -> IndexedCollectionKind {
  self.kind
}

///|
pub fn IndexedCollectionSnapshot::element_type_name(
  self : IndexedCollectionSnapshot,
) -> String {
  self.element_type_name
}

///|
pub fn IndexedCollectionSnapshot::total_length(
  self : IndexedCollectionSnapshot,
) -> Int {
  self.total_length
}

///|
pub fn IndexedCollectionSnapshot::preview_elements(
  self : IndexedCollectionSnapshot,
) -> ReadOnlyArray[IndexedVariableElement] {
  ReadOnlyArray::from_array(self.preview_elements)
}

///|
pub fn IndexedCollectionSnapshot::omitted_count(
  self : IndexedCollectionSnapshot,
) -> Int {
  self.total_length - self.preview_elements.length()
}

///|
pub fn IndexedCollectionSnapshot::is_complete(
  self : IndexedCollectionSnapshot,
) -> Bool {
  self.omitted_count() == 0
}

///|
/// One direct payload slot in an active MoonBit enum constructor.
///
/// The position is retained explicitly for a future PayloadIndex lookup step.
/// A missing scalar value means the payload is represented by its source type
/// only; it does not mean that adapter children remain available.
pub struct VariableEnumPayload {
  priv position : Int
  priv type_name : String
  priv scalar_value : String?
} derive(Eq, Debug)

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

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

///|
pub fn VariableEnumPayload::scalar(
  position : Int,
  type_name : String,
  value : String,
) -> VariableEnumPayload {
  { position, type_name, scalar_value: Some(value), }
}

///|
pub fn VariableEnumPayload::aggregate(
  position : Int,
  type_name : String,
) -> VariableEnumPayload {
  { position, type_name, scalar_value: None, }
}

///|
pub fn VariableEnumPayload::position(self : VariableEnumPayload) -> Int {
  self.position
}

///|
pub fn VariableEnumPayload::type_name(self : VariableEnumPayload) -> String {
  self.type_name
}

///|
pub fn VariableEnumPayload::scalar_value(self : VariableEnumPayload) -> String? {
  self.scalar_value
}

///|
pub struct VariableEnumSnapshot {
  priv constructor_name : String
  priv payload : Array[VariableEnumPayload]
} derive(Eq, Debug)

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

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

///|
pub fn VariableEnumSnapshot::new(
  constructor_name : String,
  payload : Array[VariableEnumPayload],
) -> VariableEnumSnapshot raise VariableSnapshotError {
  guard constructor_name.trim() != "" else {
    raise InvalidEnumConstructor(constructor_name)
  }
  for index, element in payload {
    guard element.position() == index else {
      raise InvalidEnumPayloadPosition(
        expected=index,
        actual=element.position(),
      )
    }
    guard element.type_name().trim() != "" else {
      raise InvalidEnumPayloadType(
        position=element.position(),
        type_name=element.type_name(),
      )
    }
  }
  { constructor_name, payload: payload.copy(), }
}

///|
pub fn VariableEnumSnapshot::constructor_name(
  self : VariableEnumSnapshot,
) -> String {
  self.constructor_name
}

///|
pub fn VariableEnumSnapshot::payload(
  self : VariableEnumSnapshot,
) -> ReadOnlyArray[VariableEnumPayload] {
  ReadOnlyArray::from_array(self.payload)
}

///|
pub enum VariableContent {
  VariableLeaf
  VariableStructure(VariableStructureSnapshot)
  VariableIndexedCollection(IndexedCollectionSnapshot)
  VariableEnum(VariableEnumSnapshot)
} derive(Eq, Debug)

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

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

///|
pub struct VariableSnapshot {
  priv name : String
  priv value : String
  priv type_name : String?
  priv content : VariableContent
} derive(Eq, Debug)

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

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

///|
/// Creates a debugger-domain variable value with no adapter reference.
pub fn VariableSnapshot::new(
  name : String,
  value : String,
  type_name : String?,
) -> VariableSnapshot {
  { name, value, type_name, content: VariableLeaf, }
}

///|
/// Creates a debugger-domain variable whose structure was fully materialized.
pub fn VariableSnapshot::expanded(
  name : String,
  value : String,
  type_name : String?,
  children : Array[VariableSnapshot],
) -> VariableSnapshot {
  {
    name,
    value,
    type_name,
    content: VariableStructure(VariableStructureSnapshot::new(children, false)),
  }
}

///|
/// Creates a debugger-domain structure whose remaining children were omitted.
pub fn VariableSnapshot::truncated(
  name : String,
  value : String,
  type_name : String?,
  children : Array[VariableSnapshot],
) -> VariableSnapshot {
  {
    name,
    value,
    type_name,
    content: VariableStructure(VariableStructureSnapshot::new(children, true)),
  }
}

///|
/// Creates a debugger-domain indexed collection with an adapter-free preview.
pub fn VariableSnapshot::indexed_collection(
  name : String,
  value : String,
  type_name : String?,
  collection : IndexedCollectionSnapshot,
) -> VariableSnapshot {
  { name, value, type_name, content: VariableIndexedCollection(collection), }
}

///|
/// Creates a debugger-domain enum value without retaining an adapter pointer.
pub fn VariableSnapshot::enum_value(
  name : String,
  type_name : String?,
  enum_value : VariableEnumSnapshot,
) -> VariableSnapshot {
  { name, value: "", type_name, content: VariableEnum(enum_value), }
}

///|
pub fn VariableSnapshot::name(self : VariableSnapshot) -> String {
  self.name
}

///|
pub fn VariableSnapshot::value(self : VariableSnapshot) -> String {
  self.value
}

///|
pub fn VariableSnapshot::type_name(self : VariableSnapshot) -> String? {
  self.type_name
}

///|
pub fn VariableSnapshot::content(self : VariableSnapshot) -> VariableContent {
  self.content
}

///|
/// One backend-independent step in a variable lookup path.
pub(all) enum VariablePathStep {
  Field(String)
  Index(Int)
} derive(Eq, Debug)

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

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

///|
/// A parsed variable lookup path owned by debugger core.
///
/// Adapter references and expression-evaluator text intentionally do not
/// appear in this value.
pub struct VariablePath {
  priv root : String
  priv steps : Array[VariablePathStep]
} derive(Eq, Debug)

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

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

///|
pub fn VariablePath::root(root : String) -> VariablePath {
  { root, steps: [], }
}

///|
/// Creates a variable path from an ordered sequence of field and index steps.
pub fn VariablePath::new(
  root : String,
  steps : Array[VariablePathStep],
) -> VariablePath {
  { root, steps: steps.copy(), }
}

///|
pub fn VariablePath::with_fields(
  root : String,
  fields : Array[String],
) -> VariablePath {
  VariablePath::new(root, fields.map(field => Field(field)))
}

///|
pub fn VariablePath::root_name(self : VariablePath) -> String {
  self.root
}

///|
pub fn VariablePath::steps(
  self : VariablePath,
) -> ReadOnlyArray[VariablePathStep] {
  ReadOnlyArray::from_array(self.steps)
}

///|
pub fn VariablePath::display_name(self : VariablePath) -> String {
  let output = StringBuilder(size_hint=self.root.length() + self.steps.length())
  output.write_string(self.root)
  for step in self.steps {
    match step {
      Field(field) => {
        output.write_char('.')
        output.write_string(field)
      }
      Index(index) => {
        output.write_char('[')
        output.write_string(index.to_string())
        output.write_char(']')
      }
    }
  }
  output.to_string()
}

///|
/// A domain-level reason why one variable path could not be inspected.
pub enum VariableLookupFailure {
  VariableRootNotFound(String)
  VariableFieldNotFound(String, String)
  VariableFieldAccessOnNonStruct(String, String, String?)
  VariableIndexAccessOnNonCollection(String, Int, String?)
  VariableIndexOutOfBounds(String, Int, Int)
  VariableUnavailable(String)
} derive(Eq, Debug)

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

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

///|
pub fn VariableLookupFailure::requested_path(
  self : VariableLookupFailure,
) -> String {
  match self {
    VariableRootNotFound(root) => root
    VariableFieldNotFound(owner, field) => "\{owner}.\{field}"
    VariableFieldAccessOnNonStruct(owner, field, _) => "\{owner}.\{field}"
    VariableIndexAccessOnNonCollection(owner, index, _) => "\{owner}[\{index}]"
    VariableIndexOutOfBounds(owner, index, _) => "\{owner}[\{index}]"
    VariableUnavailable(path) => path
  }
}

///|
pub enum VariableLookupResult {
  VariableFound(VariableSnapshot)
  VariableLookupFailed(VariableLookupFailure)
} derive(Eq, Debug)

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

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

///|
pub fn VariableLookupResult::found(
  variable : VariableSnapshot,
) -> VariableLookupResult {
  VariableFound(variable)
}

///|
pub fn VariableLookupResult::not_found(name : String) -> VariableLookupResult {
  VariableLookupFailed(VariableRootNotFound(name))
}

///|
pub fn VariableLookupResult::field_not_found(
  owner : String,
  field : String,
) -> VariableLookupResult {
  VariableLookupFailed(VariableFieldNotFound(owner, field))
}

///|
pub fn VariableLookupResult::field_access_on_non_struct(
  owner : String,
  field : String,
  type_name : String?,
) -> VariableLookupResult {
  VariableLookupFailed(VariableFieldAccessOnNonStruct(owner, field, type_name))
}

///|
pub fn VariableLookupResult::index_access_on_non_collection(
  owner : String,
  index : Int,
  type_name : String?,
) -> VariableLookupResult {
  VariableLookupFailed(
    VariableIndexAccessOnNonCollection(owner, index, type_name),
  )
}

///|
pub fn VariableLookupResult::index_out_of_bounds(
  owner : String,
  index : Int,
  total_length : Int,
) -> VariableLookupResult {
  VariableLookupFailed(VariableIndexOutOfBounds(owner, index, total_length))
}

///|
pub fn VariableLookupResult::unavailable(path : String) -> VariableLookupResult {
  VariableLookupFailed(VariableUnavailable(path))
}