"""
Subjective Logic Module - 统一的意见表示与融合算法实现
Author: Assistant
License: MIT

本模块实现了主观逻辑框架，其中二元意见是多元意见的特例：
- 统一的 Opinion 类，支持任意维度
- 二元情况下通过 property 访问 belief/disbelief
- 所有算法均支持任意维度
- 与 Beta/Dirichlet 分布的双射映射
"""

import typing

from dataclasses import dataclass, field

# from scipy import stats
# from scipy.special import gammaln, logsumexp
from enum import Enum

# from collections import namedtuple
from typing import TYPE_CHECKING, Any, Dict, List, Literal, Optional, Tuple, Union, cast

import numpy as np

from zyplib.utils.print import warning
from zyplib.utils.tensor_like import TensorLike, TensorLikeBackend, get_tensorlike_backend

if TYPE_CHECKING:
    from torch import Tensor


# ========================= Opinion Representation ========================= #


class FusionType(Enum):
    """融合算法类型枚举"""

    CUMULATIVE = 'cumulative'
    AVERAGING = 'averaging'
    WEIGHTED = 'weighted'
    # CONSENSUS = 'consensus'


def _as_tensorlike(value: object) -> TensorLike:
    return cast(TensorLike, value)


def _as_scalar_or_tensor(value: object) -> float | TensorLike:
    return cast(float | TensorLike, value)


def _item(backend: TensorLikeBackend, value: float | TensorLike) -> float:
    if isinstance(value, (int, float)):
        return float(value)
    scalar = backend.item(value)
    if isinstance(scalar, complex):
        return float(scalar.real)
    return float(scalar)


def _as_backend_source(
    value: TensorLike | list[object] | Literal['numpy', 'torch'],
) -> str | TensorLike:
    if isinstance(value, list):
        return np.asarray(value, dtype=float)
    return cast(str | TensorLike, value)


