///|
const LF_BCLASS : UInt16 = 0x1400

///|
const LF_VBCLASS : UInt16 = 0x1401

///|
const LF_IVBCLASS : UInt16 = 0x1402

///|
const LF_INDEX : UInt16 = 0x1404

///|
const LF_VFUNCTAB : UInt16 = 0x1409

///|
const LF_FRIENDCLS : UInt16 = 0x140A

///|
const LF_ENUMERATE : UInt16 = 0x1502

///|
const LF_MEMBER : UInt16 = 0x150D

///|
const LF_STMEMBER : UInt16 = 0x150E

///|
const LF_METHOD : UInt16 = 0x150F

///|
const LF_NESTTYPE : UInt16 = 0x1510

///|
const LF_ONEMETHOD : UInt16 = 0x1511

///|
/// CodeView field attributes shared by members, bases, and enumerators.
pub struct FieldAttributes {
  raw : UInt16
}

///|
pub fn FieldAttributes::raw(self : FieldAttributes) -> UInt16 {
  self.raw
}

///|
pub fn FieldAttributes::access(self : FieldAttributes) -> UInt16 {
  self.raw & 0x0003
}

///|
pub fn FieldAttributes::method_kind(self : FieldAttributes) -> UInt16 {
  (self.raw >> 2) & 0x0007
}

///|
pub fn FieldAttributes::is_introducing_virtual(self : FieldAttributes) -> Bool {
  let kind = self.method_kind()
  kind == 0x0004 || kind == 0x0006
}

///|
pub struct BaseClassField {
  attributes : FieldAttributes
  base_type : UInt32
  offset : NumericValue
}

///|
pub fn BaseClassField::attributes(self : BaseClassField) -> FieldAttributes {
  self.attributes
}

///|
pub fn BaseClassField::base_type(self : BaseClassField) -> UInt32 {
  self.base_type
}

///|
pub fn BaseClassField::offset(self : BaseClassField) -> NumericValue {
  self.offset
}

///|
pub struct VirtualBaseClassField {
  virtual_inheritance : Bool
  attributes : FieldAttributes
  base_type : UInt32
  vbptr_type : UInt32
  vbptr_offset : NumericValue
  vtable_index : NumericValue
}

///|
pub fn VirtualBaseClassField::virtual_inheritance(
  self : VirtualBaseClassField,
) -> Bool {
  self.virtual_inheritance
}

///|
pub fn VirtualBaseClassField::attributes(
  self : VirtualBaseClassField,
) -> FieldAttributes {
  self.attributes
}

///|
pub fn VirtualBaseClassField::base_type(self : VirtualBaseClassField) -> UInt32 {
  self.base_type
}

///|
pub fn VirtualBaseClassField::vbptr_type(
  self : VirtualBaseClassField,
) -> UInt32 {
  self.vbptr_type
}

///|
pub fn VirtualBaseClassField::vbptr_offset(
  self : VirtualBaseClassField,
) -> NumericValue {
  self.vbptr_offset
}

///|
pub fn VirtualBaseClassField::vtable_index(
  self : VirtualBaseClassField,
) -> NumericValue {
  self.vtable_index
}

///|
pub struct DataField {
  attributes : FieldAttributes
  type_index : UInt32
  offset : NumericValue
  name : String
}

///|
pub fn DataField::attributes(self : DataField) -> FieldAttributes {
  self.attributes
}

///|
pub fn DataField::type_index(self : DataField) -> UInt32 {
  self.type_index
}

///|
pub fn DataField::offset(self : DataField) -> NumericValue {
  self.offset
}

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

///|
pub struct StaticDataField {
  attributes : FieldAttributes
  type_index : UInt32
  name : String
}

///|
pub fn StaticDataField::attributes(self : StaticDataField) -> FieldAttributes {
  self.attributes
}

///|
pub fn StaticDataField::type_index(self : StaticDataField) -> UInt32 {
  self.type_index
}

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

///|
pub struct EnumeratedField {
  attributes : FieldAttributes
  value : NumericValue
  name : String
}

