#!/usr/bin/env python3
"""Permanent ノートのタイトル品質チェッカー。

create-my-stance がタイトルを決めた後に走らせ、問題を検出する。

検出項目:
1. 2主張混在: 接続助詞で独立した述語が2つ繋がっている
2. 教科書口調: 「である」定義文、抽象語の過剰使用、抽象名詞主語
3. 状態動詞締め: 発見が言い切れていない
4. 長さ: 40字超え

各 warning には関連する Permanent/MOC への WikiLink (refs) を付与する。
新規パターン発見時はこのファイルに直接追加する。refs に書く Permanent/MOC が
無ければ命題側を先に Permanent 化してから追加する。

exit code 0 = 問題なし, 1 = 警告あり
"""
from __future__ import annotations

import pathlib
import re
import sys


# MOC は規範カタログの索引。各カテゴリの ref に共通で入れて「迷ったらこの MOC を見ろ」の入口にする
MOC_ATOMIC = "[[アトミックノートを定義するノート]]"

REF_TWO_CLAIMS = [
    "[[アトミックノートのタイトルに2つの主張が詰まると並べ替えの自由度が消える]]",
    "[[包括ノートは子ノートが揃ってから書く]]",
    MOC_ATOMIC,
]
REF_STRONG_VERB = [
    "[[アトミックノートのタイトルは強い動詞で締めると発見が言い切れる]]",
    "[[状態動詞で終わるアトミックノートのタイトルはまだ発見になっていない]]",
    MOC_ATOMIC,
]
REF_TEXTBOOK = [
    "[[アトミックノートのタイトルが抽象語に逃げていたら教科書化している]]",
    MOC_ATOMIC,
]
REF_LENGTH = [MOC_ATOMIC]

# 動詞終止形・断定形（「が」「ではなく」「せず」の前後判定に使う）
# 五段動詞の終止形 (く・ぐ・す・つ・ぬ・ぶ・む・る・う) を含む
VERB_ENDING = (
    r"(?:する|できる|なる|ある|いる|れる|られる|せる|させる|ない|た|だ|である|"
    r"[くぐすつぬぶむうる])"
)
# 動詞活用語幹を含むパターン（後続節が動詞節か単純名詞かの判定に使う）
HAS_VERB = (
    r"(?:する|できる|なる|ある|いる|れる|られる|せる|させる|ない|ぬ|た|だ|"
    r"[くぐすつぬぶむう]$|る$)"
)

# 無条件警告（substring match で即警告）
STRONG_CONJUNCTIONS: list[tuple[str, str, list[str]]] = [
    ("初めて", "土台命題(その状態が望ましい・普通はならない)が圧縮されている可能性", REF_TWO_CLAIMS),
    ("ことで", "操作と効果が連結している可能性", REF_TWO_CLAIMS),
    ("により", "操作と効果が連結している可能性", REF_TWO_CLAIMS),
    ("であり", "並列で2主張化している可能性", REF_TWO_CLAIMS),
    ("一方", "「一方」で2つの主張が対比されている", REF_TWO_CLAIMS),
    ("けれど", "「けれど」で2つの主張が繋がっている", REF_TWO_CLAIMS),
    ("ものの", "「ものの」で2つの主張が繋がっている", REF_TWO_CLAIMS),
]


def _check_renyou(title: str) -> list[str]:
    """動詞連用形「〜し」「〜にして」+後続節を検出して2主張並列を警告。

    判定:
    - 「[漢字2字以上]し[漢字|カタカナ|名詞ひらがな]」で「し」直後が動詞活用接尾辞
      (て/た/な/ま/よ/ろ)でなければ連用中止形+別節として警告
    - 「にして+別節」も警告
    """
    for m in re.finditer(r"([一-龥]{2,})し([ぁ-んァ-ヶ一-龥])", title):
        next_char = m.group(2)
        # 除外: 動詞活用接尾辞 (て/た/な/ま/よ/ろ)、形容詞補助 (や/が)、
        # 複合動詞語幹 (合/続/始/終/直/過/抜/切/出)
        # 除外: 活用接尾辞 / 形容詞補助 / 複合動詞語幹 / 名詞化補助 (こと/もの) / 助詞
        # 助詞 (で/は/を/に/と/が/も/や/の) が続く場合は「〜し」名詞用法
        if next_char not in "てたなまよろやが合続始終直過抜切出こもでをにとはのも":
            return _format(
                "2主張混在", "動詞連用形「〜し」+別節で2主張並列の可能性", REF_TWO_CLAIMS
            )
    if re.search(r"にして[ぁ-んァ-ヶ一-龥]", title):
        return _format(
            "2主張混在", "「にして」+別節で2主張並列の可能性", REF_TWO_CLAIMS
        )
    # 「〜させ」連用形+別節（漢字続き）
    for m in re.finditer(r"[一-龥]させ([ぁ-んァ-ヶ一-龥])", title):
        next_char = m.group(1)
        # 活用接尾辞 (て/た/な/ま/よ/ろ/る/られ) なら除外
        if next_char not in "てたなまよろる":
            return _format(
                "2主張混在", "動詞連用形「〜させ」+別節で2主張並列の可能性", REF_TWO_CLAIMS
            )
    return []


