"""
plot_pub skill — 出版级 matplotlib rcParams 一键设置。

LLM 通过 `from skills.plot_pub.style import apply_pub_style; apply_pub_style()` 使用。
"""

from __future__ import annotations

import warnings
import matplotlib
import matplotlib.pyplot as plt
from matplotlib import font_manager


# 中文字体候选,按平台优先级排
_CHINESE_FONTS = [
    "Noto Sans CJK SC",    # sandbox 容器首选(fonts-noto-cjk),出版级字形
    "SimHei",              # Windows 黑体
    "Microsoft YaHei",     # Windows 雅黑
    "WenQuanYi Micro Hei", # Linux / 容器兜底(fonts-wqy-microhei)
    "Heiti TC",            # macOS
    "Arial Unicode MS",    # macOS 兜底
]


def _find_chinese_font() -> str | None:
    """从候选清单里挑系统装了的第一个,都没有返 None。"""
    installed = {f.name for f in font_manager.fontManager.ttflist}
    for name in _CHINESE_FONTS:
        if name in installed:
            return name
    return None


def apply_pub_style(
    chinese: bool = True,
    dpi: int = 150,
    font_size: int = 10,
    linewidth: float = 1.2,
    cmap: str = "viridis",
) -> None:
    """
    一键设置出版级 matplotlib rcParams。

    Args:
        chinese: True 时加载中文字体,失败 warn 但不抛(图继续画,中文显示方块)
        dpi: 屏幕显示 dpi。保存图请用 savefig(..., dpi=300) 单独指定
        font_size: 基础字号,论文双栏图标准 10pt
        linewidth: 线粗,出版图细些更精致
        cmap: 默认 colormap,viridis / plasma / cividis 是色觉友好选择
    """
    rc = matplotlib.rcParams

    # ---- 字体 ----
    if chinese:
        font = _find_chinese_font()
        if font:
            # 中文字体放第一位,后面接 Arial / DejaVu 处理英文
            rc["font.sans-serif"] = [font, "Arial", "DejaVu Sans"]
        else:
            warnings.warn(
                "[plot_pub] 未找到中文字体(候选: "
                + ", ".join(_CHINESE_FONTS)
                + ")。中文将显示为方块。"
                + "Windows 控制面板 → 字体,Linux 装 wqy-microhei 包。",
                RuntimeWarning,
                stacklevel=2,
            )
            rc["font.sans-serif"] = ["Arial", "DejaVu Sans"]
    else:
        rc["font.sans-serif"] = ["Arial", "DejaVu Sans"]

    rc["axes.unicode_minus"] = False  # 负号不显示成方块
    rc["font.family"] = "sans-serif"
    rc["font.size"] = font_size

    # ---- 尺寸 / dpi ----
    rc["figure.dpi"] = dpi
    rc["savefig.dpi"] = 300  # 默认保存高 dpi,临时图用 savefig(..., dpi=...) 覆盖
    rc["savefig.bbox"] = "tight"
    rc["savefig.pad_inches"] = 0.05

    # ---- 线条 ----
    rc["lines.linewidth"] = linewidth
    rc["lines.markersize"] = 4
    rc["axes.linewidth"] = 0.8

    # ---- 刻度 ----
    rc["xtick.direction"] = "in"        # 期刊偏好刻度朝内
    rc["ytick.direction"] = "in"
    rc["xtick.major.size"] = 4
    rc["ytick.major.size"] = 4
    rc["xtick.minor.size"] = 2
    rc["ytick.minor.size"] = 2
    rc["xtick.minor.visible"] = True
    rc["ytick.minor.visible"] = True

    # ---- legend ----
    rc["legend.frameon"] = False        # 出版图 legend 无框
    rc["legend.fontsize"] = font_size - 1

    # ---- colormap ----
    rc["image.cmap"] = cmap

    # ---- 数学字体 ----
    rc["mathtext.fontset"] = "stix"     # 跟 Times / Arial 配,公式不突兀

    # ---- 兜底:防止 PDF 嵌入 Type 3 字体(期刊要求 Type 42) ----
    rc["pdf.fonttype"] = 42
    rc["ps.fonttype"] = 42

    # ---- SVG 文字可编辑(投稿级要求:导出后编辑部/作者能在 AI 里改文字) ----
    # 'none' = 文字以 <text> 保留,不转 path;配 PDF Type 42 一起,矢量两路都可编辑
    rc["svg.fonttype"] = "none"


def reset_style() -> None:
    """还原 matplotlib 默认 rcParams(测试 / 切换主题时用)。"""
    matplotlib.rcdefaults()


# ============================================================
# Nature 级复合图辅助:语义配色 + spine 纪律
# 思路源自 nature-figure skill(MIT, github.com/Yuan1z0825/nature-skills),
# 砍掉 R / 生物 gallery,只留可迁移的设计纪律,改为纯 Python + 建材领域。
# ============================================================

# 语义配色:颜色承载"科学语义"而非随机区分。同族基线归一个冷色系,
# 本方法/主角归一个暖/蓝主色系 —— family consistency beats maximal hue separation。
SEMANTIC_COLORS = {
    "method":   "#1f5fa8",  # 蓝 = 本工作 / 提出的方法(主角)
    "gain":     "#2a8f5e",  # 绿 = 提升 / 增益 / 正向
    "baseline": "#c0392b",  # 红 = 对照 / baseline / 退化
    "neutral":  "#8a8f99",  # 灰 = 参照 / 背景 / 次要
    "accent":   "#e08a1e",  # 橙 = 强调 / 高亮少量点
}


def clean_spines(ax, keep=("left", "bottom")) -> None:
    """
    出版图 spine 纪律:只留指定边框(默认左 + 下),去掉上 + 右。
    复合图每个子 panel 都调一次,视觉更干净、信噪比更高。

    Args:
        ax: matplotlib Axes
        keep: 保留哪几条 spine,默认 ("left", "bottom")
    """
    for side in ("top", "right", "left", "bottom"):
        ax.spines[side].set_visible(side in keep)
    # 刻度只画在保留的边上
    ax.tick_params(
        top="top" in keep, right="right" in keep,
        bottom="bottom" in keep, left="left" in keep,
    )


def ablation_alphas(n: int, base_color: str = None):
    """
    消融 / 梯度对比:同一颜色变 alpha(0.25 → 1.0),而不是换色相。
    返回 n 个 (color, alpha) 不便用,这里直接返回 n 个 RGBA。

    Args:
        n: 系列数
        base_color: 基色,默认用 SEMANTIC_COLORS["method"]
    """
    import matplotlib.colors as mcolors
    import numpy as np

    base = base_color or SEMANTIC_COLORS["method"]
    rgb = mcolors.to_rgb(base)
    alphas = np.linspace(0.25, 1.0, n)
    return [(rgb[0], rgb[1], rgb[2], a) for a in alphas]
