// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics.CodeAnalysis;
using Microsoft.AspNetCore.Components.HotReload;
using Microsoft.AspNetCore.Components.RenderTree;

namespace Microsoft.AspNetCore.Components;

/// <summary>
/// Allows a component to interact with its renderer.
/// </summary>
public readonly struct RenderHandle
{
    private readonly Renderer? _renderer;
    private readonly int _componentId;

    internal RenderHandle(Renderer renderer, int componentId)
    {
        _renderer = renderer ?? throw new ArgumentNullException(nameof(renderer));
        _componentId = componentId;
    }

    internal ComponentsMetrics? ComponentMetrics => _renderer?.ComponentMetrics;
    internal ComponentsActivitySource? ComponentActivitySource => _renderer?.ComponentActivitySource;

    /// <summary>
    /// Gets the <see cref="Components.Dispatcher" /> associated with the component.
    /// </summary>
    public Dispatcher Dispatcher
    {
        get
        {
            if (_renderer == null)
            {
                ThrowNotInitialized();
            }

            return _renderer.Dispatcher;
        }
    }

    /// <summary>
    /// Gets a value that indicates whether the <see cref="RenderHandle"/> has been
    /// initialized and is ready to use.
    /// </summary>
    public bool IsInitialized => _renderer is not null;

    /// <summary>
    /// Gets a value that determines if the <see cref="Renderer"/> is triggering a render in response to a metadata update (hot-reload) change.
    /// </summary>
    public bool IsRenderingOnMetadataUpdate => HotReloadManager.Default.MetadataUpdateSupported && (_renderer?.IsRenderingOnMetadataUpdate ?? false);

    internal bool IsRendererDisposed => _renderer?.Disposed
        ?? throw new InvalidOperationException("No renderer has been initialized.");

    /// <summary>
    /// Gets the <see cref="Components.RendererInfo"/> the component is running on.
    /// </summary>
    public RendererInfo RendererInfo => _renderer?.RendererInfo ?? throw new InvalidOperationException("No renderer has been initialized.");

    /// <summary>
    /// Retrieves the <see cref="IComponentRenderMode"/> assigned to the component.
    /// </summary>
    /// <returns>The <see cref="IComponentRenderMode"/> assigned to the component.</returns>
    public IComponentRenderMode? RenderMode
    {
        get
        {
            if (_renderer == null)
            {
                throw new InvalidOperationException("No renderer has been initialized.");
            }

            return _renderer.GetComponentRenderMode(_componentId);
        }
    }

    /// <summary>
    /// Gets the <see cref="ResourceAssetCollection"/> associated with the <see cref="Renderer"/>.
    /// </summary>
    public ResourceAssetCollection Assets
    {
        get
        {
            return _renderer?.Assets ?? throw new InvalidOperationException("No renderer has been initialized.");
        }
    }

    /// <summary>
    /// Notifies the renderer that the component should be rendered.
    /// </summary>
    /// <param name="renderFragment">The content that should be rendered.</param>
    public void Render(RenderFragment renderFragment)
    {
        if (_renderer == null)
        {
            ThrowNotInitialized();
        }

        _renderer.AddToRenderQueue(_componentId, renderFragment);
    }

    /// <summary>
    /// Dispatches an <see cref="Exception"/> to the <see cref="Renderer"/>.
    /// </summary>
    /// <param name="exception">The <see cref="Exception"/> that will be dispatched to the renderer.</param>
    /// <returns>A <see cref="Task"/> that will be completed when the exception has finished dispatching.</returns>
    public Task DispatchExceptionAsync(Exception exception)
    {
        var renderer = _renderer;
        var componentId = _componentId;
        return Dispatcher.InvokeAsync(() => renderer!.HandleComponentException(exception, componentId));
    }

    [DoesNotReturn]
    private static void ThrowNotInitialized()
    {
        throw new InvalidOperationException("The render handle is not yet assigned.");
    }
}
