using System.Diagnostics;
using System.Globalization;
using System.Threading;
using System.Windows.Threading;
using CommunityToolkit.Mvvm.Input;
using ConeCalorimeter.Models;
using ConeCalorimeter.Services;
using Serilog;

namespace ConeCalorimeter.ViewModels;

public sealed class ConeRadiationSettingsViewModel : PageViewModel
{
    private const ushort CurrentHeatFluxRegister = 32;
    private const ushort TargetTemperatureRegister = 400;
    private const ushort HeatTransferRegister = 418;
    private const double TargetTemperatureScale = 10;
    private const double CurrentTemperatureMinimum = 10;
    private const double CurrentTemperatureMaximum = 1200;
    private const double CurrentTemperatureMaximumDropPerRefresh = 80;
    private const double CurrentHeatFluxMinimum = 0;
    private const double CurrentHeatFluxMaximum = 1000;
    private const double CurrentHeatFluxZeroTolerance = 0.005;
    private const double CurrentHeatFluxStableAbsoluteTolerance = 0.25;
    private const double CurrentHeatFluxStableRelativeTolerance = 0.1;
    private const double CurrentHeatFluxStableMaximumTolerance = 5;
    private const int CurrentHeatFluxStableReadCount = 3;
    private const int CurrentHeatFluxZeroStableReadCount = 10;
    private const double HeatTransferInputMinimum = 0;
    private const double HeatTransferReadMinimum = 0.01;
    private const double HeatTransferMaximum = 20000;
    private const int TargetTemperatureWriteConfirmationAttempts = 3;
    private const int TargetTemperatureWriteConfirmationDelayMilliseconds = 80;
    private const int ParameterRefreshReleaseDelayMilliseconds = 500;
    private const int HeatingDiagnosticLogIntervalSeconds = 5;
    private const int HeatingDiagnosticMaximumDurationMinutes = 30;
    private const int HeatingNoRiseWarningDelaySeconds = 60;
    private const double HeatingNoRiseTolerance = 1;
    private const double HeatingAtTargetTolerance = 2;
    private const double TargetTemperatureMismatchTolerance = 0.05;
    private const ushort YellowLampCoil = 57;
    private const ushort GreenLampCoil = 58;
    private const ushort RedLampCoil = 59;
    private const ushort CirculatingWaterCoil = 49;
    private const ushort HeatingCoil = 102;
    private const ushort StartHeatingCoil = 100;
    private const ushort StopHeatingCoil = 101;
    private const string CirculatingWaterAction = "循环水";
    private const string StartHeatingAction = "开始升温";
    private const string StopHeatingAction = "停止升温";

    private readonly Action _closeAction;
    private readonly Action _helpAction;
    private readonly IExperimentDataService _experimentDataService;
    private readonly ITcpDeviceConnectionService _tcpDeviceConnectionService;
    private readonly DispatcherTimer _refreshTimer;
    private readonly HashSet<ushort> _loggedFloatDiagnostics = [];
    private readonly HashSet<ushort> _loggedInvalidFloatDiagnostics = [];
    private static readonly ModbusFloatByteOrder[] CurrentHeatFluxByteOrders =
    [
        ModbusFloatByteOrder.Abcd,
        ModbusFloatByteOrder.Cdab,
        ModbusFloatByteOrder.Badc,
        ModbusFloatByteOrder.Dcba
    ];

    private string _currentTemperatureText = "";
    private string _currentHeatFluxText = "";
    private string _targetTemperatureText = "";
    private string _heatTransferText = "";
    private string _lastAction = "待机";
    private bool _yellowLampActive;
    private bool _greenLampActive;
    private bool _redLampActive;
    private bool _circulatingWaterActive;
    private bool _heatingActive;
    private bool _isEditingConeParameters;
    private double? _lastStableCurrentTemperature;
    private double? _lastStableCurrentHeatFlux;
    private double? _pendingCurrentHeatFlux;
    private int _pendingCurrentHeatFluxReadCount;
    private int _pendingCurrentHeatFluxZeroReadCount;
    private ModbusFloatByteOrder? _currentHeatFluxByteOrder;
    private DateTime _parameterRefreshBlockedUntil = DateTime.MinValue;
    private double? _lastConfirmedTargetTemperature;
    private short? _lastConfirmedTargetTemperatureRaw;
    private DateTime? _heatingDiagnosticStartedAtUtc;
    private DateTime _lastHeatingDiagnosticLoggedAtUtc = DateTime.MinValue;
    private double? _heatingDiagnosticStartTemperature;

