"""
EEG 脑电信号滤波代码

为避免边界伪影，强烈建议在连续数据上进行滤波，然后再进行分段。
对已分段的数据进行滤波可能会引入严重的边界效应。

不建议完全依赖 auto 自动滤波参数计算，建议手动设计最优参数进行滤波，以确保可复现性。
"""

import typing

import warnings
from typing import Any, Literal, Optional, Protocol, Tuple, TypedDict, Union, cast

import matplotlib.pyplot as plt
import numpy as np
from scipy import signal

from zyplib.utils.decorators import deprecated
from zyplib.utils.ensure import ensure_npy
from zyplib.utils.print import warning

# FilterType = Literal['butter', 'cheby1', 'cheby2', 'ellip', 'bessel']
__all__ = ['notch', 'bandpass_butter', 'get_filter_design', 'plot_filter_response']


class _FirwinProtocol(Protocol):
    def __call__(
        self,
        numtaps: int,
        cutoff: float | list[float] | np.ndarray,
        *,
        window: str | tuple[str, float | None],
        pass_zero: bool | str,
        fs: float,
    ) -> np.ndarray: ...


class _FreqzProtocol(Protocol):
    def __call__(
        self,
        b: np.ndarray | None,
        a: np.ndarray | None,
        *,
        worN: int | np.ndarray,
        fs: float,
    ) -> tuple[np.ndarray, np.ndarray]: ...


def _get_firwin() -> _FirwinProtocol:
    return cast(_FirwinProtocol, signal.firwin)


def _get_freqz() -> _FreqzProtocol:
    return cast(_FreqzProtocol, signal.freqz)


def notch(
    X: np.ndarray, fs: float, f0: float = 50, Q: float = 30, axis: int = -1
) -> np.ndarray:
    """陷波滤波器 (Notch Filter)

    使用零相位滤波方法实现陷波滤波器

    Parameters
    ----------
    - `X` : array_like
        - 输入信号
    - `fs` : float
        - 采样频率 (Hz)
    - `f0` : float, optional
        - 陷波频率,默认为 50 Hz
    - `Q` : float, optional
        - Q因子,默认为 30
        - 定义滤波器的带宽:`Q = f0 / Δf`
        - 参考: https://en.wikipedia.org/wiki/Q_factor
    - `axis` : int, optional
        - 应用滤波的轴,默认为 -1

    Returns
    ----------
    - `y` : ndarray
        - 滤波后的信号,零相位延迟
    """
    X = ensure_npy(X)
    b, a = signal.iirnotch(f0, Q, fs)

    # 使用 filtfilt 实现零相位滤波,更适合EEG分析
    return signal.filtfilt(b, a, X, axis=axis)


def _filter_band(lfreq: float | None = None, rfreq: float | None = None):
    if lfreq is None and rfreq is None:
        raise ValueError('lfreq 和 rfreq 不能同时为 None。')
    elif lfreq is not None and rfreq is not None:
        if lfreq >= rfreq:
            raise ValueError(f'lfreq ({lfreq}Hz) 必须小于 rfreq ({rfreq}Hz)')
        btype = 'bandpass'
        Wn = [lfreq, rfreq]
    elif lfreq is not None:
        btype = 'highpass'
        Wn = lfreq
    else:  # rfreq is not None
        btype = 'lowpass'
        Wn = rfreq
    return btype, Wn


def _check_iir_stability(b, a):
    """检查IIR滤波器稳定性"""
    poles = np.roots(a)
    unstable_poles = np.abs(poles) >= 1.0
    if np.any(unstable_poles):
        max_pole_mag = np.max(np.abs(poles))
        warning(
            f'检测到 IIR 滤波参数不稳定极点 (最大幅值: {max_pole_mag:.3f}), 建议降低阶数或调整参数'
        )
        return False
    return True