@dataclass
class Opinion:
    """
    主观逻辑意见的统一表示

    对于 k 个状态的意见：
    - 二元情况 (k=2): belief_masses = [belief, disbelief]
    - 多元情况 (k>2): belief_masses = [b1, b2, ..., bk]

    支持 Numpy/Torch 两种后端模式
    在 Torch 模式下获取一些标量的时候，返回的会是 Tensor (不调用 .item()) 以避免切断梯度流动

    Parameters
    ----------
    - `belief_masses` : `TensorLike`
        各状态的信念质量分布，sum(belief_masses) + uncertainty = 1
    - `uncertainty` : `float | TensorLike`
        不确定性质量，取值范围 [0, 1]; 可以是浮点数 (numpy 模式) 也可以是一个 0dim 的张量 (torch 模式)
    - `base_rates` : `Optional[TensorLike]`
        先验概率分布，默认为均匀分布
    """

    belief_masses: list | TensorLike
    uncertainty: float | TensorLike = 0.0  # 可能是一个 0dim 的张量
    base_rates: Optional[list | TensorLike] = None

    engine: Optional[Literal['numpy', 'torch']] = None
    skip_conversion: Optional[bool] = False
    skip_validation: Optional[bool] = False

    # 内部字段，由 __post_init__ 初始化，不参与 repr 或比较
    _backend: TensorLikeBackend = field(init=False, repr=False, compare=False)
    _epsilon: float = field(init=False, repr=False, compare=False, default=1e-12)

    def __post_init__(self):
        """初始化并验证意见的有效性"""
        self._epsilon = 1e-12
        # 1. 确定后端
        backend_source = self.engine  # 优先级 1: 用户指定的 engine
        if backend_source is None:
            # 优先级 2: 从 belief_masses 推断
            backend_source = self.belief_masses

        self._backend = get_tensorlike_backend(_as_backend_source(backend_source))

        if not self.skip_conversion:
            self.__convert()
        self._set_epsilon()

        if not self.skip_validation:
            self.__validate()

    def _set_epsilon(self):
        """根据后端和数据类型动态设置数值精度"""
        if self._backend.backend_type == 'torch':
            dtype = self._backend.dtype(_as_tensorlike(self.belief_masses))
            if dtype == self._backend.type('float32'):  # torch.float32
                self._epsilon = 1e-7
        else:
            self._epsilon = 1e-12  # numpy 默认

    def __convert(self):
        # float_dtype = self._backend.type('float64')
        if self._backend.backend_type == 'torch':
            float_dtype = self._backend.type('float32')
        elif self._backend.backend_type == 'numpy':
            float_dtype = self._backend.type('float64')
        else:
            raise ValueError(f'Unsupported backend: {self._backend.backend_type}')

        # 从原始数据推断设备信息（如果存在），以便正确转换
        device = None
        if not isinstance(self.belief_masses, (list, tuple)):
            device = self._backend.device(self.belief_masses)

        self.belief_masses = self._backend.as_tensorlike(
            self.belief_masses, dtype=float_dtype, device=device
        )

        if self.base_rates is None:
            n_states = self._backend.shape(self.belief_masses)[0]
            self.base_rates = (
                self._backend.ones((n_states,), dtype=float_dtype, device=device)
                / n_states
            )
        else:
            self.base_rates = self._backend.as_tensorlike(
                self.base_rates, dtype=float_dtype, device=device
            )

        self.uncertainty = self._backend.as_tensorlike(
            self.uncertainty, dtype=float_dtype, device=device
        )

    def __validate(self):
        """
        归一化并验证意见参数的有效性。
        - 如果信念/基率之和与1的偏差在 epsilon 范围内，则自动归一化。
        - 如果偏差过大，则抛出 ValueError。
        - 同时检查其他约束条件。

        Raises
        ------
        ValueError
            当参数不满足约束条件时
        """
        belief_masses = _as_tensorlike(self.belief_masses)
        base_rates = _as_tensorlike(self.base_rates)
        # 1. 验证其他约束
        u_val = _item(self._backend, _as_scalar_or_tensor(self.uncertainty))
        if not (0.0 - self._epsilon <= u_val <= 1.0 + self._epsilon):
            raise ValueError(f'不确定性必须在 [0,1] 范围内，当前值：{u_val}')

        if self._backend.any(belief_masses < -self._epsilon) or self._backend.any(
            belief_masses > 1 + self._epsilon
        ):
            raise ValueError('所有信念质量必须在 [0,1] 范围内')

        if self.n_states != self._backend.shape(base_rates)[0]:
            raise ValueError('信念质量和基率维度必须相同')

        if self.n_states < 2:
            raise ValueError('意见至少需要2个状态')

        # 2. 归一化并验证 belief_masses 和 uncertainty
        total = _item(self._backend, self._backend.sum(belief_masses)) + u_val
        total_deviation = abs(total - 1.0)
        if total_deviation > self._epsilon:
            # 偏差过大，是真正的错误
            raise ValueError(f'信念质量与不确定性之和不等于1，当前值：{total}')
        elif total_deviation > 0:  # 存在微小浮点数误差，自动修正
            if total_deviation > self._epsilon / 10:
                warning(
                    f'修正Opinion浮点误差 ({total_deviation:.1e}<{self._epsilon:.1e}), 进行归一化处理: [\\sum(b) + u] = {total} -> 1.0'
                )
            belief_masses /= total
            if isinstance(self.uncertainty, (int, float)):
                self.uncertainty = float(self.uncertainty) / total
            else:
                self.uncertainty = _as_tensorlike(self.uncertainty) / total
        # 如果 total 接近 0，则无需操作，后续非负确保会处理

        # 3. 归一化并验证 base_rates
        base_rates_sum = _item(self._backend, self._backend.sum(base_rates))
        base_rates_deviation = abs(base_rates_sum - 1.0)
        if base_rates_deviation > self._epsilon:
            raise ValueError(f'基率之和不等于1，当前值：{base_rates_sum}')
        elif base_rates_deviation > 0:
            if base_rates_deviation > self._epsilon / 10:
                warning(
                    f'修正Base Rate浮点误差 ({base_rates_deviation:.1e}<{self._epsilon:.1e}), 进行归一化处理: [\\sum(b)] = {base_rates_sum} -> 1.0'
                )
            base_rates /= base_rates_sum

        # 4. 最后确保数值非负
        self.belief_masses = self._backend.maximum(belief_masses, 0)
        if isinstance(self.uncertainty, (int, float)):
            self.uncertainty = max(float(self.uncertainty), 0.0)
        else:
            self.uncertainty = self._backend.maximum(_as_tensorlike(self.uncertainty), 0)
        self.base_rates = base_rates

    @property
    def n_states(self) -> int:
        """获取状态数量"""
        # return len(self.belief_masses)
        return self._backend.shape(_as_tensorlike(self.belief_masses))[0]

    @property
    def is_binomial(self) -> bool:
        """判断是否为二元意见"""
        return self.n_states == 2

    @property
    def is_vacuous(self) -> bool:
        """判断是否为完全不确定意见"""
        return (
            _item(self._backend, _as_scalar_or_tensor(self.uncertainty))
            > 1 - self._epsilon
        )

    @property
    def is_dogmatic(self) -> bool:
        """判断是否为教条意见（无不确定性）"""
        return (
            _item(self._backend, _as_scalar_or_tensor(self.uncertainty)) < self._epsilon
        )

    # 二元意见的便捷访问属性
    @property
    def belief(self) -> float | TensorLike:
        """
        二元意见的信念值

        Returns
        -------
        float | TensorLike
            信念值，仅在二元情况下有效

        Raises
        ------
        ValueError
            当不是二元意见时
        """
        if not self.is_binomial:
            raise ValueError('belief 属性仅对二元意见有效')
        return self.belief_masses[0]

    @property
    def disbelief(self) -> float | TensorLike:
        """
        二元意见的怀疑值

        Returns
        -------
        float
            怀疑值，仅在二元情况下有效
        """
        if not self.is_binomial:
            raise ValueError('disbelief 属性仅对二元意见有效')
        return self.belief_masses[1]

    @property
    def base_rate(self) -> float | TensorLike:
        """
        二元意见的基率

        Returns
        -------
        float
            第一个状态的基率，仅在二元情况下有效
        """
        if not self.is_binomial:
            raise ValueError('base_rate 属性仅对二元意见有效')
        return _as_tensorlike(self.base_rates)[0]

    @classmethod
    def from_binomial(
        cls, belief: float, disbelief: float, uncertainty: float, base_rate: float = 0.5
    ) -> 'Opinion':
        """
        从二元参数创建意见

        Parameters
        ----------
        - `belief` : `float`
            信念质量
        - `disbelief` : `float`
            怀疑质量
        - `uncertainty` : `float`
            不确定性质量
        - `base_rate` : `float`
            基率（默认0.5）

        Returns
        -------
        Opinion
            二元意见实例
        """
        belief_masses = [belief, disbelief]
        base_rates = [base_rate, 1 - base_rate]
        return cls(belief_masses, uncertainty, base_rates)

    @property
    def projected_probabilities(self) -> TensorLike:
        """
        计算投影概率分布 P(x_i) = b_i + a_i * u

        Returns
        -------
        TensorLike
            各状态的投影概率
        """
        return _as_tensorlike(self.belief_masses) + _as_tensorlike(
            self.base_rates
        ) * _as_scalar_or_tensor(self.uncertainty)

    @property
    def projected_probability(self) -> float | TensorLike:
        """
        二元意见的投影概率

        Returns
        -------
        float
            第一个状态的投影概率，仅在二元情况下有效
        """
        if not self.is_binomial:
            raise ValueError('projected_probability 属性仅对二元意见有效')
        return self.projected_probabilities[0]

    def to_dirichlet_params(self, W: float | None = None) -> TensorLike:
        """
        将意见转换为其在贝叶斯统计中等价的狄利克雷分布参数 (alpha)。

        转换公式为: α_i = e_i + W * a_i，其中 e_i 是证据，a_i 是基率。

        对于教条意见 (u=0)，其对应的证据 e_i 会非常大，因此生成的
        alpha 参数也会非常大。这会产生一个极其尖锐的狄利克雷分布，
        其峰值精确地位于信念所在的位置，这正是对“完全确定”的正确建模。

        Parameters
        ----------
        - `W` : `float`, optional
            非信息先验权重，默认为 None 时使用状态数 n_states 作为权重值

        Returns
        -------
        TensorLike
            Dirichlet 参数 α_i
        """
        # 如果 W 为 None，则使用状态数作为默认值
        if W is None:
            W = float(self.n_states)

        # 1. 首先，获取与该意见等价的证据。
        #    这确保了无论意见是否教条，我们都使用一致的逻辑。
        evidence = self.to_evidence(W)

        # 2. 然后，根据公式 α_i = e_i + W * a_i 计算 alpha 参数。
        #    W * a_i 是先验知识的贡献。
        return evidence + W * _as_tensorlike(self.base_rates)

    @classmethod
    def from_dirichlet_params(
        cls,
        alphas: TensorLike | typing.Sequence[float],
        base_rates: Optional[TensorLike | typing.Sequence[float]] = None,
        W: float | None = None,
        engine: Literal['numpy', 'torch'] = 'numpy',
    ) -> 'Opinion':
        """
        从 Dirichlet 参数创建意见

        Parameters
        ----------
        - `alphas` : `TensorLike`
            Dirichlet 分布参数
        - `base_rates` : `Optional[TensorLike]`
            基率分布，默认均匀分布
        - `W` : `float`, optional
            非信息先验权重，默认为 None 时使用状态数（len(alphas)）作为权重值
        - `engine` : `Literal['numpy', 'torch']`, optional
            后端类型，默认为 'numpy'

        Returns
        -------
        Opinion
            意见实例
        """
        backend = get_tensorlike_backend(engine)

        alphas = backend.as_tensorlike(alphas)
        k = backend.shape(alphas)[0]

        # 如果 W 为 None，则使用状态数作为默认值
        if W is None:
            W = float(k)

        if base_rates is None:
            base_rates = backend.ones((k,)) / k
        else:
            base_rates = backend.as_tensorlike(base_rates)

        # 提取证据
        evidence = alphas - W * base_rates
        evidence = backend.maximum(evidence, 0)  # 确保非负

        EPSILON = 1e-12
        if backend.backend_type == 'torch':
            dtype = backend.dtype(evidence)
            if dtype == backend.type('float32'):  # torch.float32
                EPSILON = 1e-7

        # 转换为意见
        total_evidence = backend.sum(evidence)
        if total_evidence < EPSILON:
            # 无证据情况
            return cls(backend.zeros((k,)), 1.0, base_rates)

        kappa = total_evidence + W
        belief_masses = evidence / kappa
        uncertainty = W / kappa

        return cls(belief_masses, uncertainty, base_rates)

    def to_beta_params(
        self, W: float | None = None
    ) -> Tuple[float | TensorLike, float | TensorLike]:
        """
        转换为 Beta 分布参数（仅二元情况）

        Parameters
        ----------
        - `W` : `float`, optional
            非信息先验权重，默认为 None 时使用状态数 n_states 作为权重值

        Returns
        -------
        (alpha, beta) 参数

        Raises
        ------
        ValueError
            当不是二元意见时
        """
        # 如果 W 为 None，则使用状态数作为默认值
        if W is None:
            W = float(self.n_states)
        if not self.is_binomial:
            raise ValueError('Beta 参数仅对二元意见有效')

        dirichlet_params = self.to_dirichlet_params(W)
        return dirichlet_params[0], dirichlet_params[1]

    @classmethod
    def from_beta_params(
        cls,
        alpha: float,
        beta: float,
        base_rate: float = 0.5,
        W: float | None = None,
        engine: Literal['numpy', 'torch'] = 'numpy',
    ) -> 'Opinion':
        """
        从 Beta 参数创建二元意见

        Parameters
        ----------
        - `alpha` : `float`
            Beta 分布 alpha 参数
        - `beta` : `float`
            Beta 分布 beta 参数
        - `base_rate` : `float`
            基率
        - `W` : `float`, optional
            非信息先验权重，默认为 None 时使用状态数（2）作为权重值

        Returns
        -------
        Opinion
            二元意见实例
        """
        # 对于二元意见，如果 W 为 None，则使用状态数 2 作为默认值
        if W is None:
            W = 2.0
        dirichlet_params = [alpha, beta]
        base_rates = [base_rate, 1 - base_rate]
        return cls.from_dirichlet_params(dirichlet_params, base_rates, W, engine)

    def to_evidence(self, W: float | None = None) -> TensorLike:
        """
        将意见转换为其等价的证据表示。

        该方法基于标准的主观逻辑公式: e_i = W * b_i / u。

        对于教条意见 (u=0)，理论上其证据是无穷大的。
        为了在数值上处理这种情况，通过除以 (u + EPSILON) 来近似一个非常大的证据值，从而避免了除以零的错误

        Parameters
        ----------
        - `W` : `float`, optional
            非信息先验权重，默认为 None 时使用状态数 n_states 作为权重值

        Returns
        -------
        TensorLike
            各状态的证据值
        """
        # 如果 W 为 None，则使用状态数作为默认值
        if W is None:
            W = float(self.n_states)

        # 使用 maximum 确保数值稳定性且保持梯度
        denominator = self._backend.maximum(
            _as_tensorlike(self.uncertainty), self._epsilon
        )

        return (W * _as_tensorlike(self.belief_masses)) / denominator

    @classmethod
    def from_evidence(
        cls,
        evidence: TensorLike | typing.Sequence,
        base_rates: Optional[TensorLike | typing.Sequence | float] = None,
        W: float | None = None,
        engine: Literal['numpy', 'torch'] | None = None,
    ) -> 'Opinion':
        """
        从证据创建意见

        Parameters
        ----------
        - `evidence` : `TensorLike`
            各状态的证据值
        - `base_rates` : `Optional[TensorLike]`
            基率分布
        - `W` : `float`, optional
            非信息先验权重，默认为 None 时使用状态数（len(evidence)）作为权重值

        Returns
        -------
        Opinion
            意见实例
        """
        backend_source = engine if engine is not None else evidence
        backend = get_tensorlike_backend(
            typing.cast(str | TensorLike | TensorLikeBackend, backend_source)
        )
        evidence = backend.as_tensorlike(evidence)
        k = backend.shape(evidence)[0]

        if W is None:
            W = float(k)

        if base_rates is None:
            base_rates = backend.ones((k,)) / k
        elif isinstance(base_rates, float):
            if k != 2:
                raise ValueError('float base_rates 仅适用于二元意见')
            base_rates = [base_rates, 1 - base_rates]

        total_evidence = backend.sum(evidence)
        kappa = total_evidence + W

        belief_masses = evidence / kappa
        uncertainty = W / kappa

        return cls(
            belief_masses,
            uncertainty,
            typing.cast(list | TensorLike | None, base_rates),
            engine=typing.cast(Literal['numpy', 'torch'], backend.backend_type),
        )

    def entropy(self) -> float | TensorLike:
        """
        计算意见的信息熵

        Returns
        -------
        float
            信息熵值
        """
        probs = self.projected_probabilities
        # 避免 log(0)
        probs = self._backend.maximum(probs, self._epsilon)
        return -self._backend.sum(probs * self._backend.log(probs))

    def pignistic_probability(self) -> TensorLike:
        """
        计算 pignistic 概率（用于决策）

        Returns
        -------
        TensorLike
            pignistic 概率分布
        """
        return (
            _as_tensorlike(self.belief_masses)
            + _as_scalar_or_tensor(self.uncertainty) / self.n_states
        )

    def __repr__(self) -> str:
        """字符串表示"""
        if self.is_binomial:
            belief_val = _item(self._backend, self.belief)
            disbelief_val = _item(self._backend, self.disbelief)
            uncertainty_val = _item(self._backend, _as_scalar_or_tensor(self.uncertainty))
            base_rate_val = _item(self._backend, self.base_rate)

            return (
                f'Opinion(b={belief_val:.3f}, d={disbelief_val:.3f}, '
                f'u={uncertainty_val:.3f}, a={base_rate_val:.3f})'
            )
        else:
            beliefs_np = self._backend.to_numpy(_as_tensorlike(self.belief_masses))
            uncertainty_val = _item(self._backend, _as_scalar_or_tensor(self.uncertainty))
            return (
                f'Opinion(beliefs={np.round(beliefs_np, 3)}, '
                f'u={uncertainty_val:.3f}, n_states={self.n_states})'
            )