    public ConeRadiationSettingsViewModel(
        Action closeAction,
        Action helpAction,
        IExperimentDataService experimentDataService,
        ITcpDeviceConnectionService tcpDeviceConnectionService) : base("辐射锥设置")
    {
        _closeAction = closeAction;
        _helpAction = helpAction;
        _experimentDataService = experimentDataService;
        _tcpDeviceConnectionService = tcpDeviceConnectionService;
        CloseCommand = new RelayCommand(_closeAction);
        HelpCommand = new RelayCommand(_helpAction);
        ActionCommand = new RelayCommand<string>(ExecuteAction);
        SaveParametersCommand = new RelayCommand(SaveParameters);

        UpdateCurrentReadings(_experimentDataService.CurrentSnapshot);
        _experimentDataService.SnapshotUpdated += (_, snapshot) => UpdateCurrentReadings(snapshot);
        RefreshDeviceValues();
        _refreshTimer = new DispatcherTimer
        {
            Interval = TimeSpan.FromSeconds(1)
        };
        _refreshTimer.Tick += (_, _) => RefreshDeviceValues();
        _refreshTimer.Start();
    }

    public IRelayCommand CloseCommand { get; }

    public IRelayCommand HelpCommand { get; }

    public IRelayCommand<string> ActionCommand { get; }

    public IRelayCommand SaveParametersCommand { get; }

    public string CurrentTemperatureText
    {
        get => _currentTemperatureText;
        private set => SetProperty(ref _currentTemperatureText, value);
    }

    public string CurrentHeatFluxText
    {
        get => _currentHeatFluxText;
        private set => SetProperty(ref _currentHeatFluxText, value);
    }

    public string TargetTemperatureText
    {
        get => _targetTemperatureText;
        set => SetProperty(ref _targetTemperatureText, value);
    }

    public string HeatTransferText
    {
        get => _heatTransferText;
        set => SetProperty(ref _heatTransferText, value);
    }

    public string LastAction
    {
        get => _lastAction;
        private set => SetProperty(ref _lastAction, value);
    }

    public bool YellowLampActive
    {
        get => _yellowLampActive;
        private set
        {
            if (SetProperty(ref _yellowLampActive, value))
            {
                OnPropertyChanged(nameof(LampStatusText));
            }
        }
    }

    public bool GreenLampActive
    {
        get => _greenLampActive;
        private set
        {
            if (SetProperty(ref _greenLampActive, value))
            {
                OnPropertyChanged(nameof(LampStatusText));
            }
        }
    }

    public bool RedLampActive
    {
        get => _redLampActive;
        private set
        {
            if (SetProperty(ref _redLampActive, value))
            {
                OnPropertyChanged(nameof(LampStatusText));
            }
        }
    }

    public string LampStatusText
    {
        get
        {
            if (RedLampActive)
            {
                return "M59 红灯";
            }

            if (YellowLampActive)
            {
                return "M57 黄灯";
            }

            return GreenLampActive ? "M58 绿灯" : "未触发";
        }
    }

    public bool CirculatingWaterActive
    {
        get => _circulatingWaterActive;
        private set => SetProperty(ref _circulatingWaterActive, value);
    }

    public string CirculatingWaterButtonText => CirculatingWaterAction;

    public bool HeatingActive
    {
        get => _heatingActive;
        private set
        {
            if (SetProperty(ref _heatingActive, value))
            {
                OnPropertyChanged(nameof(StartHeatingButtonText));
            }
        }
    }

