"""共享 MeSH 过滤模块。

基于 NLM mtrees 文件判断文献是否属于 C04（Neoplasms）肿瘤学大类。
提供 MeSH 树号查找和标题/摘要关键词两种过滤方式。

用法:
    from app.services.mesh_filter import get_mesh_filter

    mf = get_mesh_filter()

    # 方式1: 按 MeSH 判断
    if mf.is_oncology_by_mesh(mesh_headings):
        ...

    # 方式2: 按文本关键词判断（当无 MeSH 时备用）
    if mf.is_oncology_by_text(title, abstract):
        ...
"""

from pathlib import Path

# C04 树前缀（Neoplasms 及其子类）
C04_TREE_PREFIXES = ("C04", "C04.")

# 标题/摘要关键词（全部生效，修复了之前 keywords[:3] 的 bug）
ONCOLOGY_KEYWORDS = [
    "cancer", "carcinoma", "tumor", "tumour", "neoplasm", "malignancy",
    "leukemia", "leukaemia", "lymphoma", "sarcoma", "melanoma",
    "glioma", "glioblastoma", "myeloma", "neuroblastoma",
    "chemotherapy", "radiotherapy", "immunotherapy", "oncolog",
    "metasta", "antineoplastic",
]


class MeshFilter:
    """MeSH 过滤类，加载 mtrees 文件并提供肿瘤学判断"""

    def __init__(self, mtrees_path: str | None = None):
        if mtrees_path is None:
            mtrees_path = str(
                Path(__file__).parent.parent / "data" / "mtrees2025.bin"
            )
        self._tree_map = self._load_mtrees(mtrees_path)

    @staticmethod
    def _load_mtrees(path: str) -> dict[str, list[str]]:
        """解析 mtrees 文件。

        mtrees 格式（分号分隔）:
             Neoplasms;C04
             Leukemia;C04.557.337
             Leukemia;C15.378.508       （同个描述可能有多个树号）

        返回: {lowercase_name: [tree_number, ...]}
        """
        tree_map: dict[str, list[str]] = {}
        with open(path, "r", encoding="utf-8") as f:
            for line in f:
                line = line.strip()
                if not line or ";" not in line:
                    continue
                name, tree = line.rsplit(";", 1)
                name = name.strip().lower()
                tree = tree.strip()
                if name and tree:
                    tree_map.setdefault(name, []).append(tree)
        return tree_map

    def get_tree_numbers(self, descriptor_name: str) -> list[str]:
        """通过描述名查找所有树号"""
        return self._tree_map.get(descriptor_name.strip().lower(), [])

    def is_oncology_by_mesh(self, mesh_headings: list[dict]) -> bool:
        """按 MeSH 判断是否肿瘤相关（基于 C04 树号）"""
        for heading in mesh_headings:
            name = heading.get("descriptor", "") or heading.get("name", "")
            trees = self.get_tree_numbers(name)
            if any(t.startswith(C04_TREE_PREFIXES) for t in trees):
                return True
        return False

    @staticmethod
    def is_oncology_by_text(title: str | None, abstract: str | None) -> bool:
        """按标题/摘要关键词判断（宽松模式，全文任一关键词即返回 True）"""
        text = ((title or "") + " " + (abstract or "")).lower()
        return any(kw in text for kw in ONCOLOGY_KEYWORDS)

    @staticmethod
    def is_oncology_by_text_score(title: str | None, abstract: str | None) -> bool:
        """按标题/摘要关键词评分判断（精确模式，用于导入过滤）。

        规则：
          - 标题命中关键词 → 强信号，直接收录
          - 摘要命中 ≥2 个关键词 → 收录
          - 摘要命中 0-1 个关键词 → 放过

        数据依据：对 23.6 万条非 C04 文献的分析显示，
        85% 的误判来自摘要单关键词匹配。

        此模式仅在文献无 MeSH 标引时使用。
        有 MeSH 的文献应始终走 is_oncology_by_mesh()。
        """
        if not title and not abstract:
            return False
        title_lower = (title or "").lower()
        text_lower = title_lower + " " + (abstract or "").lower()

        # 标题命中 → 强信号
        if any(kw in title_lower for kw in ONCOLOGY_KEYWORDS):
            return True

        # 摘要评分：≥2 个关键词收录
        hit_count = sum(1 for kw in ONCOLOGY_KEYWORDS if kw in text_lower)
        return hit_count >= 2


# 全局单例
_global_filter: MeshFilter | None = None


def get_mesh_filter(mtrees_path: str | None = None) -> MeshFilter:
    """获取全局 MeshFilter 单例"""
    global _global_filter
    if _global_filter is None:
        _global_filter = MeshFilter(mtrees_path)
    return _global_filter
