namespace ZB.MOM.WW.OtOpcUa.Core.Abstractions;

/// <summary>
///     A single historized tag the recorder tracks, carrying BOTH identifiers it needs — kept distinct
///     because a <c>HistorianTagname</c> override makes them diverge:
///     <list type="bullet">
///       <item>
///         <see cref="MuxRef"/> — the driver-published reference the per-node dependency mux fans its
///         <c>DependencyValueChanged</c> by (the tag's driver-side <c>FullName</c>). The recorder
///         registers mux interest and matches incoming values by THIS.
///       </item>
///       <item>
///         <see cref="HistorianName"/> — the resolved historian tag name the value is written under (a
///         non-alarm historized value variable's <c>HistorianTagname</c> override, else its
///         <c>FullName</c>) — the SAME name the EnsureTags provisioning hook ensures.
///       </item>
///     </list>
///     In the common (no-override) case the two are the same string; an override is the only case they
///     diverge, and conflating them would silently drop that tag's values (interest registered under a
///     key the mux never fans).
/// </summary>
/// <param name="MuxRef">The driver ref the mux fans by (and the key the recorder registers interest under).</param>
/// <param name="HistorianName">The resolved historian tag name the value is historized under.</param>
public sealed record HistorizedTagRef(string MuxRef, string HistorianName)
{
    /// <summary>The no-override identity: the mux ref and historian name are the same string (the tag has
    /// no <c>HistorianTagname</c> override, so it historizes under its own driver <c>FullName</c>).</summary>
    /// <param name="reference">The driver ref that serves as both the mux key and the historian name.</param>
    /// <returns>A ref whose <see cref="MuxRef"/> and <see cref="HistorianName"/> are equal.</returns>
    public static HistorizedTagRef ForSelf(string reference) => new(reference, reference);
}

/// <summary>
///     Server-side feed that keeps the continuous-historization recorder's set of historized tag refs
///     in step with the deployed address space. The <c>AddressSpaceApplier</c> (in the
///     OpcUaServer layer) calls this on every deploy with the add/remove DELTA of historized refs the
///     plan changes — the applier only ever sees a diff (an incremental/surgical apply carries a delta,
///     not the full set), so the recorder behind this seam keeps the full set and converges it.
/// </summary>
/// <remarks>
///     The feed is <b>non-blocking</b> and best-effort: the production adapter is a single
///     fire-and-forget actor <c>Tell</c>, so it never blocks the OPC UA publish thread the applier runs
///     on, and the applier wraps the call so a faulting feed can never break a deploy. The applier
///     references this abstraction (not the Runtime recorder) so the OpcUaServer layer keeps no
///     dependency on Akka / the actor system — exactly mirroring how <see cref="IHistorianProvisioning"/>
///     decouples the EnsureTags provisioning hook.
/// </remarks>
public interface IHistorizedTagSubscriptionSink
{
    /// <summary>
    ///     Converge the recorder's historized-ref interest by an add/remove delta. Each ref carries both
    ///     its <see cref="HistorizedTagRef.MuxRef"/> (the driver ref the mux fans by) and its
    ///     <see cref="HistorizedTagRef.HistorianName"/> (the resolved override-or-FullName the value is
    ///     historized under) — the same name the EnsureTags provisioning hook ensures. The recorder
    ///     applies the delta to its tracked full set and re-registers mux interest (keyed by
    ///     <see cref="HistorizedTagRef.MuxRef"/>) only when the registered key-set actually changes.
    /// </summary>
    /// <param name="added">Historized refs newly historized by this deploy (added/changed-into tags).</param>
    /// <param name="removed">Historized refs no longer historized by this deploy (removed/changed-out tags).</param>
    void UpdateHistorizedRefs(IReadOnlyList<HistorizedTagRef> added, IReadOnlyList<HistorizedTagRef> removed);
}

/// <summary>
///     No-op <see cref="IHistorizedTagSubscriptionSink"/> — the applier's safe default when continuous
///     historization is disabled or unwired (no recorder to feed). Every call is a no-op and never
///     touches an actor system.
/// </summary>
public sealed class NullHistorizedTagSubscriptionSink : IHistorizedTagSubscriptionSink
{
    /// <summary>The shared singleton instance.</summary>
    public static readonly NullHistorizedTagSubscriptionSink Instance = new();

    private NullHistorizedTagSubscriptionSink() { }

    /// <inheritdoc />
    public void UpdateHistorizedRefs(IReadOnlyList<HistorizedTagRef> added, IReadOnlyList<HistorizedTagRef> removed)
    {
    }
}