    public string StartHeatingButtonText => HeatingActive ? "加热中" : "开始升温";

    public void BeginConeParameterEdit()
    {
        _isEditingConeParameters = true;
    }

    public void EndConeParameterEdit()
    {
        _isEditingConeParameters = false;
        _parameterRefreshBlockedUntil = DateTime.UtcNow.AddMilliseconds(ParameterRefreshReleaseDelayMilliseconds);
    }

    private void ExecuteAction(string? action)
    {
        if (string.IsNullOrWhiteSpace(action))
        {
            return;
        }

        LastAction = action;

        if (action == CirculatingWaterAction)
        {
            ToggleCirculatingWater();
            return;
        }

        if (action == StartHeatingAction)
        {
            Log.Information(
                "Cone radiation start heating requested. TargetInput={TargetInput}, CurrentTemperatureText={CurrentTemperatureText}, HeatingActive={HeatingActive}, Endpoint={Endpoint}.",
                TargetTemperatureText,
                CurrentTemperatureText,
                HeatingActive,
                _tcpDeviceConnectionService.Endpoint);
            if (WriteTargetTemperature())
            {
                if (WriteActionCoil(action))
                {
                    BeginHeatingDiagnostic();
                }
            }

            return;
        }

        if (WriteActionCoil(action) && action == StopHeatingAction)
        {
            EndHeatingDiagnostic("operator stopped heating");
        }
    }

    private void RefreshDeviceValues()
    {
        if (TryReadCurrentHeatFlux(out var currentHeatFlux))
        {
            if (TryUpdateStableCurrentHeatFlux(currentHeatFlux, out var stableCurrentHeatFlux))
            {
                CurrentHeatFluxText = stableCurrentHeatFlux.ToString("0.00", CultureInfo.InvariantCulture);
            }
        }
        else if (!_lastStableCurrentHeatFlux.HasValue)
        {
            CurrentHeatFluxText = string.Empty;
        }

        if (CanRefreshConeParameters())
        {
            TargetTemperatureText = ReadScaledInt16Text(TargetTemperatureRegister, TargetTemperatureScale, "0.#");
            if (TryReadRangedFloatText(
                "ConeHeatTransfer",
                HeatTransferRegister,
                HeatTransferReadMinimum,
                HeatTransferMaximum,
                "0.##",
                out var heatTransferText,
                out _))
            {
                HeatTransferText = heatTransferText;
            }
        }

        YellowLampActive = _tcpDeviceConnectionService.TryReadCoil(YellowLampCoil, out var yellowLampActive) && yellowLampActive;
        GreenLampActive = _tcpDeviceConnectionService.TryReadCoil(GreenLampCoil, out var greenLampActive) && greenLampActive;
        RedLampActive = _tcpDeviceConnectionService.TryReadCoil(RedLampCoil, out var redLampActive) && redLampActive;
        HeatingActive = _tcpDeviceConnectionService.TryReadCoil(HeatingCoil, out var heatingActive) && heatingActive;
        LogHeatingDiagnosticIfNeeded(HeatingActive);
        if (_tcpDeviceConnectionService.TryReadCoil(CirculatingWaterCoil, out var circulatingWaterActive))
        {
            CirculatingWaterActive = circulatingWaterActive;
        }
    }

    private void UpdateCurrentReadings(RealtimeSnapshot snapshot)
    {
        if (IsStableCurrentTemperature(snapshot.ConeTemperature))
        {
            _lastStableCurrentTemperature = snapshot.ConeTemperature;
            CurrentTemperatureText = NormalizeDisplayValue(snapshot.ConeTemperature)
                .ToString("0.0", CultureInfo.InvariantCulture);
        }
    }

