"""Coordinator orchestration loop for processing issue queue."""

import asyncio
import logging
import time
from typing import TYPE_CHECKING, Any

from mosaicstack_telemetry import (  # type: ignore[import-untyped]
    Complexity,
    Harness,
    Outcome,
    Provider,
    QualityGate,
    TaskType,
    TelemetryClient,
)

from src.circuit_breaker import CircuitBreaker, CircuitBreakerError
from src.context_monitor import ContextMonitor
from src.forced_continuation import ForcedContinuationService
from src.models import ContextAction
from src.mosaic_telemetry import build_task_event
from src.quality_orchestrator import QualityOrchestrator, VerificationResult
from src.queue import QueueItem, QueueManager
from src.tracing_decorators import trace_agent_operation

if TYPE_CHECKING:
    pass

logger = logging.getLogger(__name__)

# ---------------------------------------------------------------------------
# Agent-name → telemetry-field mapping helpers
# ---------------------------------------------------------------------------

# Maps assigned_agent strings to (model, Provider, Harness)
_AGENT_TELEMETRY_MAP: dict[str, tuple[str, Provider, Harness]] = {
    "sonnet": ("claude-sonnet-4-20250514", Provider.ANTHROPIC, Harness.CLAUDE_CODE),
    "opus": ("claude-opus-4-20250514", Provider.ANTHROPIC, Harness.CLAUDE_CODE),
    "haiku": ("claude-haiku-3.5-20241022", Provider.ANTHROPIC, Harness.CLAUDE_CODE),
    "glm": ("glm-4", Provider.CUSTOM, Harness.CUSTOM),
    "minimax": ("minimax", Provider.CUSTOM, Harness.CUSTOM),
}

_DIFFICULTY_TO_COMPLEXITY: dict[str, Complexity] = {
    "easy": Complexity.LOW,
    "medium": Complexity.MEDIUM,
    "hard": Complexity.HIGH,
}

_GATE_NAME_TO_ENUM: dict[str, QualityGate] = {
    "build": QualityGate.BUILD,
    "lint": QualityGate.LINT,
    "test": QualityGate.TEST,
    "coverage": QualityGate.COVERAGE,
}


def _resolve_agent_fields(
    assigned_agent: str,
) -> tuple[str, Provider, Harness]:
    """Resolve agent name to (model, provider, harness) for telemetry.

    Args:
        assigned_agent: The agent name string from issue metadata.

    Returns:
        Tuple of (model_name, Provider, Harness).
    """
    return _AGENT_TELEMETRY_MAP.get(
        assigned_agent,
        ("unknown", Provider.UNKNOWN, Harness.UNKNOWN),
    )


