"""Lightweight, decoupled progress reporting for long-running scheduler jobs.

A job's work function calls `report(current, total, phase)`; the scheduler binds a sink (via
`bind()`) that forwards it into the in-memory activity the admin dashboard polls. A contextvar
holds the sink, so the sync code stays unaware of the scheduler and concurrent jobs (each in
its own thread/context) never cross wires. When no sink is bound (e.g. a manual sync endpoint),
`report` is a cheap no-op.
"""
import contextvars
from typing import Callable, Optional

Sink = Callable[[int, Optional[int], Optional[str]], None]

_sink: contextvars.ContextVar[Optional[Sink]] = contextvars.ContextVar(
    "progress_sink", default=None
)


def report(current: int, total: int | None = None, phase: str | None = None) -> None:
    """Report job progress to the bound sink, if any. `total=None` means indeterminate."""
    sink = _sink.get()
    if sink is not None:
        sink(current, total, phase)


class bind:
    """Context manager that binds a progress sink for the duration of a job run."""

    def __init__(self, sink: Sink):
        self._sink = sink
        self._token = None

    def __enter__(self) -> "bind":
        self._token = _sink.set(self._sink)
        return self

    def __exit__(self, *exc) -> bool:
        if self._token is not None:
            _sink.reset(self._token)
        return False
