"""tantivy を使った全文検索インデックス構築."""

from __future__ import annotations

import re
from pathlib import Path

import tantivy

from .types import SearchResult, SkillMeta

# Markdown の見出しパターン
_HEADING_RE = re.compile(r"^(#{1,6})\s+(.+)$", re.MULTILINE)


def _split_into_sections(text: str) -> list[tuple[str, str]]:
    """Markdown テキストを見出し単位のセクションに分割.

    Args:
        text: Markdown テキスト.

    Returns:
        (heading, body) タプルのリスト. 見出し前の本文は heading="" で返す.
    """
    sections: list[tuple[str, str]] = []
    headings = list(_HEADING_RE.finditer(text))

    if not headings:
        stripped = text.strip()
        if stripped:
            sections.append(("", stripped))
        return sections

    # 最初の見出し前のテキスト
    pre_heading = text[: headings[0].start()].strip()
    if pre_heading:
        sections.append(("", pre_heading))

    for i, match in enumerate(headings):
        heading = match.group(2).strip()
        start = match.end()
        end = headings[i + 1].start() if i + 1 < len(headings) else len(text)
        body = text[start:end].strip()
        if body:
            sections.append((heading, body))

    return sections


class SkillIndex:
    """tantivy ベースの全文検索インデックス.

    スキルのドキュメントを見出し単位のチャンクに分割し、
    BM25 スコアリングで検索可能にする。
    """

    def __init__(self) -> None:
        """インデックスを初期化."""
        # スキーマ定義
        builder = tantivy.SchemaBuilder()
        builder.add_text_field("skill_name", stored=True, tokenizer_name="default")
        builder.add_text_field("file_path", stored=True)
        builder.add_text_field("section", stored=True)
        builder.add_text_field("content", stored=True, tokenizer_name="default")
        self._schema = builder.build()

        # RAM上のインデックス
        self._index = tantivy.Index(self._schema)
        self._writer = self._index.writer()
        self._doc_count = 0

    def add_document(
        self,
        skill_name: str,
        file_path: str,
        section: str,
        content: str,
    ) -> None:
        """単一ドキュメント（セクション）をインデックスに追加.

        Args:
            skill_name: スキル名.
            file_path: ファイルパス.
            section: セクション見出し.
            content: セクション本文.
        """
        self._writer.add_document(
            tantivy.Document(
                skill_name=skill_name,
                file_path=file_path,
                section=section,
                content=content,
            )
        )
        self._doc_count += 1

    def index_skill(self, skill: SkillMeta) -> None:
        """スキル全体をインデックスに登録.

        SKILL.md の内容とリソースファイルの両方をインデックスに追加する。

        Args:
            skill: インデックス対象のスキル.
        """
        # SKILL.md のセクションをインデックス
        skill_md_path = str(Path(skill.path) / "SKILL.md")
        for heading, body in _split_into_sections(skill.content):
            self.add_document(
                skill_name=skill.name,
                file_path=skill_md_path,
                section=heading,
                content=body,
            )

        # リソースファイルもインデックス
        for resource in skill.resources:
            try:
                text = Path(resource.path).read_text(encoding="utf-8")
            except (OSError, UnicodeDecodeError):
                continue

            if not text.strip():
                continue

            # .md ファイルはセクション分割
            if resource.path.endswith(".md"):
                for heading, body in _split_into_sections(text):
                    self.add_document(
                        skill_name=skill.name,
                        file_path=resource.path,
                        section=heading,
                        content=body,
                    )
            else:
                # 非 md ファイルは丸ごと1ドキュメント
                self.add_document(
                    skill_name=skill.name,
                    file_path=resource.path,
                    section="",
                    content=text,
                )

    def commit(self) -> None:
        """インデックスへの書き込みをコミット."""
        self._writer.commit()
        self._index.reload()

    def search(
        self,
        query: str,
        top_k: int = 5,
        skill_name: str | None = None,
    ) -> list[SearchResult]:
        """全文検索を実行.

        Args:
            query: 検索クエリ.
            top_k: 返却する最大件数.
            skill_name: 特定スキルに限定する場合のスキル名.

        Returns:
            検索結果のリスト (スコア降順).
        """
        if self._doc_count == 0:
            return []

        searcher = self._index.searcher()

        # クエリ構築
        if skill_name:
            # 特定スキルに限定: content と skill_name の両方でクエリ
            parsed_query = self._index.parse_query(query, ["content", "section"])
        else:
            parsed_query = self._index.parse_query(
                query, ["content", "section", "skill_name"]
            )

        results: list[SearchResult] = []
        search_results = searcher.search(
            parsed_query, top_k * 3 if skill_name else top_k
        )

        for score, doc_addr in search_results.hits:
            doc = searcher.doc(doc_addr)
            doc_skill = doc["skill_name"][0]

            # skill_name フィルタ
            if skill_name and doc_skill != skill_name:
                continue

            snippet = doc["content"][0]
            # スニペットを適度な長さに切り詰め
            if len(snippet) > 500:
                snippet = snippet[:500] + "..."

            results.append(
                SearchResult(
                    skill_name=doc_skill,
                    file_path=doc["file_path"][0],
                    section=doc["section"][0],
                    score=score,
                    snippet=snippet,
                )
            )

            if len(results) >= top_k:
                break

        return results

    @property
    def doc_count(self) -> int:
        """インデックスされたドキュメント数."""
        return self._doc_count


def build_index(skills: list[SkillMeta]) -> SkillIndex:
    """スキルリストから検索インデックスを構築.

    Args:
        skills: インデックス対象のスキルリスト.

    Returns:
        構築済みの SkillIndex.
    """
    index = SkillIndex()
    for skill in skills:
        index.index_skill(skill)
    index.commit()
    return index