class Coordinator:
    """Main orchestration loop for processing the issue queue.

    The Coordinator is responsible for:
    - Monitoring the queue for ready items
    - Spawning agents to process issues (stub implementation for Phase 0)
    - Marking items as complete when processing finishes
    - Handling errors gracefully with circuit breaker protection
    - Supporting graceful shutdown

    Circuit Breaker (SEC-ORCH-7):
    - Tracks consecutive failures in the main loop
    - After failure_threshold consecutive failures, enters OPEN state
    - In OPEN state, backs off for cooldown_seconds before retrying
    - Prevents infinite retry loops on repeated failures
    """

    def __init__(
        self,
        queue_manager: QueueManager,
        poll_interval: float = 5.0,
        circuit_breaker_threshold: int = 5,
        circuit_breaker_cooldown: float = 30.0,
        telemetry_client: TelemetryClient | None = None,
        instance_id: str = "",
    ) -> None:
        """Initialize the Coordinator.

        Args:
            queue_manager: QueueManager instance for queue operations
            poll_interval: Seconds between queue polls (default: 5.0)
            circuit_breaker_threshold: Consecutive failures before opening circuit (default: 5)
            circuit_breaker_cooldown: Seconds to wait before retry after circuit opens (default: 30)
            telemetry_client: Optional Mosaic telemetry client for tracking task events
            instance_id: UUID identifying this coordinator instance for telemetry
        """
        self.queue_manager = queue_manager
        self.poll_interval = poll_interval
        self._running = False
        self._stop_event: asyncio.Event | None = None
        self._active_agents: dict[int, dict[str, Any]] = {}
        self._telemetry_client = telemetry_client
        self._instance_id = instance_id

        # Circuit breaker for preventing infinite retry loops (SEC-ORCH-7)
        self._circuit_breaker = CircuitBreaker(
            name="coordinator_loop",
            failure_threshold=circuit_breaker_threshold,
            cooldown_seconds=circuit_breaker_cooldown,
        )

    @property
    def is_running(self) -> bool:
        """Check if the coordinator is currently running.

        Returns:
            True if the orchestration loop is running
        """
        return self._running

    @property
    def active_agents(self) -> dict[int, dict[str, Any]]:
        """Get the dictionary of active agents.

        Returns:
            Dictionary mapping issue numbers to agent info
        """
        return self._active_agents

    def get_active_agent_count(self) -> int:
        """Get the count of currently active agents.

        Returns:
            Number of active agents
        """
        return len(self._active_agents)

    @property
    def circuit_breaker(self) -> CircuitBreaker:
        """Get the circuit breaker instance.

        Returns:
            CircuitBreaker instance for this coordinator
        """
        return self._circuit_breaker

    def get_circuit_breaker_stats(self) -> dict[str, Any]:
        """Get circuit breaker statistics.

        Returns:
            Dictionary with circuit breaker stats
        """
        return self._circuit_breaker.get_stats()

    async def start(self) -> None:
        """Start the orchestration loop.

        Continuously processes the queue until stop() is called.
        Uses circuit breaker to prevent infinite retry loops on repeated failures.
        """
        self._running = True
        self._stop_event = asyncio.Event()
        logger.info("Coordinator started - beginning orchestration loop")

        try:
            while self._running:
                # Check circuit breaker state before processing
                if not self._circuit_breaker.can_execute():
                    # Circuit is open - wait for cooldown
                    wait_time = self._circuit_breaker.time_until_retry
                    logger.warning(
                        f"Circuit breaker OPEN - backing off for {wait_time:.1f}s "
                        f"(failures: {self._circuit_breaker.failure_count})"
                    )
                    await self._wait_for_cooldown_or_stop(wait_time)
                    continue

                try:
                    await self.process_queue()
                    # Successful processing - record success
                    self._circuit_breaker.record_success()
                except CircuitBreakerError as e:
                    # Circuit breaker blocked the request
                    logger.warning(f"Circuit breaker blocked request: {e}")
                except Exception as e:
                    # Record failure in circuit breaker
                    self._circuit_breaker.record_failure()
                    logger.error(
                        f"Error in process_queue: {e} "
                        f"(circuit breaker: {self._circuit_breaker.state.value}, "
                        f"failures: {self._circuit_breaker.failure_count}"
                        f"/{self._circuit_breaker.failure_threshold})"
                    )

                # Wait for poll interval or stop signal
                try:
                    await asyncio.wait_for(
                        self._stop_event.wait(),
                        timeout=self.poll_interval,
                    )
                    # If we reach here, stop was requested
                    break
                except TimeoutError:
                    # Normal timeout, continue polling
                    pass

        finally:
            self._running = False
            logger.info(
                f"Coordinator stopped "
                f"(circuit breaker stats: {self._circuit_breaker.get_stats()})"
            )

    async def _wait_for_cooldown_or_stop(self, cooldown: float) -> None:
        """Wait for cooldown period or stop signal, whichever comes first.

        Args:
            cooldown: Seconds to wait for cooldown
        """
        if self._stop_event is None:
            return

        try:
            await asyncio.wait_for(self._stop_event.wait(), timeout=cooldown)
            # Stop was requested during cooldown
        except TimeoutError:
            # Cooldown completed, continue
            pass

    async def stop(self) -> None:
        """Stop the orchestration loop gracefully.

        Signals the loop to stop and waits for current processing to complete.
        This method is idempotent - can be called multiple times safely.
        """
        logger.info("Coordinator stop requested")
        self._running = False
        if self._stop_event is not None:
            self._stop_event.set()

    @trace_agent_operation(operation_name="process_queue")
    async def process_queue(self) -> QueueItem | None:
        """Process the next ready item from the queue.

        Gets the next ready item, spawns an agent to process it,
        and marks it complete on success.  Emits a Mosaic telemetry
        TaskCompletionEvent after each task attempt.

        Returns:
            The QueueItem that was processed, or None if queue is empty
        """
        # Get next ready item
        item = self.queue_manager.get_next_ready()

        if item is None:
            logger.debug("No items in queue to process")
            return None

        logger.info(
            f"Processing issue #{item.issue_number} "
            f"(agent: {item.metadata.assigned_agent}, "
            f"difficulty: {item.metadata.difficulty})"
        )

        # Mark as in progress
        self.queue_manager.mark_in_progress(item.issue_number)

        # Track timing for telemetry
        start_mono = time.monotonic()
        outcome = Outcome.FAILURE

        # Spawn agent (stub implementation)
        try:
            success = await self.spawn_agent(item)

            if success:
                # Mark as complete
                self.queue_manager.mark_complete(item.issue_number)
                outcome = Outcome.SUCCESS
                logger.info(f"Issue #{item.issue_number} completed successfully")
            else:
                logger.warning(f"Issue #{item.issue_number} agent failed - remains in progress")

        except Exception as e:
            logger.error(f"Error spawning agent for issue #{item.issue_number}: {e}")
            # Item remains in progress on error

        finally:
            elapsed_ms = int((time.monotonic() - start_mono) * 1000)
            self._emit_task_telemetry(item, outcome=outcome, duration_ms=elapsed_ms)

        return item

    def _emit_task_telemetry(
        self,
        item: QueueItem,
        *,
        outcome: Outcome,
        duration_ms: int,
        retry_count: int = 0,
        actual_input_tokens: int = 0,
        actual_output_tokens: int = 0,
        quality_passed: bool = False,
        quality_gates_run: list[QualityGate] | None = None,
        quality_gates_failed: list[QualityGate] | None = None,
    ) -> None:
        """Emit a Mosaic telemetry TaskCompletionEvent (non-blocking).

        This method never raises; any telemetry errors are logged and swallowed
        so they do not interfere with task processing.

        Args:
            item: The QueueItem that was processed.
            outcome: Task outcome (SUCCESS, FAILURE, TIMEOUT, etc.).
            duration_ms: Wall-clock duration in milliseconds.
            retry_count: Number of retries before this attempt.
            actual_input_tokens: Actual input tokens consumed by the harness.
            actual_output_tokens: Actual output tokens consumed by the harness.
            quality_passed: Whether all quality gates passed.
            quality_gates_run: Quality gates that were executed.
            quality_gates_failed: Quality gates that failed.
        """
        if self._telemetry_client is None or not self._instance_id:
            return

        try:
            model, provider, harness = _resolve_agent_fields(
                item.metadata.assigned_agent,
            )
            complexity = _DIFFICULTY_TO_COMPLEXITY.get(
                item.metadata.difficulty, Complexity.MEDIUM
            )

            event = build_task_event(
                instance_id=self._instance_id,
                task_type=TaskType.IMPLEMENTATION,
                complexity=complexity,
                outcome=outcome,
                duration_ms=duration_ms,
                model=model,
                provider=provider,
                harness=harness,
                actual_input_tokens=actual_input_tokens,
                actual_output_tokens=actual_output_tokens,
                estimated_input_tokens=item.metadata.estimated_context,
                quality_passed=quality_passed,
                quality_gates_run=quality_gates_run,
                quality_gates_failed=quality_gates_failed,
                retry_count=retry_count,
            )
            self._telemetry_client.track(event)
            logger.debug(
                "Telemetry event emitted for issue #%d (outcome=%s)",
                item.issue_number,
                outcome.value,
            )
        except Exception:
            logger.exception(
                "Failed to emit telemetry for issue #%d (non-fatal)",
                item.issue_number,
            )

    @trace_agent_operation(operation_name="spawn_agent")
    async def spawn_agent(self, item: QueueItem) -> bool:
        """Spawn an agent to process the given item.

        This is a stub implementation for Phase 0 that always succeeds.
        Future phases will implement actual agent spawning.

        Args:
            item: QueueItem containing issue details

        Returns:
            True if agent completed successfully, False otherwise
        """
        logger.info(
            f"[STUB] Spawning {item.metadata.assigned_agent} agent "
            f"for issue #{item.issue_number} "
            f"(estimated context: {item.metadata.estimated_context} tokens)"
        )

        # Track the agent
        self._active_agents[item.issue_number] = {
            "agent_type": item.metadata.assigned_agent,
            "issue_number": item.issue_number,
            "status": "running",
        }

        # Stub implementation: always succeed
        # In future phases, this will actually spawn a Claude agent process
        logger.info(f"[STUB] Agent completed for issue #{item.issue_number}")

        return True


