"""Base classes for workflow step types.

Provides:
- ``StepBase`` — abstract base every step type must implement.
- ``StepContext`` — execution context passed to each step.
- ``StepResult`` — return value from step execution.
"""

from __future__ import annotations

from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from enum import Enum
from typing import Any


class StepStatus(str, Enum):
    """Status of a step execution."""

    PENDING = "pending"
    RUNNING = "running"
    COMPLETED = "completed"
    FAILED = "failed"
    SKIPPED = "skipped"
    PAUSED = "paused"


class RunStatus(str, Enum):
    """Status of a workflow run."""

    CREATED = "created"
    RUNNING = "running"
    PAUSED = "paused"
    COMPLETED = "completed"
    FAILED = "failed"
    ABORTED = "aborted"


@dataclass
class StepContext:
    """Execution context passed to each step.

    Contains everything the step needs to resolve expressions, dispatch
    commands, and record results.
    """

    #: Resolved workflow inputs (from user prompts / defaults).
    inputs: dict[str, Any] = field(default_factory=dict)

    #: Accumulated step results keyed by step ID. Each entry is the dict the
    #: engine persists per step:
    #: ``{"type": ..., "integration": ..., "model": ..., "options": ...,
    #:   "input": ..., "output": ..., "status": ...}``.
    steps: dict[str, dict[str, Any]] = field(default_factory=dict)

    #: Current fan-out item (set only inside fan-out iterations).
    item: Any = None

    #: Fan-in aggregated results (set only for fan-in steps).
    fan_in: dict[str, Any] = field(default_factory=dict)

    #: Workflow-level default integration key.
    default_integration: str | None = None

    #: Workflow-level default model.
    default_model: str | None = None

    #: Workflow-level default options.
    default_options: dict[str, Any] = field(default_factory=dict)

    #: Project root path.
    project_root: str | None = None

    #: Current run ID.
    run_id: str | None = None


@dataclass
class StepResult:
    """Return value from a step execution."""

    #: Step status.
    status: StepStatus = StepStatus.COMPLETED

    #: Output data (stored as ``steps.<id>.output``).
    output: dict[str, Any] = field(default_factory=dict)

    #: Nested steps to execute (for control-flow steps like if/then).
    next_steps: list[dict[str, Any]] = field(default_factory=list)

    #: Error message if step failed.
    error: str | None = None


class StepBase(ABC):
    """Abstract base class for workflow step types.

    Every step type — built-in or extension-provided — implements this
    interface and registers in ``STEP_REGISTRY``.

    Thread-safety: ``STEP_REGISTRY`` holds a single shared instance per type, so
    a concurrent ``fan-out`` (``max_concurrency > 1``) can invoke ``execute`` on
    the same instance from several threads at once. Implementations must be
    stateless / thread-safe — derive all per-run state from the ``config`` and
    ``context`` arguments and never mutate ``self`` in ``execute``. The built-in
    steps follow this rule.
    """

    #: Matches the ``type:`` value in workflow YAML.
    type_key: str = ""

    @abstractmethod
    def execute(self, config: dict[str, Any], context: StepContext) -> StepResult:
        """Execute the step with the given config and context.

        Parameters
        ----------
        config:
            The step configuration from workflow YAML.
        context:
            The execution context with inputs, accumulated step results, etc.

        Returns
        -------
        StepResult with status, output data, and optional nested steps.
        """

    def validate(self, config: dict[str, Any]) -> list[str]:
        """Validate step configuration and return a list of error messages.

        An empty list means the configuration is valid.
        """
        errors: list[str] = []
        if "id" not in config:
            errors.append("Step is missing required 'id' field.")
        return errors

    def can_resume(self, state: dict[str, Any]) -> bool:
        """Return whether this step can be resumed from the given state."""
        return True
