"""
intervention_filter.py
======================
MARK Ⅶ 介入判定 — 軽量フィルタ（プロダクション版）

ローカル判定・トークン消費0。
ヒット時のみ intervention_judge.py（claude -p）に渡す。

Python 3.9 互換（match 文不使用）
"""

from __future__ import annotations

import re
import sys
from datetime import datetime, timedelta
from typing import Optional

# ---------------------------------------------------------------------------
# A. 呼名パターン（daemon/wake_word_detector.py から揺れ吸収正規表現を流用）
# ---------------------------------------------------------------------------
_WAKE_WORD_STRICT = ["ジャービス", "Jarvis", "ジャーヴィス", "じゃーびす"]

_WAKE_WORD_RE = re.compile(
    r"(?:"
    # 「ジャービス/じゃービス/ジャーびす」等の和文揺れ（カナ/ひらがなMIX対応・2026-05-07）
    # 1文字目：ジ/じ/ザ/ざ/シ/し
    # 2文字目：ャ/ゃ（小書き・任意）
    # 3文字目：長音記号バリエーション（必須・「じゃビス」のような短縮形を弾く）
    # 4文字目：ヴ/ビ/ゔ/び（カナ/かな両方）
    # 5文字目：ィ/ぃ（任意）
    # 6文字目：ス/ズ/す/ず
    r"[ジじザざシし][ャゃ]?[ーー‐−‒–ー][ヴビゔび][ィぃ]?[スズすず]"
    r"|[Jj][Aa][Rr][Vv][Ii][Ss]"                       # ASCII 大小混在
    r")"
)

# ---------------------------------------------------------------------------
# A-2. wake_word ハルシネーション除外リスト（whisper small 誤検知対策・2026-05-06）
# ---------------------------------------------------------------------------
# whisper small が呼名に似た謎語を出力することがある（実マイクテストで確認）。
# _WAKE_WORD_RE にヒットしても、発話テキストがここに含まれるいずれかにマッチすれば
# 呼名と見なさない（除外リスト方式）。
_WAKE_WORD_HALLUCINATIONS = [
    "ジャビスキョイター",
    "ジャービスキョイター",
    "ジャービスキョータ",
    "ジャービスキャプター",
    "ジャビスキャプター",
    # 必要に応じて追加可能
]

# ---------------------------------------------------------------------------
# B. 疑問・迷い検出
# ---------------------------------------------------------------------------

# B-1. 疑問詞・迷い語（疑問符なしで単独ヒット可）
_QUESTION_WORDS_RE = re.compile(
    r"どうしよう"
    r"|迷[うっい]"
    r"|どっち"
    r"|どちら"
    r"|分かんない"
    r"|わかんない"
    r"|どうかな"
    r"|どう思う"
    r"|どうする"
    r"|困った"
    r"|悩んでる"
    r"|悩む"
    r"|どれがいい"
    r"|どれにしよう"
    r"|どうしたらいい"
    r"|どうすればいい"
)

# B-2. ハルシネーション除外パターン（短文疑問符のみ・感嘆系）
# これらにマッチする場合は「？」単独ヒットとみなしてスルー
_QUESTION_HALLUCINATION_RE = re.compile(
    r"^[ぁ-ん]{0,4}[？?]$"   # 「？」「ん？」「うーん？」等（ひらがな4文字以下+疑問符のみ）
)

# B-3. 疑問詞と「？」の共起（疑問符あり＋文脈疑問詞）
_QUESTION_CONTEXT_RE = re.compile(
    r"(?:どう|どっち|どちら|どれ|なん[でだがをに]|なぜ|なに|何|どこ|誰|いつ|どれ|どんな)[^。\n]{0,30}[？?]"
    r"|[？?][^。\n]{0,20}(?:どう|どっち|どちら|どれ)"
)


def _detect_question(text: str) -> Optional[re.Match]:
    """
    疑問・迷いを検出。ハルシネーション短文（「？」「ん？」「うーん？」等）は除外。
    ヒット時は Match オブジェクト、スルー時は None。
    """
    # B-1: 疑問詞・迷い語は単独でヒット
    m = _QUESTION_WORDS_RE.search(text)
    if m:
        return m

    # B-2+B-3: 疑問符あり → ハルシネーション短文を先に除外
    if not re.search(r"[？?]", text):
        return None  # 疑問符なし → B-1 でもなし → スルー

    # 疑問符ありだが全体がハルシネーション除外パターンに一致する場合はスルー
    stripped = text.strip()
    if _QUESTION_HALLUCINATION_RE.match(stripped):
        return None

    # 疑問符ありかつ文字数が短い（4文字以下）はスルー
    if len(stripped) <= 4:
        return None

    # 疑問詞との共起チェック
    m2 = _QUESTION_CONTEXT_RE.search(text)
    return m2  # None なら疑問符のみ短文扱いでスルー