def filter_iir(
    X,
    fs: float,
    lfreq: Optional[float] = None,
    rfreq: Optional[float] = None,
    order=3,
    method: Literal['sos', 'ba'] = 'sos',
    nan: Literal['raise', 'warn', 'ignore', 'reduce-order'] = 'raise',
    axis=-1,
    filter_type: Literal['butter', 'cheby1', 'cheby2', 'ellip', 'bessel'] = 'butter',
    rp: float = 0.1,  # 通带波纹 (dB)，用于 cheby1 和 ellip
    rs: float = 40,  # 阻带衰减 (dB)，用于 cheby2 和 ellip
    raise_if_instable: bool = True,
):
    """IIR 数字滤波器 (巴特沃斯等)

    使用零相位滤波方法实现IIR滤波器，支持低通、高通和带通。

    Parameters
    ----------
    - `X` : array_like
        - 输入信号
    - `fs` : float
        - 采样频率 (Hz)
    - `lfreq` : float or None
        - 通带下限频率 (Hz)。对于低通滤波，此项为 None。
    - `rfreq` : float or None
        - 通带上限频率 (Hz)。对于高通滤波，此项为 None。
    - `order` : int, optional
        - 滤波器阶数,默认为 3
    - `method` : str, optional
        - 滤波器方法,默认为 'sos'。
        - 'sos': 使用 sos 系数。
        - 'ba': 使用 b, a 系数。
    - `nan` : str, optional
        - 处理 NaN 值的方式,默认为 'raise'。
        - 'raise': 抛出异常。
        - 'warn': 警告并尝试降低阶数。
        - 'ignore': 忽略 NaN 值。
        - 'reduce-order': 尝试降低阶数以避免 NaN 值。
    - `axis` : int, optional
        - 应用滤波的轴,默认为 -1。
    - `filter_type`: str, optional
        - IIR 滤波器类型, 默认为 'butter'
    - `rp` : float, optional
        - 通带波纹 (dB)，用于 cheby1 和 ellip，默认 0.1
    - `rs` : float, optional
        - 阻带衰减 (dB)，用于 cheby2 和 ellip，默认 40

    Returns
    ----------
    - `y` : ndarray
        - 滤波后的信号
    """
    X = ensure_npy(X)

    btype, Wn = _filter_band(lfreq, rfreq)

    designer = getattr(signal, filter_type)

    try:
        # 根据滤波器类型准备参数
        kwargs = {'btype': btype, 'output': method, 'fs': fs}

        # 准备传递给设计函数的参数
        designer_args = cast(list[Any], [order])

        # 为不同滤波器类型添加特定参数
        if filter_type == 'cheby1':
            designer_args.append(rp)
        elif filter_type == 'cheby2':
            designer_args.append(rs)
        elif filter_type == 'ellip':
            designer_args.append(rp)
            designer_args.append(rs)
        # butter 和 bessel 不需要额外参数

        designer_args.append(Wn)

        if method == 'sos':
            sos = designer(*designer_args, **kwargs)
            y = signal.sosfiltfilt(sos, X, axis=axis, padtype='odd')
        elif method == 'ba':
            kwargs['output'] = 'ba'  # 确保输出类型正确
            b, a = designer(*designer_args, **kwargs)
            instable = _check_iir_stability(b, a)
            if raise_if_instable and instable:
                raise ValueError('IIR 滤波参数不稳定，建议降低阶数或调整参数')
            y = signal.filtfilt(b, a, X, axis=axis, padtype='odd')
        else:
            raise ValueError('Invalid method. Must be "sos" or "ba"')
    except ValueError as e:
        # Catch potential errors from scipy, e.g., invalid frequency ranges
        raise ValueError(f'滤波器设计失败，请检查频率参数: {e}')

    if np.isnan(y).any():
        if nan == 'warn':
            warning('输出中检测到NaN值。尝试降低阶数。', stacklevel=3)
        elif nan == 'raise':
            raise ValueError('输出中检测到NaN值。')
        elif nan == 'reduce-order':
            if order > 1:
                new_order = max(1, order - 1)
                return filter_iir(
                    X,
                    fs,
                    lfreq,
                    rfreq,
                    order=new_order,
                    filter_type=filter_type,
                    rp=rp,
                    rs=rs,
                    method=method,
                    nan='raise',  # 只 reduce 一次; 避免多重递归
                    axis=axis,
                )
            else:
                raise ValueError('即使在 order=1 时也检测到NaN值。')
    return y


@deprecated(message='使用 filter_iir 替代')
def bandpass_butter(
    X,
    fs: float,
    lfreq: float,
    rfreq: float,
    order=3,
    method: Literal['sos', 'ba'] = 'sos',
    nan: Literal['raise', 'warn', 'ignore', 'reduce-order'] = 'raise',
    axis=-1,
):
    """巴特沃斯带通滤波器

    使用零相位滤波方法实现巴特沃斯带通滤波器,适用于EEG信号处理。

    Parameters
    ----------
    - `X` : array_like
        - 输入信号
    - `fs` : float
        - 采样频率 (Hz)
    - `lfreq` : float
        - 通带下限频率 (Hz)
    - `rfreq` : float
        - 通带上限频率 (Hz)
    - `order` : int, optional
        - 滤波器阶数,默认为 3
    - `method` : {'sos', 'ba'}, optional
        - 滤波方法,默认为 'sos'
        - 'sos': 使用二阶截面 (Second-Order Sections) 方法,数值稳定性更好
        - 'ba': 使用传统传递函数方法
    - `nan` : {'raise', 'warn', 'ignore', 'reduce-order'}, optional
        - NaN值处理方式,默认为 'raise'
        - 'raise': 检测到NaN时抛出异常
        - 'warn': 检测到NaN时发出警告
        - 'ignore': 忽略NaN值
        - 'reduce-order': 自动降低滤波器阶数重试
    - `axis` : int, optional
        - 应用滤波的轴,默认为 -1

    Returns
    ----------
    - `y` : ndarray
        - 滤波后的信号

    Raises
    ----------
    - `ValueError`
        - 当频率范围无效时
        - 当检测到NaN值且 `nan='raise'` 时
    """
    return filter_iir(X, fs, lfreq, rfreq, order, method, nan, axis, filter_type='butter')


