"""
Scaife Viewer / Perseus CTS API client — classical and patristic texts.

Provides access to 2,699 works (1,951 Greek, 668 Latin) including:
- Classical literature (Homer, Plato, Aristotle, Sophocles, etc.)
- Patristic texts (Church Fathers in Greek/Latin)
- Historical works (Josephus, Philo, Eusebius, etc.)
- Papyri and inscriptions

CTS URN format: urn:cts:greekLit:tlg0012.tlg001.perseus-grc2

Zero external dependencies. Uses stdlib only.
Thread-safe for MCP server use.
"""

from __future__ import annotations

import json
import logging
import re
import urllib.error
import urllib.request
import xml.etree.ElementTree as ET
from typing import Any, Dict, List, Optional, Tuple
from urllib.parse import urlencode

logger = logging.getLogger(__name__)

SCAIFE_CTS_BASE = "https://scaife.perseus.org/api/cts/"

_CAPS_TIMEOUT = 20  # seconds — capabilities list can be large
_PASSAGE_TIMEOUT = 15
_AVAIL_TIMEOUT = 10

_USER_AGENT = "AccordancePort/1.0"

# ---------------------------------------------------------------------------
# Internal helpers
# ---------------------------------------------------------------------------


def _http_get(url: str, timeout: int, accept_json: bool = False) -> Optional[bytes]:
    """Perform a GET request. Returns response body bytes, or None on failure."""
    headers = {"User-Agent": _USER_AGENT}
    if accept_json:
        headers["Accept"] = "application/json"
    try:
        req = urllib.request.Request(url, headers=headers)
        with urllib.request.urlopen(req, timeout=timeout) as resp:
            if resp.status != 200:
                logger.warning("Scaife GET %s returned HTTP %d", url, resp.status)
                return None
            return resp.read()
    except (urllib.error.URLError, urllib.error.HTTPError, OSError) as e:
        logger.warning("Scaife GET %s failed: %s", url, e)
        return None


def _parse_json(raw: Optional[bytes]) -> Optional[Any]:
    """Parse JSON bytes. Returns parsed data or None on failure."""
    if raw is None:
        return None
    try:
        return json.loads(raw.decode("utf-8"))
    except (ValueError, UnicodeDecodeError) as e:
        logger.warning("Scaife JSON parse failed: %s", e)
        return None


def _strip_xml(xml_bytes: bytes) -> Optional[str]:
    """Extract plain text from TEI/CTS XML. Strips all tags and normalises whitespace."""
    try:
        text = xml_bytes.decode("utf-8", errors="replace")
        # Remove processing instructions and comments
        text = re.sub(r"<\?.*?\?>", "", text)
        text = re.sub(r"<!--.*?-->", "", text, flags=re.DOTALL)
        # Collapse tags to a single space
        text = re.sub(r"<[^>]*>", " ", text)
        # Normalise whitespace
        text = re.sub(r"&[a-z]+;", " ", text)  # entities
        text = re.sub(r"\s+", " ", text)
        return text.strip() or None
    except Exception as e:
        logger.warning("Scaife XML strip failed: %s", e)
        return None


# ---------------------------------------------------------------------------
# ScaifeClient
# ---------------------------------------------------------------------------


