///|
/// Helper to read a 16-bit unsigned integer in big-endian byte order.
fn read_uint16_be(bytes : Bytes, offset : Int) -> Int {
  let b0 = bytes[offset].to_int()
  let b1 = bytes[offset + 1].to_int()
  (b0 << 8) | b1
}

///|
/// Helper to read a 16-bit signed integer in big-endian byte order.
fn read_int16_be(bytes : Bytes, offset : Int) -> Int {
  let u = read_uint16_be(bytes, offset)
  if (u & 0x8000) != 0 {
    u - 65536
  } else {
    u
  }
}

///|
/// Parse a single miniSEED data record.
/// Supports uncompressed 32-bit float (is_float = true) or uncompressed 32-bit integer (is_float = false) data.
pub fn parse_miniseed_record(
  bytes : Bytes,
  is_float : Bool,
) -> Result[Trace, String] {
  if bytes.length() < 48 {
    return Err("Bytes too small for miniSEED FSDH")
  }

  // miniSEED headers are big-endian by definition
  let station = read_string(bytes, 8, 5)
  let channel = read_string(bytes, 15, 3)

  let _year = read_uint16_be(bytes, 20)
  let jday = read_uint16_be(bytes, 22)
  let hour = bytes[24].to_int()
  let min = bytes[25].to_int()
  let sec = bytes[26].to_int()
  let msec = read_uint16_be(bytes, 28) / 10 // 0.0001s to ms

  let num_samples = read_uint16_be(bytes, 30)
  let rate_factor = read_int16_be(bytes, 32)
  let rate_multiplier = read_int16_be(bytes, 34)

  let data_offset = read_uint16_be(bytes, 44)

  if data_offset + num_samples * 4 > bytes.length() {
    return Err("Record size too small for specified sample count and offset")
  }

  // Compute sampling rate from factor (F) and multiplier (M)
  let sampling_rate = if rate_factor > 0 && rate_multiplier > 0 {
    rate_factor.to_double() * rate_multiplier.to_double()
  } else if rate_factor > 0 && rate_multiplier < 0 {
    rate_factor.to_double() / (-rate_multiplier).to_double()
  } else if rate_factor < 0 && rate_multiplier > 0 {
    (-rate_factor).to_double() / rate_multiplier.to_double()
  } else if rate_factor < 0 && rate_multiplier < 0 {
    1.0 / ((-rate_factor).to_double() * (-rate_multiplier).to_double())
  } else {
    1.0
  }

  let data = Array::new(capacity=num_samples)
  for i = 0; i < num_samples; i = i + 1 {
    let offset = data_offset + i * 4
    let val = if is_float {
      read_float32(bytes, offset, true).to_double()
    } else {
      read_int32(bytes, offset, true).to_double()
    }
    data.push(val)
  }

  // Approximate start time in seconds relative to the beginning of the year
  let start_time = (jday * 24 * 3600 + hour * 3600 + min * 60 + sec).to_double() +
    msec.to_double() / 1000.0

  Ok(Trace::new(station, channel, sampling_rate, start_time, data))
}

