"""``mgf-hrb scan`` — enumerate broker resources, render the tree.

Reference for the bus-layout reporter (Part 8.7). The proven Phase-7 path
underneath this is ``Client.list(scope)``; this command formats the result
for human + machine consumption.

Output shape: a one-line header explaining what's about to render, the
resources grouped by family (PCI / USB / other) so composites stay
visually together, and a footer summary line. The URN list is sorted
alphabetically so two runs produce byte-identical output.
"""

from __future__ import annotations

import logging
import sys
from typing import TYPE_CHECKING

from mgf.hrb import Client
from mgf.hrb.exceptions import MgfHrbError

if TYPE_CHECKING:
    from collections.abc import Iterable

logger = logging.getLogger(__name__)


# Group URNs by the second segment after `hw:/` — `hw:/pci/...` → "pci",
# `hw:/usb/...` → "usb", etc. The bucket names below are the canonical
# labels we render; URNs whose family we don't recognise fall into "other"
# so nothing is silently dropped.
_FAMILY_LABELS: dict[str, str] = {
    "pci": "PCI functions",
    "usb": "USB interfaces",
    "cpu": "CPU sets (Family B)",
    "memory": "Memory regions (Family B)",
}


def scan_command(*, socket_path: str, scope: str = "hw:/**") -> int:
    logger.debug("scan starting", extra={"socket_path": socket_path, "scope": scope})
    try:
        with Client.connect(socket_path) as c:
            urns = list(c.list(scope))
    except FileNotFoundError:
        logger.warning("broker socket missing at %s", socket_path)
        print(
            f"error: no broker is listening at {socket_path}.\n"
            "  Start one with `mgf-hrb-server` (Zig binary built from hrb-code/) "
            "or pass --socket to point at a running broker.",
            file=sys.stderr,
        )
        return 2
    except MgfHrbError as exc:
        logger.error("broker returned error: %s", exc)
        print(f"error from broker: {exc}", file=sys.stderr)
        return 1

    logger.debug("scan returned %d urn(s)", len(urns))
    _render(urns, socket_path=socket_path, scope=scope)
    return 0


def _bucket(urn: str) -> str:
    """Pick the family bucket for a URN. URNs not starting with `hw:/<family>/`
    fall into "other" — defensive, so an unrecognised broker shape never
    crashes the renderer."""
    if not urn.startswith("hw:/"):
        return "other"
    rest = urn[len("hw:/") :]
    family, _, _ = rest.partition("/")
    return family if family in _FAMILY_LABELS else "other"


def _render(urns: Iterable[str], *, socket_path: str, scope: str) -> None:
    """Print a grouped, sorted URN tree with a header + footer summary.

    The render is intentionally plain-text (no ANSI colours, no Unicode box-
    drawing) so it stays terminal-portable and grep-able. Composites that
    map to N interfaces appear as N adjacent lines under their family
    bucket — visual grouping comes from sort order, not indentation.
    """
    items = sorted(urns)
    total = len(items)

    # Header.
    print(f"mgf-hrb scan  (broker at {socket_path})")
    print(f"Scope: {scope}")
    print()

    if total == 0:
        print("  (no resources matched the scope)")
        print()
        print(
            "  This usually means the broker is up but its backends are empty\n"
            "  (e.g. a sandbox without VFIO or USB hardware). Try a wider\n"
            "  scope like `hw:/**`, or run `mgf-hrb doctor` to verify the\n"
            "  broker's substrate health."
        )
        return

    # Group by family, render each non-empty group with a label.
    by_family: dict[str, list[str]] = {}
    for urn in items:
        by_family.setdefault(_bucket(urn), []).append(urn)

    # Stable section order: pci, usb, cpu, memory, other.
    section_order = [*_FAMILY_LABELS.keys(), "other"]
    for family in section_order:
        bucket = by_family.get(family)
        if not bucket:
            continue
        label = _FAMILY_LABELS.get(family, "Other resources")
        print(f"  {label}  ({len(bucket)}):")
        for urn in bucket:
            print(f"    {urn}")
        print()

    # Footer.
    family_counts = ", ".join(
        f"{len(b)} {_FAMILY_LABELS.get(f, f)}" for f, b in by_family.items() if b
    )
    print(f"{total} resource(s) total — {family_counts}.")
    print()
    print(
        "Each URN above is something the broker can be asked about. Use\n"
        "`mgf-hrb info <urn>` for one-resource detail (fidelity tags, state,\n"
        "parent) once the broker's inspect op lands. Today's broker answers\n"
        "list (above) and claim/release/raw (see examples/02_claim_and_use.py)."
    )
    if any(f in by_family for f in ("pci", "usb")):
        print()
        print(
            "Don't recognise a URN family or the structure? Run "
            "`mgf-hrb explain usb` /\n"
            "`mgf-hrb explain pci` for the hardware diagram and "
            "vocabulary."
        )