def _validate_fir_params(X, fs, lfreq=None, rfreq=None):
    """验证FIR滤波器参数"""
    X = cast(np.ndarray, ensure_npy(X))

    signal_length = X.shape[-1]
    if signal_length < 3:
        raise ValueError('Signal too short for filtering (must have at least 3 samples)')

    nyq = 0.5 * fs

    if lfreq is not None:
        if not 0 < lfreq < nyq:
            raise ValueError(f'lfreq ({lfreq}Hz) 必须在 0 和奈奎斯特频率 ({nyq}Hz) 之间')

    if rfreq is not None:
        if not 0 < rfreq < nyq:
            raise ValueError(f'rfreq ({rfreq}Hz) 必须在 0 和奈奎斯特频率 ({nyq}Hz) 之间')

    if lfreq is not None and rfreq is not None:
        if lfreq >= rfreq:
            raise ValueError(f'lfreq ({lfreq}Hz) 必须小于 rfreq ({rfreq}Hz)')
        if lfreq < 1.0 and (rfreq - lfreq) > 20.0:
            warning(
                f'您正在进行一个从 {lfreq}Hz 开始的宽带通滤波。'
                '这可能导致非常高的滤波器阶数和边界效应。'
                f'建议考虑分两步进行：先进行低通滤波(e.g., rfreq={rfreq}Hz)，'
                f'然后再进行高通滤波(e.g., lfreq={lfreq}Hz)。',
                stacklevel=3,
            )

    return X, nyq


def _calculate_transition_bandwidth(
    fs: float, lfreq: Optional[float], rfreq: Optional[float]
) -> float:
    """
    计算过渡带宽; 参考 MNE 策略

    https://mne.tools/stable/generated/mne.filter.filter_data.html

    过渡带通常设置为截止频率的25%。
    设置一个合理的最小带宽（例如2Hz），以防止在低频时过渡带过窄导致阶数过高。
    确保过渡带不会超出可用频率范围。
    """
    nyq = fs / 2.0

    # 统一的过渡带宽计算逻辑
    def _calc_single_tb(freq: float, filter_type: Literal['high', 'low']):
        desired = max(freq * 0.25, 2.0)
        if filter_type == 'high':
            max_allowed = freq - 1e-3
        elif filter_type == 'low':
            max_allowed = nyq - freq
        actual = min(desired, max_allowed)

        # 检查是否被过度压缩
        if actual < desired * 0.5:
            filter_name = '高通' if filter_type == 'high' else '低通'
            warning(
                f'{filter_name}过渡带宽被限制为 {actual:.3f}Hz (期望 {desired:.1f}Hz)，'
                f'可能导致高阶滤波器',
                stacklevel=4,
            )

        return actual, desired

    # Case 1: 带通滤波器
    if lfreq is not None and rfreq is not None:
        l_actual, _ = _calc_single_tb(lfreq, filter_type='high')
        h_actual, _ = _calc_single_tb(rfreq, filter_type='low')

        # 检查两侧差异
        if abs(l_actual - h_actual) > 10:
            warning(
                f'过渡带宽度 {l_actual:.1f}Hz 和 {h_actual:.1f}Hz 差别较大; '
                f'或许可以尝试分别使用高通和低通滤波器',
                stacklevel=3,
            )

        return min(l_actual, h_actual)

    # Case 2: 高通滤波器
    elif lfreq is not None:
        actual, _ = _calc_single_tb(lfreq, filter_type='high')
        return actual

    # Case 3: 低通滤波器
    elif rfreq is not None:
        actual, _ = _calc_single_tb(rfreq, filter_type='low')
        return actual

    else:
        raise ValueError('lfreq 和 rfreq 不能同时为 None')