///|
pub fn EnumeratedField::attributes(self : EnumeratedField) -> FieldAttributes {
  self.attributes
}

///|
pub fn EnumeratedField::value(self : EnumeratedField) -> NumericValue {
  self.value
}

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

///|
pub struct OverloadedMethodField {
  count : UInt16
  method_list : UInt32
  name : String
}

///|
pub fn OverloadedMethodField::count(self : OverloadedMethodField) -> UInt16 {
  self.count
}

///|
pub fn OverloadedMethodField::method_list(
  self : OverloadedMethodField,
) -> UInt32 {
  self.method_list
}

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

///|
pub struct NestedTypeField {
  type_index : UInt32
  name : String
}

///|
pub fn NestedTypeField::type_index(self : NestedTypeField) -> UInt32 {
  self.type_index
}

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

///|
pub struct OneMethodField {
  attributes : FieldAttributes
  type_index : UInt32
  vtable_offset : Int32?
  name : String
}

///|
pub fn OneMethodField::attributes(self : OneMethodField) -> FieldAttributes {
  self.attributes
}

///|
pub fn OneMethodField::type_index(self : OneMethodField) -> UInt32 {
  self.type_index
}

///|
pub fn OneMethodField::vtable_offset(self : OneMethodField) -> Int32? {
  self.vtable_offset
}

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

///|
pub struct VFunctionTableField {
  type_index : UInt32
}

///|
pub fn VFunctionTableField::type_index(self : VFunctionTableField) -> UInt32 {
  self.type_index
}

///|
pub struct FriendClassField {
  type_index : UInt32
}

///|
pub fn FriendClassField::type_index(self : FriendClassField) -> UInt32 {
  self.type_index
}

///|
pub struct UnknownFieldTail {
  kind : UInt16
  payload : Bytes
}

///|
pub fn UnknownFieldTail::kind(self : UnknownFieldTail) -> UInt16 {
  self.kind
}

///|
pub fn UnknownFieldTail::payload(self : UnknownFieldTail) -> Bytes {
  self.payload
}

///|
pub enum FieldListMember {
  BaseClass(BaseClassField)
  VirtualBaseClass(VirtualBaseClassField)
  Data(DataField)
  StaticData(StaticDataField)
  Enumerated(EnumeratedField)
  OverloadedMethod(OverloadedMethodField)
  NestedType(NestedTypeField)
  OneMethod(OneMethodField)
  VFunctionTable(VFunctionTableField)
  FriendClass(FriendClassField)
  UnknownTail(UnknownFieldTail)
}

///|
/// Resolve a field-list record and all records reached by LF_INDEX continuations.
pub fn TypeTable::field_list_members(
  self : TypeTable,
  type_index : UInt32,
) -> Array[FieldListMember] raise TpiError {
  let members = []
  let visited = []
  let mut current = Some(type_index)
  while current is Some(index) {
    if visited.contains(index) {
      raise InvalidTypeRecord("field-list continuation cycle")
    }
    visited.push(index)
    let record = match self.record(index) {
      Some(value) => value
      None => raise InvalidTypeRecord("field-list type index is out of range")
    }
    match record.details() {
      FieldList(value) => {
        for field_item in value.members() {
          members.push(field_item)
        }
        current = value.continuation()
      }
      _ => raise InvalidTypeRecord("type index does not reference LF_FIELDLIST")
    }
  }
  members
}