    private bool TryReadCurrentHeatFlux(out double value)
    {
        value = double.NaN;

        if (!_tcpDeviceConnectionService.TryReadFloatValues(CurrentHeatFluxRegister, out var result))
        {
            return false;
        }

        var preferredByteOrder = _currentHeatFluxByteOrder;
        if (!ModbusFloatSelector.TrySelectRangedFloat(
                result,
                CurrentHeatFluxMinimum,
                CurrentHeatFluxMaximum,
                out var byteOrder,
                out var selectedValue,
                out var matchCount,
                preferredByteOrder))
        {
            if (!TrySelectSingleNonZeroCurrentHeatFluxCandidate(result, out byteOrder, out selectedValue))
            {
                LogFloatDiagnostic("ConeCurrentHeatFlux", CurrentHeatFluxRegister, result, null, matchCount);
                return false;
            }
        }

        if (!preferredByteOrder.HasValue
            && IsZeroLikeCurrentHeatFlux(selectedValue)
            && TrySelectSingleNonZeroCurrentHeatFluxCandidate(result, out var nonZeroByteOrder, out var nonZeroValue))
        {
            byteOrder = nonZeroByteOrder;
            selectedValue = nonZeroValue;
        }

        LogFloatDiagnostic("ConeCurrentHeatFlux", CurrentHeatFluxRegister, result, byteOrder, matchCount);
        RememberCurrentHeatFluxByteOrder(byteOrder, selectedValue);
        value = NormalizeDisplayValue(selectedValue);
        return true;
    }

    private bool TryReadRangedFloatText(
        string label,
        ushort registerAddress,
        double minimum,
        double maximum,
        string format,
        out string text,
        out double value)
    {
        text = string.Empty;
        value = double.NaN;

        if (!_tcpDeviceConnectionService.TryReadFloatValues(registerAddress, out var result))
        {
            return false;
        }

        if (!ModbusFloatSelector.TrySelectRangedFloat(result, minimum, maximum, out var byteOrder, out var selectedValue, out var matchCount))
        {
            LogFloatDiagnostic(label, registerAddress, result, null, matchCount);
            return false;
        }

        value = selectedValue;
        LogFloatDiagnostic(label, registerAddress, result, byteOrder, matchCount);
        text = NormalizeDisplayValue(value).ToString(format, CultureInfo.InvariantCulture);
        return true;
    }

    private bool IsStableCurrentTemperature(double value)
    {
        if (!ModbusFloatSelector.IsInRange(value, CurrentTemperatureMinimum, CurrentTemperatureMaximum))
        {
            return false;
        }

        return !_lastStableCurrentTemperature.HasValue
            || value >= _lastStableCurrentTemperature.Value - CurrentTemperatureMaximumDropPerRefresh;
    }

    private bool TryUpdateStableCurrentHeatFlux(double value, out double stableValue)
    {
        value = NormalizeDisplayValue(value);
        stableValue = _lastStableCurrentHeatFlux ?? value;

        if (IsZeroLikeCurrentHeatFlux(value))
        {
            _pendingCurrentHeatFlux = null;
            _pendingCurrentHeatFluxReadCount = 0;
            _pendingCurrentHeatFluxZeroReadCount++;

            if (_pendingCurrentHeatFluxZeroReadCount < CurrentHeatFluxZeroStableReadCount)
            {
                return false;
            }

            AcceptStableCurrentHeatFlux(0);
            stableValue = 0;
            return true;
        }

        _pendingCurrentHeatFluxZeroReadCount = 0;

        if (_lastStableCurrentHeatFlux.HasValue
            && IsWithinCurrentHeatFluxStableTolerance(value, _lastStableCurrentHeatFlux.Value))
        {
            AcceptStableCurrentHeatFlux(value);
            stableValue = value;
            return true;
        }

        if (!_pendingCurrentHeatFlux.HasValue
            || !IsWithinCurrentHeatFluxStableTolerance(value, _pendingCurrentHeatFlux.Value))
        {
            _pendingCurrentHeatFlux = value;
            _pendingCurrentHeatFluxReadCount = 1;
            return false;
        }

        _pendingCurrentHeatFluxReadCount++;

        if (_pendingCurrentHeatFluxReadCount < CurrentHeatFluxStableReadCount)
        {
            return false;
        }

        AcceptStableCurrentHeatFlux(value);
        stableValue = value;
        return true;
    }

