"""
Hybrid orchestrator for wave-based parallel execution coordination.

This module provides the HybridOrchestrator class that coordinates
wave-based execution using DependencyGraph for scheduling and WaveRunner
for parallel task execution.

Requirements: EXEC-04 (orchestrator coordination)
"""

from typing import List, Callable, Optional, Dict, Any, Awaitable
from dataclasses import dataclass, field

from gsd_rlm.execution.dependency import DependencyGraph
from gsd_rlm.execution.parallel import WaveRunner, TaskResult


@dataclass
class WaveResult:
    """Result of a wave execution.

    Attributes:
        wave_id: Sequential wave number (0-indexed)
        task_ids: List of task IDs in this wave
        results: Dictionary mapping task_id to result content
        errors: Dictionary mapping task_id to error message
        success: Whether all tasks in this wave succeeded
    """

    wave_id: int
    task_ids: List[str]
    results: Dict[str, Any] = field(default_factory=dict)
    errors: Dict[str, str] = field(default_factory=dict)
    success: bool = True


class HybridOrchestrator:
    """Thin coordinator for hybrid runtime wave-based execution.

    This orchestrator delegates to DependencyGraph for wave scheduling
    and WaveRunner for parallel task execution. It coordinates the overall
    execution flow while keeping the orchestration logic minimal.

    Key responsibilities:
    - Get waves from DependencyGraph
    - Execute each wave via WaveRunner
    - Call callbacks for progress/completion
    - Collect and return all wave results

    Example:
        ```python
        import anyio
        from gsd_rlm.execution.dependency import DependencyGraph
        from gsd_rlm.execution.parallel import WaveRunner
        from gsd_rlm.coordination.orchestrator import HybridOrchestrator

        async def main():
            # Build dependency graph
            graph = DependencyGraph()
            graph.add_task("A")
            graph.add_task("B")
            graph.add_task("C", depends_on=["A", "B"])

            # Create orchestrator
            runner = WaveRunner(max_concurrent=10)
            orchestrator = HybridOrchestrator(graph, runner)

            # Execute with custom executor
            async def execute_task(task_id: str) -> str:
                return f"Completed {task_id}"

            results = await orchestrator.execute(
                executor=execute_task,
                on_wave_complete=lambda wr: print(f"Wave {wr.wave_id} done")
            )

            print(f"Total waves: {len(results)}")

        anyio.run(main)
        ```
    """

    def __init__(
        self,
        dependency_graph: DependencyGraph,
        wave_runner: WaveRunner,
        max_concurrent: int = 10,
    ):
        """Initialize the hybrid orchestrator.

        Args:
            dependency_graph: DependencyGraph instance with tasks added
            wave_runner: WaveRunner instance for parallel execution
            max_concurrent: Maximum concurrent tasks (used if wave_runner needs update)
        """
        self.graph = dependency_graph
        self.runner = wave_runner
        self.max_concurrent = max_concurrent

    async def execute(
        self,
        executor: Callable[[str], Awaitable[Any]],
        on_wave_complete: Optional[Callable[[WaveResult], None]] = None,
        on_progress: Optional[Callable[[str, str, str], None]] = None,
    ) -> List[WaveResult]:
        """Execute all tasks in dependency-aware waves.

        Gets waves from the DependencyGraph and executes each wave in
        parallel using the WaveRunner. Waves execute sequentially (wave N+1
        waits for wave N to complete).

        Args:
            executor: Async function that executes a single task
            on_wave_complete: Optional callback invoked after each wave
            on_progress: Optional callback for progress updates

        Returns:
            List of WaveResult for all waves executed

        Note:
            If a wave has partial failures, execution continues to the next
            wave. Check WaveResult.success to identify failed waves.
        """
        # Get waves from dependency graph
        waves = self.graph.get_waves()
        wave_results: List[WaveResult] = []

        for wave_id, task_ids in enumerate(waves):
            # Execute wave in parallel
            task_results: List[TaskResult] = await self.runner.run_wave(
                tasks=task_ids,
                executor=executor,
                on_progress=on_progress,
            )

            # Build WaveResult from task results
            results: Dict[str, Any] = {}
            errors: Dict[str, str] = {}

            for tr in task_results:
                if tr.success:
                    results[tr.task_id] = tr.content
                else:
                    errors[tr.task_id] = tr.error or "Unknown error"

            wave_result = WaveResult(
                wave_id=wave_id,
                task_ids=task_ids,
                results=results,
                errors=errors,
                success=len(errors) == 0,
            )

            wave_results.append(wave_result)

            # Invoke callback if provided
            if on_wave_complete:
                on_wave_complete(wave_result)

            # Check if we should continue (graceful degradation)
            if not wave_result.success and not self._can_continue(wave_result):
                break

        return wave_results

    def _can_continue(self, wave_result: WaveResult) -> bool:
        """Check if execution should continue despite failures.

        Args:
            wave_result: Result of the current wave

        Returns:
            True if execution should continue to next wave
        """
        # Continue if at least one task succeeded (for critical path)
        return len(wave_result.results) > 0