class ScaifeClient:
    """Client for the Scaife Viewer / Perseus CTS API.

    All methods degrade gracefully: empty list / None on any failure
    (network error, timeout, bad response, parse error).

    Usage::

        from accordance_parser.scaife_api import scaife
        texts = scaife.list_texts()
        passage = scaife.get_passage("urn:cts:greekLit:tlg0012.tlg001.perseus-grc2", "1.1", "1.7")
        results = scaife.search_by_author("Plato")
        text = scaife.resolve_reference("Plato", "Republic", "1.1-1.5")
    """

    # ------------------------------------------------------------------
    # KNOWN_TEXTS — canonical author/work → CTS work URN (without edition)
    # ------------------------------------------------------------------

    KNOWN_TEXTS: Dict[Tuple[str, str], str] = {
        # --- Epic Poetry ---
        ("Homer", "Iliad"): "urn:cts:greekLit:tlg0012.tlg001",
        ("Homer", "Odyssey"): "urn:cts:greekLit:tlg0012.tlg002",
        ("Hesiod", "Theogony"): "urn:cts:greekLit:tlg0020.tlg001",
        ("Hesiod", "Works and Days"): "urn:cts:greekLit:tlg0020.tlg002",
        ("Hesiod", "Shield of Heracles"): "urn:cts:greekLit:tlg0020.tlg003",
        # --- Tragedy ---
        ("Aeschylus", "Agamemnon"): "urn:cts:greekLit:tlg0085.tlg005",
        ("Aeschylus", "Persians"): "urn:cts:greekLit:tlg0085.tlg002",
        ("Aeschylus", "Prometheus Bound"): "urn:cts:greekLit:tlg0085.tlg003",
        ("Sophocles", "Oedipus Rex"): "urn:cts:greekLit:tlg0011.tlg004",
        ("Sophocles", "Antigone"): "urn:cts:greekLit:tlg0011.tlg002",
        ("Sophocles", "Ajax"): "urn:cts:greekLit:tlg0011.tlg001",
        ("Euripides", "Medea"): "urn:cts:greekLit:tlg0006.tlg003",
        ("Euripides", "Bacchae"): "urn:cts:greekLit:tlg0006.tlg002",
        ("Euripides", "Alcestis"): "urn:cts:greekLit:tlg0006.tlg001",
        # --- History ---
        ("Herodotus", "Histories"): "urn:cts:greekLit:tlg0016.tlg001",
        ("Thucydides", "Histories"): "urn:cts:greekLit:tlg0003.tlg001",
        ("Xenophon", "Anabasis"): "urn:cts:greekLit:tlg0032.tlg001",
        ("Xenophon", "Hellenica"): "urn:cts:greekLit:tlg0032.tlg002",
        ("Xenophon", "Cyropaedia"): "urn:cts:greekLit:tlg0032.tlg003",
        ("Josephus", "Antiquities"): "urn:cts:greekLit:tlg0526.tlg001",
        ("Josephus", "Jewish War"): "urn:cts:greekLit:tlg0526.tlg002",
        ("Josephus", "Against Apion"): "urn:cts:greekLit:tlg0526.tlg003",
        # --- Philosophy ---
        ("Plato", "Republic"): "urn:cts:greekLit:tlg0059.tlg030",
        ("Plato", "Apology"): "urn:cts:greekLit:tlg0059.tlg002",
        ("Plato", "Crito"): "urn:cts:greekLit:tlg0059.tlg003",
        ("Plato", "Phaedo"): "urn:cts:greekLit:tlg0059.tlg004",
        ("Plato", "Symposium"): "urn:cts:greekLit:tlg0059.tlg011",
        ("Plato", "Euthyphro"): "urn:cts:greekLit:tlg0059.tlg001",
        ("Aristotle", "Nicomachean Ethics"): "urn:cts:greekLit:tlg0086.tlg010",
        ("Aristotle", "Politics"): "urn:cts:greekLit:tlg0086.tlg035",
        ("Aristotle", "Metaphysics"): "urn:cts:greekLit:tlg0086.tlg025",
        ("Aristotle", "Poetics"): "urn:cts:greekLit:tlg0086.tlg034",
        ("Aristotle", "Physics"): "urn:cts:greekLit:tlg0086.tlg031",
        ("Epictetus", "Discourses"): "urn:cts:greekLit:tlg0557.tlg001",
        ("Epictetus", "Enchiridion"): "urn:cts:greekLit:tlg0557.tlg002",
        ("Marcus Aurelius", "Meditations"): "urn:cts:greekLit:tlg0562.tlg001",
        # --- Philo ---
        ("Philo", "On the Creation"): "urn:cts:greekLit:tlg0018.tlg001",
        ("Philo", "Allegorical Interpretation"): "urn:cts:greekLit:tlg0018.tlg002",
        ("Philo", "On Abraham"): "urn:cts:greekLit:tlg0018.tlg016",
        ("Philo", "On Joseph"): "urn:cts:greekLit:tlg0018.tlg023",
        ("Philo", "On the Life of Moses"): "urn:cts:greekLit:tlg0018.tlg022",
        # --- Patristic / Church History ---
        ("Eusebius", "Church History"): "urn:cts:greekLit:tlg2018.tlg002",
        ("Eusebius", "Life of Constantine"): "urn:cts:greekLit:tlg2018.tlg020",
        # --- Apostolic Fathers ---
        ("Didache", "Didache"): "urn:cts:greekLit:tlg1311.tlg001",
        ("Clement", "1 Clement"): "urn:cts:greekLit:tlg1271.tlg001",
        ("Ignatius", "Letters"): "urn:cts:greekLit:tlg1443.tlg001",
        ("Polycarp", "Philippians"): "urn:cts:greekLit:tlg1622.tlg001",
        ("Barnabas", "Epistle"): "urn:cts:greekLit:tlg1216.tlg001",
        ("Hermas", "Shepherd"): "urn:cts:greekLit:tlg1419.tlg001",
        # --- Early Christian Apologists ---
        ("Justin Martyr", "First Apology"): "urn:cts:greekLit:tlg0645.tlg001",
        ("Athenagoras", "Plea for the Christians"): "urn:cts:greekLit:tlg1205.tlg001",
        # --- Latin ---
        ("Augustine", "Confessions"): "urn:cts:latinLit:stoa0040.stoa001",
        ("Augustine", "City of God"): "urn:cts:latinLit:stoa0040.stoa002",
        ("Cicero", "On Duties"): "urn:cts:latinLit:phi0474.phi055",
        ("Cicero", "On the Republic"): "urn:cts:latinLit:phi0474.phi042",
        ("Seneca", "Moral Letters"): "urn:cts:latinLit:phi1017.phi013",
        ("Tacitus", "Annals"): "urn:cts:latinLit:phi1351.phi005",
        ("Tacitus", "Histories"): "urn:cts:latinLit:phi1351.phi002",
        ("Virgil", "Aeneid"): "urn:cts:latinLit:phi0690.phi003",
        ("Virgil", "Eclogues"): "urn:cts:latinLit:phi0690.phi001",
        ("Livy", "History of Rome"): "urn:cts:latinLit:phi0914.phi001",
        ("Ovid", "Metamorphoses"): "urn:cts:latinLit:phi0959.phi006",
        ("Lucretius", "De Rerum Natura"): "urn:cts:latinLit:phi0550.phi001",
        # --- Comedy ---
        ("Aristophanes", "Clouds"): "urn:cts:greekLit:tlg0019.tlg001",
        ("Aristophanes", "Frogs"): "urn:cts:greekLit:tlg0019.tlg009",
        # --- Oratory ---
        ("Demosthenes", "Philippics"): "urn:cts:greekLit:tlg0014.tlg001",
        ("Lysias", "Orations"): "urn:cts:greekLit:tlg0540.tlg001",
    }

    def __init__(self) -> None:
        self._caps: Optional[List[Dict[str, Any]]] = None

    # ------------------------------------------------------------------
    # Capabilities — list available texts
    # ------------------------------------------------------------------

    def _fetch_capabilities(self) -> List[Dict[str, Any]]:
        """Fetch and parse the GetCapabilities response (cached in memory)."""
        if self._caps is not None:
            return self._caps

        # Try JSON first
        params = urlencode({"request": "GetCapabilities"})
        url = f"{SCAIFE_CTS_BASE}?{params}"
        body = _http_get(url, _CAPS_TIMEOUT, accept_json=True)
        data = _parse_json(body)

        if isinstance(data, dict):
            self._caps = self._parse_caps_json(data)
        elif body is not None:
            self._caps = self._parse_caps_xml(body)
        else:
            # Retry without JSON accept header (some servers ignore it)
            body = _http_get(url, _CAPS_TIMEOUT, accept_json=False)
            if body is not None:
                self._caps = self._parse_caps_xml(body)
            else:
                self._caps = []

        return self._caps

    def _parse_caps_json(self, data: Dict[str, Any]) -> List[Dict[str, Any]]:
        """Parse a JSON capabilities response into a flat list of text group dicts."""
        results: List[Dict[str, Any]] = []

        # Scaife JSON format: {"members": [{"urn": ..., "title": ..., "members": [...]}]}
        texts = data.get(
            "members", data.get("texts", data.get("textgroups", data.get("works", [])))
        )
        if not isinstance(texts, list):
            return []

        for group in texts:
            if not isinstance(group, dict):
                continue
            group_urn = group.get("urn", group.get("@urn", ""))
            group_title = group.get(
                "title", group.get("label", group.get("groupname", ""))
            )

            # Members can be works or editions
            for work in group.get("members", []):
                if not isinstance(work, dict):
                    continue
                work_urn = work.get("urn", work.get("@urn", ""))
                work_title = work.get("title", work.get("label", work.get("title", "")))
                lang = work.get("lang", work.get("language", ""))

                editions: List[str] = []
                for edition in work.get("members", []):
                    if isinstance(edition, dict):
                        ed_urn = edition.get("urn", edition.get("@urn", ""))
                        if ed_urn:
                            editions.append(ed_urn)

                results.append(
                    {
                        "group": group_title,
                        "group_urn": group_urn,
                        "work": work_title,
                        "work_urn": work_urn,
                        "language": lang,
                        "editions": editions,
                    }
                )

        return results

    def _parse_caps_xml(self, xml_bytes: bytes) -> List[Dict[str, Any]]:
        """Parse TEI CTS capabilities XML into a flat list of text group dicts."""
        results: List[Dict[str, Any]] = []
        ns = {"ti": "http://chs.harvard.edu/xmlns/cts"}

        try:
            root = ET.fromstring(xml_bytes)
        except ET.ParseError as e:
            logger.warning("Scaife capabilities XML parse failed: %s", e)
            return []

        # Collect namespaces from root
        for key, value in root.attrib.items():
            if key.startswith("xmlns"):
                ns_name = key.split(":")[-1] if ":" in key else ""
                ns[ns_name] = value

        for textgroup in (
            root.iterfind(".//ti:textgroup", ns)
            or root.iterfind(".//textgroup", ns)
            or []
        ):
            group_urn = textgroup.get("urn", "")
            group_name = ""
            gn = textgroup.find("ti:groupname", ns) or textgroup.find("groupname", ns)
            if gn is not None and gn.text:
                group_name = gn.text.strip()

            for work in (
                textgroup.iterfind("ti:work", ns)
                or textgroup.iterfind("work", ns)
                or []
            ):
                work_urn = work.get("urn", "")
                work_title = ""
                wt = work.find("ti:title", ns) or work.find("title", ns)
                if wt is not None and wt.text:
                    work_title = wt.text.strip()
                lang = work.get("{http://www.w3.org/XML/1998/namespace}lang", "")

                editions: List[str] = []
                for edition in (
                    work.iterfind("ti:edition", ns)
                    or work.iterfind("edition", ns)
                    or []
                ):
                    ed_urn = edition.get("urn", "")
                    if ed_urn:
                        editions.append(ed_urn)

                for translation in (
                    work.iterfind("ti:translation", ns)
                    or work.iterfind("translation", ns)
                    or []
                ):
                    t_urn = translation.get("urn", "")
                    if t_urn:
                        editions.append(t_urn)

                results.append(
                    {
                        "group": group_name,
                        "group_urn": group_urn,
                        "work": work_title,
                        "work_urn": work_urn,
                        "language": lang,
                        "editions": editions,
                    }
                )

        return results

    def list_texts(self, max_results: int = 50) -> List[Dict[str, Any]]:
        """Get available CTS texts from the Scaife Viewer.

        Args:
            max_results: Maximum number of results to return (default 50).

        Returns:
            List of dicts with keys: ``group``, ``group_urn``, ``work``,
            ``work_urn``, ``language``, ``editions``.
            Empty list on failure.
        """
        try:
            caps = self._fetch_capabilities()
            return caps[:max_results]
        except Exception as e:
            logger.warning("Scaife list_texts failed: %s", e)
            return []

    # ------------------------------------------------------------------
    # get_passage
    # ------------------------------------------------------------------

    def _find_best_edition(self, work_urn: str) -> Optional[str]:
        """Find a usable edition URN for a given work URN from capabilities."""
        caps = self._fetch_capabilities()
        for entry in caps:
            if entry.get("work_urn", "").startswith(work_urn):
                editions = entry.get("editions", [])
                if editions:
                    return editions[0]
            # Also check if work_urn already includes an edition
            if entry.get("work_urn") == work_urn:
                editions = entry.get("editions", [])
                if editions:
                    return editions[0]
        return None

    def get_passage(
        self,
        urn: str,
        start: Optional[str] = None,
        end: Optional[str] = None,
    ) -> Optional[str]:
        """Retrieve a text passage by CTS URN.

        Args:
            urn: CTS URN (e.g. ``"urn:cts:greekLit:tlg0012.tlg001.perseus-grc2"``
                 or a work-level URN without edition).
            start: Starting passage reference (e.g. ``"1.1"``).
            end: Ending passage reference (e.g. ``"1.7"``). If omitted,
                 only the ``start`` passage is returned.

        Returns:
            Plain-text passage content, or ``None`` on failure.
        """
        if not urn or not urn.strip():
            return None

        urn = urn.strip()

        # If the URN does not contain an edition, look one up from capabilities
        if ".perseus-" not in urn and ".stoa" not in urn and ".phi" not in urn:
            edition = self._find_best_edition(urn)
            if edition:
                urn = edition
            # Otherwise proceed with URN as-is (server may default)

        # Build the full passage URN
        if start:
            passage_ref = start
            if end:
                passage_ref = f"{start}-{end}"
            full_urn = f"{urn}:{passage_ref}"
        else:
            full_urn = urn

        try:
            params = urlencode({"request": "GetPassage", "urn": full_urn})
            url = f"{SCAIFE_CTS_BASE}?{params}"
            body = _http_get(url, _PASSAGE_TIMEOUT, accept_json=False)
            if body is None:
                # Retry with JSON accept
                body = _http_get(url, _PASSAGE_TIMEOUT, accept_json=True)
                if body is not None:
                    data = _parse_json(body)
                    if isinstance(data, dict):
                        # Try common JSON response shapes
                        text = (
                            data.get("passage") or data.get("text") or data.get("body")
                        )
                        if isinstance(text, str):
                            return text
                    return None

            # Try JSON parse of the body anyway
            data = _parse_json(body)
            if isinstance(data, dict):
                text = data.get("passage") or data.get("text") or data.get("body")
                if isinstance(text, str):
                    return text

            # Fallback: strip XML tags
            if body:
                text = _strip_xml(body)
                return text
            return None
        except Exception as e:
            logger.warning("Scaife get_passage failed for urn=%r: %s", urn, e)
            return None

    # ------------------------------------------------------------------
    # search_by_author
    # ------------------------------------------------------------------

    def search_by_author(self, author_name: str) -> List[Dict[str, Any]]:
        """Find texts by author name (case-insensitive substring match).

        Searches both the capabilities list and the KNOWN_TEXTS constant.

        Args:
            author_name: Author name or substring (e.g. ``"Plato"``,
                         ``"Homer"``, ``"augustine"``).

        Returns:
            List of matching dicts with keys: ``group``, ``work``,
            ``work_urn``, ``language``, ``editions``.
        """
        if not author_name or not author_name.strip():
            return []

        query = author_name.strip().lower()
        results: List[Dict[str, Any]] = []

        # 1. Check KNOWN_TEXTS (fast, offline)
        for (author, work), work_urn in self.KNOWN_TEXTS.items():
            if query in author.lower():
                results.append(
                    {
                        "group": author,
                        "group_urn": "",
                        "work": work,
                        "work_urn": work_urn,
                        "language": "lat" if "latinLit" in work_urn else "grc",
                        "editions": [],
                        "_source": "known",
                    }
                )

        # 2. Try live capabilities (enriches with editions)
        try:
            caps = self._fetch_capabilities()
            known_urns = {r["work_urn"] for r in results}
            for entry in caps:
                group = entry.get("group", "")
                work = entry.get("work", "")
                if query in group.lower() or query in work.lower():
                    wurn = entry.get("work_urn", "")
                    if wurn not in known_urns:
                        known_urns.add(wurn)
                        results.append({**entry, "_source": "live"})
        except Exception as e:
            logger.debug("Scaife search_by_author live lookup failed: %s", e)

        # Enrich known-text results with live editions if available
        try:
            caps = self._fetch_capabilities()
            for r in results:
                if r.get("editions") or r["work_urn"] in [
                    c.get("work_urn", "") for c in caps
                ]:
                    pass
                # Try to match
                for entry in caps:
                    if entry.get("work_urn", "").startswith(
                        r["work_urn"]
                    ) and entry.get("editions"):
                        if not r.get("editions"):
                            r["editions"] = entry["editions"]
                        break
        except Exception:
            pass

        # Deduplicate while preserving order
        seen: set = set()
        unique: List[Dict[str, Any]] = []
        for r in results:
            key = (r.get("group", ""), r.get("work", ""))
            if key not in seen:
                seen.add(key)
                unique.append(r)

        # Remove internal keys
        for r in unique:
            r.pop("_source", None)

        return unique

    # ------------------------------------------------------------------
    # resolve_reference
    # ------------------------------------------------------------------

    def resolve_reference(
        self,
        author: str,
        work: str,
        passage: str,
    ) -> Optional[str]:
        """Resolve a human-readable reference to a text passage.

        Looks up the (author, work) pair in KNOWN_TEXTS, resolves the CTS
        URN, and retrieves the passage text.

        Args:
            author: Author name (e.g. ``"Plato"``, ``"Homer"``,
                    ``"Augustine"``).
            work: Work title (e.g. ``"Republic"``, ``"Iliad"``,
                  ``"Confessions"``).
            passage: Passage reference (e.g. ``"1.1-1.5"``,
                     ``"2.1"``).

        Returns:
            Plain-text passage content, or ``None`` if not found.
        """
        if not author or not work:
            return None

        # Try exact match first
        work_urn = self.KNOWN_TEXTS.get((author.strip(), work.strip()))

        # Fall back to case-insensitive match
        if work_urn is None:
            author_lower = author.strip().lower()
            work_lower = work.strip().lower()
            for (a, w), urn in self.KNOWN_TEXTS.items():
                if a.lower() == author_lower and w.lower() == work_lower:
                    work_urn = urn
                    break
                # Partial match on work
                if a.lower() == author_lower and work_lower in w.lower():
                    work_urn = urn
                    break

        if work_urn is None:
            logger.warning(
                "Scaife resolve_reference: unknown text for author=%r work=%r",
                author,
                work,
            )
            return None

        # Parse passage into start/end if it contains a hyphen
        start: Optional[str] = None
        end: Optional[str] = None
        if passage:
            if "-" in passage:
                parts = passage.split("-", 1)
                start = parts[0].strip()
                end = parts[1].strip() if len(parts) > 1 else None
            else:
                start = passage.strip()

        return self.get_passage(work_urn, start, end)

    # ------------------------------------------------------------------
    # list_authors
    # ------------------------------------------------------------------

    def list_authors(self) -> List[str]:
        """Return a sorted list of unique author names from KNOWN_TEXTS."""
        authors = sorted({author for (author, _work) in self.KNOWN_TEXTS})
        return authors


