"""Parallel prompt execution utilities.

This module provides a minimal reusable executor that preserves input order.
"""

from __future__ import annotations

from concurrent.futures import ThreadPoolExecutor, as_completed
from dataclasses import dataclass
from typing import Any, Callable, Generic, Iterable, List, Optional, TypeVar


T = TypeVar("T")
R = TypeVar("R")


@dataclass
class WorkerResult(Generic[R]):
    """Result model used to preserve input ordering and capture failures."""

    index: int
    value: Optional[R] = None
    error: Optional[Exception] = None


def execute_in_parallel(
    items: Iterable[T],
    worker: Callable[[T, int], R],
    max_workers: int,
) -> List[WorkerResult[R]]:
    """Execute worker function in parallel while preserving input order.

    `worker` receives `(item, index)` and returns a value.
    """
    indexed_items = list(enumerate(items))
    if not indexed_items:
        return []

    normalized_workers = max(1, min(max_workers, len(indexed_items)))
    results: List[WorkerResult[R]] = [WorkerResult(index=i) for i, _ in indexed_items]

    with ThreadPoolExecutor(max_workers=normalized_workers) as executor:
        future_map = {
            executor.submit(worker, item, index): index
            for index, item in indexed_items
        }

        for future in as_completed(future_map):
            index = future_map[future]
            try:
                results[index] = WorkerResult(index=index, value=future.result())
            except (KeyboardInterrupt, SystemExit):
                raise
            except Exception as exc:
                results[index] = WorkerResult(index=index, error=exc)

    return results
