"""
Battery guard - a worked example of a background monitor.

Watches the PMU battery level, warns once per threshold crossing, and can drop
the robot into a safe mode before it browns out mid-stride. Demonstrates
on_tick, ctx.push readouts and charted series.
"""

from backend.plugin_api import Plugin

plugin = Plugin(
    id="battery_guard",
    name="Battery guard",
    description="Warns on low battery and can auto-safe the robot before it browns out.",
    icon="\U0001F50B",
    order=20,
)


@plugin.slider("warn_at", label="Warn below", min=5, max=60, step=1, default=25, unit="%")
async def warn_at(ctx, value):
    ctx.storage["warn_at"] = float(value)
    ctx.storage["warned"] = False
    return f"Warning threshold set to {value:.0f}%"


@plugin.slider("safe_at", label="Auto-safe below", min=1, max=30, step=1, default=10, unit="%",
               help="Switches to Damping so the robot settles instead of collapsing.")
async def safe_at(ctx, value):
    ctx.storage["safe_at"] = float(value)
    ctx.storage["safed"] = False
    return f"Auto-safe threshold set to {value:.0f}%"


@plugin.toggle("auto_safe", label="Auto-safe enabled", default=False,
               help="Off by default - turn on only when you want the robot to act unattended.")
async def auto_safe(ctx, value):
    ctx.storage["auto_safe"] = bool(value)
    return "Auto-safe armed" if value else "Auto-safe disarmed"


@plugin.action("reset_alarms", label="Reset alarms", style="default")
async def reset_alarms(ctx):
    ctx.storage["warned"] = False
    ctx.storage["safed"] = False
    await ctx.push("status", "armed")
    return "Alarms reset"


plugin.readout("level", "Battery", unit="%", chart=True, precision=1)
plugin.readout("draw", "Draw", unit="A", precision=2)
plugin.readout("status", "Guard status", format="text")
plugin.readout("est_runtime", "Estimated runtime", unit="min", precision=0)


@plugin.on_start
async def start(ctx):
    ctx.storage.setdefault("warn_at", 25.0)
    ctx.storage.setdefault("safe_at", 10.0)
    ctx.storage.setdefault("auto_safe", False)
    ctx.storage.setdefault("warned", False)
    ctx.storage.setdefault("safed", False)
    await ctx.push("status", "armed")


@plugin.on_tick(interval=3.0)
async def tick(ctx):
    level = ctx.state.battery_pct
    if level is None:
        await ctx.push("status", "no PMU data")
        return

    current = ctx.state.battery_current
    await ctx.push("level", round(level, 1))
    ctx.record("level", level)
    if current is not None:
        await ctx.push("draw", round(abs(current), 2))

    # A crude but useful projection: remaining percent over the observed drain
    # rate. Only meaningful while discharging.
    history = ctx.hub.series("battery_pct", limit=120)
    if len(history) >= 20:
        (t0, v0), (t1, v1) = history[0], history[-1]
        elapsed = t1 - t0
        drop = v0 - v1
        if elapsed > 5 and drop > 0.01:
            minutes = (level / (drop / elapsed)) / 60.0
            await ctx.push("est_runtime", round(min(minutes, 9999), 0))

    safe_at = ctx.storage["safe_at"]
    warn_at = ctx.storage["warn_at"]

    if level <= safe_at:
        await ctx.push("status", "critical")
        if ctx.storage.get("auto_safe") and not ctx.storage.get("safed"):
            ctx.storage["safed"] = True
            await ctx.bridge.stop_motion()
            outcome = await ctx.bridge.set_mode("DAMPING_DEFAULT")
            await ctx.log(
                f"Battery {level:.1f}% - auto-safe engaged: {outcome.message}", level="error"
            )
            await ctx.bridge.speak("Battery critical. Entering safe mode.", priority=10)
    elif level <= warn_at:
        await ctx.push("status", "low")
        if not ctx.storage.get("warned"):
            ctx.storage["warned"] = True
            await ctx.log(f"Battery low: {level:.1f}%", level="warn")
            await ctx.bridge.set_led(mode=2, r=250, g=178, b=25)
    else:
        await ctx.push("status", "normal")
        ctx.storage["warned"] = False
        ctx.storage["safed"] = False
