"""CLI parameter validation helpers.

Spec source: vectl step ``wire-cli-params.fix-wire-params-validation``
(verify-wire-params blockers 1-4).
"""

# @shell_orchestration: CLI validation helpers (no I/O, orchestrates parameter validation)

from __future__ import annotations

from collections.abc import Callable
from typing import Any, TypeVar, cast
from urllib.parse import urlparse

import click


_ClickCallback = TypeVar("_ClickCallback", bound=Callable[..., Any])


# @shell_orchestration: Click decorator composition helper for CLI wiring
# @invar:allow shell_result: Returns decorator callbacks, not shell I/O Result
def _apply_click_option_stack(
    callback: _ClickCallback,
    option_decorators: tuple[Callable[[_ClickCallback], _ClickCallback], ...],
) -> _ClickCallback:
    """Apply Click option decorators preserving declaration order."""
    decorated_callback = callback
    for option_decorator in reversed(option_decorators):
        decorated_callback = option_decorator(decorated_callback)
    return cast(_ClickCallback, decorated_callback)


# @shell_orchestration: Shared Click option wiring for run/spawn command entry points
# @invar:allow shell_result: Returns Click decorator, not shell I/O Result
def with_run_spawn_agent_options(
    *,
    instance_id_help: str,
) -> Callable[[_ClickCallback], _ClickCallback]:
    """Attach run/spawn shared Click options to a command callback.

    Spec source: vectl step ``cli-chat-command.extract-agent-options-decorator``
    requires extracting common run/spawn options without changing help text,
    defaults, or behavior.
    """
    option_decorators = (
        click.option(
            "--persona",
            "-p",
            type=click.Path(exists=False),
            required=False,
            help="Persona name (from larva registry) or path to PersonaSpec file (.json, .yaml, .md)",
        ),
        click.option(
            "--system-prompt",
            "-s",
            "system_prompt",
            help="Inline system prompt for initial persona at launch (alternative to --persona).",
        ),
        click.option(
            "--job-file", type=click.Path(exists=False), help="JobSpec JSON file"
        ),
        click.option(
            "--model", help="LLM model identifier (overrides PersonaSpec.model)"
        ),
        click.option("--llm-base-url", help="LLM API base URL"),
        click.option(
            "--mcp", multiple=True, help="MCP server: 'command args' or http(s)://url"
        ),
        click.option(
            "--tools",
            multiple=True,
            help="Tool preset names from config (e.g. --tools shell,web or --tools shell --tools web)",
        ),
        click.option("--timeout", help="Wall clock timeout (e.g., 5m, 2h)"),
        click.option("--max-turns", type=int, help="Max agent turns"),
        click.option("--max-tokens", type=int, help="Token budget"),
        click.option(
            "--context-mode",
            type=click.Choice(["isolated", "shared"]),
            help="Context mode",
        ),
        click.option(
            "--context-bundle",
            type=click.Path(exists=False),
            help="ContextBundle JSON file",
        ),
        click.option("--instance-id", help=instance_id_help),
        click.option(
            "--completion-policy",
            type=click.Choice(["explicit", "auto"]),
            help="Completion policy",
        ),
        click.option(
            "--token-file",
            type=click.Path(exists=False),
            help="CapabilityToken JSON file",
        ),
    )

    def decorator(callback: _ClickCallback) -> _ClickCallback:
        return _apply_click_option_stack(callback, option_decorators)

    return decorator


# @shell_orchestration: Shared sandbox option wiring for run/spawn entry points
# @invar:allow shell_result: Returns Click decorator, not shell I/O Result
def with_run_spawn_sandbox_options() -> Callable[[_ClickCallback], _ClickCallback]:
    """Attach shared sandbox Click options to run/spawn command callbacks."""
    option_decorators = (
        click.option(
            "--sandbox/--no-sandbox",
            "sandbox",
            default=True,
            help="Enable sandbox for tool execution (default: enabled)",
        ),
        click.option(
            "--sandbox-allow",
            multiple=True,
            help="Sandbox capability (e.g., fs:/tmp, net). Repeatable. Implies --sandbox.",
        ),
    )

    def decorator(callback: _ClickCallback) -> _ClickCallback:
        return _apply_click_option_stack(callback, option_decorators)

    return decorator


# @shell_orchestration: CLI validation helper (no I/O)
# @invar:allow shell_result: Raises ValueError on invalid input
def validate_llm_base_url(value: str | None) -> None:
    """Validate ``--llm-base-url`` format.

    Spec source: verify-wire-params blocker #3 requires rejecting malformed
    ``llm_base_url`` values with a clear user-facing error.

    Args:
        value: LLM base URL from CLI, or ``None`` when omitted.

    Raises:
        ValueError: If URL is not absolute HTTP(S) URL.
    """
    if value is None:
        return

    parsed = urlparse(value)
    if parsed.scheme not in {"http", "https"} or not parsed.netloc:
        raise ValueError(
            f"Invalid --llm-base-url. Expected absolute http(s) URL, got: {value!r}"
        )


# @shell_orchestration: Shared memory provider option wiring for run/spawn entry points
# @invar:allow shell_result: Returns Click decorator, not shell I/O Result
def with_memory_provider_option(
    *,
    show_in_help: bool = True,
) -> Callable[[_ClickCallback], _ClickCallback]:
    """Attach memory provider Click option to command callbacks.

    Spec source: vectl step ``runtime-selection.wire-cli``
    requires adding --memory-provider option to run/spawn/chat commands.

    Note: 'mcp' is not yet supported - requires tool_caller architecture.
    Until supported, 'mcp' is explicitly excluded from CLI choices.
    """
    option_decorators = (
        click.option(
            "--memory-provider",
            type=click.Choice(["none", "local"]),
            help="Memory provider type (none=disabled, local=filesystem)",
            hidden=not show_in_help,
        ),
    )

    def decorator(callback: _ClickCallback) -> _ClickCallback:
        return _apply_click_option_stack(callback, option_decorators)

    return decorator


__all__ = [
    "validate_llm_base_url",
    "with_memory_provider_option",
    "with_run_spawn_agent_options",
    "with_run_spawn_sandbox_options",
]
