"""Hook actions for axm-bib — parse .bib files for protocol context injection."""

from __future__ import annotations

from dataclasses import dataclass
from pathlib import Path
from typing import Any

import bibtexparser
from axm.hooks.base import HookResult

__all__ = ["ParseBibAction"]

# Fields produced by this hook, mapped from BibTeX entry attributes.
_FIELD_MAP: dict[str, str] = {
    "paper_title": "title",
    "paper_authors": "author",
    "paper_doi": "doi",
    "paper_year": "year",
    "paper_bibtex_key": "ID",
}


@dataclass
class ParseBibAction:
    """Parse a .bib file and return paper metadata for context injection.

    Designed to be discovered via the ``axm.hooks`` entry-point group
    and executed by the ``HookRunner`` in ``axm-engine``.

    The action reads a BibTeX file, extracts metadata from the first
    entry, and returns it as a flat dict inside ``HookResult.metadata``.
    """

    def execute(
        self,
        context: dict[str, Any],
        **params: Any,
    ) -> HookResult:
        """Execute the parse_bib hook action.

        Args:
            context: Session context dictionary (used for path interpolation).
            **params: Hook parameters:
                - ``source`` (str): Path to the ``.bib`` file. Supports
                  ``{variable}`` interpolation from *context*.
                - ``inject`` (list[str], optional): Subset of field names
                  to return. If omitted, all fields are returned.

        Returns:
            ``HookResult.ok(**metadata)`` on success, or
            ``HookResult.fail(error=...)`` on failure.
        """
        source: str = params.get("source", "")

        # Interpolate context variables into source path
        try:
            source = source.format_map(context)
        except (KeyError, ValueError, IndexError) as exc:
            return HookResult.fail(
                error=f"Failed to interpolate source path: {exc}",
            )

        path = Path(source)
        if not path.is_file():
            return HookResult.fail(error=f"Bib file not found: {source}")

        raw = path.read_text(encoding="utf-8")
        library = bibtexparser.loads(raw)

        if not library.entries:
            return HookResult.fail(
                error=f"No entries found in bib file: {source}",
            )

        entry = library.entries[0]
        metadata = _extract_metadata(entry)

        # Filter by inject list if provided
        inject: list[str] | None = params.get("inject")
        if inject is not None:
            metadata = {k: v for k, v in metadata.items() if k in inject}

        return HookResult.ok(**metadata)


def _extract_metadata(entry: dict[str, Any]) -> dict[str, Any]:
    """Extract normalized metadata from a bibtexparser v1.x entry dict.

    Args:
        entry: A bibtexparser entry (dict with string keys).

    Returns:
        Dict with ``paper_title``, ``paper_authors``, ``paper_doi``,
        ``paper_year``, and ``paper_bibtex_key``.
    """
    authors_str: str = entry.get("author", "")
    authors = (
        ", ".join(a.strip() for a in authors_str.split(" and ")) if authors_str else ""
    )

    year: int | None = None
    year_str: str = entry.get("year", "")
    if year_str:
        try:
            year = int(year_str)
        except (ValueError, TypeError):
            pass

    return {
        "paper_title": entry.get("title") or None,
        "paper_authors": authors or None,
        "paper_doi": entry.get("doi") or None,
        "paper_year": year,
        "paper_bibtex_key": entry.get("ID") or None,
    }