    private void AcceptStableCurrentHeatFlux(double value)
    {
        _lastStableCurrentHeatFlux = value;
        _pendingCurrentHeatFlux = null;
        _pendingCurrentHeatFluxReadCount = 0;
        _pendingCurrentHeatFluxZeroReadCount = 0;
    }

    private static bool TrySelectSingleNonZeroCurrentHeatFluxCandidate(
        ModbusFloatReadResult result,
        out ModbusFloatByteOrder byteOrder,
        out double value)
    {
        var matches = CurrentHeatFluxByteOrders
            .Select(candidate => new
            {
                ByteOrder = candidate,
                Value = result.GetValue(candidate)
            })
            .Where(candidate => ModbusFloatSelector.IsInRange(
                    candidate.Value,
                    CurrentHeatFluxMinimum,
                    CurrentHeatFluxMaximum)
                && !IsZeroLikeCurrentHeatFlux(candidate.Value))
            .ToArray();

        if (matches.Length != 1)
        {
            byteOrder = default;
            value = double.NaN;
            return false;
        }

        byteOrder = matches[0].ByteOrder;
        value = matches[0].Value;
        return true;
    }

    private void RememberCurrentHeatFluxByteOrder(ModbusFloatByteOrder byteOrder, double value)
    {
        if (!IsZeroLikeCurrentHeatFlux(value))
        {
            _currentHeatFluxByteOrder = byteOrder;
        }
    }

    private static bool IsWithinCurrentHeatFluxStableTolerance(double value, double referenceValue)
    {
        return Math.Abs(value - referenceValue) <= GetCurrentHeatFluxStableTolerance(referenceValue);
    }

    private static double GetCurrentHeatFluxStableTolerance(double referenceValue)
    {
        return Math.Min(
            CurrentHeatFluxStableMaximumTolerance,
            Math.Max(CurrentHeatFluxStableAbsoluteTolerance, Math.Abs(referenceValue) * CurrentHeatFluxStableRelativeTolerance));
    }

    private static bool IsZeroLikeCurrentHeatFlux(double value)
    {
        return double.IsFinite(value) && Math.Abs(value) < CurrentHeatFluxZeroTolerance;
    }

    private string ReadScaledInt16Text(ushort registerAddress, double scale, string format)
    {
        return _tcpDeviceConnectionService.TryReadInt16(registerAddress, out var rawValue)
            ? (rawValue / scale).ToString(format, CultureInfo.InvariantCulture)
            : string.Empty;
    }

    private bool WriteTargetTemperature()
    {
        if (!double.TryParse(TargetTemperatureText, NumberStyles.Float, CultureInfo.InvariantCulture, out var value)
            || !double.IsFinite(value))
        {
            LastAction = "辐射温度输入无效";
            Debug.WriteLine($"Invalid cone radiation target temperature: {TargetTemperatureText}");
            Log.Warning("Invalid cone radiation target temperature input. Input={Input}.", TargetTemperatureText);
            return false;
        }

        var scaledValue = Math.Round(value * TargetTemperatureScale, MidpointRounding.AwayFromZero);
        if (scaledValue is < short.MinValue or > short.MaxValue)
        {
            LastAction = "辐射温度输入超出范围";
            Debug.WriteLine($"Cone radiation target temperature out of range: {TargetTemperatureText}");
            Log.Warning(
                "Cone radiation target temperature out of int16 range. Input={Input}, ScaledValue={ScaledValue}.",
                TargetTemperatureText,
                scaledValue);
            return false;
        }

        var registerValue = (short)scaledValue;
        if (TryWriteAndConfirmTargetTemperature(registerValue))
        {
            _parameterRefreshBlockedUntil = DateTime.UtcNow.AddMilliseconds(ParameterRefreshReleaseDelayMilliseconds);
            _lastConfirmedTargetTemperature = value;
            _lastConfirmedTargetTemperatureRaw = registerValue;
            TargetTemperatureText = value.ToString("0.#", CultureInfo.InvariantCulture);
            LastAction = "辐射温度设置成功";
            Log.Information(
                "Cone radiation target temperature confirmed. DisplayValue={DisplayValue}, RawRegisterValue={RawRegisterValue}, Register=D{RegisterAddress}.",
                value,
                registerValue,
                TargetTemperatureRegister);
            return true;
        }

        LastAction = "辐射温度写入未生效";
        Debug.WriteLine($"Cone radiation target temperature write was not confirmed. Expected raw D{TargetTemperatureRegister}={registerValue}.");
        Log.Warning(
            "Cone radiation target temperature write was not confirmed. DisplayValue={DisplayValue}, ExpectedRawValue={ExpectedRawValue}, Register=D{RegisterAddress}.",
            value,
            registerValue,
            TargetTemperatureRegister);
        return false;
    }

