using System.Text.Json;

namespace ZB.MOM.WW.ScadaBridge.SiteRuntime.Scripts;

/// <summary>
/// Read-only globals a trigger expression is compiled against. Exposes only
/// attribute reads, backed by an in-memory snapshot — no I/O, no actor Ask,
/// no side-effecting APIs. A missing attribute key reads as <c>null</c> and
/// never throws.
///
/// Canonical attribute keys are dotted (e.g. "TempSensor.Reading"); the prefix
/// logic here mirrors <see cref="AttributeAccessor.Resolve"/>.
/// </summary>
public sealed class TriggerExpressionGlobals
{
    /// <summary>
    /// Extracts the <c>"expression"</c> field from an Expression-trigger config
    /// JSON document. Returns <c>null</c> for a missing, blank, or malformed
    /// config — the single parsing idiom shared by InstanceActor, ScriptActor,
    /// and AlarmActor.
    /// </summary>
    /// <param name="triggerConfigJson">JSON string of the trigger configuration, or null.</param>
    /// <returns>The expression string, or <c>null</c> if the config is missing, blank, or malformed.</returns>
    public static string? ExtractExpression(string? triggerConfigJson)
    {
        if (string.IsNullOrEmpty(triggerConfigJson)) return null;
        try
        {
            using var doc = JsonDocument.Parse(triggerConfigJson);
            var expr = doc.RootElement.TryGetProperty("expression", out var e)
                ? e.GetString()
                : null;
            return string.IsNullOrWhiteSpace(expr) ? null : expr;
        }
        catch (JsonException)
        {
            return null;
        }
    }

    private readonly IReadOnlyDictionary<string, object?> _snapshot;

    /// <summary>
    /// Initializes the globals with a flat attribute snapshot.
    /// </summary>
    /// <param name="snapshot">Flat dictionary of canonical attribute key to current value.</param>
    public TriggerExpressionGlobals(IReadOnlyDictionary<string, object?> snapshot)
        => _snapshot = snapshot;

    /// <summary>Attributes in the expression's own scope (root prefix).</summary>
    public ReadOnlyAttributes Attributes => new(_snapshot, "");

    /// <summary>Indexed access to child compositions' attributes.</summary>
    public ReadOnlyChildren Children => new(_snapshot);

    /// <summary>
    /// Parent composition (null at root). Set by the caller for derived/composed
    /// scopes; the runtime actors evaluate at root scope, so this stays null.
    /// </summary>
    public ReadOnlyComposition? Parent { get; init; }

    /// <summary>
    /// Read-only attribute view anchored at a canonical-name prefix. Indexing
    /// resolves to the canonical key ("" → key, "TempSensor" → "TempSensor.key").
    /// </summary>
    public sealed class ReadOnlyAttributes
    {
        private readonly IReadOnlyDictionary<string, object?> _s;
        private readonly string _prefix;

        /// <summary>
        /// Initializes a read-only attribute view anchored at the given prefix.
        /// </summary>
        /// <param name="s">The backing attribute snapshot.</param>
        /// <param name="prefix">Canonical name prefix; empty string for root scope.</param>
        public ReadOnlyAttributes(IReadOnlyDictionary<string, object?> s, string prefix)
        {
            _s = s;
            _prefix = prefix;
        }

        /// <summary>Returns the attribute value for <paramref name="key"/>, or null if absent.</summary>
        /// <param name="key">The attribute key, relative to the prefix.</param>
        public object? this[string key] =>
            _s.TryGetValue(_prefix.Length == 0 ? key : _prefix + "." + key, out var v) ? v : null;
    }

    /// <summary>A read-only view of one composition at a canonical-name path.</summary>
    public sealed class ReadOnlyComposition
    {
        private readonly IReadOnlyDictionary<string, object?> _s;
        private readonly string _path;

        /// <summary>
        /// Initializes a read-only composition view anchored at a canonical path.
        /// </summary>
        /// <param name="s">The backing attribute snapshot.</param>
        /// <param name="path">Canonical path prefix for this composition's attributes.</param>
        public ReadOnlyComposition(IReadOnlyDictionary<string, object?> s, string path)
        {
            _s = s;
            _path = path;
        }

        /// <summary>Read-only attribute view scoped to this composition's canonical path.</summary>
        public ReadOnlyAttributes Attributes => new(_s, _path);
    }

    /// <summary>Dictionary-style accessor for child compositions.</summary>
    public sealed class ReadOnlyChildren
    {
        private readonly IReadOnlyDictionary<string, object?> _s;

        /// <summary>
        /// Initializes a children accessor over the supplied attribute snapshot.
        /// </summary>
        /// <param name="s">The backing attribute snapshot.</param>
        public ReadOnlyChildren(IReadOnlyDictionary<string, object?> s) => _s = s;

        /// <summary>Returns a read-only composition view for the named child composition.</summary>
        /// <param name="compositionName">The canonical composition name.</param>
        public ReadOnlyComposition this[string compositionName] => new(_s, compositionName);
    }
}