///|
fn parse_field_list(payload : Bytes) -> FieldListType raise TpiError {
  let members = []
  let mut continuation : UInt32? = None
  let mut cursor = 0
  while cursor < payload.length() {
    let start = cursor
    let (kind, after_kind) = read_tpi_u16(payload, cursor, payload.length())
    if kind == LF_INDEX {
      let (padding, cursor) = read_tpi_u16(
        payload,
        after_kind,
        payload.length(),
      )
      if padding != 0 {
        raise InvalidTypeRecord("LF_INDEX padding is not zero")
      }
      let (next_index, cursor) = read_tpi_u32(payload, cursor, payload.length())
      let cursor = skip_field_padding(payload, cursor, payload.length())
      if cursor != payload.length() {
        raise InvalidTypeRecord("LF_INDEX is not the final field member")
      }
      continuation = Some(next_index)
      break
    }
    let known = kind == LF_BCLASS ||
      kind == LF_VBCLASS ||
      kind == LF_IVBCLASS ||
      kind == LF_ENUMERATE ||
      kind == LF_MEMBER ||
      kind == LF_STMEMBER ||
      kind == LF_METHOD ||
      kind == LF_NESTTYPE ||
      kind == LF_ONEMETHOD ||
      kind == LF_VFUNCTAB ||
      kind == LF_FRIENDCLS
    if !known {
      members.push(
        UnknownTail(UnknownFieldTail::{
          kind,
          payload: payload
          .exact_view(start=after_kind, end=payload.length())
          .to_owned(),
        }),
      )
      break
    }
    let (field_item, after_member) = match kind {
      LF_BCLASS => parse_base_class(payload, after_kind)
      LF_VBCLASS => parse_virtual_base_class(payload, after_kind, false)
      LF_IVBCLASS => parse_virtual_base_class(payload, after_kind, true)
      LF_ENUMERATE => parse_enumerated(payload, after_kind)
      LF_MEMBER => parse_data_field(payload, after_kind)
      LF_STMEMBER => parse_static_data_field(payload, after_kind)
      LF_METHOD => parse_overloaded_method(payload, after_kind)
      LF_NESTTYPE => parse_nested_type(payload, after_kind)
      LF_ONEMETHOD => parse_one_method(payload, after_kind)
      LF_VFUNCTAB => parse_vfunction_table(payload, after_kind)
      LF_FRIENDCLS => parse_friend_class(payload, after_kind)
      _ => raise InvalidTypeRecord("unreachable unknown field kind")
    }
    members.push(field_item)
    cursor = skip_field_padding(payload, after_member, payload.length())
    if cursor <= start {
      raise InvalidTypeRecord("field-list member did not advance")
    }
  }
  FieldListType::{ bytes: payload, members, continuation, }
}

///|
fn parse_base_class(
  payload : Bytes,
  start : Int32,
) -> (FieldListMember, Int32) raise TpiError {
  let (attributes, cursor) = read_field_attributes(payload, start)
  let (base_type, cursor) = read_tpi_u32(payload, cursor, payload.length())
  let (offset, cursor) = read_numeric_value(payload, cursor, payload.length())
  (BaseClass(BaseClassField::{ attributes, base_type, offset, }), cursor)
}

///|
fn parse_virtual_base_class(
  payload : Bytes,
  start : Int32,
  virtual_inheritance : Bool,
) -> (FieldListMember, Int32) raise TpiError {
  let (attributes, cursor) = read_field_attributes(payload, start)
  let (base_type, cursor) = read_tpi_u32(payload, cursor, payload.length())
  let (vbptr_type, cursor) = read_tpi_u32(payload, cursor, payload.length())
  let (vbptr_offset, cursor) = read_numeric_value(
    payload,
    cursor,
    payload.length(),
  )
  let (vtable_index, cursor) = read_numeric_value(
    payload,
    cursor,
    payload.length(),
  )
  (
    VirtualBaseClass(VirtualBaseClassField::{
      virtual_inheritance,
      attributes,
      base_type,
      vbptr_type,
      vbptr_offset,
      vtable_index,
    }),
    cursor,
  )
}

///|
fn parse_enumerated(
  payload : Bytes,
  start : Int32,
) -> (FieldListMember, Int32) raise TpiError {
  let (attributes, cursor) = read_field_attributes(payload, start)
  let (value, cursor) = read_numeric_value(payload, cursor, payload.length())
  let (name, cursor) = read_tpi_string(payload, cursor, payload.length())
  (Enumerated(EnumeratedField::{ attributes, value, name, }), cursor)
}

///|
fn parse_data_field(
  payload : Bytes,
  start : Int32,
) -> (FieldListMember, Int32) raise TpiError {
  let (attributes, cursor) = read_field_attributes(payload, start)
  let (type_index, cursor) = read_tpi_u32(payload, cursor, payload.length())
  let (offset, cursor) = read_numeric_value(payload, cursor, payload.length())
  let (name, cursor) = read_tpi_string(payload, cursor, payload.length())
  (Data(DataField::{ attributes, type_index, offset, name, }), cursor)
}