class BinomialOpinion(Opinion):
    """
    二元意见类
    """

    def __init__(
        self, belief: float, disbelief: float, uncertainty: float, base_rate: float = 0.5
    ):
        super().__init__([belief, disbelief], uncertainty, [base_rate, 1 - base_rate])

    def negative(self):
        return BinomialOpinion(
            _item(self._backend, _as_scalar_or_tensor(self.disbelief)),
            _item(self._backend, _as_scalar_or_tensor(self.belief)),
            _item(self._backend, _as_scalar_or_tensor(self.uncertainty)),
            _item(self._backend, _as_scalar_or_tensor(self.base_rate)),
        )


def _ensure_same_backend(*opinions: 'Opinion') -> None:
    """确保所有 Opinion 使用相同的后端"""
    if len(opinions) < 2:
        return

    def _all_same(iterable):
        return len(set(iterable)) == 1

    backend_type = list(map(lambda opinion: opinion._backend.backend_type, opinions))
    if not _all_same(backend_type):
        raise ValueError(f'Opinion 后端不一致: {backend_type}')
    device = list(
        map(
            lambda opinion: opinion._backend.device(
                _as_tensorlike(opinion.belief_masses)
            ),
            opinions,
        )
    )
    if not _all_same(device):
        raise ValueError('Opinion 必须在相同设备上')


