using ZB.MOM.WW.ScadaBridge.Commons.Types.Alarms;
using ZB.MOM.WW.ScadaBridge.Commons.Types.Enums;

namespace ZB.MOM.WW.ScadaBridge.DataConnectionLayer.Adapters;

/// <summary>
/// Pure mapping helpers turning OPC UA Alarms &amp; Conditions event fields into the
/// protocol-neutral <see cref="AlarmConditionState"/> / transition shape. Kept
/// free of any OPC UA SDK types so it is unit-testable without a live server;
/// the SDK field extraction lives in <c>RealOpcUaClient</c> and is exercised by
/// the live smoke test (Task 28).
/// </summary>
public static class OpcUaAlarmMapper
{
    /// <summary>Clamps an OPC UA severity (1–1000, sometimes out of range) to the unified 0–1000 scale.</summary>
    /// <param name="severity">The raw OPC UA severity value to clamp.</param>
    /// <returns>The severity clamped to the range [0, 1000].</returns>
    public static int NormalizeSeverity(int severity) => Math.Clamp(severity, 0, 1000);

    /// <summary>Builds an <see cref="AlarmConditionState"/> from the orthogonal A&amp;C sub-states.</summary>
    /// <param name="active">Whether the alarm condition is currently active.</param>
    /// <param name="acked">Whether the alarm has been acknowledged.</param>
    /// <param name="confirmed">Whether the alarm has been confirmed; null if not supported by the server.</param>
    /// <param name="shelve">The current shelving state of the alarm.</param>
    /// <param name="suppressed">Whether the alarm is suppressed.</param>
    /// <param name="severity">The raw OPC UA severity (1–1000); clamped via <see cref="NormalizeSeverity"/>.</param>
    /// <returns>The composed <see cref="AlarmConditionState"/>.</returns>
    public static AlarmConditionState BuildCondition(
        bool active, bool acked, bool? confirmed, AlarmShelveState shelve, bool suppressed, int severity) =>
        new(Active: active, Acknowledged: acked, Confirmed: confirmed,
            Shelve: shelve, Suppressed: suppressed, Severity: NormalizeSeverity(severity));

    /// <summary>
    /// Derives the transition kind from the change in active/acked sub-states.
    /// Acknowledgement takes precedence over an active/inactive edge when both
    /// change in the same event; an unchanged event is reported as a StateChange.
    /// </summary>
    /// <param name="prevAcked">Acknowledged state before the event.</param>
    /// <param name="nowAcked">Acknowledged state after the event.</param>
    /// <param name="prevActive">Active state before the event.</param>
    /// <param name="nowActive">Active state after the event.</param>
    /// <returns>The <see cref="AlarmTransitionKind"/> that best describes the state change.</returns>
    public static AlarmTransitionKind DeriveKind(bool prevAcked, bool nowAcked, bool prevActive, bool nowActive)
    {
        if (!prevAcked && nowAcked)
            return AlarmTransitionKind.Acknowledge;
        if (!prevActive && nowActive)
            return AlarmTransitionKind.Raise;
        if (prevActive && !nowActive)
            return AlarmTransitionKind.Clear;
        if (prevActive && nowActive)
            return AlarmTransitionKind.Retrigger;
        return AlarmTransitionKind.StateChange;
    }

    /// <summary>Maps the OPC UA ShelvingState current-state node name to the shelve enum.</summary>
    /// <param name="shelvingStateName">The OPC UA ShelvingState node name, or null when unshelved.</param>
    /// <returns>The corresponding <see cref="AlarmShelveState"/>; defaults to <see cref="AlarmShelveState.Unshelved"/>.</returns>
    public static AlarmShelveState MapShelve(string? shelvingStateName) => shelvingStateName switch
    {
        "OneShotShelved" => AlarmShelveState.OneShotShelved,
        "TimedShelved" => AlarmShelveState.TimedShelved,
        // OPC UA does not expose a distinct "permanent" shelve; treat any other
        // shelved name as one-shot and "Unshelved"/null as unshelved.
        null or "Unshelved" => AlarmShelveState.Unshelved,
        _ => AlarmShelveState.OneShotShelved
    };
}
