namespace ScadaLink.SiteRuntime.Scripts;

/// <summary>
/// Scope-aware view onto the instance's attributes, anchored at a path prefix.
/// <c>Attributes["X"]</c> on the root scope resolves to canonical name "X";
/// on a composition with prefix "TempSensor" it resolves to "TempSensor.X".
/// Reads block on the actor Ask; async variants are provided for callers
/// that prefer to await explicitly.
/// </summary>
public class AttributeAccessor
{
    private readonly ScriptRuntimeContext _ctx;

    /// <summary>Canonical-name prefix, e.g. "" for root or "TempSensor" for a composition.</summary>
    public string ScopePrefix { get; }

    public AttributeAccessor(ScriptRuntimeContext ctx, string prefix)
    {
        _ctx = ctx;
        ScopePrefix = prefix;
    }

    public string Resolve(string key) =>
        ScopePrefix.Length == 0 ? key : ScopePrefix + "." + key;

    public object? this[string key]
    {
        // Both reads and writes block on the actor Ask; the write also blocks
        // on the DCL round-trip for data-connected attributes. The async
        // variants (GetAsync/SetAsync) are preferred where awaiting is possible.
        get => _ctx.GetAttribute(Resolve(key)).GetAwaiter().GetResult();
        set => _ctx.SetAttribute(Resolve(key), value?.ToString() ?? string.Empty).GetAwaiter().GetResult();
    }

    public Task<object?> GetAsync(string key) => _ctx.GetAttribute(Resolve(key));

    public Task SetAsync(string key, object? value)
        => _ctx.SetAttribute(Resolve(key), value?.ToString() ?? string.Empty);
}

/// <summary>
/// A view of one composition at a path. Exposes its attributes via
/// <see cref="AttributeAccessor"/> and an invokable <c>CallScript</c>.
/// </summary>
public class CompositionAccessor
{
    private readonly ScriptRuntimeContext _ctx;

    /// <summary>Canonical-name path this composition is rooted at.</summary>
    public string Path { get; }

    public AttributeAccessor Attributes { get; }

    public CompositionAccessor(ScriptRuntimeContext ctx, string path)
    {
        _ctx = ctx;
        Path = path;
        Attributes = new AttributeAccessor(ctx, path);
    }

    public string ResolveScript(string scriptName) =>
        Path.Length == 0 ? scriptName : Path + "." + scriptName;

    public Task<object?> CallScript(string scriptName, object? parameters = null)
        => _ctx.CallScript(ResolveScript(scriptName), parameters);
}

/// <summary>
/// Dictionary-style accessor for the script's child compositions. Indexing
/// returns a <see cref="CompositionAccessor"/> rooted at the child's path.
/// </summary>
public class ChildrenAccessor
{
    private readonly ScriptRuntimeContext _ctx;
    private readonly string _selfPath;

    public ChildrenAccessor(ScriptRuntimeContext ctx, string selfPath)
    {
        _ctx = ctx;
        _selfPath = selfPath;
    }

    public CompositionAccessor this[string compositionName]
    {
        get
        {
            var path = _selfPath.Length == 0
                ? compositionName
                : _selfPath + "." + compositionName;
            return new CompositionAccessor(_ctx, path);
        }
    }
}

internal static class ScopeAccessorFactory
{
    public static AttributeAccessor AttributesFor(ScriptRuntimeContext ctx, string selfPath)
        => new(ctx, selfPath);

    public static ChildrenAccessor ChildrenFor(ScriptRuntimeContext ctx, string selfPath)
        => new(ctx, selfPath);

    public static CompositionAccessor? ParentFor(ScriptRuntimeContext ctx, string? parentPath)
        => parentPath == null ? null : new CompositionAccessor(ctx, parentPath);
}
