"""Thread-safe monitor for tracking and aggregating progress from multiple concurrent jobs."""

# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/progress_monitor.ipynb.

# %% auto 0
__all__ = ['ProgressMonitor']

# %% ../nbs/progress_monitor.ipynb 3
import threading, time
from collections import deque
from typing import Dict, Any, Optional
from .progress_info import ProgressInfo
from .patch_tqdm import patch_tqdm

# %% ../nbs/progress_monitor.ipynb 5
class ProgressMonitor:
    "Thread-safe monitor for tracking progress of multiple concurrent jobs"
    def __init__(
        self,
        keep_history: bool = False,  # Whether to maintain a history of progress updates
        history_limit: int = 500  # Maximum number of historical updates to keep per job
    ):
        "Initialize a new progress monitor with optional history tracking"
        self._lock = threading.Lock()
        self._jobs: Dict[str, Dict[str, Any]] = {}
        self.keep_history = keep_history
        self.history_limit = history_limit

    def update(
        self,
        job_id: str,  # Unique identifier for the job being tracked
        info: ProgressInfo  # Progress information update for the job
    ):
        now = time.time()
        with self._lock:
            job = self._jobs.setdefault(job_id, {
                "start_time": now, "end_time": None, "completed": False,
                "bars": {}, "latest": None,
                "history": deque(maxlen=self.history_limit) if self.keep_history else None,
                "_fallback_bar_id": None,   # Track a default bar id if needed
            })
            job["latest"] = info
            if self.keep_history:
                job["history"].append(info)

            # --- Ensure we always record into a bar (even for synthetic updates) ---
            bar_key = info.bar_id
            if not bar_key:
                # stable fallback per job (first time we see a no-id update)
                if job["_fallback_bar_id"] is None:
                    # try to make it descriptive but stable
                    base = (info.description or "bar")
                    job["_fallback_bar_id"] = f"default:{base}"
                bar_key = job["_fallback_bar_id"]

            job["bars"][bar_key] = info

            # --- Recompute completion on every update (toggle on/off as bars start/finish) ---
            if job["bars"]:
                all_done = all((b.progress or 0) >= 100.0 for b in job["bars"].values())
                job["completed"] = all_done
                job["end_time"] = now if all_done else None
            else:
                job["completed"] = False
                job["end_time"] = None

    def snapshot(
        self,
        job_id: str  # Unique identifier of the job to snapshot
    ) -> Optional[Dict[str, Any]]:  # Job state dictionary or None if job not found
        "Get a point-in-time snapshot of a specific job's progress state"
        with self._lock:
            job = self._jobs.get(job_id)
            if not job:
                return None
            snap = dict(job)
            if self.keep_history and snap["history"] is not None:
                snap["history"] = list(snap["history"])
            # compute weighted overall if possible
            totals = [(b.current or 0, b.total or 0) for b in snap["bars"].values()]
            denom = sum(t for _, t in totals if t)
            numer = sum(c for c, t in totals if t)
            snap["overall_progress"] = (numer/denom*100.0) if denom else (snap["latest"].progress if snap["latest"] else 0.0)
            return snap

    def all(
        self
    ) -> Dict[str, Dict[str, Any]]:  # Dictionary mapping job IDs to their state snapshots
        "Get snapshots of all tracked jobs"
        with self._lock:
            return {k: self.snapshot(k) for k in self._jobs.keys()}

    def clear_completed(
        self,
        older_than_seconds: float = 3600  # Age threshold in seconds for removing completed jobs
    ):
        "Remove completed jobs that finished more than the specified seconds ago"
        now = time.time()
        with self._lock:
            for k in [k for k,v in self._jobs.items()
                      if v["completed"] and v["end_time"] and now - v["end_time"] > older_than_seconds]:
                del self._jobs[k]
