///|
/// Fluent HID descriptor builder. The first error is retained until build().
/// build() validates generated bytes with compile_descriptor before returning.
pub struct DescriptorBuilder {
  priv items : Array[Item]
  priv mut length : Int
  priv mut error : Diagnostic?
}

///|
pub fn DescriptorBuilder::new() -> DescriptorBuilder {
  { items: [], length: 0, error: None, }
}

///|
fn DescriptorBuilder::reject(
  self : DescriptorBuilder,
  code : String,
  message : String,
) -> DescriptorBuilder {
  if self.error is None {
    self.error = Some(diagnostic(self.length, code, message))
  }
  self
}

///|
fn DescriptorBuilder::append(
  self : DescriptorBuilder,
  type_code : Int,
  tag : Int,
  data : Bytes,
) -> DescriptorBuilder {
  if self.error is Some(_) {
    return self
  }
  if 1 + data.length() > 65536 - self.length {
    return self.reject("descriptor_limit", "Descriptor exceeds 65536 bytes")
  }
  self.items.push({ offset: self.length, type_code, tag, data, is_long: false, })
  self.length = self.length + 1 + data.length()
  self
}

///|
/// Adds an integer short item using the smallest 1, 2 or 4 byte representation.
/// Use signed=true for signed items; Push/Pop and EndCollection use methods below.
pub fn DescriptorBuilder::short_item(
  self : DescriptorBuilder,
  type_code : Int,
  tag : Int,
  value : Int64,
  signed? : Bool = false,
) -> DescriptorBuilder {
  if self.error is Some(_) {
    return self
  }
  if type_code < 0 || type_code > 2 || tag < 0 || tag > 15 {
    return self.reject(
      "invalid_item", "Builder requires Main, Global or Local short items",
    )
  }
  if !integer_fits(value, 32, signed) {
    return self.reject(
      "item_value", "Value exceeds the short item's 32-bit domain",
    )
  }
  let size = if integer_fits(value, 8, signed) {
    1
  } else if integer_fits(value, 16, signed) {
    2
  } else {
    4
  }
  self.append(
    type_code,
    tag,
    Bytes::makei(size, i => ((value >> (8 * i)) & 255L).to_byte()),
  )
}

///|
pub fn DescriptorBuilder::usage_page(
  self : DescriptorBuilder,
  page : Int,
) -> DescriptorBuilder {
  if page < 0 || page > 65535 {
    return self.reject("usage_page", "Usage Page must fit 16 bits")
  }
  self.short_item(1, 0, page.to_int64())
}

///|
pub fn DescriptorBuilder::usage(
  self : DescriptorBuilder,
  id : Int,
) -> DescriptorBuilder {
  if id < 0 || id > 65535 {
    return self.reject("usage_range", "Usage ID must fit 16 bits")
  }
  self.short_item(2, 0, id.to_int64())
}

///|
/// Emits a four-byte Usage carrying its own page, including page zero.
pub fn DescriptorBuilder::extended_usage(
  self : DescriptorBuilder,
  usage : Usage,
) -> DescriptorBuilder {
  if usage.page < 0 || usage.page > 65535 || usage.id < 0 || usage.id > 65535 {
    return self.reject(
      "usage_range", "Extended Usage components must fit 16 bits",
    )
  }
  let value = (usage.page.to_int64() << 16) | usage.id.to_int64()
  self.append(2, 0, Bytes::makei(4, i => ((value >> (8 * i)) & 255L).to_byte()))
}

///|
pub fn DescriptorBuilder::usage_range(
  self : DescriptorBuilder,
  minimum : Int,
  maximum : Int,
) -> DescriptorBuilder {
  if minimum < 0 || maximum > 65535 || minimum > maximum {
    return self.reject(
      "usage_range", "Usage range must be ordered and fit 16 bits",
    )
  }
  self.short_item(2, 1, minimum.to_int64()).short_item(2, 2, maximum.to_int64())
}

///|
pub fn DescriptorBuilder::logical_range(
  self : DescriptorBuilder,
  minimum : Int64,
  maximum : Int64,
) -> DescriptorBuilder {
  if minimum > maximum {
    return self.reject(
      "logical_range", "Logical Maximum is below Logical Minimum",
    )
  }
  self.short_item(1, 1, minimum, signed=true).maximum_item(2, minimum, maximum)
}

