///|
/// Select/reorder unique zero-based trace indices. Preserve raw headers and
/// samples; sequence_file is re-numbered from one, original line IDs remain.
pub fn Dataset::select(self : Dataset, indices : Array[Int]) -> Dataset raise {
  if indices.is_empty() || indices.length() > self.trace_count() {
    raise Failure("empty/oversized trace selection")
  }
  let seen : Map[Int, Bool] = Map([])
  let traces = Array::makei(indices.length(), i => {
    let k = indices[i]
    if seen.contains(k) {
      raise Failure("duplicate trace index")
    }
    seen[k] = true
    let t = self.trace(k)
    let head = Array::makei(240, p => t.head[p])
    put_uint(head, 4, 4, (i + 1).to_uint64(), t.byte_order)
    { ..t, head: Bytes::from_array(head), }
  })
  assemble(self.header, traces, self.extensions)
}

///|
/// Raw signed header interval, inclusive. Returns [] when no trace matches.
pub fn Dataset::find_traces(
  self : Dataset,
  field : String,
  minimum : Int,
  maximum : Int,
) -> Array[Int] raise {
  if minimum > maximum {
    raise Failure("invalid header range")
  }
  ignore(field_location(field))
  let found = []
  for i in 0..= minimum && v <= maximum {
      found.push(i)
    }
  }
  found
}

///|
pub(all) struct TraceGroup {
  value : Int
  indices : Array[Int]
} derive(ToJson)

///|
pub extend TraceGroup with ToJson::{to_json}

///|
/// Group by an unscaled header value; groups retain first-occurrence order.
pub fn Dataset::groups(
  self : Dataset,
  field : String,
) -> Array[TraceGroup] raise {
  ignore(field_location(field))
  let lookup : Map[Int, Int] = Map([])
  let result : Array[TraceGroup] = []
  for i in 0.. Trace raise {
  if first < 0 || count < 1 || first > t.count || count > t.count - first {
    raise Failure("window outside trace")
  }
  let time_scale = if t.revision == 0 {
    1.0
  } else {
    scalar(t.field("time_scalar"))
  }
  let raw_delay = t.field("delay_ms").to_double() +
    first.to_double() * t.dt / 1000.0 / time_scale
  let nearest = raw_delay.round()
  if (raw_delay - nearest).abs() > 1.0e-8 ||
    nearest < -32768.0 ||
    nearest > 32767.0 {
    raise Failure("window delay not representable with existing time scalar")
  }
  let head = Array::makei(240, i => t.head[i])
  put_uint(
    head,
    108,
    2,
    nearest.to_int64().reinterpret_as_uint64(),
    t.byte_order,
  )
  put_uint(
    head,
    114,
    2,
    if count <= 65535 {
      count.to_uint64()
    } else {
      0UL
    },
    t.byte_order,
  )
  let width = sample_width(t.code)
  {
    ..t,
    head: Bytes::from_array(head),
    payload: slice(t.payload, first * width, count * width),
    count,
  }
}

///|
/// Same sample-index window on every trace; rejects any too-short trace. Raw
/// sample bytes remain exact, including int64 and nonfinite IEEE bit patterns.
pub fn Dataset::window(
  self : Dataset,
  first : Int,
  count : Int,
) -> Dataset raise {
  let traces = Array::makei(self.trace_count(), i => {
    window_trace(self.trace(i), first, count)
  })
  assemble(self.header, traces, self.extensions)
}

///|
/// Convert samples without endian/revision changes. No silent integer rounding
/// or lossy int64-to-Double conversion. Identical format returns the same data.
pub fn Dataset::convert_samples(self : Dataset, code : Int) -> Dataset raise {
  ignore(sample_width(code))
  if code == self.header.sample_code {
    return self
  }
  let prefix = Array::makei(3600, i => self.header.raw[i])
  put_uint(prefix, 3224, 2, code.to_uint64(), self.header.endian)
  let header = parse_header(
    Bytes::from_array(prefix),
    endian=self.header.endian,
    text_encoding=self.header.text_encoding,
    legacy_revision_one=self.header.assumed_legacy_revision_one,
  )
  let traces = Array::makei(self.trace_count(), i => {
    let t = self.trace(i)
    let samples = Array::makei(t.count, j => {
      let sample = t.sample(j)
      if code == 1 || code == 5 || code == 6 {
        sample
      } else {
        match sample {
          Signed(v) => Signed(v)
          Real(v) => {
            if !finite(v) || v.abs() > 9007199254740992.0 || v != v.trunc() {
              raise Failure("float cannot be converted to exact integer")
            }
            Signed(v.to_int64())
          }
        }
      }
    })
    { ..t, payload: encode_samples(samples, code, t.byte_order), code, }
  })
  assemble(header, traces, self.extensions)
}