"""Fractal similarity measures: DTW, Hausdorff, correlation-based."""
from __future__ import annotations

import numpy as np
from scipy.spatial.distance import directed_hausdorff
from scipy.signal import correlate


def dtw_distance(s1: np.ndarray, s2: np.ndarray) -> float:
    """Dynamic Time Warping distance between two 1D sequences."""
    n, m = len(s1), len(s2)
    dtw = np.full((n + 1, m + 1), np.inf)
    dtw[0, 0] = 0.0

    for i in range(1, n + 1):
        for j in range(1, m + 1):
            cost = abs(s1[i - 1] - s2[j - 1])
            dtw[i, j] = cost + min(dtw[i - 1, j], dtw[i, j - 1], dtw[i - 1, j - 1])

    return dtw[n, m]


def hausdorff_distance(s1: np.ndarray, s2: np.ndarray) -> float:
    """Hausdorff distance between two OHLC windows (shape Nx4)."""
    if s1.ndim == 1:
        s1 = s1.reshape(-1, 1)
    if s2.ndim == 1:
        s2 = s2.reshape(-1, 1)
    d1 = directed_hausdorff(s1, s2)[0]
    d2 = directed_hausdorff(s2, s1)[0]
    return max(d1, d2)


def cross_correlation(s1: np.ndarray, s2: np.ndarray) -> float:
    """Max normalized cross-correlation between two 1D sequences."""
    if len(s1) == 0 or len(s2) == 0:
        return 0.0
    s1_norm = (s1 - np.mean(s1))
    s2_norm = (s2 - np.mean(s2))
    std1, std2 = np.std(s1), np.std(s2)
    if std1 == 0 or std2 == 0:
        return 0.0
    corr = correlate(s1_norm, s2_norm, mode="full")
    corr /= (std1 * std2 * len(s1))
    return float(np.max(np.abs(corr)))


def cosine_similarity(v1: np.ndarray, v2: np.ndarray) -> float:
    """Cosine similarity between two feature vectors."""
    dot = np.dot(v1, v2)
    n1, n2 = np.linalg.norm(v1), np.linalg.norm(v2)
    if n1 == 0 or n2 == 0:
        return 0.0
    return float(dot / (n1 * n2))


def pattern_feature_vector(ohlc_window: np.ndarray) -> np.ndarray:
    """Extract compact feature vector from normalized OHLC window.

    Features: body ratios, shadow ratios, close-to-close returns,
    volatility, trend.
    """
    o, h, l, c = ohlc_window[:, 0], ohlc_window[:, 1], ohlc_window[:, 2], ohlc_window[:, 3]
    total = h - l
    total = np.where(total == 0, 1e-10, total)

    body = np.abs(c - o) / total
    upper_shadow = (h - np.maximum(o, c)) / total
    lower_shadow = (np.minimum(o, c) - l) / total
    returns = np.diff(c) / np.where(c[:-1] == 0, 1e-10, c[:-1])

    features = np.concatenate([
        body,
        upper_shadow,
        lower_shadow,
        returns,
        [np.std(returns) if len(returns) > 0 else 0],
        [c[-1] - c[0]],
    ])
    return features
