// =============================================================================
//  services/parp_codec.dart — $PARP sentence parser and builder
// =============================================================================
//
//  Protocol reference: docs/concentrador_protocol.md
//
//  Inbound ($PARP,STATUS — broadcast by concentrador at 2 Hz):
//    $PARP,STATUS,<mode>,<setpoint>,<heading>,<rudder>,<commander>*XX\r\n
//    Example: $PARP,STATUS,HEADING_HOLD,125.0,125.3,2.5,01*3A
//
//  Outbound ($PARP commands — sent by display to concentrador):
//    $PARP,<CMD>,<value>,<station_id>*XX\r\n
//    Example: $PARP,ENGAGE,0.0,02*XX
// =============================================================================

import '../widgets/themed/mode_selector.dart';

/// Parsed content of a $PARP,STATUS sentence.
class ParpStatus {
  const ParpStatus({
    required this.mode,
    required this.setpointDeg,
    required this.headingDeg,
    required this.rudderDeg,
    required this.commander,
  });

  final AutopilotMode mode;
  final double setpointDeg;
  final double headingDeg;
  final double rudderDeg;
  final int commander; // station ID of the current commander (0 = none)
}

/// Stateless NMEA $PARP sentence codec.
abstract final class ParpCodec {
  // ---------------------------------------------------------------------------
  //  Inbound parser
  // ---------------------------------------------------------------------------

  /// Parse a complete NMEA sentence string (with or without leading $).
  ///
  /// Returns [ParpStatus] if the sentence is a valid $PARP,STATUS with correct
  /// checksum. Returns null for any other sentence type or if CRC fails.
  static ParpStatus? parseStatus(String sentence) {
    // Strip whitespace / CRLF
    final s = sentence.trim();

    // Locate checksum delimiter
    final starIdx = s.lastIndexOf('*');
    if (starIdx < 0) return null;

    final body = s.startsWith('\$') ? s.substring(1, starIdx) : s.substring(0, starIdx);
    final crcHex = s.substring(starIdx + 1);

    // Verify checksum
    if (!_crcOk(body, crcHex)) return null;

    // Tokenise
    final parts = body.split(',');
    if (parts.length < 7) return null;
    if (parts[0] != 'PARP' || parts[1] != 'STATUS') return null;

    final mode = _parseMode(parts[2]);
    final setpoint = double.tryParse(parts[3]);
    final heading = double.tryParse(parts[4]);
    final rudder = double.tryParse(parts[5]);
    final commander = int.tryParse(parts[6]);

    if (setpoint == null || heading == null || rudder == null || commander == null) {
      return null;
    }

    return ParpStatus(
      mode: mode,
      setpointDeg: setpoint,
      headingDeg: heading,
      rudderDeg: rudder,
      commander: commander,
    );
  }

  // ---------------------------------------------------------------------------
  //  Outbound builders
  // ---------------------------------------------------------------------------

  static String engage(int stationId, double currentHeadingDeg) =>
      _build('ENGAGE', currentHeadingDeg, stationId);

  static String disengage(int stationId) =>
      _build('DISENGAGE', 0.0, stationId);

  static String setHeading(int stationId, double headingDeg) =>
      _build('SETHEADING', headingDeg, stationId);

  static String portOne(int stationId, double currentSetpoint) =>
      _build('PORTONE', currentSetpoint, stationId);

  static String stbdOne(int stationId, double currentSetpoint) =>
      _build('STBDONE', currentSetpoint, stationId);

  static String portTen(int stationId, double currentSetpoint) =>
      _build('PORTTEN', currentSetpoint, stationId);

  static String stbdTen(int stationId, double currentSetpoint) =>
      _build('STBDTEN', currentSetpoint, stationId);

  static String reqCmd(int stationId) =>
      _build('REQCMD', 0.0, stationId);

  static String relCmd(int stationId) =>
      _build('RELCMD', 0.0, stationId);

  // ---------------------------------------------------------------------------
  //  Private helpers
  // ---------------------------------------------------------------------------

  static String _build(String cmd, double value, int stationId) {
    final body = 'PARP,$cmd,${value.toStringAsFixed(1)},'
        '${stationId.toString().padLeft(2, '0')}';
    final crc = _computeCrc(body);
    return '\$$body*${crc.toRadixString(16).toUpperCase().padLeft(2, '0')}\r\n';
  }

  static int _computeCrc(String body) {
    int crc = 0;
    for (final ch in body.codeUnits) {
      crc ^= ch;
    }
    return crc;
  }

  static bool _crcOk(String body, String crcHex) {
    final expected = _computeCrc(body);
    final received = int.tryParse(crcHex, radix: 16);
    return received != null && received == expected;
  }

  static AutopilotMode _parseMode(String raw) => switch (raw) {
        'HEADING_HOLD' => AutopilotMode.headingHold,
        'TRACK' => AutopilotMode.trackKeep,
        _ => AutopilotMode.standby,
      };
}
