///|
/// A typed HPROF value. Object IDs are kept as unsigned 64-bit integers.
pub enum Value {
  Reference(UInt64)
  Boolean(Bool)
  Character(Int)
  ByteValue(Int)
  ShortValue(Int)
  IntValue(Int)
  LongValue(Int64)
  FloatValue(Double)
  DoubleValue(Double)
} derive(Debug)

///|
pub extend Value with @moonbitlang/core/debug.Debug::{to_repr}

///|
pub fn Value::reference(self : Value) -> UInt64? {
  match self {
    Reference(id) => Some(id)
    _ => None
  }
}

///|
pub fn Value::kind(self : Value) -> Int {
  match self {
    Reference(_) => 2
    Boolean(_) => 4
    Character(_) => 5
    FloatValue(_) => 6
    DoubleValue(_) => 7
    ByteValue(_) => 8
    ShortValue(_) => 9
    IntValue(_) => 10
    LongValue(_) => 11
  }
}

///|
pub fn value_type_name(kind : Int) -> String raise ParseError {
  match kind {
    2 => "object"
    4 => "boolean"
    5 => "char"
    6 => "float"
    7 => "double"
    8 => "byte"
    9 => "short"
    10 => "int"
    11 => "long"
    _ => raise Invalid(0, "unknown value type: \{kind}")
  }
}

///|
pub struct FieldSpec {
  name_id : UInt64
  kind : Int
}

///|
pub struct StaticField {
  name_id : UInt64
  value : Value
}

///|
pub struct ConstantEntry {
  index : Int
  value : Value
}

///|
pub struct ClassInfo {
  id : UInt64
  stack_serial : UInt64
  superclass_id : UInt64
  loader_id : UInt64
  signers_id : UInt64
  protection_domain_id : UInt64
  instance_size : Int
  constants : Array[ConstantEntry]
  statics : Array[StaticField]
  fields : Array[FieldSpec]
}

///|
pub enum ObjectKind {
  Instance
  ObjectArray
  PrimitiveArray
} derive(Eq)

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

///|
pub fn ObjectKind::label(self : ObjectKind) -> String {
  match self {
    Instance => "instance"
    ObjectArray => "object_array"
    PrimitiveArray => "primitive_array"
  }
}

///|
/// Payload byte lengths describe the file, never JVM allocation/retained size.
pub struct ObjectInfo {
  id : UInt64
  kind : ObjectKind
  class_id : UInt64
  stack_serial : UInt64
  primitive_kind : Int
  length : Int
  payload_offset : Int
  payload_bytes : Int
}

///|
pub struct RootInfo {
  tag : Int
  object_id : UInt64
  related_id : UInt64
  thread_serial : UInt64
  frame_number : UInt64
}

///|
pub fn RootInfo::label(self : RootInfo) -> String {
  match self.tag {
    0xff => "unknown"
    1 => "jni_global"
    2 => "jni_local"
    3 => "java_frame"
    4 => "native_stack"
    5 => "sticky_class"
    6 => "thread_block"
    7 => "monitor_used"
    8 => "thread_object"
    _ => "unsupported"
  }
}

///|
pub struct StackFrame {
  id : UInt64
  method_name_id : UInt64
  signature_id : UInt64
  source_name_id : UInt64
  class_serial : UInt64
  line_number : Int
}

///|
pub struct StackTrace {
  serial : UInt64
  thread_serial : UInt64
  frame_ids : Array[UInt64]
}

///|
pub struct ThreadInfo {
  serial : UInt64
  object_id : UInt64
  stack_serial : UInt64
  name_id : UInt64
  group_name_id : UInt64
  parent_group_name_id : UInt64
}

///|
pub struct FieldValue {
  declaring_class_id : UInt64
  name : String
  value : Value
}

///|
pub struct ReferenceEdge {
  source_id : UInt64
  target_id : UInt64
  label : String
}

///|
/// Histogram row for one class identity or primitive-array element type.
pub struct HistogramRow {
  name : String
  class_id : UInt64
  loader_id : UInt64
  kind : String
  count : Int
  payload_bytes : Int64
  elements : Int64
}

///|
pub struct ResolvedFrame {
  id : UInt64
  class_name : String
  method_name : String
  signature : String
  source_name : String
  line_number : Int
}

///|
fn Reader::value(self : Reader, kind : Int) -> Value raise ParseError {
  match kind {
    2 => Reference(self.id())
    4 => {
      let n = self.u1()
      if n > 1 {
        raise Invalid(self.pos - 1, "invalid boolean value")
      }
      Boolean(n == 1)
    }
    5 => Character(self.u2())
    6 =>
      FloatValue(
        Float::reinterpret_from_uint(self.number(4).to_uint()).to_double(),
      )
    7 => DoubleValue(self.number(8).reinterpret_as_double())
    8 => {
      let n = self.u1()
      ByteValue(if n >= 128 { n - 256 } else { n })
    }
    9 => {
      let n = self.u2()
      ShortValue(if n >= 32768 { n - 65536 } else { n })
    }
    10 => IntValue(self.number(4).to_int())
    11 => LongValue(self.number(8).reinterpret_as_int64())
    _ => raise Invalid(self.pos, "invalid field type: \{kind}")
  }
}