# © 四川森普力科技有限公司  联系人：姚工  电话：159-2808-9752/177-8067-0842
# 邮件：mylanview@126.com   网址：www.sploptics.com
"""E1 — 碳材料品质鉴定插件（D/G/2D峰分析）"""

import json
from dataclasses import dataclass
from pathlib import Path
from typing import Optional

import numpy as np
from scipy.optimize import curve_fit
from scipy.signal import find_peaks

_HERE = Path(__file__).parent

try:
    from plugins.base_plugin import PluginInterface, PluginInfo, PluginInput, PluginOutput  # type: ignore
except ImportError:
    import sys
    sys.path.insert(0, str(_HERE.parent.parent))
    from plugins.base_plugin import PluginInterface, PluginInfo, PluginInput, PluginOutput


@dataclass
class PeakInfo:
    center_cm: float    # 峰位（cm⁻¹）
    intensity: float    # 峰强度
    fwhm_cm: float      # 半高宽（cm⁻¹）
    r_squared: float    # Lorentzian 拟合 R²


@dataclass
class CarbonAnalysisResult:
    D_peak: Optional[PeakInfo]
    G_peak: Optional[PeakInfo]
    peak_2D: Optional[PeakInfo]
    ID_IG: Optional[float]
    I2D_IG: Optional[float]
    material: str
    confidence: float


def _load_config() -> dict:
    cfg_path = _HERE / "config.json"
    if cfg_path.exists():
        return json.loads(cfg_path.read_text(encoding="utf-8"))
    return {
        "D_peak_range_cm": [1250, 1450],
        "G_peak_range_cm": [1500, 1650],
        "2D_peak_range_cm": [2600, 2800],
        "min_snr": 3.0,
    }


def _lorentzian(x, x0, gamma, A):
    return A * (gamma / 2) ** 2 / ((x - x0) ** 2 + (gamma / 2) ** 2)


def _fit_lorentzian(wl: np.ndarray, intensities: np.ndarray) -> Optional[PeakInfo]:
    """在给定窗口内做 Lorentzian 拟合，返回 PeakInfo 或 None（SNR < 3）。"""
    if len(wl) < 5:
        return None

    noise = float(np.std(intensities[:5]))
    peak_val = float(np.max(intensities))
    snr = peak_val / (noise + 1e-9)
    cfg = _load_config()
    if snr < cfg.get("min_snr", 3.0):
        return None

    x0_init = float(wl[np.argmax(intensities)])
    gamma_init = float(wl[-1] - wl[0]) / 4.0
    A_init = peak_val

    try:
        popt, _ = curve_fit(
            _lorentzian, wl, intensities,
            p0=[x0_init, gamma_init, A_init],
            maxfev=2000,
        )
        x0, gamma, A = popt
        fit_vals = _lorentzian(wl, *popt)
        ss_res = float(np.sum((intensities - fit_vals) ** 2))
        ss_tot = float(np.sum((intensities - np.mean(intensities)) ** 2))
        r2 = 1.0 - ss_res / (ss_tot + 1e-9)
        return PeakInfo(
            center_cm=float(x0),
            intensity=float(A),
            fwhm_cm=float(abs(gamma)),
            r_squared=float(r2),
        )
    except Exception:
        return PeakInfo(
            center_cm=x0_init,
            intensity=peak_val,
            fwhm_cm=gamma_init,
            r_squared=0.0,
        )


def _extract_region(wl: np.ndarray, intensities: np.ndarray,
                    lo: float, hi: float):
    mask = (wl >= lo) & (wl <= hi)
    return wl[mask], intensities[mask]


def find_d_peak(
    wl: np.ndarray,
    intensities: np.ndarray,
    search_range: tuple = (1250.0, 1450.0),
) -> Optional[PeakInfo]:
    """Lorentzian 拟合 D 峰（~1350 cm⁻¹），SNR < 3 返回 None。"""
    x, y = _extract_region(wl, intensities, *search_range)
    return _fit_lorentzian(x, y)


def find_g_peak(
    wl: np.ndarray,
    intensities: np.ndarray,
    search_range: tuple = (1500.0, 1650.0),
) -> Optional[PeakInfo]:
    """Lorentzian 拟合 G 峰（~1580 cm⁻¹）。"""
    x, y = _extract_region(wl, intensities, *search_range)
    return _fit_lorentzian(x, y)


