using Hc900Crawler.Core.Application.Feedforward;

namespace Hc900Crawler.Infrastructure.Control;

/// <summary>
/// WP3 안전 피드램프 계산기 — 순수 함수(부수효과 0, DB 무관). 단위테스트 대상.
/// 한계치(ceiling)·램프율(rate)·예상시간·binding 제약·스팀(FIQ-6115) 목표를 산출. 쓰기 없음.
/// 공식: docs/안전피드램프-검증시나리오매트릭스.md §0, docs/안전피드램프-한계치-브레인스토밍.md §3·§7.
/// </summary>
public static class FeedRampCalculator
{
    public static FeedRampAdvisory Compute(
        ColumnConfig cfg, PvSnapshot pv, double targetFeed, double deltaIAllow,
        double sensibleGain, double feedTempRef, RampExtraInputs extra, bool simOverrideActive)
    {
        var warnings = new List<string>();

        // 1) 피드 — bad/정지 시 HOLD
        if (!pv.Feed.Good || !Num.IsFinite(pv.Feed.Value) || pv.Feed.Value <= 0)
        {
            warnings.Add("feed-bad: 피드 PV 불량/정지/stale → HOLD");
            return new FeedRampAdvisory(cfg.Id, double.NaN, targetFeed, double.NaN,
                new Bound(double.NaN, "hold"), new Bound(double.NaN, "hold"), 0,
                new SteamTarget(null, null, null), simOverrideActive, true, warnings.ToArray());
        }
        double currentFeed = pv.Feed.Value;

        // 2) Ceiling
        double valveCeiling = double.PositiveInfinity; string valveBind = "none";
        foreach (var s in cfg.Streams)
        {
            if (s.TargetCoeff <= 0 || !Num.IsFinite(s.SpMax) || s.SpMax >= double.MaxValue) continue;
            double c = s.SpMax / s.TargetCoeff;
            if (c < valveCeiling) { valveCeiling = c; valveBind = $"{s.Key} sp_max"; }
        }

        double floodCeiling = double.PositiveInfinity;
        if (extra.Pica.HasValue && extra.Pi6111b.HasValue && Num.IsFinite(extra.FloodLimit) && extra.FloodLimit > 0)
        {
            double dPnow = extra.Pi6111b.Value - extra.Pica.Value;   // 전탑 ΔP (진공: 탑저>탑정)
            if (dPnow > 0)
            {
                double n = extra.FloodExponentN > 0 ? extra.FloodExponentN : 1.8;
                floodCeiling = currentFeed * Math.Pow(extra.FloodLimit / dPnow, 1.0 / n);
            }
            else warnings.Add("flooding: ΔP(pi-6111b−pica-6111)≤0 → ceiling 미산정");
        }
        else warnings.Add("flooding ceiling 미산정(ΔP 또는 floodLimit 부재)");
        warnings.Add("steam ceiling 미산정(최대 OP/스팀 근거 부재)");

        double ceiling; string ceilBind;
        if (floodCeiling < valveCeiling) { ceiling = floodCeiling; ceilBind = "flooding"; }
        else { ceiling = valveCeiling; ceilBind = valveBind; }
        if (double.IsPositiveInfinity(ceiling)) warnings.Add("ceiling 무제한(밸브 sp_max 부재)");

        // 3) Ramp rate (kg/hr per min)
        double valveSlew = double.PositiveInfinity; string slewBind = "valveSlew";
        foreach (var s in cfg.Streams)
        {
            if (s.Role != StreamRole.Commanded || s.RateUpPerMin <= 0 || s.TargetCoeff <= 0) continue;
            double r = s.RateUpPerMin / s.TargetCoeff;
            if (r < valveSlew) { valveSlew = r; slewBind = $"valveSlew@{s.Key}"; }
        }

        // dynamic: lagging Commanded 합 ΣK_i(τ_i+θ_up_i) (C-6111은 P만 τ≠0)
        double denom = 0;
        foreach (var s in cfg.Streams)
            if (s.Role == StreamRole.Commanded) denom += s.TargetCoeff * (s.TauSec + s.ThetaUpSec);
        double dynamic = double.PositiveInfinity;
        if (denom > 0 && deltaIAllow > 0) dynamic = deltaIAllow * 60.0 / denom;
        else warnings.Add("dynamic 제약 미산정(lag 0 또는 ΔI≤0)");

        warnings.Add("energyLoop 제약 미산정(에너지루프 시상수 부재)");

        double rFeed; string rateBind;
        if (valveSlew <= dynamic) { rFeed = valveSlew; rateBind = slewBind; }
        else { rFeed = dynamic; rateBind = "dynamic"; }
        if (double.IsPositiveInfinity(rFeed)) { rateBind = "none"; warnings.Add("램프율 무제한(제약 없음)"); }

        // 4) clamp
        double clampedTarget = Math.Min(targetFeed, ceiling);

        // 5) time
        double rampTimeMin = 0;
        if (clampedTarget > currentFeed && Num.IsFinite(rFeed) && rFeed > 0)
            rampTimeMin = (clampedTarget - currentFeed) / rFeed;
        else if (clampedTarget < currentFeed)
            warnings.Add("down-ramp: rate_dn 분기 미구현(시간 0 표기)");

        // 6) Steam target = 처리량비례 + 현열보정(앵커 대비 편차) (a안)
        double? steamFrom = extra.Fiq6115;
        double? steamTo = null, startOp = null;
        if (extra.Fiq6115.HasValue && currentFeed > 0)
        {
            double ratio = extra.Fiq6115.Value * (clampedTarget / currentFeed);
            double sensible = 0;
            if (Num.IsFinite(sensibleGain) && sensibleGain != 0 && Num.IsFinite(feedTempRef) && extra.Ti6103.HasValue)
                sensible = sensibleGain * clampedTarget * (feedTempRef - extra.Ti6103.Value);
            else warnings.Add("현열보정 off(sensibleGain/feedTempRef/ti-6103 부재)");
            steamTo = ratio + sensible;
            warnings.Add("startOP 미산정(local gain 부재)");
        }
        else warnings.Add("steam 미산정(ficq-6115 부재)");

        return new FeedRampAdvisory(cfg.Id, currentFeed, targetFeed, clampedTarget,
            new Bound(ceiling, ceilBind), new Bound(rFeed, rateBind), rampTimeMin,
            new SteamTarget(steamFrom, steamTo, startOp), simOverrideActive, false, warnings.ToArray());
    }
}
