from __future__ import annotations

import numpy as np
from numba import njit

from numbatalib._core._validation import Range, as_1d_float64, nan_like, validate_int_param
from numbatalib._func._dmi_shared import _dm_deltas, _ta_is_zero, _true_range


@njit(cache=True)
def _minus_di_kernel(
    high: np.ndarray, low: np.ndarray, close: np.ndarray, timeperiod: int, out: np.ndarray
) -> None:
    n = high.shape[0]
    if n == 0:
        return

    if timeperiod > 1:
        lookback_total = timeperiod
    else:
        lookback_total = 1

    if lookback_total >= n:
        return

    # No smoothing: -DI1 = -DM1 / TR1 (no *100).
    if timeperiod <= 1:
        prev_high = high[lookback_total - 1]
        prev_low = low[lookback_total - 1]
        prev_close = close[lookback_total - 1]
        for today in range(lookback_total, n):
            curr_high = high[today]
            curr_low = low[today]
            diff_p, diff_m = _dm_deltas(curr_high, curr_low, prev_high, prev_low)
            prev_high = curr_high
            prev_low = curr_low
            if (diff_m > 0.0) and (diff_p < diff_m):
                tr = _true_range(curr_high, curr_low, prev_close)
                out[today] = 0.0 if _ta_is_zero(tr) else (diff_m / tr)
            else:
                out[today] = 0.0
            prev_close = close[today]
        return

    prev_minus_dm = 0.0
    prev_tr = 0.0
    today = 0
    prev_high = high[today]
    prev_low = low[today]
    prev_close = close[today]

    for _ in range(timeperiod - 1):
        today += 1
        curr_high = high[today]
        curr_low = low[today]
        diff_p, diff_m = _dm_deltas(curr_high, curr_low, prev_high, prev_low)
        prev_high = curr_high
        prev_low = curr_low
        if (diff_m > 0.0) and (diff_p < diff_m):
            prev_minus_dm += diff_m
        tr = _true_range(curr_high, curr_low, prev_close)
        prev_tr += tr
        prev_close = close[today]

    # Unstable period assumed 0; execute once to get first DI.
    today += 1
    curr_high = high[today]
    curr_low = low[today]
    diff_p, diff_m = _dm_deltas(curr_high, curr_low, prev_high, prev_low)
    prev_high = curr_high
    prev_low = curr_low

    prev_minus_dm -= prev_minus_dm / timeperiod
    if (diff_m > 0.0) and (diff_p < diff_m):
        prev_minus_dm += diff_m

    tr = _true_range(curr_high, curr_low, prev_close)
    prev_tr = prev_tr - (prev_tr / timeperiod) + tr
    prev_close = close[today]

    if not _ta_is_zero(prev_tr):
        out[lookback_total] = 100.0 * (prev_minus_dm / prev_tr)
    else:
        out[lookback_total] = 0.0

    for today in range(lookback_total + 1, n):
        curr_high = high[today]
        curr_low = low[today]
        diff_p, diff_m = _dm_deltas(curr_high, curr_low, prev_high, prev_low)
        prev_high = curr_high
        prev_low = curr_low

        prev_minus_dm -= prev_minus_dm / timeperiod
        if (diff_m > 0.0) and (diff_p < diff_m):
            prev_minus_dm += diff_m

        tr = _true_range(curr_high, curr_low, prev_close)
        prev_tr = prev_tr - (prev_tr / timeperiod) + tr
        prev_close = close[today]

        out[today] = 0.0 if _ta_is_zero(prev_tr) else (100.0 * (prev_minus_dm / prev_tr))


def MINUS_DI(high, low, close, timeperiod: int = 14):
    """
    Minus Directional Indicator
    """
    h = as_1d_float64(high)
    l = as_1d_float64(low)
    c = as_1d_float64(close)
    n = h.shape[0]
    if l.shape[0] != n or c.shape[0] != n:
        raise ValueError("inputs must have the same length")

    tp = validate_int_param("timeperiod", timeperiod, Range(min=1, max=100000))
    out = nan_like(h, dtype=np.float64)
    _minus_di_kernel(h, l, c, tp, out)
    return out

