namespace Hc900Crawler.Infrastructure.Control;

public static class Num
{
    public static double Clamp(double x, double lo, double hi) => Math.Max(lo, Math.Min(hi, x));
    public static bool   IsFinite(double x) => !double.IsNaN(x) && !double.IsInfinity(x);
}

public sealed class FirstOrderLag
{
    private double _y;
    private bool   _seeded;
    public double Value => _y;
    public bool   Seeded => _seeded;
    public void   Seed(double v) { _y = v; _seeded = true; }
    public double Step(double x, double tauSec, double tsSec)
    {
        if (!_seeded) { Seed(x); return _y; }
        if (tauSec <= 0.0) { _y = x; return _y; }
        var a = tsSec / (tauSec + tsSec);
        _y += (x - _y) * a;
        return _y;
    }
}

public sealed class MovingAverage
{
    private readonly Queue<double> _buf = new();
    private readonly int _window;
    private double _sum;
    public MovingAverage(int windowSamples) => _window = Math.Max(1, windowSamples);
    public double Value => _buf.Count > 0 ? _sum / _buf.Count : double.NaN;
    public double Push(double x)
    {
        _buf.Enqueue(x); _sum += x;
        while (_buf.Count > _window) _sum -= _buf.Dequeue();
        return _sum / _buf.Count;
    }
}

/// <summary>
/// 가변 전달지연(데드타임) 링버퍼. 용량은 요청된 최대 n 으로 **증가만**(축소 금지)하며
/// θ(=n)가 스캔마다 바뀌어도(비대칭 θup/θdn, D7) **읽기 오프셋만 가변** — 히스토리를 보존한다.
/// (이전 구현은 n 변경 시 Resize 로 버퍼를 재시드해 비대칭 θ에서 지연선이 매 부호반전마다 소실됨)
/// </summary>
public sealed class DeadTimeBuffer
{
    private double[] _buf = Array.Empty<double>();   // 항상 가득 찬 링(시드로 사전충전)
    private int  _cap;                                // 용량(보존 샘플 수). 증가만.
    private int  _head;                               // 다음 덮어쓸 위치(=가장 오래된 값)
    private bool _seeded;

    public double Through(double x, double thetaSec, double tsSec)
    {
        int n = (int)Math.Round(thetaSec / Math.Max(1e-6, tsSec));
        if (n <= 0) return x;                         // θ<ts → 지연 없음

        EnsureCapacity(n + 1, x);                     // n 샘플 지연엔 n+1 슬롯 필요

        _buf[_head] = x;                              // 가장 오래된 자리에 현재값 기록
        int newest = _head;
        _head = (_head + 1) % _cap;

        int idx = ((newest - n) % _cap + _cap) % _cap; // n 스캔 전 값
        return _buf[idx];
    }

    /// <summary>용량을 need 이상으로 확보(증가 전용). 기존 이력은 논리순서로 보존.</summary>
    private void EnsureCapacity(int need, double seed)
    {
        if (_seeded && need <= _cap) return;
        int newCap = Math.Max(need, Math.Max(_cap, 1));
        var nb = new double[newCap];
        if (!_seeded)
        {
            Array.Fill(nb, seed);                     // 최초: 현재값으로 사전충전(bumpless)
        }
        else
        {
            int extra = newCap - _cap;
            double oldest = _buf[_head];
            for (int i = 0; i < extra; i++) nb[i] = oldest;            // 앞쪽 패딩=가장 오래된 값
            for (int k = 0; k < _cap; k++) nb[extra + k] = _buf[(_head + k) % _cap]; // 오래된→최신
        }
        _buf = nb; _cap = newCap; _head = 0;          // head=0: 인덱스0이 가장 오래된, cap-1이 최신
        _seeded = true;
    }
}

public sealed class RateLimiter
{
    private double _last;
    private bool   _seeded;
    public double Last => _last;
    public void   Seed(double v) { _last = v; _seeded = true; }
    public double Step(double target, double rateUpPerMin, double rateDnPerMin, double tsSec)
    {
        if (!_seeded) { Seed(target); return _last; }
        var up = Math.Abs(rateUpPerMin) * tsSec / 60.0;
        var dn = Math.Abs(rateDnPerMin) * tsSec / 60.0;
        var d  = Num.Clamp(target - _last, -dn, up);
        _last += d;
        return _last;
    }
}

public sealed class Derivative
{
    private double _prev;
    private bool   _seeded;
    public double Update(double x, double tsSec)
    {
        if (!_seeded) { _prev = x; _seeded = true; return 0.0; }
        var d = (x - _prev) / Math.Max(1e-6, tsSec);
        _prev = x;
        return d;
    }
}

public static class TempCorrection
{
    public static double PressureCompensated(double tMeas, double p, double pRef, double dTdP)
        => tMeas - dTdP * (p - pRef);
}

/// <summary>차온/이중차온 — 공통모드(압력·계절) 상쇄, 프론트 이동만 부각. spec §13.3.
/// 본 WO-2에선 블록만 추가(단위테스트 대상). 실제 소비는 WO-5(FrontPositionIndicator).</summary>
public static class DiffTemp
{
    /// <summary>두 트레이 차온 (상단 - 하단).</summary>
    public static double Delta(double tHi, double tLo) => tHi - tLo;

    /// <summary>이중차온(곡률) — 프론트 위치 민감.</summary>
    public static double Double(double tA, double tB, double tC) => (tA - tB) - (tB - tC);
}
