namespace Hc900Crawler.Infrastructure.Control;

/// <summary>
/// Passive 전달지연(θ) 식별. ΔF(피드 변화)와 ΔR(응답=PCT 변화)의 교차상관 최대 지연 = θ.
/// 스팀 ΔS(=TICA.OP 변화)를 2번째 입력으로 **부분상관**(partial corr)해 폐루프 오염 제거(spec §13.4).
/// 입력은 이미 1차차분(Δ=사전백색화)된 값. I/O 없음, 컬럼 루프 단일 소유(락 불필요).
/// 비용 분산을 위해 매 호출 누적하되 argmax 계산은 recomputeEvery 호출마다 1회(나머지는 캐시 반환).
/// </summary>
public sealed class CrossCorrLagEstimator
{
    private readonly int    _maxLag;          // 탐색할 최대 지연(샘플)
    private readonly int    _hist;            // 보존 이력(샘플)
    private readonly double _minStd;          // 외란 최소 표준편차(미달 시 억제)
    private readonly int    _recomputeEvery;  // argmax 재계산 주기(호출 수)
    private readonly Queue<double> _f = new();
    private readonly Queue<double> _r = new();
    private readonly Queue<double> _s = new();
    private int _sinceCompute;
    private (double thetaUpSec, double thetaDnSec, double conf)? _last;

    public CrossCorrLagEstimator(int maxLagSamples, int historySamples, double minSignalStd, int recomputeEvery = 30)
    {
        _maxLag         = Math.Max(1, maxLagSamples);
        _hist           = Math.Max(_maxLag * 2, historySamples);
        _minStd         = minSignalStd;
        _recomputeEvery = Math.Max(1, recomputeEvery);
    }

    public (double thetaUpSec, double thetaDnSec, double conf)? Push(
        double dFeed, double dResponse, double dSteam, double tsSec)
    {
        _f.Enqueue(dFeed); _r.Enqueue(dResponse); _s.Enqueue(dSteam);
        while (_f.Count > _hist) { _f.Dequeue(); _r.Dequeue(); _s.Dequeue(); }

        if (_f.Count < _maxLag * 2) return _last;          // 외란 누적 부족 → 직전 결과(초기 null)

        _sinceCompute++;
        if (_last is not null && _sinceCompute < _recomputeEvery) return _last;  // 캐시
        _sinceCompute = 0;

        var f = _f.ToArray(); var r = _r.ToArray(); var s = _s.ToArray();
        int n = f.Length;

        if (Std(f) < _minStd) { _last = null; return null; } // 피드 외란 없음 → 억제

        // 부분상관: r에서 s의 동시점 선형성분 제거 (잔차)
        double beta = Cov(r, s) / Math.Max(1e-12, Var(s));
        var resid = new double[n];
        for (int i = 0; i < n; i++) resid[i] = r[i] - beta * s[i];

        // 방향별 θ (상승/하강 비대칭). 표본 부족 시 NaN.
        var (tu, cu) = BestLag(f, resid, n, x => x > 0, tsSec);
        var (td, cd) = BestLag(f, resid, n, x => x < 0, tsSec);

        bool haveUp = !double.IsNaN(tu), haveDn = !double.IsNaN(td);
        if (!haveUp && !haveDn) { _last = null; return null; }
        if (!haveUp) { tu = td; cu = cd; }
        if (!haveDn) { td = tu; cd = cu; }

        double conf = Math.Min(cu, cd);
        if (conf < 0.3) { _last = null; return null; }       // 신뢰 부족 → 억제

        _last = (tu, td, conf);
        return _last;
    }

    /// <summary>mask를 만족하는 피드 표본에 대해 ρ(τ) 최대인 τ(초)와 conf 반환. 표본 부족 시 (NaN,0).</summary>
    private (double theta, double conf) BestLag(double[] f, double[] resid, int n, Func<double, bool> mask, double tsSec)
    {
        int masked = 0;
        for (int i = 0; i < n; i++) if (mask(f[i])) masked++;
        if (masked < _maxLag) return (double.NaN, 0.0);

        double bestRho = double.NegativeInfinity; int bestTau = 0;
        for (int tau = 0; tau <= _maxLag; tau++)
        {
            double sfr = 0, sff = 0, srr = 0; int m = 0;
            for (int i = 0; i + tau < n; i++)
            {
                if (!mask(f[i])) continue;
                double a = f[i], b = resid[i + tau];
                sfr += a * b; sff += a * a; srr += b * b; m++;
            }
            if (m < 3 || sff <= 0 || srr <= 0) continue;
            double rho = sfr / Math.Sqrt(sff * srr);          // Δ신호라 비중심 상관
            if (rho > bestRho) { bestRho = rho; bestTau = tau; }
        }
        if (double.IsNegativeInfinity(bestRho)) return (double.NaN, 0.0);
        return (bestTau * tsSec, Math.Max(0.0, bestRho));
    }

    private static double Mean(double[] a) { double s = 0; foreach (var x in a) s += x; return s / a.Length; }
    private static double Var(double[] a)  { double m = Mean(a), s = 0; foreach (var x in a) s += (x - m) * (x - m); return s / a.Length; }
    private static double Std(double[] a)  => Math.Sqrt(Var(a));
    private static double Cov(double[] a, double[] b)
    { double ma = Mean(a), mb = Mean(b), s = 0; for (int i = 0; i < a.Length; i++) s += (a[i] - ma) * (b[i] - mb); return s / a.Length; }
}