    private bool TryWriteAndConfirmTargetTemperature(short registerValue)
    {
        for (var attempt = 1; attempt <= TargetTemperatureWriteConfirmationAttempts; attempt++)
        {
            if (_tcpDeviceConnectionService.TryWriteInt16(TargetTemperatureRegister, registerValue)
                && _tcpDeviceConnectionService.TryReadInt16(TargetTemperatureRegister, out var readBackValue))
            {
                if (readBackValue == registerValue)
                {
                    Log.Information(
                        "Cone radiation target temperature write confirmed. Attempt={Attempt}, Register=D{RegisterAddress}, RawRegisterValue={RawRegisterValue}.",
                        attempt,
                        TargetTemperatureRegister,
                        registerValue);
                    return true;
                }

                Log.Warning(
                    "Cone radiation target temperature readback mismatch. Attempt={Attempt}, Register=D{RegisterAddress}, ExpectedRawValue={ExpectedRawValue}, ActualRawValue={ActualRawValue}.",
                    attempt,
                    TargetTemperatureRegister,
                    registerValue,
                    readBackValue);
            }

            if (attempt < TargetTemperatureWriteConfirmationAttempts)
            {
                Thread.Sleep(TargetTemperatureWriteConfirmationDelayMilliseconds);
            }
        }

        return false;
    }

    private void BeginHeatingDiagnostic()
    {
        _heatingDiagnosticStartedAtUtc = DateTime.UtcNow;
        _lastHeatingDiagnosticLoggedAtUtc = DateTime.MinValue;
        _heatingDiagnosticStartTemperature = _lastStableCurrentTemperature;

        Log.Information(
            "Cone radiation heating diagnostic started. TargetTemperature={TargetTemperature}, RawRegisterValue={RawRegisterValue}, StartTemperature={StartTemperature}, StartCoil=M{StartCoil}, HeatingStateCoil=M{HeatingStateCoil}.",
            _lastConfirmedTargetTemperature,
            _lastConfirmedTargetTemperatureRaw,
            _heatingDiagnosticStartTemperature,
            StartHeatingCoil,
            HeatingCoil);
    }

    private void EndHeatingDiagnostic(string reason)
    {
        if (!_heatingDiagnosticStartedAtUtc.HasValue)
        {
            return;
        }

        Log.Information(
            "Cone radiation heating diagnostic ended. Reason={Reason}, TargetTemperature={TargetTemperature}, CurrentTemperature={CurrentTemperature}, HeatingActive={HeatingActive}.",
            reason,
            _lastConfirmedTargetTemperature,
            _lastStableCurrentTemperature,
            HeatingActive);
        _heatingDiagnosticStartedAtUtc = null;
        _heatingDiagnosticStartTemperature = null;
        _lastHeatingDiagnosticLoggedAtUtc = DateTime.MinValue;
    }

