#!/usr/bin/env python3
"""Loop guard — idle detection + exponential backoff for the dev loop.

Checks .loop/queue.json for ready items before spawning hermes.
When the queue is empty, applies exponential backoff (60s → 600s max)
instead of burning empty cycles every 3 seconds.

Usage (called by the dev loop before each cycle):
  python3 scripts/loop_guard.py          # exits 0 if ready, 1 if idle
  python3 scripts/loop_guard.py --wait   # same, but sleeps the backoff first
  python3 scripts/loop_guard.py --status # print current idle state

Exit codes:
  0 — queue has work, proceed with cycle
  1 — queue empty, idle backoff applied (skip cycle)
"""

from __future__ import annotations

import json
import sys
import time
from pathlib import Path

REPO_ROOT = Path(__file__).resolve().parent.parent
QUEUE_FILE = REPO_ROOT / ".loop" / "queue.json"
IDLE_STATE_FILE = REPO_ROOT / ".loop" / "idle_state.json"

# Backoff sequence: 60s, 120s, 240s, 600s max
BACKOFF_BASE = 60
BACKOFF_MAX = 600
BACKOFF_MULTIPLIER = 2


def load_queue() -> list[dict]:
    """Load queue.json and return ready items."""
    if not QUEUE_FILE.exists():
        return []
    try:
        data = json.loads(QUEUE_FILE.read_text())
        if isinstance(data, list):
            return [item for item in data if item.get("ready")]
        return []
    except (json.JSONDecodeError, OSError):
        return []


def load_idle_state() -> dict:
    """Load persistent idle state."""
    if not IDLE_STATE_FILE.exists():
        return {"consecutive_idle": 0, "last_idle_at": 0}
    try:
        return json.loads(IDLE_STATE_FILE.read_text())
    except (json.JSONDecodeError, OSError):
        return {"consecutive_idle": 0, "last_idle_at": 0}


def save_idle_state(state: dict) -> None:
    """Persist idle state."""
    IDLE_STATE_FILE.parent.mkdir(parents=True, exist_ok=True)
    IDLE_STATE_FILE.write_text(json.dumps(state, indent=2) + "\n")


def compute_backoff(consecutive_idle: int) -> int:
    """Exponential backoff: 60, 120, 240, 600 (capped)."""
    return min(BACKOFF_BASE * (BACKOFF_MULTIPLIER ** consecutive_idle), BACKOFF_MAX)


def main() -> int:
    wait_mode = "--wait" in sys.argv
    status_mode = "--status" in sys.argv

    state = load_idle_state()

    if status_mode:
        ready = load_queue()
        backoff = compute_backoff(state["consecutive_idle"])
        print(json.dumps({
            "queue_ready": len(ready),
            "consecutive_idle": state["consecutive_idle"],
            "next_backoff_seconds": backoff if not ready else 0,
        }, indent=2))
        return 0

    ready = load_queue()

    if ready:
        # Queue has work — reset idle state, proceed
        if state["consecutive_idle"] > 0:
            print(f"[loop-guard] Queue active ({len(ready)} ready) — "
                  f"resuming after {state['consecutive_idle']} idle cycles")
        state["consecutive_idle"] = 0
        state["last_idle_at"] = 0
        save_idle_state(state)
        return 0

    # Queue empty — apply backoff
    backoff = compute_backoff(state["consecutive_idle"])
    state["consecutive_idle"] += 1
    state["last_idle_at"] = time.time()
    save_idle_state(state)

    print(f"[loop-guard] Queue empty — idle #{state['consecutive_idle']}, "
          f"backoff {backoff}s")

    if wait_mode:
        time.sleep(backoff)

    return 1


if __name__ == "__main__":
    sys.exit(main())
