"""
wake_word_detector.py — Lv2 Jarvis常駐デーモン 呼名+NGパターン検出モジュール
"""
import re
from pathlib import Path
from typing import Optional

WAKE_WORDS = ["ジャービス", "Jarvis", "ジャーヴィス", "じゃーびす"]

# Whisper揺れ吸収用正規表現
# カタカナ揺れ：[ジザシ][ャ][ー‐−‒–ー][ヴビ][スズ] → ジャービス/ザービス/シャービス/ジャービズ 等を捕捉
# ひらがな揺れ：[じざし][ゃ][ー‐−‒–ー][ヴびゔ][すず] を補完
_WAKE_WORD_RE = re.compile(
    r"(?:"
    r"[ジザシ][ャ]?[ーー‐−‒–ー]?[ヴビ][ィ]?[スズ]"   # カタカナ揺れ（ジャービス/ザービス/ジャーヴィス等）
    r"|[じざし][ゃ]?[ーー‐−‒–ー]?[びゔ][ぃ]?[すず]"   # ひらがな揺れ
    r"|[Jj][Aa][Rr][Vv][Ii][Ss]"                       # ASCII大小混在
    r")"
)

NG_PATTERNS_PATH = Path(__file__).parent.parent / "wake_word_learning" / "ng_patterns.md"

FALSE_POSITIVE_MARKERS = [
    "ジャービス誤反応", "ジャービス違う", "誤反応", "呼んでない",
    "黙ってて", "違うジャービス", "今のじゃない", "そうじゃない"
]

# 命令・依頼・疑問を示すパターン（日本語＋英語）
_COMMAND_PATTERNS = [
    r"[？?]",
    r"(やって|みて|教えて|調べて|確認して|送って|メモ|書いて|読んで|開いて|閉めて|止めて|消して|作って|追加して|削除して)",
    r"て(くれ|よ|ね|ください|もらえ)",
    r"どう(思う|かな|する|して|だろ|でしょ)",
    r"(何|なに|いつ|どこ|誰|だれ|どれ|どっち|どちら)(が|を|に|は|で|か)",
    # 英語命令文：動詞で始まる / please / can you / what / how 等
    r"\bplease\b",
    r"\b(summarize|tell|show|find|get|make|create|check|open|close|play|stop|search|explain|write|read|translate|remind)\b",
    r"\b(what|when|where|who|how|why|can you|could you|would you)\b",
]
_COMMAND_RE = re.compile("|".join(_COMMAND_PATTERNS))


def detect_wake_word(transcript: str) -> dict:
    """
    呼名検出。返り値:
    {
        "matched": bool,
        "wake_word": str | None,
        "position": int | None,
        "after_text": str | None,  # 呼名直後200文字以内
    }
    """
    # 1. 厳格リストで先にチェック（高精度）
    for word in WAKE_WORDS:
        idx = transcript.find(word)
        if idx != -1:
            after_start = idx + len(word)
            after_text = transcript[after_start:after_start + 200].lstrip("、，, 　")
            return {
                "matched": True,
                "wake_word": word,
                "position": idx,
                "after_text": after_text,
            }
    # 2. 揺れ吸収正規表現でフォールバック
    m = _WAKE_WORD_RE.search(transcript)
    if m:
        after_start = m.end()
        after_text = transcript[after_start:after_start + 200].lstrip("、，, 　")
        return {
            "matched": True,
            "wake_word": m.group(),
            "position": m.start(),
            "after_text": after_text,
        }
    return {"matched": False, "wake_word": None, "position": None, "after_text": None}


def load_ng_patterns() -> list[str]:
    """
    ng_patterns.md からNGパターンの後続文字列リストを抽出。
    「後続：「XXX」で始まる」の XXX を拾う。複数列挙にも対応。
    """
    if not NG_PATTERNS_PATH.exists():
        return []
    try:
        text = NG_PATTERNS_PATH.read_text(encoding="utf-8")
        # 「XXX」または「YYY」 形式を全て抽出
        patterns = re.findall(r"「([^」]+)」", text)
        # 空文字・重複除去
        seen = set()
        result = []
        for p in patterns:
            if p and p not in seen:
                seen.add(p)
                result.append(p)
        return result
    except Exception as e:
        print(f"[wake_word_detector] ng_patterns.md 読み込み失敗: {e}")
        return []


def is_ng_pattern(after_text: str, ng_patterns: Optional[list] = None) -> tuple:
    """
    呼名直後のテキストがNGパターンに該当するか（先頭一致）。
    戻り値: (該当するか, マッチしたパターン or None)
    """
    if ng_patterns is None:
        ng_patterns = load_ng_patterns()
    if not after_text:
        return False, None
    for pattern in ng_patterns:
        if after_text.startswith(pattern):
            return True, pattern
    return False, None