def _estimate_fir_taps(
    transition_width: float, fs: float, window: str, stop_atten: float = 60
) -> Tuple[int, Optional[float]]:
    """
    估算FIR滤波器阶数 (taps)，优先使用Scipy原生方法。

    Parameters
    ----------
    transition_width : float
        过渡带宽 (Hz)。
    fs : float
        采样频率 (Hz)。
    stop_atten : float
        期望的停带衰减 (dB)。

    Returns
    -------
    numtaps : int
        估算的滤波器阶数。
    kaiser_beta : float or None
        如果 window='kaiser'，返回beta值，否则为None。
    """
    if window == 'kaiser':
        # 使用 Scipy 的标准方法来计算 Kaiser 窗的阶数和 beta 值
        # 注意: kaiserord 需要归一化到奈奎斯特频率的宽度
        width_nyq = transition_width / (0.5 * fs)
        if width_nyq >= 1.0:
            raise ValueError(f'过渡带宽 {transition_width}Hz 相对于采样率过大')
        if width_nyq <= 0:
            raise ValueError(f'过渡带宽 {transition_width}Hz 必须为正数')
        numtaps, kaiser_beta = signal.kaiserord(stop_atten, width_nyq)
        return int(numtaps), float(kaiser_beta)

    # 对于其他窗函数，使用经典的经验公式
    window_factors = {
        'boxcar': 1.8,
        'triang': 2.9,
        'hann': 3.1,
        'hamming': 3.3,
        'blackman': 5.5,
        'blackmanharris': 7.9,
        'nuttall': 7.9,
    }

    factor = window_factors.get(window.lower())
    if factor is None:
        warning(
            f"未知窗函数 '{window}'，使用 'hamming' 的参数作为保守估计。", stacklevel=4
        )
        factor = 3.3

    tw_norm = transition_width / fs
    numtaps = int(np.ceil(factor / tw_norm))
    if numtaps % 2 == 0:
        numtaps += 1
    return max(3, numtaps), None


def _get_optimal_padding(X, numtaps, axis=-1):
    """
    为EEG信号计算最优的边界填充参数

    Parameters
    ----------
    X : array
        输入信号
    numtaps : int
        滤波器阶数
    axis : int
        滤波轴

    Returns
    -------
    padlen : int
        最优填充长度
    """
    signal_length = X.shape[axis]

    # 保守的padding策略：至少3倍滤波器延迟
    # 但不超过信号长度的1/4，确保有足够的"真实"信号
    min_padlen = 3 * (numtaps - 1)
    max_padlen = signal_length // 4

    if min_padlen > max_padlen:
        # 信号太短，使用最大可能的padding
        padlen = max_padlen
        if padlen < numtaps:
            warning(
                f'信号长度 {signal_length} 相对于滤波器阶数 {numtaps} 太短，'
                f'可能导致边界效应。建议使用更低阶数的滤波器。',
                stacklevel=3,
            )
    else:
        padlen = min_padlen

    # 确保padding长度为正且小于信号长度
    padlen = max(0, min(padlen, signal_length - 1))

    return padlen


def _check_signal_quality_for_filtering(X, fs, lfreq, rfreq, numtaps):
    """
    检查信号质量是否适合滤波

    Parameters
    ----------
    X : array
        输入信号
    fs : float
        采样频率
    lfreq, rfreq : float
        滤波频率范围
    numtaps : int
        滤波器阶数
    """
    signal_length = X.shape[-1]
    duration = signal_length / fs

    # 检查1：信号长度是否足够 (仅对高通和带通有效)
    if lfreq is not None:
        min_cycles = 3
        min_duration = min_cycles / lfreq
        if duration < min_duration:
            warning(
                f'信号时长 {duration:.2f}s 可能不足以可靠滤波 {lfreq}Hz，建议至少 {min_duration:.2f}s',
                stacklevel=3,
            )

    # 检查2：滤波器阶数相对于信号长度 (通用)
    if numtaps > signal_length // 3:  # MNE uses 1/3 as a rule of thumb
        warning(
            f'滤波器阶数 {numtaps} 相对于信号长度 {signal_length} 过高，可能导致严重的边界效应或错误。',
            stacklevel=3,
        )

    # 检查3：通带宽度 (仅对带通有效)
    if lfreq is not None and rfreq is not None:
        passband_width = rfreq - lfreq
        if passband_width < 1.0:
            warning(
                f'通带宽度 {passband_width}Hz 很窄，滤波器可能过于尖锐。', stacklevel=3
            )


