// =============================================================================
//  data/autopilot_state.dart — Live autopilot data model
// =============================================================================
//
//  Sprint 4: simulated demo data (vessel drifting / heading-hold correction).
//  Sprint 7: internals replaced by Modbus RTU over USB serial.
//  The public API (fields + methods) must not change between sprints.
// =============================================================================

import 'dart:async';
import 'dart:math' as math;

import 'package:flutter/foundation.dart';

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

/// Live state of the autopilot and navigation instruments.
///
/// Consumed by [CockpitScreen] via `context.watch<AutopilotState>()`.
///
/// ## Data sources (Sprint 4 → Sprint 7)
/// Sprint 4: internal [Timer]-driven demo that simulates a vessel at sea.
/// Sprint 7: [AutopilotStateModbus] subclass (or internal swap) feeds real
///   Modbus RTU data received over USB serial from the AR-Concentrador.
class AutopilotState extends ChangeNotifier {
  // ── Navigation data ──────────────────────────────────────────────────────────
  /// Current vessel heading from NMEA 2000 backbone (degrees, magnetic, 0–359.9).
  double headingDeg = 125.0;

  /// Operator-commanded heading setpoint sent to autopilot (degrees, magnetic).
  double setpointDeg = 125.0;

  /// Rudder angle in degrees. Negative = port, positive = starboard.
  double rudderDeg = 0.0;

  /// Speed over ground (knots).
  double sogKn = 6.2;

  /// Course over ground (degrees, true).
  double cogDeg = 127.0;

  /// Rate of turn (degrees per minute). Positive = turning starboard.
  double rotDpm = 0.0;

  /// Water depth below keel (metres).
  double depthM = 42.5;

  // ── Autopilot state ──────────────────────────────────────────────────────────
  /// Current autopilot mode as reported by the concentrador $PARP,STATUS.
  AutopilotMode mode = AutopilotMode.standby;

  /// True when the Modbus RTU link to the concentrador is active.
  /// Sprint 4: always false (demo mode label shown in UI).
  bool isConnected = false;

  // ── Internal ─────────────────────────────────────────────────────────────────
  Timer? _demoTimer;
  final _rng = math.Random();

  AutopilotState() {
    _startDemo();
  }

  // ---------------------------------------------------------------------------
  //  Demo simulation (Sprint 4 only)
  // ---------------------------------------------------------------------------

  void _startDemo() {
    _demoTimer = Timer.periodic(const Duration(milliseconds: 500), (_) {
      _tick();
    });
  }

  void _tick() {
    switch (mode) {
      case AutopilotMode.standby:
        // Vessel drifts slightly — small random walk on heading and rudder.
        headingDeg = (headingDeg + (_rng.nextDouble() - 0.5) * 0.5) % 360;
        if (headingDeg < 0) headingDeg += 360;
        rudderDeg = (rudderDeg + (_rng.nextDouble() - 0.5) * 0.8)
            .clamp(-5.0, 5.0);
        rotDpm = rudderDeg * 0.3 + (_rng.nextDouble() - 0.5) * 0.2;

      case AutopilotMode.headingHold:
        // Simulated P-controller: autopilot drives rudder toward setpoint.
        final error = _angleDiff(setpointDeg, headingDeg);
        rudderDeg = (error * 1.2 + (_rng.nextDouble() - 0.5) * 0.5)
            .clamp(-35.0, 35.0);
        headingDeg =
            (headingDeg + error * 0.025 + (_rng.nextDouble() - 0.5) * 0.08)
                % 360;
        if (headingDeg < 0) headingDeg += 360;
        rotDpm = error * 0.4;

      case AutopilotMode.trackKeep:
        // Sprint 6: identical to headingHold for demo purposes.
        final error = _angleDiff(setpointDeg, headingDeg);
        rudderDeg =
            (error * 1.2).clamp(-35.0, 35.0);
        headingDeg = (headingDeg + error * 0.025) % 360;
        if (headingDeg < 0) headingDeg += 360;
        rotDpm = error * 0.4;
    }

    // COG tracks heading with a small lag.
    cogDeg = (headingDeg + rudderDeg * 0.15 + (_rng.nextDouble() - 0.5) * 0.3)
        % 360;
    if (cogDeg < 0) cogDeg += 360;

    notifyListeners();
  }

  /// Signed difference in [−180, +180]: target − current.
  double _angleDiff(double target, double current) {
    double d = (target - current) % 360;
    if (d > 180) d -= 360;
    if (d < -180) d += 360;
    return d;
  }

  // ---------------------------------------------------------------------------
  //  Commands — called by UI; Sprint 7 will also send Modbus frames here.
  // ---------------------------------------------------------------------------

  /// Engage heading hold on the current heading.
  void engage() {
    setpointDeg = headingDeg;
    mode = AutopilotMode.headingHold;
    notifyListeners();
  }

  /// Return to STANDBY (manual helm).
  void disengage() {
    mode = AutopilotMode.standby;
    notifyListeners();
  }

  /// Adjust the heading setpoint while in heading-hold mode.
  /// No-op in STANDBY or TRACK.
  void adjustSetpoint(double deltaDeg) {
    if (mode != AutopilotMode.headingHold) return;
    setpointDeg = (setpointDeg + deltaDeg) % 360;
    if (setpointDeg < 0) setpointDeg += 360;
    notifyListeners();
  }

  /// Handle ModeSelector taps.
  void selectMode(AutopilotMode newMode) {
    switch (newMode) {
      case AutopilotMode.standby:
        disengage();
      case AutopilotMode.headingHold:
        engage();
      case AutopilotMode.trackKeep:
        // Sprint 6: engage track-keep mode (requires GPS cross-track error).
        mode = AutopilotMode.trackKeep;
        setpointDeg = headingDeg;
        notifyListeners();
    }
  }

  @override
  void dispose() {
    _demoTimer?.cancel();
    super.dispose();
  }
}
