"""False-Discovery-Rate control — faithful port of Rust ml-data-validation/fdr.rs. The multiple-testing
complement to the Deflated Sharpe: when the factory runs a gauntlet over many strategies/features, BH/BY
adjustment bounds the expected fraction of false 'discoveries'. Conservation of honesty at search scale."""
from __future__ import annotations

import math
from dataclasses import dataclass


def harmonic_sum(m: int) -> float:
    return sum(1.0 / i for i in range(1, m + 1))


@dataclass(frozen=True, slots=True)
class FDRResult:
    adjusted_pvalues: list[float]
    rejected: list[bool]
    num_significant: int


def fdr_correct(pvalues: list[float], *, method: str = "bh", alpha: float = 0.05) -> FDRResult:
    """Adjust p-values controlling the FDR. method: 'bh' (Benjamini-Hochberg, independence/PRDS) or
    'by' (Benjamini-Yekutieli, arbitrary dependence). Returns adjusted p-values in the ORIGINAL order."""
    if not pvalues:
        raise ValueError("FDR correction requires at least one p-value")
    for i, p in enumerate(pvalues):
        if math.isnan(p) or p < 0.0 or p > 1.0:
            raise ValueError(f"p-value at index {i} invalid: {p}")

    m = len(pvalues)
    indexed = sorted(enumerate(pvalues), key=lambda t: t[1])   # (original_index, pvalue) by pvalue asc
    factor = 1.0 if method == "bh" else harmonic_sum(m)
    adjusted = [p * m * factor / (k + 1) for k, (_, p) in enumerate(indexed)]

    for k in range(m - 2, -1, -1):                             # enforce monotonicity backwards
        if adjusted[k] > adjusted[k + 1]:
            adjusted[k] = adjusted[k + 1]
    adjusted = [min(1.0, max(0.0, a)) for a in adjusted]

    result = [0.0] * m
    for (orig, _), a in zip(indexed, adjusted):                # restore original order
        result[orig] = a
    rejected = [a <= alpha for a in result]
    return FDRResult(result, rejected, sum(rejected))
