import pandas as pd
import numpy as np


def adx(df: pd.DataFrame, window: int = 14, high_col: str = 'High', low_col: str = 'Low', close_col: str = 'Close') -> pd.DataFrame:
    """
    Calculates the Average Directional Index (ADX) along with the Positive
    Directional Indicator (+DI) and Negative Directional Indicator (-DI).

    The ADX is a technical indicator used to measure the strength of a trend.
    The +DI and -DI indicate the direction of the trend.

    Args:
        df (pd.DataFrame): The dataframe containing price data. Must have high, low, and close columns.
        window (int): The window size for the ADX calculation.
        high_col (str): The name of the high price column (default: 'high').
        low_col (str): The name of the low price column (default: 'Low').
        close_col (str): The name of the close price column (default: 'Close').

    Returns:
        pd.DataFrame: A DataFrame containing the ADX, +DI, and -DI values.

    The Average Directional Index (ADX) is a technical indicator used to
    measure the strength of a trend. It is calculated from the high, low, and
    closing prices of a period, along with a smoothing factor (window).
    The ADX is non-directional, meaning it measures the strength of the trend,
    but not its direction. The direction is indicated by the Positive
    Directional Indicator (+DI) and Negative Directional Indicator (-DI).

    The formula for calculating the ADX is as follows:

    1. Calculate the True Range (TR).
    2. Calculate the Positive Directional Movement (+DM) and Negative
       Directional Movement (-DM).
    3. Smooth the TR, +DM, and -DM using a moving average (typically 14 periods).
    4. Calculate the Positive Directional Indicator (+DI) and Negative
       Directional Indicator (-DI).
    5. Calculate the Directional Index (DX).
    6. Smooth the DX to obtain the ADX.

    Use Cases:

    - Identifying trend strength: The ADX can be used to determine whether a
      trend is strong or weak.
    - Identifying trend direction: The +DI and -DI can be used to determine
      the direction of the trend.
    - Generating buy and sell signals: The ADX, +DI, and -DI can be used in
      combination to generate buy and sell signals.
    - Confirming other indicators: The ADX can be used to confirm signals
      generated by other technical indicators.
    """
    high = df[high_col]
    low = df[low_col]
    close = df[close_col]

    plus_dm = high.diff()
    minus_dm = low.diff().abs()
    plus_dm[plus_dm < 0] = 0
    minus_dm[minus_dm < 0] = 0
    tr1 = high - low
    tr2 = (high - close.shift()).abs()
    tr3 = (low - close.shift()).abs()
    tr = pd.concat([tr1, tr2, tr3], axis=1).max(axis=1)
    atr = tr.rolling(window=window).mean()
    plus_di = 100 * (plus_dm.rolling(window=window).mean() / atr)
    minus_di = 100 * (minus_dm.rolling(window=window).mean() / atr)
    dx = (abs(plus_di - minus_di) / (plus_di + minus_di)) * 100
    adx_ = dx.rolling(window=window).mean()
    df_adx = pd.DataFrame({
        f'ADX_{window}': adx_,
        f'+DI_{window}': plus_di,
        f'-DI_{window}': minus_di
    })
    df_adx.index = close.index
    return df_adx