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

using System;
using System.Globalization;
using System.Threading;

namespace Microsoft.AspNetCore.Components
{
    /// <summary>
    /// Represents a reference to a rendered element.
    /// </summary>
    public readonly struct ElementReference
    {
        private static long _nextIdForWebAssemblyOnly = 1;

        /// <summary>
        /// Gets a unique identifier for <see cref="ElementReference" />.
        /// </summary>
        /// <remarks>
        /// The Id is unique at least within the scope of a given user/circuit.
        /// This property is public to support Json serialization and should not be used by user code.
        /// </remarks>
        public string Id { get; }

        /// <summary>
        /// Gets the <see cref="ElementReferenceContext"/> instance.
        /// </summary>
        public ElementReferenceContext? Context { get; }

        /// <summary>
        /// Instantiates a new <see cref="ElementReference" />.
        /// </summary>
        /// <param name="id">A unique identifier for this <see cref="ElementReference"/>.</param>
        /// <param name="context">The nullable <see cref="ElementReferenceContext"/> instance.</param>
        public ElementReference(string id, ElementReferenceContext? context)
        {
            Id = id;
            Context = context;
        }

        /// <summary>
        /// Instantiates a new <see cref="ElementReference"/>.
        /// </summary>
        /// <param name="id">A unique identifier for this <see cref="ElementReference"/>.</param>
        public ElementReference(string id) : this(id, null)
        {
        }

        internal static ElementReference CreateWithUniqueId(ElementReferenceContext? context)
            => new ElementReference(CreateUniqueId(), context);

        private static string CreateUniqueId()
        {
            if (OperatingSystem.IsBrowser())
            {
                // On WebAssembly there's only one user, so it's fine to expose the number
                // of IDs that have been assigned, and this is cheaper than creating a GUID.
                // It's unfortunate that this still involves a heap allocation. If that becomes
                // a problem we could extend RenderTreeFrame to have both "string" and "long"
                // fields for ElementRefCaptureId, of which only one would be in use depending
                // on the platform.
                var id = Interlocked.Increment(ref _nextIdForWebAssemblyOnly);
                return id.ToString(CultureInfo.InvariantCulture);
            }
            else
            {
                // For remote rendering, it's important not to disclose any cross-user state,
                // such as the number of IDs that have been assigned.
                return Guid.NewGuid().ToString("D", CultureInfo.InvariantCulture);
            }
        }
    }
}