# ---------------------------------------------------------------------------
# C. 記憶曖昧シグナル
# ---------------------------------------------------------------------------
_UNCERTAINTY_RE = re.compile(
    r"気がする"
    r"|気がした"
    r"|確か[にはー]?"
    r"|たぶん"
    r"|多分"
    r"|かも(しれ)?"
    r"|はずだ"
    r"|はずだった"
    r"|だっけ"
    r"|だったっけ"
    r"|だよね[？?]?"
    r"|でしたっけ"
    r"|記憶が"
    r"|うろ覚え"
)

# ---------------------------------------------------------------------------
# E. モード切替コマンド
# ---------------------------------------------------------------------------
_MODE_SWITCH_RE = re.compile(
    r"(ジャービス|ジャーヴィス|じゃーびす|[Jj]arvis).{0,8}(静かに|黙って|おとなしく|控えて)"
    r"|(ジャービス|ジャーヴィス|じゃーびす|[Jj]arvis).{0,8}(積極的に|どんどん|ガンガン|いっぱい)"
    r"|(ジャービス|ジャーヴィス|じゃーびす|[Jj]arvis).{0,8}(観察して|見てて|静観して|観察に戻して)"
    r"|(ジャービス|ジャーヴィス|じゃーびす|[Jj]arvis).{0,8}(即答|すぐ答えて|早く答えて|参加して)"
    r"|観察モード"
    r"|積極モード"
    r"|静かにモード"
    r"|即答モード"
)

# ---------------------------------------------------------------------------
# D. 沈黙閾値 — 廃止（2026-05-06 暴走事故対策）
# ---------------------------------------------------------------------------
# silence trigger は永久廃止。喋っていない状態で介入する設計は
# クールダウン未初期化バグを誘発し、claude -p を連続発火させる暴走の
# 直接原因になった（HANDOVER_mark7_resume.md 参照）。
# 沈黙→介入が欲しい場面は「呼名」or「明示的なIMMEDIATE切替」で代替。


# 句読点・スペースの除去パターン（whisperが「ジャー、ビス」等で分断する対策・2026-05-07）
_PUNCT_RE = re.compile(r"[\s、。,.　・…!！?？]+")


def _detect_wake_word(text: str) -> Optional[str]:
    """
    呼名を検出して該当部分を返す。なければ None。
    whisper small ハルシネーション語（_WAKE_WORD_HALLUCINATIONS）は除外する。
    """
    # ハルシネーション除外チェック（正規表現マッチより先に実施）
    for hallucination in _WAKE_WORD_HALLUCINATIONS:
        if hallucination in text:
            return None
    for word in _WAKE_WORD_STRICT:
        if word in text:
            return word
    m = _WAKE_WORD_RE.search(text)
    if m:
        return m.group()
    # 二段目：句読点・空白を除去してから再判定（「ジャー、ビス」等の分断対策）
    cleaned = _PUNCT_RE.sub("", text)
    if cleaned != text:
        for word in _WAKE_WORD_STRICT:
            if word in cleaned:
                return word
        m2 = _WAKE_WORD_RE.search(cleaned)
        if m2:
            return m2.group()
    return None