def _design_fir_kernel(
    fs: float,
    lfreq: Optional[float],
    rfreq: Optional[float],
    numtaps_param: Union[int, str],
    window: str
    | Literal[
        'boxcar',
        'triang',
        'hann',
        'hamming',
        'blackman',
        'blackmanharris',
        'nuttall',
        'kaiser',
    ],
    stop_atten: float,
    transition_width_param: Union[float, str],
    max_numtaps: Optional[Union[int, float]] = None,
    exceed_numtaps: Literal['warn', 'raise', 'clip'] = 'warn',
    signal_len: Optional[int] = None,
) -> Tuple[np.ndarray, int]:
    """
    核心FIR滤波器设计函数 (私有)。

    根据给定的参数设计FIR滤波器核(系数h)并返回最终使用的阶数。
    整合了过渡带和阶数的自动计算逻辑，包括 max_numtaps 限制处理。

    Parameters
    ----------
    - `max_numtaps` : Union[int, float, None], optional
        - 最大滤波器阶数限制，默认为 None（无限制）
        - 如果为 int，代表最大阶数；如果为 float，代表最大阶数占信号长度的比例
    - `signal_len` : int, optional
        - 当 max_numtaps 为比例时，用于计算实际最大阶数的信号长度

    Returns
    -------
    - h : np.ndarray
        - 设计好的FIR滤波器系数。
    - final_numtaps : int
        - 最终用于设计的滤波器阶数（已应用所有限制）。
    """
    # 1. 确定截止频率和通带类型
    if lfreq is not None and rfreq is not None:
        cutoff, pass_zero = [lfreq, rfreq], False
    elif lfreq is not None:
        cutoff, pass_zero = lfreq, False
    elif rfreq is not None:
        cutoff, pass_zero = rfreq, True
    else:
        raise ValueError('lfreq 和 rfreq 不能同时为 None')

    # 2. 确定过渡带宽
    transition_width = (
        _calculate_transition_bandwidth(fs, lfreq, rfreq)
        if transition_width_param == 'auto'
        else float(transition_width_param)
    )

    # 3. 确定滤波器阶数 (numtaps)
    kaiser_beta = None
    if numtaps_param == 'auto':
        numtaps, kaiser_beta = _estimate_fir_taps(
            transition_width, fs, window, stop_atten
        )
    else:
        numtaps = int(numtaps_param)
        # 如果手动指定阶数且使用kaiser窗，需要计算beta值
        if window == 'kaiser':
            # 使用默认的beta值或根据停带衰减计算
            kaiser_beta = signal.kaiser_beta(stop_atten)

    # 4. 应用所有阶数限制
    # 4.1 确定最大允许阶数
    max_taps_allowed = float('inf')
    if signal_len is not None:
        # 物理限制：阶数必须小于信号长度
        max_taps_allowed = min(max_taps_allowed, signal_len - 1)

    if max_numtaps is not None:
        if isinstance(max_numtaps, float):
            if signal_len is None:
                raise ValueError('当 max_numtaps 为比例时，必须提供 signal_len 参数')
            max_from_param = int(max_numtaps * signal_len)
        else:
            max_from_param = max_numtaps
        max_taps_allowed = min(max_taps_allowed, max_from_param)

    # 4.2 应用限制
    if numtaps > max_taps_allowed:
        if exceed_numtaps == 'warn':
            warning(
                f'计算或指定的阶数 ({numtaps}) 超过了上限 ({int(max_taps_allowed)}) (transition_width={transition_width})。\n'
                f'当前使用的阶数不会变更，如果你认为阶数过高，可尝试 1) 设置 exceed_numtaps = "clip"，2) 设置 max_numtaps 参数。3）降低 transition_width 参数。',
                stacklevel=4,
            )
        elif exceed_numtaps == 'raise':
            raise ValueError(
                f'计算或指定的阶数 ({numtaps}) 超过了上限 ({int(max_taps_allowed)}) (transition_width={transition_width})。'
            )
        elif exceed_numtaps == 'clip':
            warning(
                f'Numtaps Clipped | 计算或指定的阶数 ({numtaps}) 超过了上限 ({int(max_taps_allowed)}) (transition_width={transition_width})。被强制限制为 {int(max_taps_allowed)}。',
                stacklevel=4,
            )
            numtaps = int(max_taps_allowed)

    # 5. 确保阶数为奇数 (用于Type I线性相位)
    if numtaps % 2 == 0:
        numtaps += 1

    final_numtaps = max(3, numtaps)  # 确保最小为3

    # 6. 设计滤波器系数 (使用 firwin)
    window_args: str | tuple[str, float | None] = (
        ('kaiser', kaiser_beta) if window == 'kaiser' else window
    )

    h = _get_firwin()(
        final_numtaps, cutoff, window=window_args, pass_zero=pass_zero, fs=fs
    )

    return h, final_numtaps