    private void LogHeatingDiagnosticIfNeeded(bool heatingActive)
    {
        if (!_heatingDiagnosticStartedAtUtc.HasValue)
        {
            return;
        }

        var now = DateTime.UtcNow;
        var elapsed = now - _heatingDiagnosticStartedAtUtc.Value;
        if (elapsed > TimeSpan.FromMinutes(HeatingDiagnosticMaximumDurationMinutes))
        {
            EndHeatingDiagnostic("diagnostic time window expired");
            return;
        }

        if (now - _lastHeatingDiagnosticLoggedAtUtc < TimeSpan.FromSeconds(HeatingDiagnosticLogIntervalSeconds))
        {
            return;
        }

        _lastHeatingDiagnosticLoggedAtUtc = now;

        var targetReadSucceeded = _tcpDeviceConnectionService.TryReadInt16(TargetTemperatureRegister, out var rawTargetReadback);
        double? targetReadback = targetReadSucceeded ? rawTargetReadback / TargetTemperatureScale : null;
        var currentTemperature = _lastStableCurrentTemperature;
        var targetTemperature = _lastConfirmedTargetTemperature;
        var deviation = currentTemperature.HasValue && targetTemperature.HasValue
            ? currentTemperature.Value - targetTemperature.Value
            : (double?)null;
        var targetMismatch = targetReadSucceeded
            && targetTemperature.HasValue
            && Math.Abs(targetReadback!.Value - targetTemperature.Value) > TargetTemperatureMismatchTolerance;
        var hasNotRisen = elapsed >= TimeSpan.FromSeconds(HeatingNoRiseWarningDelaySeconds)
            && targetTemperature.HasValue
            && _heatingDiagnosticStartTemperature.HasValue
            && currentTemperature.HasValue
            && targetTemperature.Value > _heatingDiagnosticStartTemperature.Value + HeatingNoRiseTolerance
            && currentTemperature.Value < _heatingDiagnosticStartTemperature.Value + HeatingNoRiseTolerance;
        var reachedTarget = currentTemperature.HasValue
            && targetTemperature.HasValue
            && Math.Abs(currentTemperature.Value - targetTemperature.Value) <= HeatingAtTargetTolerance;

        if (!targetReadSucceeded)
        {
            Log.Warning(
                "Cone radiation heating diagnostic: target readback failed. ElapsedSeconds={ElapsedSeconds}, TargetTemperature={TargetTemperature}, CurrentTemperature={CurrentTemperature}, HeatingActive={HeatingActive}.",
                elapsed.TotalSeconds,
                targetTemperature,
                currentTemperature,
                heatingActive);
            return;
        }

        if (!heatingActive || targetMismatch || hasNotRisen)
        {
            Log.Warning(
                "Cone radiation heating diagnostic warning. ElapsedSeconds={ElapsedSeconds}, TargetTemperature={TargetTemperature}, RawTargetReadback={RawTargetReadback}, TargetReadback={TargetReadback}, CurrentTemperature={CurrentTemperature}, Deviation={Deviation}, HeatingActive={HeatingActive}, TargetMismatch={TargetMismatch}, HasNotRisen={HasNotRisen}.",
                elapsed.TotalSeconds,
                targetTemperature,
                rawTargetReadback,
                targetReadback,
                currentTemperature,
                deviation,
                heatingActive,
                targetMismatch,
                hasNotRisen);
        }
        else
        {
            Log.Information(
                "Cone radiation heating diagnostic sample. ElapsedSeconds={ElapsedSeconds}, TargetTemperature={TargetTemperature}, RawTargetReadback={RawTargetReadback}, TargetReadback={TargetReadback}, CurrentTemperature={CurrentTemperature}, Deviation={Deviation}, HeatingActive={HeatingActive}.",
                elapsed.TotalSeconds,
                targetTemperature,
                rawTargetReadback,
                targetReadback,
                currentTemperature,
                deviation,
                heatingActive);
        }

        if (reachedTarget)
        {
            EndHeatingDiagnostic("target temperature reached");
        }
    }

