"""{{ display_name }} MCP server — generated by mcpify. Do not hand-edit.

CLOSED -> OPEN -> HALF_OPEN state machine around outbound calls to the
target API (REQ-2.3.3). Opens after `failure_threshold` consecutive
failures; after `reset_timeout_s`, lets one trial call through (HALF_OPEN)
to decide whether to close again or re-open.
"""

from __future__ import annotations

import asyncio
import time
from enum import Enum, auto
from typing import Awaitable, Callable, TypeVar

from .errors import CircuitBreakerOpenError

T = TypeVar("T")


class _State(Enum):
    CLOSED = auto()
    OPEN = auto()
    HALF_OPEN = auto()


class CircuitBreaker:
    def __init__(self, failure_threshold: int = 5, reset_timeout_s: float = 30.0) -> None:
        self._failure_threshold = failure_threshold
        self._reset_timeout_s = reset_timeout_s
        self._state = _State.CLOSED
        self._failure_count = 0
        self._opened_at: float | None = None
        self._lock = asyncio.Lock()

    async def execute(self, fn: Callable[[], Awaitable[T]]) -> T:
        async with self._lock:
            if self._state is _State.OPEN:
                elapsed = time.monotonic() - (self._opened_at or 0.0)
                if elapsed < self._reset_timeout_s:
                    raise CircuitBreakerOpenError()
                self._state = _State.HALF_OPEN

        try:
            value = await fn()
        except Exception:
            await self._on_failure()
            raise
        await self._on_success()
        return value

    async def _on_success(self) -> None:
        async with self._lock:
            self._failure_count = 0
            self._state = _State.CLOSED

    async def _on_failure(self) -> None:
        async with self._lock:
            self._failure_count += 1
            if self._state is _State.HALF_OPEN or self._failure_count >= self._failure_threshold:
                self._state = _State.OPEN
                self._opened_at = time.monotonic()
