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
///     <see cref="ITagUpstreamSource"/> subscription, virtual tags updated by prior
///     evaluations). Writes route through the engine's <c>SetVirtualTag</c> callback so
///     cross-tag write side effects still participate in change-trigger cascades.
/// </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 in scope for Phase 7.
///     </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;

    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);
    }

    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());
    }

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

    public override DateTime Now => _clock();

    public override ILogger Logger { get; }
}
