"""
charge_monitor.py — Battery charge monitoring for auto-dock return (Issue #158).

Consumes BatteryState data (voltage, current, percentage) each control tick
and emits discrete ChargeEvents when meaningful thresholds are crossed.

Events
──────
  CHARGING_STARTED   : charge current rises above charging_current_threshold_a
                       (docking pins making contact and charger active)
  CHARGING_STOPPED   : charge current drops back to near-zero (unplugged or
                       charge complete at hardware level)
  THRESHOLD_LOW      : SOC dropped below low_threshold_pct  → trigger auto-dock
  THRESHOLD_HIGH     : SOC rose above high_threshold_pct    → resume mission

Each event is emitted at most once per crossing (edge-triggered).

Pure module — no ROS2 dependency.
"""

from __future__ import annotations

from enum import Enum, auto
from typing import List


# ── Events ────────────────────────────────────────────────────────────────────

class ChargeEvent(Enum):
    CHARGING_STARTED = auto()
    CHARGING_STOPPED = auto()
    THRESHOLD_LOW    = auto()
    THRESHOLD_HIGH   = auto()


# ── ChargeMonitor ─────────────────────────────────────────────────────────────

class ChargeMonitor:
    """
    Edge-triggered monitor for battery charging state and SOC thresholds.

    Parameters
    ----------
    low_threshold_pct         : SOC below which THRESHOLD_LOW fires (default 15 %)
    high_threshold_pct        : SOC above which THRESHOLD_HIGH fires (default 80 %)
    charging_current_threshold_a : minimum current (A) to declare charging
                                   (positive = charging convention)
    """

    def __init__(
        self,
        low_threshold_pct:            float = 15.0,
        high_threshold_pct:           float = 80.0,
        charging_current_threshold_a: float = 0.10,
    ):
        self._low_pct    = float(low_threshold_pct)
        self._high_pct   = float(high_threshold_pct)
        self._i_thresh   = float(charging_current_threshold_a)

        # Internal state (edge-detection flags)
        self._is_charging: bool  = False
        self._was_low:     bool  = False    # True while SOC is in low zone
        self._was_high:    bool  = True     # True while SOC is in high zone
        self._percentage:  float = 100.0

    # ── Properties ────────────────────────────────────────────────────────────

    @property
    def is_charging(self) -> bool:
        """True when charge current has been detected."""
        return self._is_charging

    @property
    def percentage(self) -> float:
        """Most recently observed state of charge (0–100 %)."""
        return self._percentage

    @property
    def is_depleted(self) -> bool:
        """True when SOC is currently below the low threshold."""
        return self._percentage < self._low_pct

    @property
    def is_charged(self) -> bool:
        """True when SOC is currently above the high threshold."""
        return self._percentage >= self._high_pct

    # ── Update ────────────────────────────────────────────────────────────────

    def update(
        self,
        percentage: float,
        current_a:  float,
    ) -> List[ChargeEvent]:
        """
        Process one sample from BatteryState.

        Parameters
        ----------
        percentage : state of charge 0–100 (from BatteryState.percentage × 100)
        current_a  : charge/discharge current (A); positive = charging

        Returns
        -------
        List of ChargeEvents (may be empty; never duplicates per edge).
        """
        events: List[ChargeEvent] = []
        self._percentage = float(percentage)
        now_charging = current_a >= self._i_thresh

        # ── Charging edge detection ───────────────────────────────────────────
        if now_charging and not self._is_charging:
            events.append(ChargeEvent.CHARGING_STARTED)
        elif not now_charging and self._is_charging:
            events.append(ChargeEvent.CHARGING_STOPPED)
        self._is_charging = now_charging

        # ── Low threshold — edge on falling through ───────────────────────────
        in_low_zone = self._percentage < self._low_pct
        if in_low_zone and not self._was_low:
            events.append(ChargeEvent.THRESHOLD_LOW)
        self._was_low = in_low_zone

        # ── High threshold — edge on rising through ───────────────────────────
        in_high_zone = self._percentage >= self._high_pct
        if in_high_zone and not self._was_high:
            events.append(ChargeEvent.THRESHOLD_HIGH)
        self._was_high = in_high_zone

        return events

    def reset(self) -> None:
        """Clear all state (use when node restarts)."""
        self._is_charging = False
        self._was_low     = False
        self._was_high    = True
        self._percentage  = 100.0