def evaluate_filter(
    transcript_60sec: str,
    last_speech_at: datetime,
    current_mode: str = "OBSERVING",  # SILENT / OBSERVING / ACTIVE / IMMEDIATE
    now: Optional[datetime] = None,
) -> dict:
    """
    MARK Ⅶ 軽量フィルタ本体。

    Parameters
    ----------
    transcript_60sec : str
        直近 60 秒分の transcript テキスト。
    last_speech_at : datetime
        最後に発話が検出された時刻。
    current_mode : str
        現在の動作モード。SILENT / OBSERVING / ACTIVE / IMMEDIATE
    now : datetime | None
        現在時刻（省略時は datetime.now()）。テスト用に注入可。

    Returns
    -------
    dict
        {
            "should_judge_full": bool,       # Stage 2（claude -p）に送るべきか
            "trigger_reason": str | None,    # wake_word | question | uncertainty
                                             # | silence | mode_switch | None
            "matched_text": str,             # ヒットした該当テキスト（なければ ""）
            "context_60sec": str,            # 渡された transcript そのまま
            "current_mode": str,             # 入力モードをそのまま返す
        }
    """
    if now is None:
        now = datetime.now()

    # silence_secs は廃止（D. 沈黙検出ロジック削除）。引数は後方互換で残す。
    _ = last_speech_at

    # --- A. 直接呼名（最優先・全モード共通） ---
    wake = _detect_wake_word(transcript_60sec)
    if wake:
        return {
            "should_judge_full": True,
            "trigger_reason": "wake_word",
            "matched_text": wake,
            "context_60sec": transcript_60sec,
            "current_mode": current_mode,
        }

    # IMMEDIATE: A のみ・他は無視
    if current_mode == "IMMEDIATE":
        return {
            "should_judge_full": False,
            "trigger_reason": None,
            "matched_text": "",
            "context_60sec": transcript_60sec,
            "current_mode": current_mode,
        }

    # SILENT: A のみ
    if current_mode == "SILENT":
        return {
            "should_judge_full": False,
            "trigger_reason": None,
            "matched_text": "",
            "context_60sec": transcript_60sec,
            "current_mode": current_mode,
        }

    # OBSERVING / ACTIVE: A + B + C + D（ACTIVE は + E も）

    # --- E. モード切替コマンド（ACTIVE のみ追加評価） ---
    if current_mode == "ACTIVE":
        m_mode = _MODE_SWITCH_RE.search(transcript_60sec)
        if m_mode:
            return {
                "should_judge_full": True,
                "trigger_reason": "mode_switch",
                "matched_text": m_mode.group(),
                "context_60sec": transcript_60sec,
                "current_mode": current_mode,
            }

    # --- B. 疑問・迷い（ハルシネーション耐性フィルタ経由） ---
    m_q = _detect_question(transcript_60sec)
    if m_q:
        return {
            "should_judge_full": True,
            "trigger_reason": "question",
            "matched_text": m_q.group(),
            "context_60sec": transcript_60sec,
            "current_mode": current_mode,
        }

    # --- C. 記憶曖昧 ---
    m_u = _UNCERTAINTY_RE.search(transcript_60sec)
    if m_u:
        return {
            "should_judge_full": True,
            "trigger_reason": "uncertainty",
            "matched_text": m_u.group(),
            "context_60sec": transcript_60sec,
            "current_mode": current_mode,
        }

    # --- D. 沈黙検出 — 廃止（2026-05-06）---

    # --- 通過なし ---
    return {
        "should_judge_full": False,
        "trigger_reason": None,
        "matched_text": "",
        "context_60sec": transcript_60sec,
        "current_mode": current_mode,
    }