# ---------------------------------------------------------------------------
# Availability check
# ---------------------------------------------------------------------------


def scaife_available() -> bool:
    """Check if the Scaife CTS API is reachable.

    Returns ``True`` if the capabilities endpoint responds within the
    timeout; ``False`` otherwise.
    """
    try:
        params = urlencode({"request": "GetCapabilities"})
        url = f"{SCAIFE_CTS_BASE}?{params}"
        req = urllib.request.Request(url, headers={"User-Agent": _USER_AGENT})
        with urllib.request.urlopen(req, timeout=_AVAIL_TIMEOUT) as resp:
            return resp.status == 200
    except Exception as e:
        logger.debug("Scaife availability check failed: %s", e)
        return False


# ---------------------------------------------------------------------------
# Module-level singleton
# ---------------------------------------------------------------------------

scaife = ScaifeClient()


# ---------------------------------------------------------------------------
# MCP tool snippets — copy into accordance_mcp_server.py to register tools
# ---------------------------------------------------------------------------

# --- search_classical_texts ---
"""
@mcp.tool()
def search_classical_texts(query: str) -> str:
    \\\"\\\"\\\"Search the Scaife Viewer for classical and patristic texts.

    Searches 2,699 works (1,951 Greek, 668 Latin) from the Perseus
    Digital Library via the Scaife CTS API.  Covers classical
    literature, Church Fathers, papyri, and inscriptions.

    Args:
        query: Author name or work title to search for.
               Examples: 'Plato', 'Homer Iliad', 'Eusebius',
               'Augustine', 'Apostolic Fathers'.

    Returns:
        Markdown-formatted list of matching works with CTS URNs.
    \\\"\\\"\\\"
    try:
        from accordance_parser.scaife_api import scaife
        results = scaife.search_by_author(query)

        if not results:
            return f\\\"**No classical texts found** for query: `{query}`\\\"

        lines = [
            f\\\"# Scaife Viewer — {len(results)} result(s) for `{query}`\\\",
            \\\"\\\",
        ]
        for r in results:
            urn = r.get('work_urn', '')
            language = r.get('language', '')
            lang_label = 'Greek' if language in ('grc', 'greek') else 'Latin' if language in ('lat', 'latin') else language
            lines.append(f\\\"## {r.get('group', 'Unknown')} — {r.get('work', '')}\\\")
            lines.append(f\\\"- **CTS URN:** `{urn}`\\\")
            if lang_label:
                lines.append(f\\\"- **Language:** {lang_label}\\\")
            editions = r.get('editions', [])
            if editions:
                lines.append(f\\\"- **Edition:** `{editions[0]}`\\\")
            lines.append(\\\"\\\")
        return \\\"\\\\n\\\".join(lines)
    except Exception as e:
        return f\\\"**Classical text search error:** {e}\\\"
"""