    private void SaveParameters()
    {
        if (!float.TryParse(HeatTransferText, NumberStyles.Float, CultureInfo.InvariantCulture, out var heatTransfer))
        {
            LastAction = "热传递输入无效";
            Debug.WriteLine($"Invalid cone radiation heat transfer: {HeatTransferText}");
            return;
        }

        if (!ModbusFloatSelector.IsInRange(heatTransfer, HeatTransferInputMinimum, HeatTransferMaximum))
        {
            LastAction = "热传递输入超出范围";
            Debug.WriteLine($"Cone radiation heat transfer out of range: {HeatTransferText}");
            return;
        }

        if (_tcpDeviceConnectionService.TryWriteFloat(HeatTransferRegister, heatTransfer))
        {
            _parameterRefreshBlockedUntil = DateTime.UtcNow.AddMilliseconds(ParameterRefreshReleaseDelayMilliseconds);
            HeatTransferText = heatTransfer.ToString("0.##", CultureInfo.InvariantCulture);
            LastAction = "热传递保存成功";
            return;
        }

        LastAction = "热传递保存失败";
        Debug.WriteLine("Cone radiation heat transfer write failed.");
    }

    private void LogFloatDiagnostic(
        string label,
        ushort registerAddress,
        ModbusFloatReadResult result,
        ModbusFloatByteOrder? selectedByteOrder,
        int matchCount)
    {
        if (selectedByteOrder is null)
        {
            if (!_loggedInvalidFloatDiagnostics.Add(registerAddress))
            {
                return;
            }
        }
        else if (!_loggedFloatDiagnostics.Add(registerAddress))
        {
            return;
        }

        var selectedText = selectedByteOrder?.ToString() ?? "none";

        Debug.WriteLine(
            $"Cone radiation float {label} register {registerAddress} raw [{result.RawHex}], "
            + $"ABCD={result.Abcd:G9}, CDAB={result.Cdab:G9}, "
            + $"BADC={result.Badc:G9}, DCBA={result.Dcba:G9}, "
            + $"selected={selectedText}, matches={matchCount}.");
    }

    private static double NormalizeDisplayValue(double value)
    {
        return Math.Abs(value) < 0.005 ? 0 : value;
    }

    private bool CanRefreshConeParameters()
    {
        return !_isEditingConeParameters && DateTime.UtcNow >= _parameterRefreshBlockedUntil;
    }

    private void ToggleCirculatingWater()
    {
        if (!_tcpDeviceConnectionService.TryReadCoil(CirculatingWaterCoil, out var isActive))
        {
            LastAction = "循环水状态读取失败";
            Debug.WriteLine("Cone radiation circulating water state read failed.");
            return;
        }

        var nextValue = !isActive;
        if (_tcpDeviceConnectionService.TryWriteCoil(CirculatingWaterCoil, nextValue))
        {
            CirculatingWaterActive = nextValue;
            LastAction = $"循环水{(nextValue ? "开启" : "关闭")}";
            return;
        }

        LastAction = $"循环水{(nextValue ? "开启" : "关闭")}失败";
        Debug.WriteLine("Cone radiation circulating water write failed.");
    }

    private bool WriteActionCoil(string action)
    {
        if (!TryGetActionCoil(action, out var coilAddress))
        {
            return false;
        }

        if (!_tcpDeviceConnectionService.TryWriteCoil(coilAddress, true))
        {
            LastAction = $"{action}失败";
            Debug.WriteLine($"Cone radiation action '{action}' write failed.");
            Log.Warning(
                "Cone radiation action coil write failed. Action={Action}, Coil=M{CoilAddress}.",
                action,
                coilAddress);
            return false;
        }

        Log.Information(
            "Cone radiation action coil write succeeded. Action={Action}, Coil=M{CoilAddress}.",
            action,
            coilAddress);
        return true;
    }

    private static bool TryGetActionCoil(string action, out ushort coilAddress)
    {
        switch (action)
        {
            case StartHeatingAction:
                coilAddress = StartHeatingCoil;
                return true;
            case StopHeatingAction:
                coilAddress = StopHeatingCoil;
                return true;
            default:
                coilAddress = 0;
                return false;
        }
    }
}