///|
/// Serialize a Trace into miniSEED binary bytes.
pub fn to_miniseed_bytes(
  trace : Trace,
  sequence_number : Int,
  is_float : Bool,
) -> Bytes {
  let npts = trace.npts()
  let data_offset = 64
  let required_size = data_offset + npts * 4
  let record_size = if required_size % 256 == 0 {
    required_size
  } else {
    (required_size / 256 + 1) * 256
  }

  let bytes = FixedArray::make(record_size, b'\x00')

  // Sequence number (6 ASCII chars, e.g. "000001")
  let seq_str = sequence_number.to_string()
  let seq_len = seq_str.length()
  for i = 0; i < 6; i = i + 1 {
    if i < 6 - seq_len {
      bytes[i] = b'\x30' // '0'
    } else {
      let idx = i - (6 - seq_len)
      bytes[i] = seq_str[idx].to_int().to_byte()
    }
  }

  bytes[6] = b'\x44' // 'D'
  bytes[7] = b'\x20' // space

  write_string_field_bytes(bytes, 8, trace.station, 5)
  write_string_field_bytes(bytes, 15, trace.channel, 3)
  write_string_field_bytes(bytes, 13, "", 2)
  write_string_field_bytes(bytes, 18, "XX", 2)

  // Derive start time parameters
  let total_sec = trace.start_time.to_int()
  let msec = ((trace.start_time - total_sec.to_double()) * 10000.0).to_int()
  let jday = total_sec / (24 * 3600)
  let rem = total_sec % (24 * 3600)
  let hour = rem / 3600
  let rem2 = rem % 3600
  let min = rem2 / 60
  let sec = rem2 % 60

  write_uint16_be_bytes(bytes, 20, 2026) // default year
  write_uint16_be_bytes(bytes, 22, jday + 1)
  bytes[24] = hour.to_byte()
  bytes[25] = min.to_byte()
  bytes[26] = sec.to_byte()
  bytes[27] = b'\x00'
  write_uint16_be_bytes(bytes, 28, msec)

  write_uint16_be_bytes(bytes, 30, npts)

  // Format sample rate
  let rate_int = trace.sampling_rate.to_int()
  if rate_int.to_double() == trace.sampling_rate {
    write_int16_be_bytes(bytes, 32, rate_int)
    write_int16_be_bytes(bytes, 34, 1)
  } else {
    let factor = (trace.sampling_rate * 100.0).to_int()
    write_int16_be_bytes(bytes, 32, factor)
    write_int16_be_bytes(bytes, 34, -100)
  }

  write_uint16_be_bytes(bytes, 44, data_offset)

  // Write data
  for i = 0; i < npts; i = i + 1 {
    let offset = data_offset + i * 4
    if is_float {
      let val = Float::from_double(trace.data[i])
      let bits = Float::reinterpret_as_int(val)
      write_int32_bytes(bytes, offset, bits, true)
    } else {
      let bits = trace.data[i].to_int()
      write_int32_bytes(bytes, offset, bits, true)
    }
  }

  bytes.unsafe_reinterpret_as_bytes()
}

///|
fn write_string_field_bytes(
  bytes : FixedArray[Byte],
  offset : Int,
  s : String,
  max_length : Int,
) -> Unit {
  let len = s.length()
  for i = 0; i < max_length; i = i + 1 {
    if i < len {
      bytes[offset + i] = s[i].to_int().to_byte()
    } else {
      bytes[offset + i] = b'\x20'
    }
  }
}

///|
fn write_uint16_be_bytes(
  bytes : FixedArray[Byte],
  offset : Int,
  value : Int,
) -> Unit {
  bytes[offset] = (value >> 8).to_byte()
  bytes[offset + 1] = (value & 0xff).to_byte()
}

///|
fn write_int16_be_bytes(
  bytes : FixedArray[Byte],
  offset : Int,
  value : Int,
) -> Unit {
  write_uint16_be_bytes(bytes, offset, value)
}

///|
fn write_int32_bytes(
  bytes : FixedArray[Byte],
  offset : Int,
  value : Int,
  is_big_endian : Bool,
) -> Unit {
  let b0 = (value & 0xff).to_byte()
  let b1 = ((value >> 8) & 0xff).to_byte()
  let b2 = ((value >> 16) & 0xff).to_byte()
  let b3 = ((value >> 24) & 0xff).to_byte()
  if is_big_endian {
    bytes[offset] = b3
    bytes[offset + 1] = b2
    bytes[offset + 2] = b1
    bytes[offset + 3] = b0
  } else {
    bytes[offset] = b0
    bytes[offset + 1] = b1
    bytes[offset + 2] = b2
    bytes[offset + 3] = b3
  }
}