from typing import Set
from api.task import Task, Status
from api.execution_context import ExecutionContext


class Scheduler:
    """
    DAG-based execution scheduler.
    Decides WHEN a Task can run based on port readiness and orchestrates execution.
    """

    def __init__(self):
        self._completed: Set[Task] = set()
        self._failed: Set[Task] = set()

    def run(self, root: Task, context: ExecutionContext):
        self._execute_task(root, context)

    def _execute_task(self, task: Task, context: ExecutionContext):
        if task.status in (Status.COMPLETED, Status.FAILED, Status.STOPPED):
            return

        if hasattr(task, "children"):
            self._run_workflow(task, context)
            return

        if not task.check_inputs_ready(context):
            print(f"[Scheduler] Task '{task.name}' not ready, skipping for now.")
            return

        try:
            task._set_status(Status.RUNNING)
            task.run(context)
            task._set_status(Status.COMPLETED)
            self._completed.add(task)
        except Exception as e:
            task._set_status(Status.FAILED)
            self._failed.add(task)
            print(f"[Scheduler] Task '{task.name}' failed: {e}")
            raise

    def _run_workflow(self, workflow: Task, context: ExecutionContext):
        workflow._set_status(Status.RUNNING)

        remaining = set(workflow.children)

        while remaining:
            progress_made = False

            for child in list(remaining):
                if child.check_inputs_ready(context):
                    self._execute_task(child, context)
                    remaining.remove(child)
                    progress_made = True

            if not progress_made:
                raise RuntimeError(
                    f"Workflow '{workflow.name}' deadlock: unresolved dependencies"
                )

        if any(c.status == Status.FAILED for c in workflow.children):
            workflow._set_status(Status.FAILED)
        else:
            workflow._set_status(Status.COMPLETED)
