"""
utils.py — Custom helper functions for data science workflows.
Add your own reusable functions here and export them in __init__.py
"""

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns


def load_csv(path: str, **kwargs) -> pd.DataFrame:
    """
    Load a CSV file and print shape + preview.

    Usage:
        df = load_csv("data.csv")
        df = load_csv("data.csv", sep=";", encoding="latin-1")
    """
    df = pd.read_csv(path, **kwargs)
    print(f"✅ Loaded: {df.shape[0]} rows x {df.shape[1]} columns")
    print(df.head(3))
    return df


def quick_summary(df: pd.DataFrame) -> None:
    """
    Print a full overview of a DataFrame:
    shape, dtypes, missing values, and basic stats.

    Usage:
        quick_summary(df)
    """
    print("=" * 50)
    print(f"  Shape      : {df.shape[0]} rows x {df.shape[1]} columns")
    print(f"  Duplicates : {df.duplicated().sum()}")
    print("=" * 50)

    print("\n📌 Data Types:")
    print(df.dtypes.to_string())

    missing = df.isnull().sum()
    missing = missing[missing > 0]
    if not missing.empty:
        print("\n⚠️  Missing Values:")
        for col, count in missing.items():
            pct = (count / len(df)) * 100
            print(f"   {col}: {count} ({pct:.1f}%)")
    else:
        print("\n✅ No missing values found.")

    print("\n📊 Numeric Summary:")
    print(df.describe().T.to_string())


def missing_report(df: pd.DataFrame) -> pd.DataFrame:
    """
    Return a DataFrame showing missing value counts and percentages.

    Usage:
        report = missing_report(df)
        print(report)
    """
    missing = df.isnull().sum()
    pct = (missing / len(df)) * 100
    report = pd.DataFrame({
        "missing_count": missing,
        "missing_pct": pct.round(2)
    })
    return report[report["missing_count"] > 0].sort_values("missing_pct", ascending=False)


def plot_distribution(df: pd.DataFrame, column: str) -> None:
    """
    Plot histogram + boxplot for a numeric column side by side.

    Usage:
        plot_distribution(df, "price")
    """
    fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 4))

    # Histogram
    df[column].hist(ax=ax1, bins=30, edgecolor="black", color="steelblue")
    ax1.set_title(f"Histogram: {column}")
    ax1.set_xlabel(column)
    ax1.set_ylabel("Frequency")

    # Boxplot
    df.boxplot(column=column, ax=ax2)
    ax2.set_title(f"Boxplot: {column}")

    plt.tight_layout()
    plt.show()


def encode_categoricals(df: pd.DataFrame, strategy: str = "label") -> pd.DataFrame:
    """
    Encode all categorical (object) columns in a DataFrame.

    Args:
        df       : Input DataFrame
        strategy : "label" for LabelEncoder, "onehot" for pd.get_dummies

    Usage:
        df_encoded = encode_categoricals(df, strategy="label")
        df_encoded = encode_categoricals(df, strategy="onehot")
    """
    from sklearn.preprocessing import LabelEncoder

    df = df.copy()
    cat_cols = df.select_dtypes(include="object").columns.tolist()

    if not cat_cols:
        print("No categorical columns found.")
        return df

    if strategy == "label":
        le = LabelEncoder()
        for col in cat_cols:
            df[col] = le.fit_transform(df[col].astype(str))
        print(f"✅ Label encoded: {cat_cols}")

    elif strategy == "onehot":
        df = pd.get_dummies(df, columns=cat_cols)
        print(f"✅ One-hot encoded: {cat_cols}")

    else:
        raise ValueError("strategy must be 'label' or 'onehot'")

    return df