"""H5P library metadata extraction.

Reads the ``library.json`` file inside each mirrored repository at
HEAD and at every semver-shaped tag, builds a
``(machineName, version, deps)`` triple per point, and aggregates
them into ``library-index.json``.

The index drives two downstream behaviours:

- Version resolution in
  :mod:`~byteb4rb1e.sphinxcontrib.h5p.utils.pkg.bootstrap`, where a
  user may request ``H5P.TrueFalse@1.8`` (prefix) or
  ``H5P.TrueFalse@1.8.2`` (exact) and the bootstrap needs to pick
  the matching ref.
- Transitive dependency closure in the same bootstrap, since each
  version records its ``preloadedDependencies``.

The :class:`IndexCommand` CLI wrapper invokes
:func:`extract_versions` over every cached mirror and writes the
combined JSON.
"""
import argparse
import json
import logging
import re
from pathlib import Path
from typing import Any, Dict, Optional, Tuple

from byteb4rb1e.utils.vcs import git

INDEX = Path("data") / "library-index.json"

_TAG_RE = re.compile(r"\d+\.\d+(\.\d+)?")

logger = logging.getLogger(__name__)


def parse_library(
    content: str,
) -> Optional[Tuple[str, str, Dict[str, str]]]:
    """Parse a library.json string into (machineName, version, deps).

    Returns None if essential fields are missing.
    """
    try:
        lib = json.loads(content)
    except (json.JSONDecodeError, TypeError):
        return None

    machine_name = lib.get("machineName")
    major = lib.get("majorVersion")
    minor = lib.get("minorVersion")
    patch = lib.get("patchVersion")

    if not machine_name or major is None or minor is None:
        return None

    if patch is None:
        patch = 0

    version_str = "%d.%d.%d" % (major, minor, patch)

    deps_map = {}  # type: Dict[str, str]
    for dep in lib.get("preloadedDependencies", []):
        dep_name = dep.get("machineName", "")
        dep_major = dep.get("majorVersion")
        dep_minor = dep.get("minorVersion")
        if dep_name and dep_major is not None and dep_minor is not None:
            deps_map[dep_name] = "%d.%d" % (dep_major, dep_minor)

    return (machine_name, version_str, deps_map)


def extract_versions(
    bare_repo: Path,
) -> Optional[Tuple[str, Dict[str, Any]]]:
    """Extract all versions from an existing bare repo.

    Returns (machineName, {"versions": {ver: {ref, dependencies}}})
    or None if the repo has no parseable library.json.
    """
    versions = {}  # type: Dict[str, Dict[str, Any]]
    machine_name = None  # type: Optional[str]

    # HEAD pass
    try:
        sha = git.head_ref(bare_repo)
    except git.GitError:
        logger.warning("Cannot resolve HEAD in %s", bare_repo)
        return None

    content = git.read_file(bare_repo, "library.json")
    if content is not None:
        parsed = parse_library(content)
        if parsed is not None:
            machine_name, ver, deps = parsed
            versions[ver] = {"ref": sha, "dependencies": deps}
            logger.info(f"[{machine_name}] {ver} ({sha})")

    # Tag pass
    tags = git.list_tags(bare_repo)
    for tag in tags:
        if not _TAG_RE.fullmatch(tag):
            continue
        try:
            tag_sha = git.resolve_ref(bare_repo, tag)
        except git.GitError:
            continue
        tag_content = git.read_file(
            bare_repo, "library.json", ref=tag,
        )
        if tag_content is None:
            continue
        parsed = parse_library(tag_content)
        if parsed is None:
            continue
        tag_name, tag_ver, tag_deps = parsed
        if machine_name is None:
            machine_name = tag_name
        if tag_ver not in versions:
            versions[tag_ver] = {
                "ref": tag_sha,
                "dependencies": tag_deps,
            }
            logger.info(f"[{machine_name}] {tag_ver} ({tag_sha})")

    if machine_name is None:
        logger.warning("No library.json found in %s", bare_repo)
        return None

    return (machine_name, {"versions": versions})
