"""Serialized ddcutil worker thread.

i2c is not concurrency-safe per bus and ddcutil calls are slow, so all backend
access funnels through a single worker thread. Jobs are plain callables; results
and errors are marshalled back onto the GTK main loop via GLib.idle_add. A
background thread NEVER touches GTK widgets — only the idle callbacks do.
"""

from __future__ import annotations

import queue
import threading
from collections.abc import Callable
from typing import Any

from gi.repository import GLib

Job = Callable[[], Any]
OnDone = Callable[[Any], None]
OnError = Callable[[Exception], None]


def _idle(cb: Callable[..., Any], *args: Any) -> None:
    def runner() -> bool:
        cb(*args)
        return False

    GLib.idle_add(runner)


class Worker:
    """Single background thread executing submitted jobs in order."""

    def __init__(self) -> None:
        self._queue: queue.Queue[tuple[Job, OnDone | None, OnError | None] | None] = queue.Queue()
        self._thread = threading.Thread(target=self._run, name="ddcutil-worker", daemon=True)
        self._thread.start()

    def submit(
        self,
        job: Job,
        on_done: OnDone | None = None,
        on_error: OnError | None = None,
    ) -> None:
        self._queue.put((job, on_done, on_error))

    def stop(self) -> None:
        self._queue.put(None)

    def _run(self) -> None:
        while True:
            item = self._queue.get()
            if item is None:
                return
            job, on_done, on_error = item
            try:
                result = job()
            except Exception as exc:  # noqa: BLE001 - report any backend failure to UI
                if on_error is not None:
                    _idle(on_error, exc)
            else:
                if on_done is not None:
                    _idle(on_done, result)