# ========================= Conflict ========================= #


def degree_of_conflict(
    op1: 'Opinion', op2: 'Opinion'
) -> Tuple[float | TensorLike, float | TensorLike, float | TensorLike]:
    """
    计算两个主观意见之间的冲突度 (Degree of Conflict, DC)。

    冲突度是投影距离 (PD) 和联合确定性 (CC) 的乘积。

    Parameters
    ----------
    op1 : Opinion
        第一个主观意见。
    op2 : Opinion
        第二个主观意见。

    Returns
    -------
    Tuple[float, float, float]
        一个包含三个值的元组：
        - `dc` (float): 冲突度 (Degree of Conflict)
        - `pd` (float): 投影距离 (Projected Distance)
        - `cc` (float): 联合确定性 (Conjunctive Certainty)

    Raises
    ------
    ValueError
        当两个意见的状态数不一致时。

    Notes
    -----
    - **投影距离 (PD)**: 衡量两个意见的期望概率分布之间的差异，
      其值在 [0, 1] 范围内。PD = 0 表示期望概率完全一致。
      PD = sum(|P1_k - P2_k|) / 2

    - **联合确定性 (CC)**: 衡量两个意见共同的确定性程度，
      其值在 [0, 1] 范围内。CC = 0 表示至少有一个意见是完全不确定的。
      CC = (1 - u1) * (1 - u2)

    - **冲突度 (DC)**: 结合了距离和确定性。只有当两个意见都比较确定
      (CC 较高) 且期望概率相差很大 (PD 较高) 时，冲突度才会高。
      DC = PD * CC
    """
    _ensure_same_backend(op1, op2)
    backend = op1._backend

    # 1. 验证输入
    if op1.n_states != op2.n_states:
        raise ValueError(
            'Opinions must have the same number of states to calculate conflict.'
        )

    # 2. 计算投影距离 (Projected Distance, PD)
    #    PD(ω¹, ω²) = Σ|P¹_k - P²_k| / 2
    p1 = op1.projected_probabilities
    p2 = op2.projected_probabilities
    # projected_distance = 0.5 * np.sum(np.abs(p1 - p2))
    projected_distance = 0.5 * backend.sum(backend.abs(p1 - p2))

    # 3. 计算联合确定性 (Conjunctive Certainty, CC)
    #    CC(ω¹, ω²) = (1 - u¹) * (1 - u²)
    conjunctive_certainty = (1.0 - op1.uncertainty) * (1.0 - op2.uncertainty)

    # 4. 计算冲突度 (Degree of Conflict, DC)
    #    DC(ω¹, ω²) = PD * CC
    conflict = projected_distance * conjunctive_certainty

    return conflict, projected_distance, conjunctive_certainty


# ========================= Opinion Representation ========================= #


def cumulative_fusion(
    opinions: typing.Sequence[Opinion], W: float | None = None, *, EPSILON=1e-12
) -> Opinion:
    """
    累积融合：组合多个独立信息源的证据

    Parameters
    ----------
    - `opinions : List[Opinion]`
        要融合的意见列表
    - `W : float`, optional
        非信息先验权重，默认为 None 时使用状态数作为权重值
    - `EPSILON` : `float`, optional
        数值精度，默认为 1e-12; 如果使用 torch.float32 数值, 建议设置为 1e-7 等稍微低精度的数值

    Returns
    -------
    Opinion
        融合后的意见

    Raises
    ------
    ValueError
        当意见维度不一致时


    Notes
    ----------

    累积融合算子（⊕）在数学上对应于贝叶斯定理的更新，而在证据空间中，贝叶斯更新就表现为证据的简单加法。即 evidence(A ⊕ B) = evidence(A) + evidence(B)。
    累积融合算子满足结合律，即 (A ⊕ B) ⊕ C = A ⊕ (B ⊕ C)。这意味着融合多个意见的顺序无关紧要。

    """
    if not opinions:
        raise ValueError('至少需要一个意见')

    _ensure_same_backend(*opinions)
    backend = opinions[0]._backend

    # 检查维度一致性
    n_states = opinions[0].n_states
    if not all(op.n_states == n_states for op in opinions):
        raise ValueError('所有意见必须具有相同的状态数')

    # 如果 W 为 None，则使用状态数作为默认值
    if W is None:
        W = float(n_states)

    # 检查基率一致性
    base_rates_list = [_as_tensorlike(op.base_rates) for op in opinions]
    first_br = base_rates_list[0]
    is_allclose = all(
        backend.allclose(br, first_br, atol=EPSILON) for br in base_rates_list[1:]
    )
    if not is_allclose:
        warning('基率不一致；使用平均值')
    # avg_base_rates = backend.mean(base_rates_list, axis=0)
    stacked_base_rates = backend.stack(base_rates_list, axis=0)
    avg_base_rates = backend.mean(stacked_base_rates, axis=0)

    # 累积证据
    # total_evidence = np.zeros(n_states)
    total_evidence = backend.zeros((n_states,))
    for op in opinions:
        evidence = op.to_evidence(W)
        total_evidence += evidence

    return Opinion.from_evidence(total_evidence, avg_base_rates, W)


