"""Layer 2 — A small typed exception hierarchy with structured fields.

Shows the practical shape: a project-specific base + a category
subclass + a concrete that carries machine-readable fields. The
caller can filter by category (broad) or by concrete (narrow), and
read structured attributes without parsing the message.
"""

from __future__ import annotations

from mgf.common.exceptions import (
    AppError,
    OperationError,
    ResourceNotFoundError,
)

# ── Project-specific hierarchy ───────────────────────────────────


class MyAppError(AppError):
    """Base for every error this app raises."""


class VMError(OperationError):
    """Category: VM-management failures (subset of OperationError)."""


class VMNotFound(ResourceNotFoundError):
    """A VM with the given name does not exist.

    Inherits ``.name`` from ``ResourceNotFoundError`` and refines
    the message with an actionable hint (rule EH-09).
    """

    def __init__(self, name: str) -> None:
        self.name = name
        super().__init__(
            f"no VM named {name!r}; run 'myapp vm list' to see available VMs"
        )


# ── A function that raises typed errors ──────────────────────────


def lookup_vm(name: str) -> dict[str, int]:
    known = {"prod-1": {"id": 1}, "prod-2": {"id": 2}}
    if name not in known:
        raise VMNotFound(name)
    return known[name]


def main() -> None:
    # Happy path
    print(f"prod-1 → {lookup_vm('prod-1')}")

    # Sad path — caught by the concrete type, read structured field
    try:
        lookup_vm("ghost")
    except VMNotFound as e:
        print(f"VM not found: {e}")
        print(f"  field .name = {e.name!r}")

    # Sad path — caught by the broader category
    try:
        raise VMError("hypervisor connection lost")
    except OperationError as e:
        print(f"operation failed: {e}")

    # Sad path — caught at the project base
    try:
        raise MyAppError("generic failure")
    except AppError as e:
        print(f"app error: {e}")


if __name__ == "__main__":
    main()
