"""Wave scheduler for the Agent pipeline."""

from concurrent.futures import ThreadPoolExecutor, as_completed
from dataclasses import dataclass, field
from typing import Callable, Dict, Iterable, List, Optional

from backend.agents.base import AgentResult, AgentScope, ScopedAgent
from backend.agents.memory import ProjectMemory


@dataclass
class AgentStats:
    """Job-local accounting; never shared across concurrent jobs."""

    calls: int = 0
    scopes: int = 0
    merges: int = 0
    failed_scopes: List[str] = field(default_factory=list)

    def as_dict(self) -> Dict:
        return {
            "calls": self.calls,
            "scopes": self.scopes,
            "merges": self.merges,
            "failed_scopes": list(self.failed_scopes),
        }


class Orchestrator:
    """Coordinates bounded fan-out/fan-in waves against one ProjectMemory."""

    def __init__(
        self,
        memory: ProjectMemory,
        on_stage: Optional[Callable[[str], None]] = None,
    ) -> None:
        self.memory = memory
        self.on_stage = on_stage
        self.stats = AgentStats()

    def stage(self, name: str) -> None:
        print(f"\n=== {name} ===")
        if self.on_stage:
            self.on_stage(name)

    def initialize(self) -> Dict:
        """Initialize the job-local artifact store."""
        self.stage("Initialize agent pipeline")
        self.memory.dump()
        return self.stats.as_dict()

    def run_scopes(
        self,
        agent: ScopedAgent,
        scopes: Iterable[AgentScope],
        concurrency: int,
    ) -> List[AgentResult]:
        """Run independent scopes; one failure never aborts the wave."""
        scope_list = list(scopes)
        if not scope_list:
            return []
        results: List[AgentResult] = []
        with ThreadPoolExecutor(max_workers=max(1, concurrency)) as pool:
            futures = {pool.submit(agent.run, scope): scope for scope in scope_list}
            for future in as_completed(futures):
                scope = futures[future]
                self.stats.scopes += 1
                try:
                    result = future.result()
                except Exception as exc:
                    result = AgentResult(scope_id=scope.scope_id, error=str(exc))
                if result.error:
                    self.stats.failed_scopes.append(
                        f"{agent.name}:{scope.scope_id}: {result.error}"
                    )
                results.append(result)
        results.sort(key=lambda result: result.scope_id)
        return results
