/**
 * Big-endian (network order) integer read/write helpers shared by every binary codec.
 * Kept dependency-free (no Buffer) so the browser bundle (P6) imports it unchanged.
 */
import { ContractDecodeError, ContractEncodeError } from './errors.js'

export const U32_MAX = 0xffff_ffff
export const U64_MAX = 0xffff_ffff_ffff_ffffn

/** Assert a value is a non-negative integer that fits in uint32; throw otherwise. */
export function assertUint32(value: number, field: string): void {
  if (!Number.isInteger(value) || value < 0 || value > U32_MAX) {
    throw new ContractEncodeError(`${field} must be a uint32 (0..${U32_MAX}); got ${value}`)
  }
}

/** Write a uint32 big-endian into `out` at `offset`. Returns the next offset. */
export function writeUint32BE(out: Uint8Array, offset: number, value: number): number {
  assertUint32(value, 'uint32')
  out[offset] = (value >>> 24) & 0xff
  out[offset + 1] = (value >>> 16) & 0xff
  out[offset + 2] = (value >>> 8) & 0xff
  out[offset + 3] = value & 0xff
  return offset + 4
}

/** Read a uint32 big-endian from `buf` at `offset`. */
export function readUint32BE(buf: Uint8Array, offset: number): number {
  if (offset + 4 > buf.length) {
    throw new ContractDecodeError(`truncated uint32 at offset ${offset} (len ${buf.length})`)
  }
  return (
    ((buf[offset]! << 24) >>> 0) +
    (buf[offset + 1]! << 16) +
    (buf[offset + 2]! << 8) +
    buf[offset + 3]!
  )
}

/** Write a uint64 big-endian (from a bigint) into `out` at `offset`. Returns next offset. */
export function writeUint64BE(out: Uint8Array, offset: number, value: bigint): number {
  if (value < 0n || value > U64_MAX) {
    throw new ContractEncodeError(`uint64 out of range: ${value}`)
  }
  for (let i = 7; i >= 0; i--) {
    out[offset + i] = Number(value & 0xffn)
    value >>= 8n
  }
  return offset + 8
}

/** Read a uint64 big-endian as a bigint from `buf` at `offset`. */
export function readUint64BE(buf: Uint8Array, offset: number): bigint {
  if (offset + 8 > buf.length) {
    throw new ContractDecodeError(`truncated uint64 at offset ${offset} (len ${buf.length})`)
  }
  let value = 0n
  for (let i = 0; i < 8; i++) {
    value = (value << 8n) | BigInt(buf[offset + i]!)
  }
  return value
}

/** Read a uint64 that must fit in a JS safe integer (§4.1 payloadLen safe-int guard). */
export function readSafeUint64BE(buf: Uint8Array, offset: number, field: string): number {
  const value = readUint64BE(buf, offset)
  if (value > BigInt(Number.MAX_SAFE_INTEGER)) {
    throw new ContractDecodeError(`${field} exceeds Number.MAX_SAFE_INTEGER: ${value}`)
  }
  return Number(value)
}

/** Concatenate byte chunks into one Uint8Array. */
export function concatBytes(chunks: readonly Uint8Array[]): Uint8Array {
  const total = chunks.reduce((n, c) => n + c.length, 0)
  const out = new Uint8Array(total)
  let offset = 0
  for (const c of chunks) {
    out.set(c, offset)
    offset += c.length
  }
  return out
}
