"""三层去重的指纹算法。

核心:
    - normalize_content:把 content 折叠成纯净文本,用于跨源比对;
    - content_hash:normalize 后 SHA1[:16];
    - simhash64:字符 3-gram + md5 加权累加,产出 64 位无符号整数;
    - hamming:两个 SimHash 的汉明距离。

设计取舍:
    SimHash 的"分词"用字符 3-gram 而非 jieba。理由:
        1. 中文场景下字符 3-gram 与词级 SimHash 在重复识别上效果接近,
           而前者无外部依赖、ARM/嵌入式友好;
        2. M5 Embedding 后续不依赖 jieba,引入只为 M3 不划算;
        3. 重复率验收门槛 ≤ 5%(project_plan.md 第七章),3-gram 经验上
           足以分辨。
"""

from __future__ import annotations

import hashlib
import unicodedata

# 64 位 SimHash 位宽
SIMHASH_BITS = 64
SIMHASH_MASK = (1 << SIMHASH_BITS) - 1

# 默认 SimHash 汉明距离阈值(<= 此值视为重复)
DEFAULT_HAMMING_THRESHOLD = 3

# 字符 n-gram 长度
NGRAM_SIZE = 3


def normalize_content(text: str) -> str:
    """把 content 折叠成"无空白无标点"形式,用于 L2 内容 hash 与 SimHash 输入。

    使用 Unicode 类别判断:
        - P*  Punctuation(所有中英文标点)
        - Z*  Separator(空格 / 行 / 段分隔符)
        - C*  Control(NUL / 换行控制等)
    保留 L*(字母)、N*(数字)、S*(符号,如 +/-、% 等),以及 CJK 字符。
    """
    if not text:
        return ""
    return "".join(
        ch for ch in text if unicodedata.category(ch)[0] not in ("P", "Z", "C")
    )


def content_hash(text: str) -> str:
    """对 normalize_content(text) 做 SHA1,取前 16 hex 字符。"""
    norm = normalize_content(text)
    return hashlib.sha1(norm.encode("utf-8")).hexdigest()[:16]


def _ngrams(text: str, n: int = NGRAM_SIZE) -> list[str]:
    """字符级 n-gram。文本短于 n 时,直接整体作为单个 token。"""
    if len(text) < n:
        return [text] if text else []
    return [text[i : i + n] for i in range(len(text) - n + 1)]


def simhash64(text: str) -> int:
    """64 位 SimHash。返回无符号整数,空文本返回 0。"""
    norm = normalize_content(text)
    if not norm:
        return 0

    grams = _ngrams(norm)
    if not grams:
        return 0

    v = [0] * SIMHASH_BITS
    for gram in grams:
        h = int(hashlib.md5(gram.encode("utf-8"), usedforsecurity=False).hexdigest(), 16)
        # 取低 64 位
        h64 = h & SIMHASH_MASK
        for i in range(SIMHASH_BITS):
            if (h64 >> i) & 1:
                v[i] += 1
            else:
                v[i] -= 1

    fp = 0
    for i in range(SIMHASH_BITS):
        if v[i] > 0:
            fp |= 1 << i
    return fp


def hamming(a: int, b: int) -> int:
    """两个 SimHash 的汉明距离。"""
    return bin((a ^ b) & SIMHASH_MASK).count("1")