def is_command_or_question(after_text: str) -> bool:
    """
    呼名直後のテキストが指示文/疑問文かを判定。
    - 命令形・依頼形・疑問符・疑問句のいずれかがあれば True
    - 句読点なしでも後続が2文字以上あれば True（Whisper句読点省略対応）
    """
    if not after_text:
        return False
    # 既存の命令・疑問パターンに合致
    if _COMMAND_RE.search(after_text):
        return True
    # 句読点なしフォールバック：後続2文字以上あれば指示とみなす
    stripped = after_text.strip()
    if len(stripped) >= 2:
        return True
    return False


def evaluate(transcript: str) -> dict:
    """
    総合判定。返り値:
    {
        "should_fire": bool,
        "wake_matched": bool,
        "wake_word": str | None,
        "after_text": str | None,
        "ng_matched": bool,
        "ng_pattern": str | None,
        "is_command": bool,
        "reason": str,
    }
    """
    wake = detect_wake_word(transcript)

    base = {
        "wake_matched": wake["matched"],
        "wake_word": wake["wake_word"],
        "after_text": wake["after_text"],
        "ng_matched": False,
        "ng_pattern": None,
        "is_command": False,
    }

    if not wake["matched"]:
        return {**base, "should_fire": False, "reason": "no_wake_word"}

    ng_patterns = load_ng_patterns()
    ng_hit, ng_pat = is_ng_pattern(wake["after_text"] or "", ng_patterns)
    if ng_hit:
        return {**base, "should_fire": False, "ng_matched": True, "ng_pattern": ng_pat,
                "reason": f"ng_pattern: {ng_pat}"}

    cmd = is_command_or_question(wake["after_text"] or "")
    if not cmd:
        return {**base, "should_fire": False, "is_command": False, "reason": "not_command"}

    return {**base, "should_fire": True, "is_command": True, "reason": "ok"}


def detect_false_positive_marker(transcript: str) -> bool:
    """
    「違う」検出。FALSE_POSITIVE_MARKERS のいずれかが部分一致するか。
    True なら直前の発火を誤発火としてマーク対象。
    """
    return any(marker in transcript for marker in FALSE_POSITIVE_MARKERS)


if __name__ == "__main__":
    test_cases = [
        # --- 既存ケース ---
        ("ジャービス、今日の天気どう？", True),
        ("ジャービスってさ、すごいよね", False),   # NG: ってさ
        ("ジャービスはまだ未完成", False),          # NG: は
        ("ねぇジャービス、メモして", True),
        ("ジャービスみたいなAI欲しい", False),      # NG: みたい
        ("Jarvis, summarize this please", True),
        ("普通の会話で何もない", False),
        ("ジャービス、呼んでない", True),           # FP検出は別レイヤー、evaluate()自体はfire=True
        # --- 新規：合格すべきケース ---
        ("ジャービス、聞こえてる？", True),
        ("ジャービス聞こえてる", True),             # 句読点なし
        ("ジャービス聞こえてる？", True),           # 句読点なし＋疑問符
        ("ジャービス今日の天気", True),             # 句読点なし
        ("ザービス聞こえている", True),             # Whisper揺れ
        ("ザービス、テスト中", True),               # Whisper揺れ＋句読点あり
        ("Jarvis、聞こえてる", True),               # ASCII
        ("じゃーびす、テスト", True),               # ひらがな
        ("ねぇジャービス聞いて", True),             # 前置詞あり・句読点なし
        ("おいジャービス答えて", True),             # 前置詞あり・句読点なし
        # --- 新規：弾くべきケース ---
        ("ジャービスってさ、最高じゃん", False),    # ってさ
        ("ジャービスは映画のキャラクター", False),  # は
        ("ジャービスについて話そう", False),        # について
        ("ジャービスみたいに動く", False),          # みたい
        ("視聴ありがとう", False),                  # 呼名なし
        ("聞こえてるテスト中", False),              # 呼名なし
        ("ご視聴ありがとうございました", False),    # whisperノイズ・呼名なし
    ]

    print("=== wake_word_detector 自己テスト ===")
    # ng_patterns.md が存在しない環境用のフォールバックパターン
    fallback_ng = ["ってさ", "は", "みたい", "について", "って"]
    _orig_load = load_ng_patterns

    def _patched_load():
        result = _orig_load()
        if not result:
            return fallback_ng
        return result

    import sys
    # モジュールレベルで差し替え（テスト用）
    import wake_word_detector as _self
    _self.load_ng_patterns = _patched_load  # type: ignore

    pass_count = 0
    fail_count = 0
    for transcript, expected in test_cases:
        result = evaluate(transcript)
        fp = detect_false_positive_marker(transcript)
        ok = result["should_fire"] == expected
        status = "✅" if ok else "❌"
        fp_tag = " [FP検出]" if fp else ""
        print(f"{status} '{transcript[:45]}' -> fire={result['should_fire']} ({result['reason']}){fp_tag}")
        if ok:
            pass_count += 1
        else:
            fail_count += 1

    print(f"\n結果: {pass_count}/{len(test_cases)} passed")
    if fail_count > 0:
        sys.exit(1)
    sys.exit(0)
