"""Shared actor-config resolution for the ``actor run`` CLI commands.

Both ``actor.py`` and ``actor_run.py`` need to resolve an actor name
to one or more YAML config-file paths.  This module centralises that
logic so the two CLI entry-points stay DRY.

Spec Deviation
--------------
The specification (lines 4562-4566) defines ``agents actor run`` with
no ``--config/-c`` option.  The implementation preserves ``--config``
as an optional convenience for direct YAML invocation.  When provided,
``--config`` takes precedence over registry-based name resolution.
Accepted in issue #901 AC: "`--config/-c` is removed or made optional
(for direct YAML invocation as a convenience)".
"""

from __future__ import annotations

import atexit
import tempfile
from pathlib import Path

import typer
import yaml

from cleveragents.application.container import get_container
from cleveragents.core.exceptions import NotFoundError

# Module-level set + single atexit handler to avoid unbounded handler
# accumulation when resolve_config_files is called multiple times in the
# same process (e.g. test suites using CliRunner).
_temp_files: set[str] = set()


def _cleanup_temp_files() -> None:
    """Remove all temporary YAML files created by resolve_config_files."""
    snapshot = list(_temp_files)
    _temp_files.difference_update(snapshot)
    for p in snapshot:
        Path(p).unlink(missing_ok=True)


atexit.register(_cleanup_temp_files)


def _sanitize_name(name: str) -> str:
    """Strip non-printable and ANSI control characters from *name*.

    Prevents user-controlled CLI input from injecting terminal escape
    sequences into error messages rendered in CI logs or web dashboards.
    """
    return "".join(c for c in name if c.isprintable())


def resolve_config_files(name: str, config: list[Path]) -> list[Path]:
    """Return config file paths, resolving *name* from the actor registry
    when *config* is empty.

    When ``--config`` is provided the caller already has file paths and
    *name* is ignored (backward-compatible).  Otherwise *name* is looked
    up in the actor registry and the YAML text stored on the actor is
    written to a temporary file so the existing
    :class:`ReactiveCleverAgentsApp` flow can consume it unchanged.

    Raises:
        typer.Exit(code=2): When the actor name is empty, the actor is
            not found in the registry and no ``--config`` was provided,
            or when the actor has no configuration data.
        Other exceptions from ``get_container()`` or
            ``container.actor_registry()`` are **not** caught here so
            callers can handle infrastructure failures in their own
            ``try`` blocks.
    """
    if config:
        return config

    if not name or not name.strip():
        typer.echo("Error: Actor name must not be empty.", err=True)
        raise typer.Exit(code=2)

    safe_name = _sanitize_name(name)

    container = get_container()
    actor_registry = container.actor_registry()

    try:
        actor = actor_registry.get(name)
    except NotFoundError:
        typer.echo(
            f"Error: Actor '{safe_name}' not found in registry "
            "and no --config provided.",
            err=True,
        )
        raise typer.Exit(code=2) from None

    yaml_text = (actor.yaml_text or "").strip()
    if not yaml_text:
        config_blob = actor.config_blob
        if not config_blob:
            typer.echo(
                f"Error: Actor '{safe_name}' has no configuration data.",
                err=True,
            )
            raise typer.Exit(code=2)
        try:
            yaml_text = yaml.safe_dump(config_blob, default_flow_style=False)
        except yaml.YAMLError:
            typer.echo(
                f"Error: Actor '{safe_name}' config_blob could not be "
                "serialised to YAML.",
                err=True,
            )
            raise typer.Exit(code=2) from None

    with tempfile.NamedTemporaryFile(
        delete=False, suffix=".yaml", mode="w", encoding="utf-8"
    ) as tmp:
        _temp_files.add(tmp.name)
        tmp.write(yaml_text)
        tmp.flush()

    return [Path(tmp.name)]