def averaging_fusion(
    opinions: typing.Sequence[Opinion], W: float | None = None, *, EPSILON=1e-12
) -> Opinion:
    """
    平均融合：组合多个依赖信息源的意见。

    平均融合等价于在证据空间中对各意见的证据参数进行简单的算术平均。

    Parameters
    ----------
    - `opinions : List[Opinion]`
        要融合的意见列表。
    - `W : float`, optional
        非信息先验权重。如果为 None，则使用状态数作为默认值。
    - `EPSILON` : `float`, optional
        数值精度，默认为 1e-12; 如果使用 torch.float32 数值, 建议设置为 1e-7 等稍微低精度的数值

    Returns
    -------
    Opinion
        平均融合后的意见。
    """
    if not opinions:
        raise ValueError('至少需要一个意见')

    n = len(opinions)
    if n == 1:
        return opinions[0]

    _ensure_same_backend(*opinions)
    backend = opinions[0]._backend

    # 检查维度一致性
    n_states = opinions[0].n_states
    if not all(op.n_states == n_states for op in opinions):
        raise ValueError('所有意见必须具有相同的状态数')

    # 如果 W 未指定，使用状态数作为默认值
    if W is None:
        W = float(n_states)

    # 检查并计算平均基率
    base_rates_list = [_as_tensorlike(op.base_rates) for op in opinions]
    first_br = base_rates_list[0]
    is_allclose = all(
        backend.allclose(br, first_br, atol=EPSILON) for br in base_rates_list[1:]
    )
    if not is_allclose:
        warning('基率不一致；将使用其算术平均值作为融合后意见的基率。')
    # avg_base_rates = backend.mean(base_rates_list, axis=0)
    stacked_base_rates = backend.stack(base_rates_list, axis=0)
    avg_base_rates = backend.mean(stacked_base_rates, axis=0)

    # 1. & 2. 累加所有意见的证据
    # total_evidence = np.zeros(n_states, dtype=np.float64)
    total_evidence = backend.zeros((n_states,))
    for op in opinions:
        total_evidence += op.to_evidence(W)

    # 3. 计算平均证据
    avg_evidence = total_evidence / n

    # 4. 从平均证据映射回意见
    return Opinion.from_evidence(avg_evidence, avg_base_rates, W)


def weighted_fusion(
    opinions: typing.Sequence[Opinion], W: float | None = None, *, EPSILON=1e-12
) -> Opinion:
    """
    加权融合 (Weighted Belief Fusion, WBF): 基于置信度组合意见。

    WBF 等价于在证据空间中对各意见的证据参数进行以“置信度 (confidence = 1 - u)”为权重的加权平均。

    Parameters
    ----------
    - `opinions : List[Opinion]`
        要融合的意见列表。
    - `W : float`, optional
        非信息先验权重。如果为 None，则使用状态数作为默认值。
    - `EPSILON` : `float`, optional
        数值精度，默认为 1e-12; 如果使用 torch.float32 数值, 建议设置为 1e-7 等稍微低精度的数值

    Returns
    -------
    Opinion
        加权融合后的意见。
    """
    if not opinions:
        raise ValueError('至少需要一个意见')

    if len(opinions) == 1:
        return opinions[0]

    # 检查维度一致性
    n_states = opinions[0].n_states
    if not all(op.n_states == n_states for op in opinions):
        raise ValueError('所有意见必须具有相同的状态数')

    # 如果 W 未指定，使用状态数作为默认值
    if W is None:
        W = float(n_states)

    _ensure_same_backend(*opinions)
    backend = opinions[0]._backend

    # 1. & 2. 计算证据的置信度加权和以及基率的置信度加权和
    weighted_sum_evidence = backend.zeros((n_states,))
    weighted_sum_base_rates = backend.zeros((n_states,))
    total_confidence = 0.0

    for op in opinions:
        confidence = 1.0 - op.uncertainty
        confidence_val = _item(backend, _as_scalar_or_tensor(confidence))

        # 只有在置信度大于零时，意见才有贡献
        if confidence_val > EPSILON:
            evidence = op.to_evidence(W)
            weighted_sum_evidence += evidence * confidence
            weighted_sum_base_rates += _as_tensorlike(op.base_rates) * confidence
            total_confidence += confidence_val

    # 3. 处理所有意见都完全不确定的情况
    if total_confidence < EPSILON:
        # 如果所有意见都完全不确定，结果也是完全不确定
        # 基率可以是所有输入基率的简单平均
        # avg_base_rates = backend.mean([op.base_rates for op in opinions], axis=0)
        stacked_base_rates = backend.stack(
            [_as_tensorlike(op.base_rates) for op in opinions], axis=0
        )
        avg_base_rates = backend.mean(stacked_base_rates, axis=0)
        return Opinion(
            belief_masses=backend.zeros((n_states,)),
            uncertainty=1.0,
            base_rates=avg_base_rates,
        )

    # 4. 计算加权平均证据和加权平均基率
    fused_evidence = weighted_sum_evidence / total_confidence
    fused_base_rates = weighted_sum_base_rates / total_confidence

    # 5. 从融合后的证据映射回意见
    return Opinion.from_evidence(fused_evidence, fused_base_rates, W)


# ========================= Trust Network ========================= #


def trust_discounting(trust_opinion: Opinion, source_opinion: Opinion) -> Opinion:
    """
    对源意见进行信任折扣 (Trust Discounting for a two-edge path)。

    该操作符用于根据对信息源的信任度来调整（折扣）其提供的意见。
    例如，在信任链 A -> B -> X 中，A 对 B 的信任意见 (trust_opinion) 会折扣
    B 对 X 的原始意见 (source_opinion)，从而得出 A 对 X 的派生意见。

    Parameters
    ----------
    - `trust_opinion` : `Opinion`
        代表信任的意见，必须是二元意见 (Binomial Opinion)。
        例如，A 对 B 的信任 `ω_B^A`。
    - `source_opinion` : `Opinion`
        需要被折扣的原始意见，可以是多元意见 (Multinomial Opinion)。
        例如，B 对 X 的看法 `ω_X^B`。

    Returns
    -------
    Opinion
        折扣后得到的派生意见。例如，A 对 X 的看法 `ω_X^[A;B]`。

    Raises
    ------
    ValueError
        当信任意见不是二元意见时。
    """
    if not trust_opinion.is_binomial:
        raise ValueError('信任意见 (trust_opinion) 必须是二元意见。')

    _ensure_same_backend(trust_opinion, source_opinion)
    backend = trust_opinion._backend

    # 1. 计算信任意见的投影概率 P(A,B)，作为折扣系数。
    #    这个系数代表了 A 对 B 的整体信任度。
    discount_coefficient = trust_opinion.projected_probability

    # 2. 对源意见的信念质量进行折扣。
    #    每个信念 b_i 都乘以折扣系数。
    discounted_beliefs = discount_coefficient * _as_tensorlike(
        source_opinion.belief_masses
    )

    # 3. 计算新的不确定性。
    #    原始信念被折扣掉的部分 (1 - P) * sum(b_i) 全部转化为不确定性，
    #    并与原始的不确定性 u_B 相加。
    #    根据公式 u_[A;B] = 1 - P(A,B) * sum(b_i^B)，
    #    这等价于 u_[A;B] = 1 - sum(b_i^[A;B])。
    # discounted_uncertainty = 1.0 - np.sum(discounted_beliefs)
    discounted_uncertainty = 1.0 - backend.sum(_as_tensorlike(discounted_beliefs))

    # 4. 基率保持不变，继承自源意见。
    #    A's derived opinion inherits the base rate from B's opinion.
    derived_base_rates = source_opinion.base_rates

    return Opinion(
        belief_masses=discounted_beliefs,
        uncertainty=discounted_uncertainty,
        base_rates=derived_base_rates,
    )


