# -*- coding: utf-8 -*-
"""
层级化中断控制器（借鉴 zhikuncode engine/AbortController.java）

设计要点：
- 借鉴 JS AbortController 模型，支持父子层级：父 abort 时级联传播到所有子 controller
- CAS（compare-and-set）保证 abort() 幂等：多次调用只触发一次回调
- onAbort 监听器列表：单个监听器异常不阻塞其他监听器
- AbortSignal 只读视图：传递给工具执行器，避免误调用 abort

设计动机（abyss 当前状态）：
- src/abyss/interrupt.py 仅有 EscInterrupt 单层 Win32 轮询
- subagent/runner.py 子 Agent 无法感知主流程中断信号
- agent_loop.execute_tool 工具执行无中断检查点
- 中断只能"终止整个对话"，无法"取消当前工具调用"

借鉴价值：是 Phase 4 Q (AgentConcurrencyController) 和 Phase 4 P (TerminationStrategy)
的数据基础；CLI 长任务必备：用户按 Esc 应能取消当前工具而非杀进程。
"""
import threading
from enum import Enum
from typing import Callable, List, Optional


class AbortReason(Enum):
    """中断原因枚举。

    - USER_CANCEL: 用户主动按下 Esc / Ctrl+C
    - TIMEOUT: 工具或 LLM 调用超时
    - PARENT_ABORTED: 父 controller 被中断，本级联传播
    - BUDGET_EXCEEDED: token / 时间 / 成本预算超限
    """

    USER_CANCEL = "user_cancel"
    TIMEOUT = "timeout"
    PARENT_ABORTED = "parent_aborted"
    BUDGET_EXCEEDED = "budget_exceeded"


class AbortError(Exception):
    """中断异常。throw_if_aborted 检查到中断状态时抛出。"""

    def __init__(self, reason: AbortReason):
        self.reason = reason
        super().__init__(f"操作被中断: {reason.value}")


class _ListenerHandle:
    """on_abort 返回的监听器句柄，支持 remove() 取消注册。"""

    def __init__(self, controller: "AbortController", callback: Callable[[AbortReason], None]):
        self._controller = controller
        self._callback = callback

    def remove(self) -> None:
        """从 controller 中移除该监听器。多次调用安全。"""
        self._controller._remove_listener(self._callback)


class AbortSignal:
    """AbortController 的只读视图。

    传递给工具执行器（agent_loop.execute_tool）或 LLM 客户端（api_client.chat），
    允许它们检查中断状态但不能触发中断。
    """

    def __init__(self, controller: "AbortController"):
        self._controller = controller

    def is_aborted(self) -> bool:
        """检查是否已中断。"""
        return self._controller.is_aborted()

    def throw_if_aborted(self) -> None:
        """若已中断，抛出 AbortError。"""
        self._controller.throw_if_aborted()

    @property
    def reason(self) -> Optional[AbortReason]:
        """返回中断原因，未中断时为 None。"""
        return self._controller.reason


class AbortController:
    """层级化中断控制器。

    使用示例：

        # 主流程创建 root controller
        root = AbortController()

        # 子 Agent 创建 child controller，自动注册到 root
        subagent_ctrl = AbortController(parent=root)

        # 工具执行时检查中断
        def execute_tool(...):
            root.signal.throw_if_aborted()
            ...

        # 用户按 Esc 时
        root.abort(AbortReason.USER_CANCEL)
        # 所有子 controller 自动级联中断
    """

    def __init__(self, parent: Optional["AbortController"] = None):
        # CAS 状态：_aborted 单向从 False 转 True，永不回退
        self._aborted: bool = False
        self._reason: Optional[AbortReason] = None
        self._listeners: List[Callable[[AbortReason], None]] = []
        self._children: List["AbortController"] = []
        self._lock = threading.Lock()

        if parent is not None:
            parent._add_child(self)

    # -- 状态查询 --

    def is_aborted(self) -> bool:
        """是否已被中断。线程安全（读无锁，CAS 单次写入保证可见性）。"""
        return self._aborted

    @property
    def reason(self) -> Optional[AbortReason]:
        """中断原因，未中断时为 None。"""
        return self._reason

    @property
    def signal(self) -> AbortSignal:
        """返回只读 AbortSignal 视图。每次调用返回新实例，但状态共享。"""
        return AbortSignal(self)

    # -- 中断触发 --

    def abort(self, reason: AbortReason = AbortReason.USER_CANCEL) -> None:
        """触发中断，CAS 保证幂等。

        首次调用：
        1. 标记 _aborted=True，记录 _reason
        2. 级联到所有子 controller（reason 转为 PARENT_ABORTED）
        3. 触发所有 on_abort 监听器（异常不阻塞）

        后续调用：直接返回，不触发任何副作用。
        """
        # CAS：进入临界区前先检查，避免重复加锁开销
        if self._aborted:
            return

        with self._lock:
            # 双重检查锁定（double-checked locking）
            if self._aborted:
                return
            self._aborted = True
            self._reason = reason

        # 级联到子 controller（reason 强制改为 PARENT_ABORTED）
        # 注意：先级联再触发监听器，保证子任务能尽快感知
        children_snapshot = list(self._children)  # 拷贝避免并发修改
        for child in children_snapshot:
            try:
                child.abort(AbortReason.PARENT_ABORTED)
            except Exception:
                # 子 controller 级联失败不应阻塞其他子
                pass

        # 触发监听器
        listeners_snapshot = list(self._listeners)
        for cb in listeners_snapshot:
            try:
                cb(reason)
            except Exception:
                # 单个监听器异常不阻塞其他监听器
                pass

    def throw_if_aborted(self) -> None:
        """若已中断，抛出 AbortError。用于工具执行的检查点。"""
        if self._aborted:
            raise AbortError(self._reason if self._reason is not None else AbortReason.USER_CANCEL)

    # -- 监听器 --

    def on_abort(self, callback: Callable[[AbortReason], None]) -> _ListenerHandle:
        """注册中断监听器，返回可移除的句柄。

        若 controller 已中断，注册的监听器不会被调用（调用方应在注册前
        自行检查 is_aborted()）。

        Args:
            callback: 中断时调用的回调，参数为 AbortReason

        Returns:
            _ListenerHandle，调用 .remove() 取消注册
        """
        # 已中断时不注册（避免回调永远不触发导致资源泄漏）
        if self._aborted:
            return _ListenerHandle(self, callback)

        with self._lock:
            if self._aborted:
                return _ListenerHandle(self, callback)
            self._listeners.append(callback)
        return _ListenerHandle(self, callback)

    def _remove_listener(self, callback: Callable[[AbortReason], None]) -> None:
        """从监听器列表中移除回调。多次调用安全。"""
        with self._lock:
            try:
                self._listeners.remove(callback)
            except ValueError:
                pass  # 已移除或从未注册

    # -- 子 controller 管理 --

    def _add_child(self, child: "AbortController") -> None:
        """注册子 controller。父 abort 时会级联到子。"""
        with self._lock:
            # 若父已中断，立即中断子（避免子在父中断后才注册而错过信号）
            if self._aborted:
                child.abort(AbortReason.PARENT_ABORTED)
                return
            self._children.append(child)

    def _child_count(self) -> int:
        """测试辅助：返回子 controller 数量。"""
        with self._lock:
            return len(self._children)