def _check_tame(title: str) -> list[str]:
    """「ため」が独立節接続なら警告。動詞連体形+ため(に|の|は)... は名詞修飾=除外。"""
    # 「するため」「したため」「であるため」などの動詞連体形 + ため + に/の/は は除外
    # それ以外の「ため」（〜のため・〜になるため・名詞+ため など）を警告
    for m in re.finditer(r"ため", title):
        before = title[max(0, m.start() - 6) : m.start()]
        after = title[m.end() : m.end() + 2]
        # 直前が動詞活用形（する/た/ない/れる/られる など）+ 直後が に|の|は|、|に|の
        if re.search(VERB_ENDING + r"$", before) and after.startswith(("に", "の", "は")):
            continue
        return _format("2主張混在", "「ため」で2つの主張が繋がっている", REF_TWO_CLAIMS)
    return []


def _check_ga(title: str) -> list[str]:
    """「が」の前が動詞終止形なら逆接=2主張連結、名詞なら主格=OK。"""
    # 「が」の前 1-5 文字を見て VERB_ENDING にマッチするか
    for m in re.finditer(r"(.{1,5})が", title):
        before = m.group(1)
        if re.search(VERB_ENDING + r"$", before):
            return _format("2主張混在", "「が」で逆接の2主張が繋がっている", REF_TWO_CLAIMS)
    return []


def _check_dewanaku(title: str) -> list[str]:
    """「ではなく」の直後が動詞節なら2主張連結、単純名詞なら強調1主張=OK。"""
    m = re.search(r"ではなく(.+)", title)
    if not m:
        return []
    after = m.group(1)
    if re.search(HAS_VERB, after):
        return _format("2主張混在", "「ではなく」+動詞節で2主張化している可能性", REF_TWO_CLAIMS)
    return []


def _check_sezu(title: str) -> list[str]:
    """「せず」の直後が動詞節を伴う独立節なら2主張連結、修飾なら強調=OK。

    判定:「せず」の直後5文字以内に動詞終止形があれば2主張、なければ強調修飾。
    """
    m = re.search(r"せず(.{0,15})", title)
    if not m:
        return []
    after = m.group(1)
    # 直後が「、」や助詞「に」「と」などの修飾接続なら強調寄り
    # 動詞終止形が出現すれば2主張
    if re.search(VERB_ENDING + r"(?:$|。)", after):
        return _format("2主張混在", "「せず」+動詞節で2主張化している可能性", REF_TWO_CLAIMS)
    return []

STATE_VERB_SUFFIXES: list[tuple[str, str, list[str]]] = [
    ("である", "状態動詞締め(である定義文)。発見の核を担う強い動詞に置き換えられないか", REF_STRONG_VERB),
    ("にある", "状態動詞締め。発見の核を担う強い動詞に置き換えられないか", REF_STRONG_VERB),
    ("になる", "状態動詞締め。発見の核を担う強い動詞に置き換えられないか", REF_STRONG_VERB),
    ("に宿る", "状態動詞締め。発見の核を担う強い動詞に置き換えられないか", REF_STRONG_VERB),
]

ABSTRACT_WORDS = [
    "原理", "規範", "手続き", "アーキテクチャ", "プロセス",
    "メカニズム", "フレームワーク", "パラダイム", "コンテキスト",
    "アプローチ", "メソッド", "ストラテジー", "本質", "設計",
]

ABSTRACT_SUBJECTS = [
    "概念", "思考", "知識", "情報", "構造", "原則", "理論",
    "手法", "方法論", "体系", "仕組み", "設計",
]


def _format(label: str, msg: str, refs: list[str]) -> list[str]:
    out = [f"⚠ {label}: {msg}"]
    for r in refs:
        out.append(f"    関連: {r}")
    return out


def check_compound_claims(title: str) -> list[str]:
    warnings: list[str] = []
    for sign, msg, refs in STRONG_CONJUNCTIONS:
        if sign in title:
            warnings.extend(_format("2主張混在", msg, refs))
    warnings.extend(_check_ga(title))
    warnings.extend(_check_dewanaku(title))
    warnings.extend(_check_sezu(title))
    warnings.extend(_check_tame(title))
    warnings.extend(_check_renyou(title))
    return warnings


def check_state_verb(title: str) -> list[str]:
    warnings: list[str] = []
    for suffix, msg, refs in STATE_VERB_SUFFIXES:
        if title.endswith(suffix):
            warnings.extend(_format("状態動詞締め", msg, refs))
            break
    return warnings