def transitive_trust_discounting(
    trust_path: typing.Sequence[Opinion], source_opinion: Opinion
) -> Opinion:
    """
    对源意见进行多级信任传递折扣 (Trust Discounting for a multi-edge path)。

    处理一个长的信任链，例如 A1 -> A2 -> ... -> An -> X。
    最终的折扣系数是路径上所有信任意见投影概率的乘积。

    Parameters
    ----------
    - `trust_path` : `List[Opinion]`
        一个信任意见的列表，代表从第一个主体到最后一个信息源的信任路径。
        例如，[ω_A2^A1, ω_A3^A2, ..., ω_An^A(n-1)]。
        列表中的每个意见都必须是二元意见。
    - `source_opinion` : `Opinion`
        路径末端提供的原始意见。例如，An 对 X 的看法 `ω_X^An`。

    Returns
    -------
    Opinion
        经过整个信任链折扣后的最终派生意见。例如，A1 对 X 的看法 `ω_X^A1`。

    Raises
    ------
    ValueError
        当信任路径为空，或路径中有任何一个意见不是二元意见时。
    """
    if not trust_path:
        raise ValueError('信任路径 (trust_path) 不能为空。')

    _ensure_same_backend(*trust_path, source_opinion)
    backend = trust_path[0]._backend

    # 1. 计算整个信任路径的总体折扣系数。
    #    根据 Definition 14.7，它是路径上每个信任环节投影概率的乘积。
    overall_discount_coefficient = 1.0
    for i, trust_op in enumerate(trust_path):
        if not trust_op.is_binomial:
            raise ValueError(f'信任路径中位置 {i} 的意见不是二元意见。')
        overall_discount_coefficient *= trust_op.projected_probability

    # 2. 对源意见的信念质量进行折扣。
    discounted_beliefs = overall_discount_coefficient * _as_tensorlike(
        source_opinion.belief_masses
    )

    # 3. 计算新的不确定性。
    # discounted_uncertainty = 1.0 - np.sum(discounted_beliefs)
    discounted_uncertainty = 1.0 - backend.sum(_as_tensorlike(discounted_beliefs))

    # 4. 基率继承自源意见。
    derived_base_rates = source_opinion.base_rates

    return Opinion(
        belief_masses=discounted_beliefs,
        uncertainty=discounted_uncertainty,
        base_rates=derived_base_rates,
    )


# ========================= 其他 ========================= #


def opinion_distance(
    op1: Opinion, op2: Opinion, metric: str = 'euclidean'
) -> float | TensorLike:
    """
    计算两个意见之间的距离 (注意，这并非 Subjective logic 中有理论定义的计算方式)

    如果要衡量两个意见之间的冲突程度，可以使用 `degree_of_conflict`

    Parameters
    ----------
    - `op1 : Opinion`
        第一个意见
    - `op2 : Opinion`
        第二个意见
    - `metric : str`
        距离度量 ("euclidean", "manhattan", "cosine")

    Returns
    -------
    float
        距离值
    """
    if op1.n_states != op2.n_states:
        raise ValueError('意见必须具有相同的状态数')

    _ensure_same_backend(op1, op2)
    backend = op1._backend

    # 构建完整向量 [beliefs, uncertainty]
    uncertainty1 = _as_scalar_or_tensor(op1.uncertainty)
    if isinstance(uncertainty1, (int, float)):
        u1 = backend.as_tensorlike([uncertainty1])
    else:
        u1 = (
            backend.unsqueeze(uncertainty1, 0)
            if backend.ndim(uncertainty1) == 0
            else uncertainty1
        )
    uncertainty2 = _as_scalar_or_tensor(op2.uncertainty)
    if isinstance(uncertainty2, (int, float)):
        u2 = backend.as_tensorlike([uncertainty2])
    else:
        u2 = (
            backend.unsqueeze(uncertainty2, 0)
            if backend.ndim(uncertainty2) == 0
            else uncertainty2
        )
    vec1 = backend.concatenate([_as_tensorlike(op1.belief_masses), u1])
    vec2 = backend.concatenate([_as_tensorlike(op2.belief_masses), u2])

    if metric == 'euclidean':
        # return np.linalg.norm(vec1 - vec2)
        return backend.norm(vec1 - vec2)
    elif metric == 'manhattan':
        # return np.sum(np.abs(vec1 - vec2))
        return backend.sum(backend.abs(vec1 - vec2))
    elif metric == 'cosine':
        # return 1 - np.dot(vec1, vec2) / (np.linalg.norm(vec1) * np.linalg.norm(vec2))
        return 1 - backend.dot(vec1, vec2) / (backend.norm(vec1) * backend.norm(vec2))
    else:
        raise ValueError(f'不支持的距离度量：{metric}')


def aleatory_transform(prob_dist: TensorLike, confidence: float = 0.9) -> Opinion:
    """
    从概率分布创建意见（aleatory 到 epistemic 不确定性转换）

    Parameters
    ----------
    - `prob_dist : np.ndarray`
        概率分布
    - `confidence : float`
        置信度（1 - uncertainty）

    Returns
    -------
    Opinion
        转换后的意见
    """
    backend = get_tensorlike_backend(prob_dist)

    prob_dist = backend.as_tensorlike(prob_dist, dtype=np.float64)
    prob_dist = prob_dist / backend.sum(prob_dist)  # 归一化

    uncertainty = 1 - confidence
    belief_masses = prob_dist * confidence

    return Opinion(belief_masses, uncertainty, prob_dist)


