"""Exec command implementation."""

from __future__ import annotations

from dataclasses import dataclass
from typing import TYPE_CHECKING

from pymelos.commands.base import Command, CommandContext
from pymelos.execution import BatchResult, ParallelExecutor

if TYPE_CHECKING:
    from pymelos.workspace import Package
    from pymelos.workspace.workspace import Workspace


@dataclass
class ExecOptions:
    """Options for exec command."""

    command: str
    scope: str | None = None
    since: str | None = None
    ignore: list[str] | None = None
    concurrency: int = 4
    fail_fast: bool = False
    topological: bool = False  # exec doesn't default to topological
    include_dependents: bool = False


class ExecCommand(Command[BatchResult]):
    """Execute an arbitrary command across packages.

    Unlike 'run', exec takes a direct command string rather
    than a script name from configuration.
    """

    def __init__(self, context: CommandContext, options: ExecOptions) -> None:
        super().__init__(context)
        self.options = options

    def get_packages(self) -> list[Package]:
        """Get packages to execute command in."""
        from pymelos.filters import apply_filters_with_since

        packages = list(self.workspace.packages.values())

        return apply_filters_with_since(
            packages,
            self.workspace,
            scope=self.options.scope,
            since=self.options.since,
            ignore=self.options.ignore,
            include_dependents=self.options.include_dependents,
        )

    async def execute(self) -> BatchResult:
        """Execute the command."""
        packages = self.get_packages()
        if not packages:
            return BatchResult(results=[])

        # Build environment
        env = dict(self.context.env)
        env.update(self.workspace.config.env)

        executor = ParallelExecutor(
            concurrency=self.options.concurrency,
            fail_fast=self.options.fail_fast,
        )

        if self.options.topological:
            batches = self.workspace.parallel_batches(packages)
            return await executor.execute_batches(batches, self.options.command, env=env)
        else:
            return await executor.execute(packages, self.options.command, env=env)


async def exec_command(
    workspace: Workspace,
    command: str,
    *,
    scope: str | None = None,
    since: str | None = None,
    ignore: list[str] | None = None,
    concurrency: int = 4,
    fail_fast: bool = False,
    topological: bool = False,
) -> BatchResult:
    """Convenience function to execute a command.

    Args:
        workspace: Workspace to run in.
        command: Command to execute.
        scope: Package scope filter.
        since: Git reference.
        ignore: Patterns to exclude.
        concurrency: Parallel jobs.
        fail_fast: Stop on first failure.
        topological: Respect dependency order.

    Returns:
        Batch result.
    """

    context = CommandContext(workspace=workspace)
    options = ExecOptions(
        command=command,
        scope=scope,
        since=since,
        ignore=ignore,
        concurrency=concurrency,
        fail_fast=fail_fast,
        topological=topological,
    )
    cmd = ExecCommand(context, options)
    return await cmd.execute()