# --- get_classical_passage ---
"""
@mcp.tool()
def get_classical_passage(urn: str, start: str = \\\"\\\", end: str = \\\"\\\") -> str:
    \\\"\\\"\\\"Retrieve a passage from the Scaife Viewer by CTS URN.

    Fetches classical or patristic text from the Perseus Digital
    Library using the CTS URN protocol.  Supports Homer, Plato,
    Aristotle, Church Fathers, and 2,600+ other works.

    Args:
        urn: CTS URN of the work (e.g.
             'urn:cts:greekLit:tlg0012.tlg001.perseus-grc2').
        start: Starting passage reference (e.g. '1.1').
        end: Ending passage reference (e.g. '1.7').

    Returns:
        Markdown-formatted passage text, or an error message.
    \\\"\\\"\\\"
    try:
        from accordance_parser.scaife_api import scaife
        s = start.strip() if start else None
        e = end.strip() if end else None
        text = scaife.get_passage(urn, s, e)

        if text is None:
            return (
                f\\\"**Passage not found:** `{urn}`\\\\n\\\\n\\\"
                f\\\"Verify the URN. Use `search_classical_texts` to find \\\"
                f\\\"available works.\\\"
            )

        passage_ref = f\\\" {start}\\\" if start else \\\"\\\"
        if end:
            passage_ref += f\\\"-{end}\\\"
        return (
            f\\\"# Scaife Viewer: {urn}{passage_ref}\\\\n\\\\n\\\"
            f\\\"{text}\\\"
        )
    except Exception as e:
        return f\\\"**Classical passage error:** {e}\\\"
"""

