///|
/// Header fields are retained; counts and extents can be audited against records.
pub(all) struct Header {
  minor : Int32
  file_source_id : Int32
  global_encoding : Int32
  project_id : Bytes
  system : String
  software : String
  day : Int32
  year : Int32
  point_offset : Int32
  vlr_count : Int32
  format : Int32
  record_length : Int32
  point_count : Int32
  by_return : Array[Int32]
  transform : Transform
  bounds : (Double, Double, Double, Double, Double, Double)
} derive(Debug)

///|
pub fn read_header(
  data : Bytes,
  limits~ : Limits = Limits::default(),
) -> Header raise LasError {
  limits.validate()
  if data.length() > limits.max_bytes {
    raise Limit("input byte budget exceeded")
  }
  let r = Reader::new(data, 0, data.length())
  require(r.take(4) == b"LASF", "missing LASF signature")
  let file_source_id = r.u16()
  let global_encoding = r.u16()
  let project_id = r.take(16)
  let major = r.u8()
  let minor = r.u8()
  if major != 1 || (minor != 2 && minor != 4) {
    raise Unsupported("only LAS 1.2 and 1.4 are supported")
  }
  let system = r.text(32)
  let software = r.text(32)
  let day = r.u16()
  let year = r.u16()
  require(day <= 366, "invalid creation day")
  let header_size = r.u16()
  require(
    header_size == (if minor == 2 { 227 } else { 375 }),
    "extended public headers are unsupported",
  )
  let point_offset = r.u32(limits.max_bytes)
  let vlr_count = r.u32(limits.max_vlrs)
  let format = r.u8()
  if format >= 64 {
    raise Unsupported("compressed or reserved point format bits")
  }
  let width = record_size(format)
  require(minor == 4 || format <= 3, "point format requires LAS 1.4")
  let record_length = r.u16()
  require(record_length >= width, "point record is shorter than the format")
  let legacy_count = r.u32(limits.max_points)
  let legacy_returns = Array::makei(5, _ => r.u32(limits.max_points))
  let sx = r.f64()
  let sy = r.f64()
  let sz = r.f64()
  let ox = r.f64()
  let oy = r.f64()
  let oz = r.f64()
  let transform = { sx, sy, sz, ox, oy, oz, }
  transform.validate()
  let xmax = r.f64()
  let xmin = r.f64()
  let ymax = r.f64()
  let ymin = r.f64()
  let zmax = r.f64()
  let zmin = r.f64()
  require(
    xmax >= xmin && ymax >= ymin && zmax >= zmin,
    "reversed header bounds",
  )
  let mut point_count = legacy_count
  let mut by_return = legacy_returns
  if minor == 4 {
    require(r.u64(0) == 0, "waveform packet data unsupported")
    require(r.u64(0) == 0, "EVLR data unsupported")
    require(r.u32(0) == 0, "EVLR records unsupported")
    point_count = r.u64(limits.max_points)
    by_return = Array::makei(15, _ => r.u64(limits.max_points))
    if format >= 6 {
      require(
        legacy_count == 0 && legacy_returns.all(x => x == 0),
        "LAS 1.4 formats require zero legacy counts",
      )
    } else if legacy_count != 0 || !legacy_returns.all(x => x == 0) {
      require(
        legacy_count == point_count,
        "legacy and extended point counts disagree",
      )
      for i in 0..<5 {
        require(
          legacy_returns[i] == by_return[i],
          "legacy return counts disagree",
        )
      }
    }
  }
  let allowed = if minor == 2 { 1 } else { 25 }
  require(
    (global_encoding & allowed) == global_encoding,
    "unsupported global encoding bits",
  )
  require(
    point_offset >= header_size && point_offset <= data.length(),
    "point offset outside input",
  )
  checked_size(point_count, record_length, data.length() - point_offset)
  {
    minor,
    file_source_id,
    global_encoding,
    project_id,
    system,
    software,
    day,
    year,
    point_offset,
    vlr_count,
    format,
    record_length,
    point_count,
    by_return,
    transform,
    bounds: (xmin, ymin, zmin, xmax, ymax, zmax),
  }
}

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