# scripts/helpers/utils.py
import os
import numpy as np
import random
from typing import Callable, Union

# ---------------------------------------------
# Noise utilities
# ---------------------------------------------

NoiseType = Union[float, tuple[float, float], Callable[[], float], None]


def apply_noise(base_value: float, noise: NoiseType) -> float:
    """
    Apply different types of noise to a numeric base value.

    Parameters
    ----------
    base_value : float
        The original value.
    noise : float | tuple(mean, std) | callable | None
        Defines the noise model:
        - float → Gaussian noise with std = noise
        - tuple(mean, std) → Gaussian with explicit mean and std
        - callable → function returning random noise value
        - None → no noise

    Returns
    -------
    float
        The base_value with applied noise.
    """
    if noise is None:
        return base_value
    if isinstance(noise, (int, float)):
        return base_value + np.random.normal(0, noise)
    if isinstance(noise, tuple):
        mean, std = noise
        return base_value + np.random.normal(mean, std)
    if callable(noise):
        return base_value + noise()
    raise ValueError(f"Unsupported noise type: {type(noise)}")


# ---------------------------------------------
# Reproducibility utilities
# ---------------------------------------------

def set_global_seed(seed: int = 42) -> None:
    """
    Set global random seed for reproducibility across numpy and random.
    """
    np.random.seed(seed)
    random.seed(seed)


# ---------------------------------------------
# File utilities
# ---------------------------------------------

def save_to_csv(df, output_path: str, filename: str, index: bool = False) -> str:
    """
    Save a DataFrame to CSV inside a given folder.
    Creates the folder if it does not exist.

    Parameters
    ----------
    df : pandas.DataFrame
        The dataset to save.
    output_path : str
        Folder path (will be created if needed).
    filename : str
        Output CSV filename.
    index : bool
        Whether to include index column.

    Returns
    -------
    str
        Full path of the saved CSV file.
    """
    import pandas as pd  # local import to avoid hard dependency when not needed
    os.makedirs(output_path, exist_ok=True)
    file_path = os.path.join(output_path, filename)
    df.to_csv(file_path, index=index)
    print(f"✅ Saved dataset → {file_path} ({len(df)} rows)")
    return file_path


# ---------------------------------------------
# Misc helpers
# ---------------------------------------------

def clip_value(value: float, min_val: float, max_val: float) -> float:
    """Simple manual clamp without numpy dependency."""
    return max(min_val, min(max_val, value))