class OrchestrationLoop:
    """Advanced orchestration loop integrating all coordinator components.

    The OrchestrationLoop coordinates:
    - Issue queue processing with priority sorting
    - Agent assignment using 50% rule
    - Quality gate verification on completion claims
    - Rejection handling with forced continuation prompts
    - Context monitoring during agent execution

    Circuit Breaker (SEC-ORCH-7):
    - Tracks consecutive failures in the main loop
    - After failure_threshold consecutive failures, enters OPEN state
    - In OPEN state, backs off for cooldown_seconds before retrying
    - Prevents infinite retry loops on repeated failures
    """

    def __init__(
        self,
        queue_manager: QueueManager,
        quality_orchestrator: QualityOrchestrator,
        continuation_service: ForcedContinuationService,
        context_monitor: ContextMonitor,
        poll_interval: float = 5.0,
        circuit_breaker_threshold: int = 5,
        circuit_breaker_cooldown: float = 30.0,
        telemetry_client: TelemetryClient | None = None,
        instance_id: str = "",
    ) -> None:
        """Initialize the OrchestrationLoop.

        Args:
            queue_manager: QueueManager instance for queue operations
            quality_orchestrator: QualityOrchestrator for running quality gates
            continuation_service: ForcedContinuationService for rejection prompts
            context_monitor: ContextMonitor for tracking agent context usage
            poll_interval: Seconds between queue polls (default: 5.0)
            circuit_breaker_threshold: Consecutive failures before opening circuit (default: 5)
            circuit_breaker_cooldown: Seconds to wait before retry after circuit opens (default: 30)
            telemetry_client: Optional Mosaic telemetry client for tracking task events
            instance_id: UUID identifying this coordinator instance for telemetry
        """
        self.queue_manager = queue_manager
        self.quality_orchestrator = quality_orchestrator
        self.continuation_service = continuation_service
        self.context_monitor = context_monitor
        self.poll_interval = poll_interval
        self._running = False
        self._stop_event: asyncio.Event | None = None
        self._active_agents: dict[int, dict[str, Any]] = {}
        self._telemetry_client = telemetry_client
        self._instance_id = instance_id

        # Per-issue retry tracking
        self._retry_counts: dict[int, int] = {}

        # Metrics tracking
        self._processed_count = 0
        self._success_count = 0
        self._rejection_count = 0

        # Circuit breaker for preventing infinite retry loops (SEC-ORCH-7)
        self._circuit_breaker = CircuitBreaker(
            name="orchestration_loop",
            failure_threshold=circuit_breaker_threshold,
            cooldown_seconds=circuit_breaker_cooldown,
        )

    @property
    def is_running(self) -> bool:
        """Check if the orchestration loop is currently running.

        Returns:
            True if the orchestration loop is running
        """
        return self._running

    @property
    def active_agents(self) -> dict[int, dict[str, Any]]:
        """Get the dictionary of active agents.

        Returns:
            Dictionary mapping issue numbers to agent info
        """
        return self._active_agents

    @property
    def processed_count(self) -> int:
        """Get total number of processed issues.

        Returns:
            Number of issues processed
        """
        return self._processed_count

    @property
    def success_count(self) -> int:
        """Get number of successfully completed issues.

        Returns:
            Number of issues that passed quality gates
        """
        return self._success_count

    @property
    def rejection_count(self) -> int:
        """Get number of rejected issues (failed quality gates).

        Returns:
            Number of issues that failed quality gates
        """
        return self._rejection_count

    def get_active_agent_count(self) -> int:
        """Get the count of currently active agents.

        Returns:
            Number of active agents
        """
        return len(self._active_agents)

    @property
    def circuit_breaker(self) -> CircuitBreaker:
        """Get the circuit breaker instance.

        Returns:
            CircuitBreaker instance for this orchestration loop
        """
        return self._circuit_breaker

    def get_circuit_breaker_stats(self) -> dict[str, Any]:
        """Get circuit breaker statistics.

        Returns:
            Dictionary with circuit breaker stats
        """
        return self._circuit_breaker.get_stats()

    async def start(self) -> None:
        """Start the orchestration loop.

        Continuously processes the queue until stop() is called.
        Uses circuit breaker to prevent infinite retry loops on repeated failures.
        """
        self._running = True
        self._stop_event = asyncio.Event()
        logger.info("OrchestrationLoop started - beginning orchestration")

        try:
            while self._running:
                # Check circuit breaker state before processing
                if not self._circuit_breaker.can_execute():
                    # Circuit is open - wait for cooldown
                    wait_time = self._circuit_breaker.time_until_retry
                    logger.warning(
                        f"Circuit breaker OPEN - backing off for {wait_time:.1f}s "
                        f"(failures: {self._circuit_breaker.failure_count})"
                    )
                    await self._wait_for_cooldown_or_stop(wait_time)
                    continue

                try:
                    await self.process_next_issue()
                    # Successful processing - record success
                    self._circuit_breaker.record_success()
                except CircuitBreakerError as e:
                    # Circuit breaker blocked the request
                    logger.warning(f"Circuit breaker blocked request: {e}")
                except Exception as e:
                    # Record failure in circuit breaker
                    self._circuit_breaker.record_failure()
                    logger.error(
                        f"Error in process_next_issue: {e} "
                        f"(circuit breaker: {self._circuit_breaker.state.value}, "
                        f"failures: {self._circuit_breaker.failure_count}"
                        f"/{self._circuit_breaker.failure_threshold})"
                    )

                # Wait for poll interval or stop signal
                try:
                    await asyncio.wait_for(
                        self._stop_event.wait(),
                        timeout=self.poll_interval,
                    )
                    # If we reach here, stop was requested
                    break
                except TimeoutError:
                    # Normal timeout, continue polling
                    pass

        finally:
            self._running = False
            logger.info(
                f"OrchestrationLoop stopped "
                f"(circuit breaker stats: {self._circuit_breaker.get_stats()})"
            )

    async def _wait_for_cooldown_or_stop(self, cooldown: float) -> None:
        """Wait for cooldown period or stop signal, whichever comes first.

        Args:
            cooldown: Seconds to wait for cooldown
        """
        if self._stop_event is None:
            return

        try:
            await asyncio.wait_for(self._stop_event.wait(), timeout=cooldown)
            # Stop was requested during cooldown
        except TimeoutError:
            # Cooldown completed, continue
            pass

    async def stop(self) -> None:
        """Stop the orchestration loop gracefully.

        Signals the loop to stop and waits for current processing to complete.
        This method is idempotent - can be called multiple times safely.
        """
        logger.info("OrchestrationLoop stop requested")
        self._running = False
        if self._stop_event is not None:
            self._stop_event.set()

    @trace_agent_operation(operation_name="process_next_issue")
    async def process_next_issue(self) -> QueueItem | None:
        """Process the next ready issue from the queue.

        This method:
        1. Gets the next ready item (priority sorted)
        2. Marks it as in progress
        3. Spawns an agent to process it
        4. Runs quality gates on completion
        5. Handles rejection with forced continuation or marks complete
        6. Emits a Mosaic telemetry TaskCompletionEvent

        Returns:
            The QueueItem that was processed, or None if queue is empty
        """
        # Get next ready item
        item = self.queue_manager.get_next_ready()

        if item is None:
            logger.debug("No items in queue to process")
            return None

        logger.info(
            f"Processing issue #{item.issue_number} "
            f"(agent: {item.metadata.assigned_agent}, "
            f"difficulty: {item.metadata.difficulty}, "
            f"context: {item.metadata.estimated_context} tokens)"
        )

        # Mark as in progress
        self.queue_manager.mark_in_progress(item.issue_number)
        self._processed_count += 1

        # Track the agent
        agent_id = f"agent-{item.issue_number}"
        self._active_agents[item.issue_number] = {
            "agent_type": item.metadata.assigned_agent,
            "issue_number": item.issue_number,
            "agent_id": agent_id,
            "status": "running",
        }

        # Track timing for telemetry
        start_mono = time.monotonic()
        outcome = Outcome.FAILURE
        quality_passed = False
        gates_run: list[QualityGate] = []
        gates_failed: list[QualityGate] = []
        retry_count = self._retry_counts.get(item.issue_number, 0)

        try:
            # Spawn agent (stub implementation)
            agent_success = await self._spawn_agent(item)

            if not agent_success:
                logger.warning(f"Issue #{item.issue_number} agent failed - remains in progress")
                self._retry_counts[item.issue_number] = retry_count + 1
                return item

            # Check context usage (stub - no real monitoring in Phase 0)
            await self._check_context(agent_id)

            # Run quality gates on completion
            verification = await self._verify_quality(item)

            # Map gate results for telemetry
            gates_run = [
                _GATE_NAME_TO_ENUM[name]
                for name in verification.gate_results
                if name in _GATE_NAME_TO_ENUM
            ]
            gates_failed = [
                _GATE_NAME_TO_ENUM[name]
                for name, result in verification.gate_results.items()
                if name in _GATE_NAME_TO_ENUM and not result.passed
            ]
            quality_passed = verification.all_passed

            if verification.all_passed:
                # All gates passed - mark as complete
                self.queue_manager.mark_complete(item.issue_number)
                self._success_count += 1
                outcome = Outcome.SUCCESS
                # Clear retry counter on success
                self._retry_counts.pop(item.issue_number, None)
                logger.info(
                    f"Issue #{item.issue_number} completed successfully - all gates passed"
                )
            else:
                # Gates failed - generate continuation prompt
                self._rejection_count += 1
                outcome = Outcome.FAILURE
                self._retry_counts[item.issue_number] = retry_count + 1
                await self._handle_rejection(item, verification)

        except Exception as e:
            logger.error(f"Error processing issue #{item.issue_number}: {e}")
            # Item remains in progress on error

        finally:
            elapsed_ms = int((time.monotonic() - start_mono) * 1000)
            self._emit_task_telemetry(
                item,
                outcome=outcome,
                duration_ms=elapsed_ms,
                retry_count=retry_count,
                quality_passed=quality_passed,
                quality_gates_run=gates_run,
                quality_gates_failed=gates_failed,
            )

        return item

    def _emit_task_telemetry(
        self,
        item: QueueItem,
        *,
        outcome: Outcome,
        duration_ms: int,
        retry_count: int = 0,
        actual_input_tokens: int = 0,
        actual_output_tokens: int = 0,
        quality_passed: bool = False,
        quality_gates_run: list[QualityGate] | None = None,
        quality_gates_failed: list[QualityGate] | None = None,
    ) -> None:
        """Emit a Mosaic telemetry TaskCompletionEvent (non-blocking).

        This method never raises; any telemetry errors are logged and swallowed
        so they do not interfere with task processing.

        Args:
            item: The QueueItem that was processed.
            outcome: Task outcome (SUCCESS, FAILURE, TIMEOUT, etc.).
            duration_ms: Wall-clock duration in milliseconds.
            retry_count: Number of retries before this attempt.
            actual_input_tokens: Actual input tokens consumed by the harness.
            actual_output_tokens: Actual output tokens consumed by the harness.
            quality_passed: Whether all quality gates passed.
            quality_gates_run: Quality gates that were executed.
            quality_gates_failed: Quality gates that failed.
        """
        if self._telemetry_client is None or not self._instance_id:
            return

        try:
            model, provider, harness = _resolve_agent_fields(
                item.metadata.assigned_agent,
            )
            complexity = _DIFFICULTY_TO_COMPLEXITY.get(
                item.metadata.difficulty, Complexity.MEDIUM
            )

            event = build_task_event(
                instance_id=self._instance_id,
                task_type=TaskType.IMPLEMENTATION,
                complexity=complexity,
                outcome=outcome,
                duration_ms=duration_ms,
                model=model,
                provider=provider,
                harness=harness,
                actual_input_tokens=actual_input_tokens,
                actual_output_tokens=actual_output_tokens,
                estimated_input_tokens=item.metadata.estimated_context,
                quality_passed=quality_passed,
                quality_gates_run=quality_gates_run,
                quality_gates_failed=quality_gates_failed,
                retry_count=retry_count,
            )
            self._telemetry_client.track(event)
            logger.debug(
                "Telemetry event emitted for issue #%d (outcome=%s)",
                item.issue_number,
                outcome.value,
            )
        except Exception:
            logger.exception(
                "Failed to emit telemetry for issue #%d (non-fatal)",
                item.issue_number,
            )

    async def _spawn_agent(self, item: QueueItem) -> bool:
        """Spawn an agent to process the given item.

        This is a stub implementation for Phase 0 that always succeeds.
        Future phases will implement actual agent spawning.

        Args:
            item: QueueItem containing issue details

        Returns:
            True if agent completed successfully, False otherwise
        """
        logger.info(
            f"[STUB] Spawning {item.metadata.assigned_agent} agent "
            f"for issue #{item.issue_number} "
            f"(estimated context: {item.metadata.estimated_context} tokens)"
        )

        # Stub implementation: always succeed
        logger.info(f"[STUB] Agent completed for issue #{item.issue_number}")

        return True

    async def _check_context(self, agent_id: str) -> ContextAction:
        """Check context usage and determine action.

        Args:
            agent_id: Unique identifier for the agent

        Returns:
            ContextAction based on usage thresholds
        """
        try:
            action = await self.context_monitor.determine_action(agent_id)

            if action == ContextAction.COMPACT:
                logger.info(f"Agent {agent_id}: Context at 80%, compaction recommended")
            elif action == ContextAction.ROTATE_SESSION:
                logger.warning(f"Agent {agent_id}: Context at 95%, session rotation needed")

            return action
        except Exception as e:
            logger.error(f"Error checking context for {agent_id}: {e}")
            return ContextAction.CONTINUE

    async def _verify_quality(self, item: QueueItem) -> VerificationResult:
        """Run quality gates to verify completion.

        Args:
            item: QueueItem that claims completion

        Returns:
            VerificationResult from quality orchestrator
        """
        logger.info(f"Running quality gates for issue #{item.issue_number}")

        try:
            result = await self.quality_orchestrator.verify_completion()

            if result.all_passed:
                logger.info(f"Issue #{item.issue_number}: All quality gates passed")
            else:
                failed_gates = [
                    name for name, r in result.gate_results.items() if not r.passed
                ]
                logger.warning(
                    f"Issue #{item.issue_number}: Quality gates failed: {failed_gates}"
                )

            return result

        except Exception as e:
            logger.error(f"Error verifying quality for issue #{item.issue_number}: {e}")
            # Return a failure result on error
            from src.gates.quality_gate import GateResult
            return VerificationResult(
                all_passed=False,
                gate_results={
                    "error": GateResult(
                        passed=False,
                        message=f"Quality verification error: {e}",
                        details={"error": str(e)},
                    )
                },
            )

    async def _handle_rejection(
        self, item: QueueItem, verification: VerificationResult
    ) -> None:
        """Handle quality gate rejection by generating continuation prompt.

        Args:
            item: QueueItem that failed quality gates
            verification: VerificationResult with failure details
        """
        logger.info(f"Generating forced continuation for issue #{item.issue_number}")

        try:
            prompt = self.continuation_service.generate_prompt(verification)
            logger.info(
                f"Issue #{item.issue_number}: Forced continuation generated "
                f"({len(prompt)} chars)"
            )

            # Update agent status
            if item.issue_number in self._active_agents:
                self._active_agents[item.issue_number]["status"] = "needs_continuation"
                self._active_agents[item.issue_number]["continuation_prompt"] = prompt

        except Exception as e:
            logger.error(
                f"Error generating continuation for issue #{item.issue_number}: {e}"
            )
