import 'dart:typed_data';
import '../../utils/audio_math.dart';
import '../../utils/constants.dart';

/// Immutable representation of one decoded packet.
class Packet {
  final int      seq;      // 16-bit sequence number
  final int      type;     // packet type (see C.pkType*)
  final Uint8List payload; // decrypted payload bytes (≤ C.pktPayloadLen)
  final bool     valid;    // CRC check passed

  const Packet({
    required this.seq,
    required this.type,
    required this.payload,
    required this.valid,
  });

  bool get isVoice     => type == C.pkTypeVoice;
  bool get isControl   => type == C.pkTypeControl;
  bool get isHandshake => type == C.pkTypeHandshake;

  @override
  String toString() =>
      'Packet(seq=$seq, type=0x${type.toRadixString(16)}, '
      'len=${payload.length}, valid=$valid)';
}

/// Build and parse the raw (pre-FEC, pre-encryption) packet byte layout.
///
/// Wire layout:
///   [SYNC:2][SEQ_HI:1][SEQ_LO:1][TYPE:1][LEN:1][PAYLOAD:LEN][CRC_HI:1][CRC_LO:1]
///
/// The SYNC bytes (0x5A, 0xA5) are written but intentionally NOT included in
/// the CRC computation — they are only used by the demodulator frame detector.
/// CRC-16/CCITT covers bytes [2..end-2].
abstract final class PacketBuilder {
  // ── Serialise ──────────────────────────────────────────────────────

  /// Build a raw packet byte array from components.
  ///
  /// [payload] must be ≤ C.pktPayloadLen bytes. It is zero-padded to exactly
  /// C.pktPayloadLen before the CRC is computed so the on-wire size is fixed.
  static Uint8List build(int seq, int type, Uint8List payload) {
    assert(payload.length <= C.pktPayloadLen,
        'Payload too large: ${payload.length} > ${C.pktPayloadLen}');

    final buf = Uint8List(C.pktTotalBytes);
    int pos = 0;

    // Sync bytes.
    buf[pos++] = C.syncWord[0];   // 0x5A
    buf[pos++] = C.syncWord[1];   // 0xA5

    // Sequence number (big-endian 16-bit).
    buf[pos++] = (seq >> 8) & 0xFF;
    buf[pos++] =  seq       & 0xFF;

    // Type and length.
    buf[pos++] = type & 0xFF;
    buf[pos++] = C.pktPayloadLen; // always fixed length on-wire

    // Payload (zero-padded to C.pktPayloadLen).
    for (int i = 0; i < C.pktPayloadLen; i++) {
      buf[pos++] = i < payload.length ? payload[i] : 0;
    }

    // CRC-16 over bytes [2..pos-1] (excludes sync bytes).
    final crc = AudioMath.crc16(buf.sublist(2, pos));
    buf[pos++] = (crc >> 8) & 0xFF;
    buf[pos++] =  crc       & 0xFF;

    assert(pos == C.pktTotalBytes);
    return buf;
  }

  // ── Parse ──────────────────────────────────────────────────────────

  /// Parse and CRC-validate a raw packet byte array.
  ///
  /// Returns a [Packet] with [Packet.valid] = false if the CRC fails.
  /// Does NOT decrypt — the caller must decrypt [Packet.payload] with AES.
  static Packet parse(Uint8List raw) {
    if (raw.length < C.pktTotalBytes) {
      return Packet(seq: 0, type: 0, payload: Uint8List(0), valid: false);
    }

    int pos = 0;

    // Skip sync (already validated by demodulator frame sync).
    pos += 2;

    // Sequence number.
    final seq = (raw[pos] << 8) | raw[pos + 1];
    pos += 2;

    // Type and length.
    final type = raw[pos++];
    final len  = raw[pos++];
    final payLen = len.clamp(0, C.pktPayloadLen);

    // Payload.
    final payload = Uint8List.fromList(raw.sublist(pos, pos + payLen));
    pos += C.pktPayloadLen; // always advance by fixed payload length

    // CRC check: cover bytes [2..pos-1].
    final bodyCrc = AudioMath.crc16(raw.sublist(2, pos));
    final wireCrc = (raw[pos] << 8) | raw[pos + 1];
    final valid   = (bodyCrc == wireCrc);

    return Packet(seq: seq, type: type, payload: payload, valid: valid);
  }
}
