using Serilog;
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
using ZB.MOM.WW.OtOpcUa.Core.Scripting;

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

/// <summary>
///     Per-evaluation <see cref="ScriptContext"/> for a virtual-tag script. Reads come
///     out of the engine's last-known-value cache (driver tags updated via the
///     <c>ITagUpstreamSource</c> subscription, virtual tags updated by prior
///     evaluations). Writes route through <c>VirtualTagEngine</c>'s
///     <c>OnScriptSetVirtualTag</c> callback so cross-tag write side effects still
///     participate in change-trigger cascades (via the engine's <c>CascadeAsync</c>).
/// </summary>
/// <remarks>
///     <para>
///         Context instances are evaluation-scoped, not tag-scoped. The engine
///         constructs a fresh context for every run — cheap because the constructor
///         just captures references — so scripts can't cache mutable state across runs
///         via <c>ctx</c>. Mutable state across runs is a future decision (e.g. a
///         dedicated <c>ctx.Memory</c> dictionary); not currently in scope.
///     </para>
///     <para>
///         The <see cref="Now"/> clock is injectable so tests can pin time
///         deterministically. Production wires to <see cref="DateTime.UtcNow"/>.
///     </para>
/// </remarks>
public sealed class VirtualTagContext : ScriptContext
{
    private readonly IReadOnlyDictionary<string, DataValueSnapshot> _readCache;
    private readonly Action<string, object?> _setVirtualTag;
    private readonly Func<DateTime> _clock;

    /// <summary>Initializes a new instance of the VirtualTagContext class.</summary>
    /// <param name="readCache">The cache of tag values available for reading during script execution.</param>
    /// <param name="setVirtualTag">Callback to invoke when the script writes a virtual tag value.</param>
    /// <param name="logger">The Serilog logger instance.</param>
    /// <param name="clock">Optional function to get the current UTC time; defaults to DateTime.UtcNow if not provided.</param>
    public VirtualTagContext(
        IReadOnlyDictionary<string, DataValueSnapshot> readCache,
        Action<string, object?> setVirtualTag,
        ILogger logger,
        Func<DateTime>? clock = null)
    {
        _readCache = readCache ?? throw new ArgumentNullException(nameof(readCache));
        _setVirtualTag = setVirtualTag ?? throw new ArgumentNullException(nameof(setVirtualTag));
        Logger = logger ?? throw new ArgumentNullException(nameof(logger));
        _clock = clock ?? (() => DateTime.UtcNow);
    }

    /// <inheritdoc />
    public override DataValueSnapshot GetTag(string path)
    {
        if (string.IsNullOrWhiteSpace(path))
            return new DataValueSnapshot(null, 0x80340000u /* BadNodeIdUnknown */, null, _clock());
        return _readCache.TryGetValue(path, out var v)
            ? v
            : new DataValueSnapshot(null, 0x80340000u /* BadNodeIdUnknown */, null, _clock());
    }

    /// <inheritdoc />
    public override void SetVirtualTag(string path, object? value)
    {
        if (string.IsNullOrWhiteSpace(path))
            throw new ArgumentException("Virtual tag path required.", nameof(path));
        _setVirtualTag(path, value);
    }

    /// <inheritdoc />
    public override DateTime Now => _clock();

    /// <inheritdoc />
    public override ILogger Logger { get; }
}