def filter_fir(
    X,
    fs: float,
    lfreq: Optional[float] = None,
    rfreq: Optional[float] = None,
    numtaps: Union[int, str] = 'auto',
    max_numtaps: Union[int, float, None] = 0.33,
    exceed_numtaps: Literal['warn', 'raise', 'clip'] = 'clip',
    window: Literal[
        'boxcar',
        'triang',
        'hann',
        'hamming',
        'blackman',
        'blackmanharris',
        'nuttall',
        'kaiser',
    ] = 'kaiser',
    stop_atten: float = 53,
    transition_width: Union[float, Literal['auto']] = 'auto',
    phase: Literal['zero', 'linear'] = 'zero',
    quality_check: bool = True,
    axis: int = -1,
):
    """
    FIR 数字滤波器 (低通, 高通, 带通)

    使用FIR窗函数法实现数字滤波，特别适合EEG信号处理

    注: 本函数中两侧的过渡带带宽相同, 如果需要不同的过渡带，建议分两阶段分别做高通和低通滤波

    **注意**: 并不鼓励使用 `auto` 计算滤波器参数，更建议手动使用 _design_fir_kernel 调试最优参数，然后
    手动设置 numtaps。

    Parameters
    ----------
    - `X` : array_like
        - 输入信号
    - `fs` : float
        - 采样频率 (Hz)
    - `lfreq` : float
        - 通带下限频率 (Hz)
    - `rfreq` : float
        - 通带上限频率 (Hz)
    - `numtaps` : int or 'auto', optional
        - 滤波器阶数（系数个数），默认为 'auto'。
        - 'auto': 根据过渡带宽自动计算合适的奇数阶数。
        - int: 手动指定阶数，如果提供偶数会自动加1变为奇数。
    - `max_numtaps` : Union[int, float, None], optional
        - 最大滤波器阶数, 默认为 0.33
        - 当自动计算得到的 numtaps (auto) 超过 max_numtaps 时，会自动截断为 max_numtaps
        - 如果为 int, 代表最大阶数; 如果为 float, 代表最大阶数占信号长度的比例
        - 默认 0.33 表示最大阶数占信号长度的 33%
    - `exceed_numtaps` : Literal['warn', 'raise', 'clip'], optional
        - 当自动计算得到的 numtaps (auto) 超过 max_numtaps 时的处理方式
        - 'warn': 警告但不截断
        - 'raise': 抛出异常
        - 'clip': 截断
    - `window` : str, optional
        - 窗函数类型。
        - 可选: 'hamming', 'hann', 'blackman', 'kaiser', 等。
    - `stop_atten` : float, optional
        - 期望的停带最小衰减 (dB)，仅在 `window='kaiser'` 时用于计算beta值，
          并在自动估算阶数时作为参考。
    - `transition_width` : float or 'auto', optional
        - 通带到阻带的过渡带宽 (Hz)。
        - 'auto': 自动根据频率范围计算一个合理的过渡带宽。默认为 'auto'。
    - `phase` : {'zero', 'linear'}, optional
        - 相位响应类型，默认为 'zero'。
        - 'zero': 零相位（使用filtfilt），EEG离线分析首选。
        - 'linear': 线性相位（使用lfilter），用于因果滤波。
    - `quality_check` : bool, optional
        - 是否进行信号质量检查，默认为 True。
    - `axis` : int, optional
        - 应用滤波的轴，默认为 -1。

    Returns
    ----------
    - `y` : ndarray
        - 滤波后的信号
    """
    X, _ = _validate_fir_params(X, fs, lfreq, rfreq)
    X = cast(np.ndarray, X)

    # 1. 设计滤波器核
    h, final_numtaps = _design_fir_kernel(
        fs=fs,
        lfreq=lfreq,
        rfreq=rfreq,
        numtaps_param=numtaps,
        window=window,
        stop_atten=stop_atten,
        transition_width_param=transition_width,
        max_numtaps=max_numtaps,
        exceed_numtaps=exceed_numtaps,
        signal_len=X.shape[axis],  # 传递信号长度用于限制
    )

    # 2. 信号质量检查
    if quality_check:
        _check_signal_quality_for_filtering(X, fs, lfreq, rfreq, final_numtaps)

    # 3. 应用滤波
    if phase == 'zero':
        padlen = _get_optimal_padding(X, final_numtaps, axis)
        y = signal.filtfilt(h, 1.0, X, axis=axis, padlen=padlen, padtype='odd')
    elif phase == 'linear':
        y = signal.lfilter(h, 1.0, X, axis=axis)
    else:
        raise ValueError(f'不支持的相位类型: {phase}')

    return y


