"""Кооперативный shutdown-флаг для scraper-планировщика (#1182 Phase 2).

Зачем: деплой (docker recreate `tradein-scraper`) шлёт SIGTERM с
`stop_grace_period=120s`. Раньше `scheduler_main` отвечал hard `task.cancel()` →
бегущий scrape-unit получал CancelledError посреди браузер-карточки → карточка
гибла без COMMIT. Теперь SIGTERM лишь ВЫСТАВЛЯЕТ этот флаг; tick-loop планировщика
и длинные задачи опрашивают `shutdown_requested()` на СВОИХ существующих
between-unit checkpoint'ах, докоммичивают текущий unit и выходят сами.

Модуль намеренно standalone (зависит только от `asyncio`): планировщик импортирует
задачи, а задачи должны читать флаг — общий нижний слой без app-зависимостей
исключает циклический импорт.

Без SIGTERM `shutdown_requested()` всегда False → поведение идентично прежнему.
"""

from __future__ import annotations

import asyncio

# Module-level Event: создаётся на import'е. В Python 3.10+ asyncio.Event не
# привязывается к loop'у в __init__ (loop берётся лениво в .wait()), поэтому
# set()/is_set() безопасны даже до запуска event loop'а.
_shutdown_event = asyncio.Event()


def request_shutdown() -> None:
    """Запросить кооперативный drain. Идемпотентно — повторные вызовы безвредны."""
    _shutdown_event.set()


def shutdown_requested() -> bool:
    """True после первого request_shutdown() (идёт кооперативный drain)."""
    return _shutdown_event.is_set()


async def wait_for_shutdown() -> None:
    """Заблокироваться до request_shutdown(). Нужно safety-net drain'у в scheduler_main."""
    await _shutdown_event.wait()


def reset_shutdown() -> None:
    """Сбросить флаг — ТОЛЬКО для тестов. Пересоздаёт Event (а не .clear()): asyncio.Event
    кеширует event loop при первом .wait(), а pytest-asyncio даёт каждому тесту НОВЫЙ loop;
    свежий объект избегает 'bound to a different event loop'. В проде не вызывается
    (один asyncio.run → один loop), поэтому переприсвоение глобала здесь безопасно."""
    global _shutdown_event
    _shutdown_event = asyncio.Event()
