using Mbproxy.Bcd;
using Mbproxy.Proxy.Cache;
using Mbproxy.Proxy.Multiplexing;

namespace Mbproxy.Proxy;

/// <summary>
/// Per-PLC context holding the resolved BCD tag map, live counters, and a logger.
/// Derives from <see cref="PduContext"/> so it can be passed wherever a
/// <see cref="PduContext"/> is expected.
///
/// One instance per configured PLC is constructed at <see cref="ProxyWorker"/> startup
/// and lives for the lifetime of the listener. It is shared across all upstream pipes
/// served by the same <see cref="Multiplexing.PlcMultiplexer"/>; all mutable state is
/// accessed through <see cref="ProxyCounters"/> which uses Interlocked for thread-safety.
///
/// <para><b>Request correlation:</b> the multiplexer sets <see cref="CurrentRequest"/>
/// before calling the pipeline on each direction. On the request path the pipeline can
/// peek at the future correlation entry it just enqueued; on the response path the
/// pipeline uses the request's <c>StartAddress</c>/<c>Qty</c> to decode FC03/FC04 BCD
/// slots. Different in-flight responses use different <see cref="InFlightRequest"/>
/// instances, so there is no cross-talk between concurrent multiplexed requests.</para>
///
/// <para><b>Concurrency:</b> a single <see cref="PerPlcContext"/> instance is shared
/// across the per-upstream read tasks (which call the pipeline on the request path) and
/// the single backend reader task (which calls the pipeline on the response path).
/// Because the per-call <see cref="CurrentRequest"/> would be racy if mutated on the
/// shared context, the multiplexer constructs a lightweight per-call clone
/// (<see cref="WithCurrentRequest"/>) for each pipeline invocation. The shared mutable
/// state — the tag map, counters, logger — is read-only or Interlocked.</para>
/// </summary>
internal class PerPlcContext : PduContext
{
    public BcdTagMap TagMap { get; init; } = BcdTagMap.Empty;

    public ProxyCounters Counters { get; init; } = new();

    public ILogger Logger { get; init; } = Microsoft.Extensions.Logging.Abstractions.NullLogger.Instance;

    /// <summary>
    /// Per-PDU-call correlation entry. Non-null on response calls (set by the multiplexer's
    /// backend reader task to the matched <see cref="InFlightRequest"/>); <c>null</c> on
    /// request calls. The BCD rewriter reads this on response to learn the originating
    /// FC03/FC04 start address and quantity (which are not present in the response PDU).
    /// </summary>
    internal InFlightRequest? CurrentRequest { get; init; }

    /// <summary>
    /// Optional per-PLC response cache. <c>null</c> on contexts that opt out (every BCD
    /// tag has <see cref="BcdTag.CacheTtlMs"/> = 0) or in unit tests that don't exercise
    /// the cache. The multiplexer constructs and disposes the cache alongside itself.
    /// </summary>
    internal ResponseCache? Cache { get; init; }

    /// <summary>
    /// Optional per-PLC tag-value capture feeding the connection-detail debug view.
    /// Wired in production from <see cref="TagCaptureRegistry"/>; <c>null</c> in unit
    /// tests that don't exercise it. The <see cref="BcdPduPipeline"/> records into it
    /// with <c>?.</c>, and the capture itself no-ops unless a detail page has armed it,
    /// so the cost on the hot path with no viewer is one nullable-deref + one volatile
    /// read.
    /// </summary>
    internal TagValueCapture? Capture { get; init; }

    /// <summary>
    /// Returns a shallow clone of this context with <see cref="CurrentRequest"/> set to
    /// <paramref name="req"/>. The clone is cheap (one allocation per response) and avoids
    /// any race on the shared context across concurrent multiplexed responses.
    /// </summary>
    internal PerPlcContext WithCurrentRequest(InFlightRequest? req) => new()
    {
        PlcName        = PlcName,
        TagMap         = TagMap,
        Counters       = Counters,
        Logger         = Logger,
        CurrentRequest = req,
        Cache          = Cache,
        Capture        = Capture,
    };
}
