"""Undefine a domain from the hypervisor — pure orchestration.

**This module is exclusively about the domain definition.** It does
not touch the filesystem. ``vmanager vm delete --remove-storage``
orchestrates both the undefine here and the image-file removal in the
adapter / storage layer — neither imports the other.

A running domain is refused by default; the caller must pass
``force=True`` to undefine a running VM. The sequence is:

1. Read the domain's current state via
   :meth:`HypervisorBackend.get_domain`.
2. If it is running and ``force`` is false, raise
   :class:`~mgf.vm.core.exceptions.OperationError`.
3. If it is running and ``force`` is true, force-stop it first, then
   undefine.
4. Undefine via :meth:`HypervisorBackend.undefine`.

Purity
------
The :class:`HypervisorBackend` is injected; this module performs no
concrete engine / subprocess / file I/O. ``delete_storage`` is passed
straight through to the backend's ``undefine`` so the *adapter* owns
the actual storage removal (the only layer allowed to touch files).

Deferred
--------
The legacy ``delete`` returned ``provider.domain_disk_paths(handle)``
(a snapshot of on-disk paths, taken before undefine, for a follow-up
``disk_ops`` call). ``domain_disk_paths`` is **not** part of the
:class:`HypervisorBackend`/:class:`StorageBackend` contracts, and a
``list[Path]`` of host paths is host-filesystem state that does not
belong in the pure core. That disk-path enumeration is deferred to the
adapter. The core ``delete`` instead exposes ``delete_storage`` so the
adapter can do the removal atomically inside ``undefine``.
"""

from __future__ import annotations

from typing import TYPE_CHECKING

from mgf.common.observability import operation_span

from mgf.vm.core.exceptions import (
    DomainNotFound,
    OperationError,
    ProviderError,
)
from mgf.vm.core.lifecycle import power

if TYPE_CHECKING:
    from mgf.vm.core.cancellation import CancellationToken
    from mgf.vm.core.common.progress import ProgressReporter
    from mgf.vm.core.contracts import HypervisorBackend
    from mgf.vm.core.ids import DomainId


def delete(
    backend: HypervisorBackend,
    domain: DomainId,
    *,
    force: bool = False,
    delete_storage: bool = False,
    token: CancellationToken | None = None,
    reporter: ProgressReporter | None = None,
) -> None:
    """Undefine a domain from the hypervisor.

    Args:
        backend: injected :class:`HypervisorBackend`.
        domain: domain to remove.
        force: when ``True``, a running domain is force-stopped first.
            When ``False`` (the default), attempting to delete a
            running domain raises :class:`OperationError` with an
            actionable message.
        delete_storage: forwarded to :meth:`HypervisorBackend.undefine`;
            when ``True`` the adapter removes the backing storage as
            part of the undefine (the core never touches files).
        token: optional cancellation token, threaded into the
            force-stop step.
        reporter: optional progress reporter.

    Raises:
        :class:`~mgf.vm.core.exceptions.DomainNotFound`: no such domain.
        :class:`~mgf.vm.core.exceptions.OperationError`: the domain is
            running and ``force`` is false, or the engine refused the
            undefine.
    """
    with operation_span(
        "vm.delete",
        domain_id=domain.value,
        force=force,
        delete_storage=delete_storage,
    ):
        try:
            info = backend.get_domain(domain)
        except DomainNotFound:
            raise
        except ProviderError as e:
            raise OperationError(f"could not read state of {domain.value!r}: {e}") from e

        if info.is_running:
            if not force:
                raise OperationError(
                    f"{domain.value!r} is running; pass force=True to delete it (CLI: `--force`)"
                )
            _announce(reporter, f"force-stopping {domain.value} before delete")
            power.stop(backend, domain, force=True, token=token, reporter=reporter)

        _announce(reporter, f"undefining {domain.value}")
        try:
            backend.undefine(domain, delete_storage=delete_storage)
        except DomainNotFound:
            raise
        except ProviderError as e:
            raise OperationError(f"could not undefine {domain.value!r}: {e}") from e

        _announce(reporter, f"{domain.value} undefined")


def _announce(reporter: ProgressReporter | None, message: str) -> None:
    if reporter is not None:
        reporter.report(message)


__all__ = ["delete"]