def _xy2bc(xy, corners, tol=1e-3):
    """
    将笛卡尔坐标映射到重心坐标系

    Parameters
    ----------
    - `xy` : `array-like`
        笛卡尔坐标点
    - `corners` : `numpy.ndarray`
        三角形顶点坐标
    - `tol` : `float`, optional
        容差值，防止边界数值问题

    Returns
    -------
    numpy.ndarray
        重心坐标
    """
    # 每条边中点位置
    midpoints = [(corners[(i + 1) % 3] + corners[(i + 2) % 3]) / 2.0 for i in range(3)]
    s = [(corners[i] - midpoints[i]).dot(xy - midpoints[i]) / 0.75 for i in range(3)]
    return np.clip(s, tol, 1.0 - tol)


class DirichletDistribution:
    """
    Dirichlet分布类，用于计算概率密度函数
    """

    def __init__(self, alpha):
        from functools import reduce
        from math import gamma, log
        from operator import mul

        self.alpha = np.array(alpha, dtype=np.float64)

        # 使用对数空间计算，避免数值溢出
        try:
            # 计算 log(Γ(Σα_i)) - Σlog(Γ(α_i))
            log_coef = log(gamma(np.sum(self.alpha))) - sum(
                log(gamma(a)) for a in self.alpha
            )
            self._log_coef = log_coef
            self._coef = float(np.exp(log_coef))
            self._use_log = True
        except (OverflowError, ValueError):
            # 如果gamma函数溢出，使用近似或处理
            self._log_coef = 0.0
            self._coef = 1.0
            self._use_log = True

    def pdf(self, x):
        """计算概率密度函数值"""
        x = np.array(x, dtype=np.float64)

        # 确保x的值在有效范围内
        x = np.clip(x, 1e-10, 1 - 1e-10)

        if self._use_log:
            # 在对数空间计算
            log_pdf = self._log_coef + np.sum((self.alpha - 1) * np.log(x))
            return np.exp(np.clip(log_pdf, -500, 500))  # 限制指数范围
        else:
            # 原始计算（保留作为备用）
            from functools import reduce
            from operator import mul

            return self._coef * reduce(
                mul, [xx ** (aa - 1) for (xx, aa) in zip(x, self.alpha)]
            )