# --- list_classical_authors ---
"""
@mcp.tool()
def list_classical_authors() -> str:
    \\\"\\\"\\\"List all authors in the Scaife KNOWN_TEXTS catalog.

    Returns approximately 40+ authors from the curated catalog covering
    Greek and Latin classical literature, Church Fathers, and early
    Christian writings.

    These authors are resolveable via `get_classical_passage` by
    constructing a CTS URN from the work identifier.

    Returns:
        Markdown-formatted list of authors grouped by category.
    \\\"\\\"\\\"
    try:
        from accordance_parser.scaife_api import scaife

        # Group by category
        categories: dict[str, list[str]] = {}
        epic = {'Homer', 'Hesiod'}
        tragedy = {'Aeschylus', 'Sophocles', 'Euripides'}
        history = {'Herodotus', 'Thucydides', 'Xenophon', 'Josephus'}
        philosophy = {'Plato', 'Aristotle', 'Epictetus', 'Marcus Aurelius'}
        latin = {'Cicero', 'Virgil', 'Livy', 'Ovid', 'Lucretius', 'Seneca', 'Tacitus'}
        patristic = {'Eusebius', 'Augustine', 'Philo'}
        apostolic = {'Didache', 'Clement', 'Ignatius', 'Polycarp', 'Barnabas', 'Hermas'}
        apolog = {'Justin Martyr', 'Athenagoras'}
        comedy = {'Aristophanes'}
        oratory = {'Demosthenes', 'Lysias'}

        authors = scaife.list_authors()
        for author in authors:
            if author in epic:
                categories.setdefault('Epic Poetry', []).append(author)
            elif author in tragedy:
                categories.setdefault('Tragedy', []).append(author)
            elif author in history:
                categories.setdefault('History', []).append(author)
            elif author in philosophy:
                categories.setdefault('Philosophy', []).append(author)
            elif author in latin:
                categories.setdefault('Latin Literature', []).append(author)
            elif author in patristic:
                categories.setdefault('Patristic & Hellenistic Jewish', []).append(author)
            elif author in apostolic:
                categories.setdefault('Apostolic Fathers', []).append(author)
            elif author in apolog:
                categories.setdefault('Early Apologists', []).append(author)
            elif author in comedy:
                categories.setdefault('Comedy', []).append(author)
            elif author in oratory:
                categories.setdefault('Oratory', []).append(author)
            else:
                categories.setdefault('Other', []).append(author)

        lines = [
            '# Scaife Classical Authors',
            '',
            f'{len(authors)} authors in catalog (2,699 works available via Scaife)',
            '',
        ]
        for cat, auths in categories.items():
            lines.append(f'## {cat}')
            for a in sorted(auths):
                uris = [
                    f'`{urn}` — {w}'
                    for (au, w), urn in scaife.KNOWN_TEXTS.items()
                    if au == a
                ]
                lines.append(f'- **{a}**')
                for u in uris:
                    lines.append(f'  {u}')
            lines.append('')
        return '\\\\n'.join(lines)
    except Exception as e:
        return f'**Author list error:** {e}'
"""