///|
fn parse_static_data_field(
  payload : Bytes,
  start : Int32,
) -> (FieldListMember, Int32) raise TpiError {
  let (attributes, cursor) = read_field_attributes(payload, start)
  let (type_index, cursor) = read_tpi_u32(payload, cursor, payload.length())
  let (name, cursor) = read_tpi_string(payload, cursor, payload.length())
  (StaticData(StaticDataField::{ attributes, type_index, name, }), cursor)
}

///|
fn parse_overloaded_method(
  payload : Bytes,
  start : Int32,
) -> (FieldListMember, Int32) raise TpiError {
  let (count, cursor) = read_tpi_u16(payload, start, payload.length())
  let (method_list, cursor) = read_tpi_u32(payload, cursor, payload.length())
  let (name, cursor) = read_tpi_string(payload, cursor, payload.length())
  (
    OverloadedMethod(OverloadedMethodField::{ count, method_list, name, }),
    cursor,
  )
}

///|
fn parse_nested_type(
  payload : Bytes,
  start : Int32,
) -> (FieldListMember, Int32) raise TpiError {
  let (padding, cursor) = read_tpi_u16(payload, start, payload.length())
  if padding != 0 {
    raise InvalidTypeRecord("LF_NESTTYPE padding is not zero")
  }
  let (type_index, cursor) = read_tpi_u32(payload, cursor, payload.length())
  let (name, cursor) = read_tpi_string(payload, cursor, payload.length())
  (NestedType(NestedTypeField::{ type_index, name, }), cursor)
}

///|
fn parse_one_method(
  payload : Bytes,
  start : Int32,
) -> (FieldListMember, Int32) raise TpiError {
  let (attributes, cursor) = read_field_attributes(payload, start)
  let (type_index, cursor) = read_tpi_u32(payload, cursor, payload.length())
  let (vtable_offset, cursor) = if attributes.is_introducing_virtual() {
    let (value, next) = read_tpi_u32(payload, cursor, payload.length())
    (Some(value.reinterpret_as_int()), next)
  } else {
    (None, cursor)
  }
  let (name, cursor) = read_tpi_string(payload, cursor, payload.length())
  (
    OneMethod(OneMethodField::{ attributes, type_index, vtable_offset, name, }),
    cursor,
  )
}

///|
fn parse_vfunction_table(
  payload : Bytes,
  start : Int32,
) -> (FieldListMember, Int32) raise TpiError {
  let (padding, cursor) = read_tpi_u16(payload, start, payload.length())
  if padding != 0 {
    raise InvalidTypeRecord("LF_VFUNCTAB padding is not zero")
  }
  let (type_index, cursor) = read_tpi_u32(payload, cursor, payload.length())
  (VFunctionTable(VFunctionTableField::{ type_index, }), cursor)
}

///|
fn parse_friend_class(
  payload : Bytes,
  start : Int32,
) -> (FieldListMember, Int32) raise TpiError {
  let (padding, cursor) = read_tpi_u16(payload, start, payload.length())
  if padding != 0 {
    raise InvalidTypeRecord("LF_FRIENDCLS padding is not zero")
  }
  let (type_index, cursor) = read_tpi_u32(payload, cursor, payload.length())
  (FriendClass(FriendClassField::{ type_index, }), cursor)
}

///|
fn read_field_attributes(
  payload : Bytes,
  start : Int32,
) -> (FieldAttributes, Int32) raise TpiError {
  let (raw, cursor) = read_tpi_u16(payload, start, payload.length())
  (FieldAttributes::{ raw, }, cursor)
}

///|
fn skip_field_padding(payload : Bytes, start : Int32, end : Int32) -> Int32 {
  let mut cursor = start
  while cursor < end && payload.at(cursor).to_int() >= 0xF0 {
    cursor = cursor + 1
  }
  cursor
}