def _create_professional_simplex(
    op: Opinion,
    ax,
    vertices,
    resolution: int = 100,
    style: str = 'dirichlet',
    cmap=None,
    show_colorbar: bool = True,
    show_contour: bool = True,
    contour_levels: int = 10,
    W: float | None = None,
):
    """
    创建专业的单纯形可视化

    Parameters
    ----------
    - `op` : `Opinion`
        要可视化的意见
    - `ax` : `matplotlib.axes.Axes`
        绘图轴
    - `vertices` : `numpy.ndarray`
        三角形顶点坐标
    - `resolution` : `int`, optional
        网格分辨率，默认100
    - `style` : `str`, optional
        可视化风格：'dirichlet' (基于Dirichlet分布) 或 'intensity' (基于距离强度)
    - `cmap` : `str | matplotlib.colors.Colormap`, optional
        色彩映射，默认None时使用预设颜色
    - `show_colorbar` : `bool`, optional
        是否显示颜色条，默认True
    - `show_contour` : `bool`, optional
        是否显示等高线，默认True
    - `contour_levels` : `int`, optional
        等高线层数，默认10
    - `W` : `float`, optional
        非信息先验权重，用于Dirichlet转换
    """
    try:
        import matplotlib.pyplot as plt
        import matplotlib.tri as tri
        from matplotlib.colors import LinearSegmentedColormap
        from mpl_toolkits.axes_grid1.inset_locator import inset_axes
    except ImportError:
        raise ImportError('需要安装 matplotlib 进行高级可视化')

    # 创建高精度三角剖分网格
    triangle = tri.Triangulation(vertices[:, 0], vertices[:, 1])
    refiner = tri.UniformTriRefiner(triangle)
    trimesh = refiner.refine_triangulation(subdiv=resolution // 20)  # 调整细分级别

    if style == 'dirichlet':
        # 基于Dirichlet分布的可视化
        dirichlet_params = op.to_dirichlet_params(W)
        dist = DirichletDistribution(dirichlet_params)

        # 计算每个网格点的概率密度
        pvals = []
        for x, y in zip(trimesh.x, trimesh.y):
            bc = _xy2bc(np.array([x, y]), vertices)
            pvals.append(dist.pdf(bc))
        pvals = np.array(pvals)

    else:  # intensity style
        # 基于距离强度的可视化（改进版）
        opinion_proj = np.dot(op.projected_probabilities, vertices)
        distances = np.sqrt(
            (trimesh.x - opinion_proj[0]) ** 2 + (trimesh.y - opinion_proj[1]) ** 2
        )

        # 改进的强度计算，更好地反映不确定性
        uncertainty_factor = max(op.uncertainty, 0.01)
        # 使用更平滑的衰减函数
        pvals = np.exp(-(distances**2) / (2 * uncertainty_factor**2 * 0.1))

    # 设置默认色彩映射
    if cmap is None:
        if style == 'dirichlet':
            cmap = 'Reds'
        else:
            # 自定义渐变色彩
            colors = ['#2E1A47', '#8B3A8B', '#E8A87E', '#FFF3C7']
            cmap = LinearSegmentedColormap.from_list('opinion', colors, N=256)

    # 绘制填充等高线
    tcf = ax.tricontourf(trimesh, pvals, levels=50, cmap=cmap, alpha=0.8)

    # 绘制等高线
    if show_contour:
        ax.tricontour(
            trimesh,
            pvals,
            levels=contour_levels,
            colors='white',
            linewidths=0.5,
            alpha=0.7,
        )

    # 添加颜色条
    if show_colorbar:
        try:
            axins = inset_axes(
                ax,
                width='4%',
                height='60%',
                loc='upper left',
                bbox_to_anchor=(1.02, 0.0, 1, 1),
                bbox_transform=ax.transAxes,
                borderpad=0,
            )
            cbar = plt.colorbar(tcf, cax=axins)
            if style == 'dirichlet':
                cbar.set_label('Probability Density', rotation=270, labelpad=15)
            else:
                cbar.set_label('Belief Intensity', rotation=270, labelpad=15)
        except Exception:
            # 如果颜色条创建失败，静默跳过
            pass

    # 绘制三角形边界（加粗处理）
    edge_coords = [
        ([vertices[0, 0], vertices[1, 0]], [vertices[0, 1], vertices[1, 1]]),
        ([vertices[1, 0], vertices[2, 0]], [vertices[1, 1], vertices[2, 1]]),
        ([vertices[2, 0], vertices[0, 0]], [vertices[2, 1], vertices[0, 1]]),
    ]

    for x_coords, y_coords in edge_coords:
        ax.plot(x_coords, y_coords, 'white', linewidth=3, alpha=0.9)
        ax.plot(x_coords, y_coords, 'black', linewidth=1.5, alpha=0.8)

    # 绘制意见点
    opinion_proj = np.dot(op.projected_probabilities, vertices)
    ax.plot(
        opinion_proj[0],
        opinion_proj[1],
        'o',
        color='yellow',
        markersize=10,
        markeredgecolor='black',
        markeredgewidth=2,
        alpha=0.9,
        zorder=10,
    )

    return tcf


def visualize_opinion_simplex(
    op: Opinion,
    ax=None,
    style='dirichlet',
    resolution: int = 100,
    cmap=None,
    show_colorbar: bool = True,
    show_contour: bool = False,
    contour_levels: int = 10,
    W: float | None = None,
    labels: Optional[typing.Sequence[str]] = None,
    title: Optional[str] = None,
    figsize: Tuple[int, int] = (10, 8),
    dpi: int = 150,
):
    """
    在单纯形上可视化意见（仅适用于三元意见）

    Parameters
    ----------
    - `op` : `Opinion`
        要可视化的意见（必须是3个状态）
    - `ax` : `matplotlib.axes.Axes`, optional
        绘图轴，如果为 None 则创建新图
    - `style` : `str`, optional
        可视化风格：
        - 'basic': 简单点和圆圈表示
        - 'dirichlet': 基于Dirichlet分布的概率密度可视化（推荐）
        - 'intensity': 基于距离强度的渐变可视化
    - `resolution` : `int`, optional
        网格分辨率，默认100。值越大越精细但计算越慢
    - `cmap` : `str | matplotlib.colors.Colormap`, optional
        色彩映射。可以是matplotlib内置名称（如'Reds', 'viridis'）或自定义Colormap对象
    - `show_colorbar` : `bool`, optional
        是否显示颜色条，默认True
    - `show_contour` : `bool`, optional
        是否显示等高线，默认True
    - `contour_levels` : `int`, optional
        等高线层数，默认10
    - `W` : `float`, optional
        非信息先验权重，用于Dirichlet转换。默认使用状态数
    - `labels` : `Optional[List[str]]`, optional
        状态标签，默认为 ['State 0', 'State 1', 'State 2']
    - `title` : `Optional[str]`, optional
        图表标题，默认自动生成
    - `figsize` : `Tuple[int, int]`, optional
        图形尺寸，默认(10, 8)
    - `dpi` : `int`, optional
        图形DPI，默认150

    Returns
    -------
    matplotlib.axes.Axes
        绘图轴

    Examples
    --------
    ```python
    # 基本使用
    op = Opinion([0.6, 0.2, 0.1], uncertainty=0.1)
    visualize_opinion_simplex(op)

    # 自定义色彩映射
    visualize_opinion_simplex(op, cmap='plasma', style='dirichlet')

    # 自定义标签和标题
    visualize_opinion_simplex(
        op,
        labels=['Class A', 'Class B', 'Class C'],
        title='Classification Confidence',
        style='dirichlet'
    )
    ```
    """
    if op.n_states != 3:
        raise ValueError('单纯形可视化仅支持3个状态的意见')

    try:
        import matplotlib.pyplot as plt
        from matplotlib.patches import Circle, Polygon
    except ImportError:
        raise ImportError('需要安装 matplotlib 进行可视化')

    # 创建图形
    if ax is None:
        fig, ax = plt.subplots(figsize=figsize, dpi=dpi)
        fig.patch.set_facecolor('white')

    # 三角形顶点（单纯形）- 使用标准等边三角形
    vertices = np.array([[0, 0], [1, 0], [0.5, np.sqrt(3) / 2]])

    if style in ['dirichlet', 'intensity']:
        # 专业可视化模式
        _create_professional_simplex(
            op,
            ax,
            vertices,
            resolution,
            style,
            cmap,
            show_colorbar,
            show_contour,
            contour_levels,
            W,
        )
    else:
        # 基础模式
        # 绘制三角形
        triangle = Polygon(vertices, fill=False, edgecolor='black', linewidth=2)
        ax.add_patch(triangle)

        # 计算意见在单纯形上的位置
        coords = np.dot(op.projected_probabilities, vertices)

        # 绘制意见点
        ax.plot(coords[0], coords[1], 'ro', markersize=10)

        # 绘制不确定性圆
        uncertainty = _as_scalar_or_tensor(op.uncertainty)
        uncertainty_val = _item(op._backend, uncertainty)
        if uncertainty_val > 0:
            circle = Circle(
                coords,
                uncertainty_val * 0.3,
                fill=False,
                edgecolor='red',
                linestyle='--',
                linewidth=2,
            )
            ax.add_patch(circle)

    # 标注顶点
    if labels is None:
        labels = ['State 0', 'State 1', 'State 2']

    label_color = 'white' if style in ['dirichlet', 'intensity'] else 'black'
    label_positions = [
        (vertices[0] + np.array([-0.08, -0.05])),  # 左下
        (vertices[1] + np.array([0.08, -0.05])),  # 右下
        (vertices[2] + np.array([0.0, 0.05])),  # 顶部
    ]

    alignments = ['right', 'left', 'center']

    for i, (pos, label, align) in enumerate(zip(label_positions, labels, alignments)):
        ax.text(
            pos[0],
            pos[1],
            label,
            ha=align,
            va='center',
            color=label_color,
            fontweight='bold',
            fontsize=12,
            bbox=dict(boxstyle='round,pad=0.3', facecolor='black', alpha=0.7)
            if style in ['dirichlet', 'intensity']
            else None,
        )

    # 设置坐标轴
    ax.set_xlim(-0.15, 1.15)
    ax.set_ylim(-0.1, 0.95)
    ax.set_aspect('equal')
    ax.axis('off')

    # 设置标题
    if title is None:
        if op.is_binomial:
            belief_val = _item(op._backend, op.belief)
            disbelief_val = _item(op._backend, op.disbelief)
            uncertainty_val = _item(op._backend, _as_scalar_or_tensor(op.uncertainty))
            title = f'Opinion: b={belief_val:.3f}, d={disbelief_val:.3f}, u={uncertainty_val:.3f}'
        else:
            beliefs_np = op._backend.to_numpy(_as_tensorlike(op.belief_masses))
            uncertainty_val = _item(op._backend, _as_scalar_or_tensor(op.uncertainty))
            title = f'Opinion: beliefs={np.round(beliefs_np, 3)}, u={uncertainty_val:.3f}'

    title_color = 'white' if style in ['dirichlet', 'intensity'] else 'black'
    ax.set_title(title, color=title_color, fontweight='bold', fontsize=14, pad=20)

    return ax