class FilterDesignResult(TypedDict):
    """滤波器设计结果的类型字典"""

    filter_system: Literal['ba', 'sos']
    b: Optional[np.ndarray]
    a: Optional[np.ndarray]
    sos: Optional[np.ndarray]
    filter_type: str


def get_filter_design(
    func: Literal['notch', 'filter_iir', 'filter_fir'],
    fs: float,
    func_params: typing.Mapping,
    target_signal_len: int | None = None,
) -> FilterDesignResult:
    """
    获取滤波器设计参数, 适配新的统一函数。

    Parameters
    ----------
    func : Literal['notch', 'filter_iir', 'filter_fir']
        滤波器函数名。
    fs : float
        采样频率 (Hz)。
    func_params : dict
        对应滤波器所需的参数字典。
        - For 'filter_iir': {'lfreq', 'rfreq', 'order', 'filter_type' (optional)}
        - For 'filter_fir': {'lfreq', 'rfreq', 'numtaps' (optional), ...}
    """
    if func == 'notch':
        # ... (无变化)
        f0, Q = func_params['f0'], func_params['Q']
        b, a = signal.iirnotch(f0, Q, fs=fs)
        return {
            'filter_system': 'ba',
            'b': b,
            'a': a,
            'sos': None,
            'filter_type': 'notch',
        }

    elif func == 'filter_iir':
        params = dict(func_params)
        lfreq = params.pop('lfreq', None)
        rfreq = params.pop('rfreq', None)
        order = params.pop('order')
        filter_type = params.pop('filter_type', 'butter')
        output_format = params.pop('output', 'ba')

        btype, Wn = _filter_band(lfreq, rfreq)
        designer = getattr(signal, filter_type)

        designer_args = cast(list[Any], [order])
        if filter_type == 'cheby1':
            designer_args.append(params.pop('rp'))
        elif filter_type == 'cheby2':
            designer_args.append(params.pop('rs'))
        elif filter_type == 'ellip':
            designer_args.append(params.pop('rp'))
            designer_args.append(params.pop('rs'))
        designer_args.append(Wn)

        coeffs = designer(
            *designer_args, btype=btype, fs=fs, output=output_format, **params
        )
        if output_format == 'ba':
            return {
                'filter_system': 'ba',
                'b': coeffs[0],
                'a': coeffs[1],
                'sos': None,
                'filter_type': f'{filter_type}_{btype}',
            }
        else:  # sos
            return {
                'filter_system': 'sos',
                'b': None,
                'a': None,
                'sos': coeffs,
                'filter_type': f'{filter_type}_{btype}_sos',
            }

    elif func == 'filter_fir':
        params = dict(func_params)
        h, _ = _design_fir_kernel(
            fs=fs,
            lfreq=params.pop('lfreq', None),
            rfreq=params.pop('rfreq', None),
            numtaps_param=params.pop('numtaps', 'auto'),
            window=params.pop('window', 'kaiser'),
            stop_atten=params.pop('stop_atten', 60.0),
            transition_width_param=params.pop('transition_width', 'auto'),
            # 确保传递限制参数
            max_numtaps=params.pop('max_numtaps', None),
            signal_len=target_signal_len,
        )
        return {
            'filter_system': 'ba',
            'b': h,
            'a': np.array([1.0]),
            'sos': None,
            'filter_type': 'fir',
        }

    else:
        raise ValueError(f'Unsupported filter function: {func}.')


def _prepare_freq_vector(
    fs: float,
    worN: int = 2048,
    xlim: Optional[tuple[float, float]] = None,
    x_log_scale: bool = False,
) -> int | np.ndarray:
    """为freqz/sosfreqz准备频率向量。"""
    nyq = fs / 2.0

    if xlim is None:
        # 默认返回 worN 点数，让 freqz 自行处理
        return worN

    f_min, f_max = xlim
    f_max = min(f_max, nyq)
    f_min = max(f_min, 1e-3 if x_log_scale else 0.0)

    if f_min >= f_max:
        warnings.warn(
            f'调整后的频率范围 [{f_min:.2f}, {f_max:.2f}] 无效。将使用默认范围。',
            UserWarning,
        )
        return worN

    num_points = max(worN, 2000)
    if x_log_scale:
        return np.logspace(np.log10(f_min), np.log10(f_max), num_points)
    else:
        return np.linspace(f_min, f_max, num_points)


