"""Pure parsers for skill files and string formats (#82 Phase 1).

No external dependencies beyond the standard library — anything
imported here must remain stdlib-only so the `:parsers` py_library
stays a true leaf in the Bazel dep graph and unit tests of these
helpers run without pulling in github / mcp / pydantic.
"""
from __future__ import annotations

import re

# ----- Skill version / changelog parsing (#60) ------------------------------

_VERSION_HEADER_RE = re.compile(
    r"^\*\*Version:\*\*\s*([0-9]+(?:\.[0-9]+){1,2})\s*$", re.MULTILINE
)

_CHANGELOG_SECTION_RE = re.compile(
    r"##\s+Changelog\s*\n(.*?)(?=\n##\s+|\Z)", re.DOTALL
)

_CHANGELOG_ENTRY_RE = re.compile(
    r"^- \*\*([0-9]+(?:\.[0-9]+){1,2})\*\*\s*"
    r"\(([0-9]{4}-[0-9]{2}-[0-9]{2})\)\s*"
    r"—\s*(.+?)"
    r"(?=\n- \*\*|\Z)",
    re.MULTILINE | re.DOTALL,
)


def parse_skill_version(content: str) -> str | None:
    """Extract the X.Y.Z from a `**Version:** X.Y.Z` header line."""
    if not content:
        return None
    m = _VERSION_HEADER_RE.search(content)
    return m.group(1) if m else None


def parse_changelog(content: str) -> list[dict]:
    """Parse a `## Changelog` section into ordered entries.

    Each entry: {"version": str, "date": str, "summary": str}
    Order matches the file — typically newest first.
    """
    if not content:
        return []
    section_match = _CHANGELOG_SECTION_RE.search(content)
    if not section_match:
        return []
    section_text = section_match.group(1)
    entries: list[dict] = []
    for m in _CHANGELOG_ENTRY_RE.finditer(section_text):
        version, date, summary = m.group(1), m.group(2), m.group(3).strip()
        entries.append({"version": version, "date": date, "summary": summary})
    return entries


def semver_gt(a: str, b: str) -> bool:
    """Return True if semver string `a` is strictly greater than `b`.

    Treats missing components as 0 (e.g. "1.2" → (1, 2, 0)).
    """
    def _parts(s: str) -> tuple[int, ...]:
        nums = [int(x) for x in s.split(".")]
        while len(nums) < 3:
            nums.append(0)
        return tuple(nums)

    return _parts(a) > _parts(b)


def parse_owner_repo(full_name: str) -> tuple[str, str]:
    """Split 'owner/repo' into (owner, repo). Raises ValueError if invalid."""
    parts = full_name.split("/")
    if len(parts) != 2 or not parts[0] or not parts[1]:
        raise ValueError(
            f"Expected 'owner/repo' format, got: {full_name!r}"
        )
    return parts[0], parts[1]