///|
pub fn DescriptorBuilder::physical_range(
  self : DescriptorBuilder,
  minimum : Int64,
  maximum : Int64,
) -> DescriptorBuilder {
  self.short_item(1, 3, minimum, signed=true).maximum_item(4, minimum, maximum)
}

///|
/// With a non-negative Minimum, Linux reads Maximum as unsigned while other
/// parsers may read it as signed. Below 2^31 a width where both readings
/// agree is chosen (255 becomes 26 ff 00, see lint logical_max_sign); larger
/// unsigned values need all four bytes.
fn DescriptorBuilder::maximum_item(
  self : DescriptorBuilder,
  tag : Int,
  minimum : Int64,
  maximum : Int64,
) -> DescriptorBuilder {
  if minimum >= 0L && maximum < 0L {
    return self.reject(
      "item_value", "A negative Maximum needs a negative Minimum",
    )
  }
  self.short_item(
    1,
    tag,
    maximum,
    signed=minimum < 0L || maximum <= 2147483647L,
  )
}

///|
pub fn DescriptorBuilder::unit(
  self : DescriptorBuilder,
  raw : Int64,
) -> DescriptorBuilder {
  self.short_item(1, 6, raw)
}

///|
pub fn DescriptorBuilder::unit_exponent(
  self : DescriptorBuilder,
  exponent : Int,
) -> DescriptorBuilder {
  if exponent < -8 || exponent > 7 {
    return self.reject("unit_exponent", "Unit Exponent must be -8..7")
  }
  self.short_item(1, 5, (exponent & 15).to_int64())
}

///|
pub fn DescriptorBuilder::report_size(
  self : DescriptorBuilder,
  bits : Int,
) -> DescriptorBuilder {
  if bits < 1 || bits > 65536 {
    return self.reject("report_size", "Report Size must be 1..65536")
  }
  self.short_item(1, 7, bits.to_int64())
}

///|
pub fn DescriptorBuilder::report_count(
  self : DescriptorBuilder,
  count : Int,
) -> DescriptorBuilder {
  if count < 1 || count > 65536 {
    return self.reject("report_count", "Report Count must be 1..65536")
  }
  self.short_item(1, 9, count.to_int64())
}

///|
pub fn DescriptorBuilder::report_id(
  self : DescriptorBuilder,
  id : Int,
) -> DescriptorBuilder {
  if id < 1 || id > 255 {
    return self.reject("report_id", "Report ID must be 1..255")
  }
  self.short_item(1, 8, id.to_int64())
}

///|
pub fn DescriptorBuilder::collection(
  self : DescriptorBuilder,
  collection_type : Int,
) -> DescriptorBuilder {
  if collection_type < 0 || collection_type > 255 {
    return self.reject("collection", "Collection type must fit one byte")
  }
  self.short_item(0, 10, collection_type.to_int64())
}

///|
pub fn DescriptorBuilder::end_collection(
  self : DescriptorBuilder,
) -> DescriptorBuilder {
  self.append(0, 12, b"")
}

///|
pub fn DescriptorBuilder::push(self : DescriptorBuilder) -> DescriptorBuilder {
  self.append(1, 10, b"")
}

///|
pub fn DescriptorBuilder::pop(self : DescriptorBuilder) -> DescriptorBuilder {
  self.append(1, 11, b"")
}

///|
pub fn DescriptorBuilder::input(
  self : DescriptorBuilder,
  flags : Int,
) -> DescriptorBuilder {
  self.short_item(0, 8, flags.to_int64())
}

///|
pub fn DescriptorBuilder::output(
  self : DescriptorBuilder,
  flags : Int,
) -> DescriptorBuilder {
  self.short_item(0, 9, flags.to_int64())
}

///|
pub fn DescriptorBuilder::feature(
  self : DescriptorBuilder,
  flags : Int,
) -> DescriptorBuilder {
  self.short_item(0, 11, flags.to_int64())
}

///|
/// Returns only descriptors accepted by the layout compiler. Repeated calls are
/// safe; appending after build() continues the same builder without clearing it.
pub fn DescriptorBuilder::build(
  self : DescriptorBuilder,
) -> Result[Bytes, Diagnostic] {
  if self.error is Some(error) {
    return Err(error)
  }
  let bytes = match serialize_items(self.items) {
    Ok(bytes) => bytes
    Err(error) => return Err(error)
  }
  match compile_descriptor(bytes) {
    Ok(_) => Ok(bytes)
    Err(error) => Err(error)
  }
}