def plot_filter_response(
    filter_result: FilterDesignResult,
    fs: float,
    xlim: Optional[tuple[float, float]] = None,
    figsize: tuple[float, float] = (10, 6),
    title: Optional[str] = None,
    show_phase: bool = True,
    dB_min: float = -80.0,
    x_log_scale: bool = False,
):
    """绘制滤波器的频率响应曲线。

    Parameters
    ----------
    - `filter_result` : FilterDesignResult
        使用 get_filter_design 函数生成的滤波器设计结果。
    - `fs` : float
        采样频率 (Hz)。
    - `xlim` : tuple[float, float], optional
        X轴频率范围 (Hz)，默认为 None，表示自动设置。
    - `figsize` : tuple[float, float], optional
        图形大小，默认为 (10, 6)。
    - `title` : str, optional
        图形标题，默认为 None，会根据 filter_result 自动生成标题。
    - `show_phase` : bool, optional
        是否显示相位响应，默认为 True。
    - `dB_min` : float, optional
        幅度响应图的最小 dB 值，默认为 -80.0 dB。
    - `x_log_scale` : bool, optional
        如果为 True，X轴（频率）将使用对数刻度。默认为 False (线性刻度)。

    Returns
    -------
    - `fig` : matplotlib.figure.Figure
        生成的图形对象。
    """
    import zyplib.vis as vis  # 为了导入默认样式

    if show_phase:
        fig, (ax_mag, ax_phase) = plt.subplots(2, 1, figsize=figsize, sharex=True)
    else:
        fig, ax_mag = plt.subplots(1, 1, figsize=figsize)

    # 1. 准备频率向量
    w_input = _prepare_freq_vector(fs, worN=4096, xlim=xlim, x_log_scale=x_log_scale)

    # 2. 计算频率响应
    if filter_result['filter_system'] == 'ba':
        w, h = _get_freqz()(filter_result['b'], filter_result['a'], worN=w_input, fs=fs)
    else:  # 'sos'
        w, h = signal.sosfreqz(filter_result['sos'], worN=w_input, fs=fs)

    # 3. 计算幅度和相位
    with np.errstate(divide='ignore'):  # 忽略 log10(0) 的警告
        mag = 20 * np.log10(np.abs(h))
    phase_deg = np.rad2deg(np.unwrap(np.angle(h)))

    # 4. 绘图 (此部分逻辑与原版基本相同)
    plot_func = ax_mag.semilogx if x_log_scale else ax_mag.plot
    plot_func(w, mag)
    mag_floor = (
        float(np.nanmin(mag[np.isfinite(mag)]) - 10)
        if np.any(np.isfinite(mag))
        else dB_min
    )
    ax_mag.set_ylim(max(dB_min, mag_floor), 5)
    ax_mag.grid(True, which='both' if x_log_scale else 'major', linestyle='--', alpha=0.7)
    ax_mag.set_ylabel('Amplitude [dB]')

    # 根据滤波器类型设置标题
    if title is None:
        filter_type = filter_result['filter_type']
        if filter_type == 'notch':
            title = f'Notch Filter Response (fs={fs} Hz)'
        elif filter_type == 'bandpass_butter':
            title = f'Butterworth Bandpass Filter Response (fs={fs} Hz)'
        elif filter_type == 'bandpass_fir':
            title = f'FIR Bandpass Filter Response (fs={fs} Hz)'
        else:
            title = f'Filter Response (fs={fs} Hz)'

    ax_mag.set_title(title)

    # 绘制相位响应（如果需要）
    if show_phase:
        if x_log_scale:
            ax_phase.semilogx(w, phase_deg)
        else:
            ax_phase.plot(w, phase_deg)
        ax_phase.grid(
            True, which='both' if x_log_scale else 'major', linestyle='--', alpha=0.7
        )
        ax_phase.set_ylabel('Phase [degrees]')
        ax_phase.set_xlabel('Frequency [Hz]')
        ax_phase.set_yticks([-180, -90, 0, 90, 180])
        ax_phase.set_ylim([-190, 190])
    else:
        ax_mag.set_xlabel('Frequency [Hz]')

    # 设置X轴范围
    if xlim is not None:
        f_min_orig, f_max_orig = xlim
        f_max_plot = min(f_max_orig, fs / 2)
        if x_log_scale:
            f_min_plot = max(f_min_orig, 1e-2)  # Ensure f_min > 0 for log scale
        else:
            f_min_plot = max(f_min_orig, 0.0)  # Allow f_min = 0 for linear scale

        if f_min_plot < f_max_plot:
            plot_xlim_tuple = (f_min_plot, f_max_plot)
            if show_phase:  # ax_mag and ax_phase share x-axis
                ax_phase.set_xlim(plot_xlim_tuple)
            else:  # Only ax_mag exists
                ax_mag.set_xlim(plot_xlim_tuple)
        # else: A warning about invalid range would have been issued during w generation, and plot uses auto-range or default freqz range.

    # 调整布局
    plt.tight_layout()

    return fig