# 複合名詞判定: 抽象語の直前が「名詞語幹文字」なら複合名詞扱いで除外
# 助詞 (は/が/を/に/で/と/の/も/や) は除く
JP_NOUN_STEM = r"[ァ-ヶーA-Za-z0-9一-龥]"  # カタカナ・英数字・漢字のみ


def check_textbook_tone(title: str) -> list[str]:
    """抽象語の検出。複合名詞内（直前がカタカナ・漢字・英数字）の用法は除外する。"""
    warnings: list[str] = []
    found: list[str] = []
    for word in ABSTRACT_WORDS:
        for m in re.finditer(re.escape(word), title):
            # 前後どちらかが名詞語幹(カタカナ・漢字・英数)なら複合名詞扱いで除外
            before_is_noun = m.start() > 0 and re.match(JP_NOUN_STEM, title[m.start() - 1])
            after_is_noun = m.end() < len(title) and re.match(
                JP_NOUN_STEM, title[m.end()]
            )
            if not before_is_noun and not after_is_noun:
                found.append(word)
                break
    if found:
        warnings.extend(
            _format("教科書口調", f"抽象語 [{', '.join(found)}] が含まれる", REF_TEXTBOOK)
        )
    for word in ABSTRACT_SUBJECTS:
        if title.startswith(word):
            after = title[len(word):]
            if not after:
                continue
            # 助詞 (の/に/を/が/へ/と) で具体対象として続く → 除外
            if after[0] in "のにをがへと":
                continue
            # 直後が名詞語幹 (カタカナ・漢字・英数) → 複合名詞として除外
            if re.match(JP_NOUN_STEM, after[0]):
                continue
            warnings.extend(
                _format("教科書口調", f"抽象名詞「{word}」で始まる", REF_TEXTBOOK)
            )
            break
    return warnings


def check_taigen_dome(title: str) -> list[str]:
    """末尾が動詞・形容詞活用形でなければ体言止め=命題未満の可能性として警告。

    動詞活用語尾 (る/う/く/ぐ/す/つ/ぬ/ぶ/む/た/だ/ない/ぬ) または形容詞 (い) で
    終わっていなければ警告。状態動詞締めは別途 check_state_verb が拾う。
    """
    if re.search(r"(?:る|う|く|ぐ|す|つ|ぬ|ぶ|む|た|だ|ない|ぬ|い|ぜ|べ|め|ね|え|け|せ|て)$", title):
        return []
    return _format(
        "命題未満",
        "末尾が動詞・形容詞活用形でない（体言止め・命題未満の可能性）",
        REF_STRONG_VERB,
    )


def check_length(title: str) -> list[str]:
    if len(title) > 40:
        out = [f"⚠ 長すぎ: {len(title)}字（40字以内推奨）"]
        for r in REF_LENGTH:
            out.append(f"    関連: {r}")
        return out
    return []


VAULT_ROOT = pathlib.Path(
    "/Users/goryugo/Library/Mobile Documents/iCloud~md~obsidian/Documents/Obsidian_local"
)


def _check_ghost_refs() -> list[str]:
    """REF 定数の WikiLink 先が Vault 内に実在するかを検査。ゴーストがあれば返す。"""
    all_refs = set(REF_TWO_CLAIMS + REF_STRONG_VERB + REF_TEXTBOOK + REF_LENGTH)
    ghosts: list[str] = []
    for ref in all_refs:
        m = re.match(r"\[\[(.+)\]\]", ref)
        if not m:
            continue
        name = m.group(1)
        # Vault 内のどこかに <name>.md があるか
        hits = list(VAULT_ROOT.rglob(f"{name}.md"))
        if not hits:
            ghosts.append(name)
    return ghosts


def main() -> int:
    if len(sys.argv) < 2:
        print('Usage: check_title.py "タイトル文字列"', file=sys.stderr)
        return 2
    title = sys.argv[1]
    warnings: list[str] = []
    warnings.extend(check_compound_claims(title))
    warnings.extend(check_state_verb(title))
    warnings.extend(check_textbook_tone(title))
    warnings.extend(check_taigen_dome(title))
    warnings.extend(check_length(title))
    ghosts = _check_ghost_refs()
    if ghosts:
        warnings.append("⚠ refs ゴーストリンク検出（scripts/check_title.py の REF を更新せよ）:")
        for g in ghosts:
            warnings.append(f"    不在: [[{g}]]")
    if warnings:
        print(f"タイトル: {title}")
        for w in warnings:
            print(w)
        print("→ 各 warning が分割サイン/教科書化/状態動詞締めに該当するか判定してください")
        print("→ 規範カタログ: .claude/skills/create-my-stance/references/title_quality.md")
        return 1
    print(f"OK: {title}")
    return 0


if __name__ == "__main__":
    sys.exit(main())
