///|
fn read_strings(
  r : Reader,
  index : RawIndex,
  target : Array[Value],
) -> Unit raise TdmsError {
  demand(index.size <= r.end - r.pos, "truncated string chunk")
  let offset_bytes = checked_product(index.count, 4, index.size)
  let payload_bytes = index.size - offset_bytes
  let ends : Array[Int32] = []
  let mut previous = 0
  for i = 0; i < index.count; i = i + 1 {
    let end = r.bounded(4, payload_bytes, "string offset")
    demand(
      end >= previous && end - previous <= r.limits.max_string_bytes,
      "invalid string offsets",
    )
    ends.push(end)
    previous = end
  }
  demand(previous == payload_bytes, "string offsets do not cover payload")
  previous = 0
  for end in ends {
    let b = r.take(end - previous)
    let text = @utf8.decode(b) catch {
      _ => raise Invalid("invalid UTF-8 channel value")
    }
    target.push(Textual(text))
    previous = end
  }
}

///|
fn read_contiguous(
  bytes : Bytes,
  seg : SegmentInfo,
  state : ReadState,
  limits : Limits,
) -> Unit raise TdmsError {
  if !flag(seg.toc, 8U) {
    return
  }

  let r = Reader::new(
    bytes,
    seg.raw_offset,
    seg.end_offset,
    seg.big_endian,
    limits,
  )
  let channels : Array[(Int32, RawIndex)] = []
  let mut chunk_size = 0
  let mut sample_count = 0
  for i in state.active {
    if !state.enabled[i] {
      continue
    }
    let idx = state.cache[i].unwrap()
    demand(idx.size <= limits.max_bytes - chunk_size, "chunk byte overflow")
    demand(
      idx.count <= limits.max_samples - sample_count,
      "chunk sample overflow",
    )
    chunk_size += idx.size
    sample_count += idx.count
    channels.push((i, idx))
    match state.endian[i] {
      Some(big) => demand(big == seg.big_endian, "channel endianness changed")
      None => state.endian[i] = Some(seg.big_endian)
    }
  }
  let raw_size = r.end - r.pos
  if chunk_size == 0 {
    demand(raw_size == 0, "raw bytes without nonempty indexes")
    return
  }
  demand(raw_size % chunk_size == 0, "incomplete trailing raw chunk")
  let chunks = raw_size / chunk_size
  let total = checked_product(
    chunks,
    sample_count,
    limits.max_samples - state.samples,
  )
  state.samples += total
  for chunk = 0; chunk < chunks; chunk = chunk + 1 {
    if flag(seg.toc, 32U) {
      read_interleaved(r, channels, state)
      continue
    }
    for (i, idx) in channels {
      let target = state.doc.objects[i].values
      if idx.kind == Text {
        read_strings(r, idx, target)
      } else {
        for sample = 0; sample < idx.count; sample = sample + 1 {
          target.push(r.scalar(idx.kind))
        }
      }
    }
  }
  demand(r.pos == r.end, "unconsumed raw bytes")
}

///|
pub fn read(
  bytes : Bytes,
  limits~ : Limits = Limits::default(),
) -> Document raise TdmsError {
  let segments = inspect(bytes, limits~)
  let state = ReadState::new()
  for seg in segments {
    read_metadata(bytes, seg, state, limits)
    read_contiguous(bytes, seg, state, limits)
  }
  state.doc
}