# ---------------------------------------------------------------------------
# セルフテスト
# ---------------------------------------------------------------------------
if __name__ == "__main__":
    _NOW = datetime(2026, 5, 6, 12, 0, 0)
    _RECENT = _NOW - timedelta(seconds=1)        # 直近1秒前に発話（沈黙なし）
    _SILENT_10 = _NOW - timedelta(seconds=10)    # 10秒沈黙
    _SILENT_130 = _NOW - timedelta(seconds=130)  # 130秒沈黙（観察モード閾値120秒超え）

    # (transcript, last_speech_at, mode, exp_should, exp_reason, label)
    test_cases = [
        # A: 直接呼名
        (
            "ジャービス、聞こえる？",
            _RECENT, "OBSERVING",
            True, "wake_word",
            "A: 呼名（カタカナ）",
        ),
        # B: 疑問・迷い
        (
            "これどうしようかな",
            _RECENT, "OBSERVING",
            True, "question",
            "B: 疑問・迷い",
        ),
        # C: 記憶曖昧
        (
            "確か昨日言ってたかも",
            _RECENT, "OBSERVING",
            True, "uncertainty",
            "C: 記憶曖昧",
        ),
        # D: 沈黙 — 廃止（暴走対策・2026-05-06）。発火しないことを確認
        (
            "",
            _SILENT_130, "OBSERVING",
            False, None,
            "D: 沈黙廃止（OBSERVING 130sec → 無反応）",
        ),
        # E: モード切替（ACTIVE モード）
        (
            "ジャービス静かに",
            _RECENT, "ACTIVE",
            True, "wake_word",   # A が E より優先（wake_word ヒット）
            "E: モード切替（呼名含む→wake_word優先）",
        ),
        # E: モード切替（呼名なし・ACTIVE）
        (
            "観察モード",
            _RECENT, "ACTIVE",
            True, "mode_switch",
            "E: モード切替（呼名なし・ACTIVE）",
        ),
        # スルー: 雑談
        (
            "明日のミーティング楽しみだね",
            _RECENT, "OBSERVING",
            False, None,
            "スルー: 雑談",
        ),
        # SILENT モードでは B をスルー
        (
            "迷うなあ",
            _RECENT, "SILENT",
            False, None,
            "SILENT: 迷い → スルー",
        ),
        # SILENT モードでも呼名は通す
        (
            "ジャービス聞こえる",
            _RECENT, "SILENT",
            True, "wake_word",
            "SILENT: 呼名→通す",
        ),
        # IMMEDIATE モードでは A のみ（B はスルー）
        (
            "どうしよう",
            _RECENT, "IMMEDIATE",
            False, None,
            "IMMEDIATE: 疑問→スルー",
        ),
        # IMMEDIATE モードでも呼名は通す
        (
            "ジャービス",
            _RECENT, "IMMEDIATE",
            True, "wake_word",
            "IMMEDIATE: 呼名→通す",
        ),
        # OBSERVING モードでは E（呼名なし）はスルー
        (
            "観察モード",
            _RECENT, "OBSERVING",
            False, None,
            "OBSERVING: モード切替文字列のみ→スルー（Eは ACTIVE 専用）",
        ),
        # ASCII 呼名
        (
            "Jarvis, are you there?",
            _RECENT, "OBSERVING",
            True, "wake_word",
            "A: 呼名（ASCII）",
        ),
        # Whisper 揺れ
        (
            "ザービス聞こえてる",
            _RECENT, "OBSERVING",
            True, "wake_word",
            "A: 呼名（Whisper揺れ）",
        ),
        # ---- バグA修正: ハルシネーション耐性テスト ----
        # 単独「？」はスルー
        (
            "？",
            _RECENT, "OBSERVING",
            False, None,
            "バグA: 単独「？」→スルー",
        ),
        # 「ん？」はスルー
        (
            "ん？",
            _RECENT, "OBSERVING",
            False, None,
            "バグA: 「ん？」→スルー",
        ),
        # 「うーん？」はスルー
        (
            "うーん？",
            _RECENT, "OBSERVING",
            False, None,
            "バグA: 「うーん？」→スルー",
        ),
        # 「どうしよう？」は通す（疑問詞あり）
        (
            "どうしよう？",
            _RECENT, "OBSERVING",
            True, "question",
            "バグA: 「どうしよう？」→通す",
        ),
        # 「どっちにする？」は通す
        (
            "どっちにする？",
            _RECENT, "OBSERVING",
            True, "question",
            "バグA: 「どっちにする？」→通す",
        ),
        # 「うーん、どうしよう…？」は通す
        (
            "うーん、どうしよう…？",
            _RECENT, "OBSERVING",
            True, "question",
            "バグA: 「うーん、どうしよう…？」→通す",
        ),
        # ---- バグB修正: wake_word ハルシネーション除外テスト ----
        # ハルシネーション語「ジャビスキョイター」→ スルー
        (
            "ジャビスキョイター",
            _RECENT, "OBSERVING",
            False, None,
            "バグB: ハルシネーション「ジャビスキョイター」→スルー",
        ),
        # ハルシネーション語「ジャービスキョータ」→ スルー
        (
            "ジャービスキョータ",
            _RECENT, "OBSERVING",
            False, None,
            "バグB: ハルシネーション「ジャービスキョータ」→スルー",
        ),
        # ハルシネーション語「ジャービスキョイター」→ スルー
        (
            "ジャービスキョイター",
            _RECENT, "OBSERVING",
            False, None,
            "バグB: ハルシネーション「ジャービスキョイター」→スルー",
        ),
        # 正常呼名「ジャービス、聞こえる？」→ 通す（既存維持確認）
        (
            "Jarvis hello",
            _RECENT, "OBSERVING",
            True, "wake_word",
            "バグB: 正常呼名「Jarvis hello」→通す",
        ),
    ]

    print("=== intervention_filter セルフテスト ===")
    passed = 0
    failed = 0
    for transcript, last_speech, mode, exp_should, exp_reason, label in test_cases:
        result = evaluate_filter(transcript, last_speech, current_mode=mode, now=_NOW)
        ok_should = result["should_judge_full"] == exp_should
        ok_reason = result["trigger_reason"] == exp_reason
        ok = ok_should and ok_reason
        status = "OK" if ok else "FAIL"
        print(
            f"[{status}] {label}\n"
            f"       should={result['should_judge_full']} (exp={exp_should})  "
            f"reason={result['trigger_reason']} (exp={exp_reason})  "
            f"matched='{result['matched_text']}'  mode={result['current_mode']}"
        )
        if ok:
            passed += 1
        else:
            failed += 1

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