///|
/// Helper to read a 32-bit integer from bytes with specified endianness.
fn read_int32(bytes : Bytes, offset : Int, is_big_endian : Bool) -> Int {
  let b0 = bytes[offset].to_int()
  let b1 = bytes[offset + 1].to_int()
  let b2 = bytes[offset + 2].to_int()
  let b3 = bytes[offset + 3].to_int()
  if is_big_endian {
    (b0 << 24) | (b1 << 16) | (b2 << 8) | b3
  } else {
    b0 | (b1 << 8) | (b2 << 16) | (b3 << 24)
  }
}

///|
/// Helper to read a 32-bit float from bytes with specified endianness.
fn read_float32(bytes : Bytes, offset : Int, is_big_endian : Bool) -> Float {
  let bits = read_int32(bytes, offset, is_big_endian)
  Float::reinterpret_from_int(bits)
}

///|
/// Helper to write a 32-bit integer to a FixedArray[Byte] with specified endianness.
fn write_int32(
  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
  }
}

///|
/// Helper to write a 32-bit float to a FixedArray[Byte] with specified endianness.
fn write_float32(
  bytes : FixedArray[Byte],
  offset : Int,
  value : Float,
  is_big_endian : Bool,
) -> Unit {
  let bits = Float::reinterpret_as_int(value)
  write_int32(bytes, offset, bits, is_big_endian)
}

///|
/// Read an ASCII string from a fixed-length header field in Bytes.
fn read_string(bytes : Bytes, offset : Int, length : Int) -> String {
  let mut s = ""
  for i = 0; i < length; i = i + 1 {
    let b = bytes[offset + i].to_int()
    if b == 0 {
      break
    }
    if b >= 32 && b <= 126 {
      let char_opt = b.to_char()
      match char_opt {
        Some(c) => s = s + c.to_string()
        None => ()
      }
    }
  }

  // Trim spaces
  let mut start = 0
  let mut end = s.length()
  while start < end && s[start] == ' ' {
    start = start + 1
  }
  while end > start && s[end - 1] == ' ' {
    end = end - 1
  }
  s.unsafe_substring(start~, end~)
}

///|
/// Write a string to a fixed-length header field in a FixedArray[Byte], padding with spaces.
fn write_string_field(
  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 {
      let char_code = s[i].to_int()
      bytes[offset + i] = char_code.to_byte()
    } else {
      bytes[offset + i] = b'\x20' // space
    }
  }
}

///|
/// Parse a SAC binary file from a byte array.
pub fn parse_sac(bytes : Bytes) -> Result[Trace, String] {
  if bytes.length() < 632 {
    return Err("Byte array too small for SAC header")
  }

  // Detect endianness using nvhdr (offset 304, integer index 6)
  let nvhdr_le = read_int32(bytes, 304, false)
  let (is_big_endian, version) = if nvhdr_le == 6 {
    (false, 6)
  } else {
    let nvhdr_be = read_int32(bytes, 304, true)
    if nvhdr_be == 6 {
      (true, 6)
    } else {
      (false, nvhdr_le)
    }
  }

  if version != 6 {
    return Err("Unsupported SAC version: \{version}")
  }

  let npts = read_int32(bytes, 316, is_big_endian) // integer index 9
  if npts < 0 {
    return Err("Invalid npts in SAC header: \{npts}")
  }

  let expected_size = 632 + npts * 4
  if bytes.length() < expected_size {
    return Err(
      "Byte array too small for data: expected \{expected_size}, got \{bytes.length()}",
    )
  }

  let delta = read_float32(bytes, 0, is_big_endian).to_double()
  let b = read_float32(bytes, 20, is_big_endian).to_double()

  let station = read_string(bytes, 440, 8)
  let channel = read_string(bytes, 608, 8)

  // Read data points
  let data = Array::new(capacity=npts)
  for i = 0; i < npts; i = i + 1 {
    let val = read_float32(bytes, 632 + i * 4, is_big_endian).to_double()
    data.push(val)
  }

  let sampling_rate = if delta <= 0.0 { 1.0 } else { 1.0 / delta }

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

///|
/// Serialize a Trace into SAC binary bytes.
pub fn to_sac_bytes(trace : Trace, is_big_endian : Bool) -> Bytes {
  let npts = trace.npts()
  let size = 632 + npts * 4
  let bytes = FixedArray::make(size, b'\x00')

  // Fill float headers with standard undefined values (-12345.0)
  for i = 0; i < 70; i = i + 1 {
    write_float32(bytes, i * 4, -12345.0, is_big_endian)
  }

  // Fill integer headers with -12345
  for i = 0; i < 40; i = i + 1 {
    write_int32(bytes, 280 + i * 4, -12345, is_big_endian)
  }

  // Fill string headers with spaces (ASCII 32)
  for i = 0; i < 192; i = i + 1 {
    bytes[440 + i] = b'\x20'
  }

  // Set specific fields
  let delta = Float::from_double(trace.delta())
  write_float32(bytes, 0, delta, is_big_endian)

  let b = Float::from_double(trace.start_time)
  write_float32(bytes, 20, b, is_big_endian)

  // nvhdr (version) = 6
  write_int32(bytes, 304, 6, is_big_endian)

  // npts
  write_int32(bytes, 316, npts, is_big_endian)

  // iftype = 1 (Time series)
  write_int32(bytes, 340, 1, is_big_endian)

  // station
  write_string_field(bytes, 440, trace.station, 8)
  // channel
  write_string_field(bytes, 608, trace.channel, 8)

  // Write data
  for i = 0; i < npts; i = i + 1 {
    let val = Float::from_double(trace.data[i])
    write_float32(bytes, 632 + i * 4, val, is_big_endian)
  }

  bytes.unsafe_reinterpret_as_bytes()
}