# flownarrator/io/csv_loader.py
from __future__ import annotations

import warnings
from pathlib import Path
from typing import Any

import pandas as pd

from ..errors import ColumnValidationError, CSVReadError, FileNotFoundErrorFN
from ..utils.validation import ensure_required_columns_exist


def load_csv(
    path: str | Path,
    *,
    columns: list[str] | None = None,
    dtype: dict[str, Any] | None = None,
    parse_dates: list[str] | None = None,
    sep: str = ",",
    encoding: str | None = "utf-8",
    na_values: list[str] | None = None,
    nrows: int | None = None,
    on_error: str = "raise",  # "raise" | "warn" | "ignore"
) -> pd.DataFrame:
    """Load a CSV into a DataFrame with optional column filtering and validation.

    Args:
        path: CSV path.
        columns: If provided, restrict output to these columns (and validate).
        dtype, parse_dates, sep, encoding, na_values, nrows: forwarded to pandas.read_csv.
        on_error: Behavior on errors: "raise" (default), "warn", "ignore".

    Raises:
        FileNotFoundErrorFN: when file is missing and on_error == "raise".
        CSVReadError: when pandas.read_csv fails and on_error == "raise".
        ColumnValidationError: when required columns are missing and on_error == "raise".
    """
    p = Path(path)

    if not p.exists():
        msg = f"File not found: {p}"
        if on_error == "raise":
            raise FileNotFoundErrorFN(msg)
        if on_error == "warn":
            warnings.warn(msg, stacklevel=2)
            return pd.DataFrame()
        return pd.DataFrame()

    try:
        df = pd.read_csv(
            p,
            sep=sep,
            dtype=dtype,
            parse_dates=parse_dates,
            encoding=encoding,
            na_values=na_values,
            nrows=nrows,
            low_memory=False,
        )
    except Exception as e:  # pragma: no cover
        msg = f"CSV read error: {e}"
        if on_error == "raise":
            raise CSVReadError(msg) from e
        if on_error == "warn":
            warnings.warn(msg, stacklevel=2)
            return pd.DataFrame()
        return pd.DataFrame()

    if columns is not None:
        missing = ensure_required_columns_exist(df.columns, columns)
        if missing:
            msg = f"Missing required columns in CSV: {missing}"
            if on_error == "raise":
                raise ColumnValidationError(msg)
            # warn or ignore: keep existing subset only
            warnings.warn(msg, stacklevel=2)
            keep = [c for c in columns if c in df.columns]
            df = df[keep]
        else:
            # exact selection in desired order
            df = df[columns]

    return df