def find_2d_peak(
    wl: np.ndarray,
    intensities: np.ndarray,
    search_range: tuple = (2600.0, 2800.0),
) -> Optional[PeakInfo]:
    """Lorentzian 拟合 2D 峰（~2700 cm⁻¹）。"""
    x, y = _extract_region(wl, intensities, *search_range)
    return _fit_lorentzian(x, y)


def _classify(D: Optional[PeakInfo], G: Optional[PeakInfo],
               peak_2D: Optional[PeakInfo]) -> tuple:
    """返回 (material, confidence)。"""
    if G is None:
        return "未知/非碳材料", 0.5

    id_ig = (D.intensity / G.intensity) if D else 0.0
    i2d_ig = (peak_2D.intensity / G.intensity) if peak_2D else None

    if id_ig < 0.1:
        if i2d_ig is not None and i2d_ig > 2.0:
            return "单层石墨烯", 0.92
        if i2d_ig is not None and 1.0 <= i2d_ig <= 2.0:
            return "少层石墨烯", 0.85
        return "高质量石墨", 0.75
    elif id_ig < 0.5:
        return "多层石墨烯/高质量石墨", 0.80
    elif id_ig < 1.0:
        return "纳米晶石墨", 0.78
    else:
        return "无定形碳/高缺陷", 0.82


def analyze(wl: np.ndarray, intensities: np.ndarray) -> CarbonAnalysisResult:
    """主接口：自动搜索 D/G/2D 峰，计算比值，输出分类结果。"""
    cfg = _load_config()
    D = find_d_peak(wl, intensities, tuple(cfg["D_peak_range_cm"]))
    G = find_g_peak(wl, intensities, tuple(cfg["G_peak_range_cm"]))
    peak_2D = find_2d_peak(wl, intensities, tuple(cfg["2D_peak_range_cm"]))

    id_ig = (D.intensity / G.intensity) if (D and G) else None
    i2d_ig = (peak_2D.intensity / G.intensity) if (peak_2D and G) else None
    material, confidence = _classify(D, G, peak_2D)

    return CarbonAnalysisResult(
        D_peak=D, G_peak=G, peak_2D=peak_2D,
        ID_IG=id_ig, I2D_IG=i2d_ig,
        material=material, confidence=confidence,
    )


class CarbonPlugin(PluginInterface):

    def get_info(self) -> PluginInfo:
        return PluginInfo(
            plugin_id="carbon_analysis_v1",
            name="碳材料品质鉴定",
            version="1.0.0",
            author="四川森普力科技有限公司",
            description="分析碳纳米管/石墨烯等碳材料的D/G/2D特征峰，计算ID/IG比，评估材料品质",
            input_type="spectrum",
            output_type="json",
            requires_stop=True,
        )

    def validate_input(self, plugin_input: PluginInput) -> tuple:
        wl = plugin_input.wl
        if float(wl[0]) > 1250.0 or float(wl[-1]) < 1650.0:
            return False, "输入谱线需覆盖1250–1650 cm⁻¹范围"
        if len(wl) < 50:
            return False, "数据点数不足（需 ≥ 50）"
        return True, ""

    def run(self, plugin_input: PluginInput) -> PluginOutput:
        result = analyze(plugin_input.wl, plugin_input.intensities)

        id_ig_str = f"{result.ID_IG:.3f}" if result.ID_IG is not None else "N/A"
        i2d_str = f"{result.I2D_IG:.3f}" if result.I2D_IG is not None else "N/A"
        summary = (f"材料类型：{result.material}（置信度{result.confidence:.0%}）  "
                   f"ID/IG={id_ig_str}  I2D/IG={i2d_str}")

        def _pk_dict(p: Optional[PeakInfo]):
            if p is None:
                return None
            return {"center_cm": p.center_cm, "intensity": p.intensity,
                    "fwhm_cm": p.fwhm_cm, "r_squared": p.r_squared}

        return PluginOutput(
            plugin_id="carbon_analysis_v1",
            result={
                "D_peak": _pk_dict(result.D_peak),
                "G_peak": _pk_dict(result.G_peak),
                "2D_peak": _pk_dict(result.peak_2D),
                "ID_IG": result.ID_IG,
                "I2D_IG": result.I2D_IG,
                "material": result.material,
                "confidence": result.confidence,
            },
            summary=summary,
        )
