// 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.Collections.Generic;
using System.Linq;

namespace Microsoft.AspNetCore.Razor.Language
{
	internal class DefaultTagHelperDescriptorBuilder : TagHelperDescriptorBuilder
	{
		// Required values
		private readonly Dictionary<string, string> _metadata;

		private List<DefaultAllowedChildTagDescriptorBuilder> _allowedChildTags;
		private List<DefaultBoundAttributeDescriptorBuilder> _attributeBuilders;
		private List<DefaultTagMatchingRuleDescriptorBuilder> _tagMatchingRuleBuilders;
		private RazorDiagnosticCollection _diagnostics;

		public DefaultTagHelperDescriptorBuilder( string kind, string name, string assemblyName )
		{
			Kind = kind;
			Name = name;
			AssemblyName = assemblyName;

			_metadata = new Dictionary<string, string>( StringComparer.Ordinal );

			// Tells code generation that these tag helpers are compatible with ITagHelper.
			// For now that's all we support.
			_metadata.Add( TagHelperMetadata.Runtime.Name, TagHelperConventions.DefaultKind );
		}

		public override string Name { get; }

		public override string AssemblyName { get; }

		public override string Kind { get; }

		public override string DisplayName { get; set; }

		public override string TagOutputHint { get; set; }

		public override bool CaseSensitive { get; set; }

		public override string Documentation { get; set; }

		public override IDictionary<string, string> Metadata => _metadata;

		public override RazorDiagnosticCollection Diagnostics
		{
			get
			{
				if ( _diagnostics == null )
				{
					_diagnostics = new RazorDiagnosticCollection();
				}

				return _diagnostics;
			}
		}

		public override IReadOnlyList<AllowedChildTagDescriptorBuilder> AllowedChildTags
		{
			get
			{
				EnsureAllowedChildTags();

				return _allowedChildTags;
			}
		}

		public override IReadOnlyList<BoundAttributeDescriptorBuilder> BoundAttributes
		{
			get
			{
				EnsureAttributeBuilders();

				return _attributeBuilders;
			}
		}

		public override IReadOnlyList<TagMatchingRuleDescriptorBuilder> TagMatchingRules
		{
			get
			{
				EnsureTagMatchingRuleBuilders();

				return _tagMatchingRuleBuilders;
			}
		}

		public override void AllowChildTag( Action<AllowedChildTagDescriptorBuilder> configure )
		{
			if ( configure == null )
			{
				throw new ArgumentNullException( nameof( configure ) );
			}

			EnsureAllowedChildTags();

			var builder = new DefaultAllowedChildTagDescriptorBuilder( this );
			configure( builder );
			_allowedChildTags.Add( builder );
		}

		public override void BindAttribute( Action<BoundAttributeDescriptorBuilder> configure )
		{
			if ( configure == null )
			{
				throw new ArgumentNullException( nameof( configure ) );
			}

			EnsureAttributeBuilders();

			var builder = new DefaultBoundAttributeDescriptorBuilder( this, Kind );
			configure( builder );
			_attributeBuilders.Add( builder );
		}

		public override void TagMatchingRule( Action<TagMatchingRuleDescriptorBuilder> configure )
		{
			if ( configure == null )
			{
				throw new ArgumentNullException( nameof( configure ) );
			}

			EnsureTagMatchingRuleBuilders();

			var builder = new DefaultTagMatchingRuleDescriptorBuilder( this );
			configure( builder );
			_tagMatchingRuleBuilders.Add( builder );
		}

		public override TagHelperDescriptor Build()
		{
			var diagnostics = new HashSet<RazorDiagnostic>();
			if ( _diagnostics != null )
			{
				diagnostics.UnionWith( _diagnostics );
			}

			var allowedChildTags = Array.Empty<AllowedChildTagDescriptor>();
			if ( _allowedChildTags != null )
			{
				var allowedChildTagsSet = new HashSet<AllowedChildTagDescriptor>( AllowedChildTagDescriptorComparer.Default );
				for ( var i = 0; i < _allowedChildTags.Count; i++ )
				{
					allowedChildTagsSet.Add( _allowedChildTags[i].Build() );
				}

				allowedChildTags = allowedChildTagsSet.ToArray();
			}

			var tagMatchingRules = Array.Empty<TagMatchingRuleDescriptor>();
			if ( _tagMatchingRuleBuilders != null )
			{
				var tagMatchingRuleSet = new HashSet<TagMatchingRuleDescriptor>( TagMatchingRuleDescriptorComparer.Default );
				for ( var i = 0; i < _tagMatchingRuleBuilders.Count; i++ )
				{
					tagMatchingRuleSet.Add( _tagMatchingRuleBuilders[i].Build() );
				}

				tagMatchingRules = tagMatchingRuleSet.ToArray();
			}

			var attributes = Array.Empty<BoundAttributeDescriptor>();
			if ( _attributeBuilders != null )
			{
				var attributeSet = new HashSet<BoundAttributeDescriptor>( BoundAttributeDescriptorComparer.Default );
				for ( var i = 0; i < _attributeBuilders.Count; i++ )
				{
					attributeSet.Add( _attributeBuilders[i].Build() );
				}

				attributes = attributeSet.ToArray();
			}

			var descriptor = new DefaultTagHelperDescriptor(
				Kind,
				Name,
				AssemblyName,
				GetDisplayName(),
				Documentation,
				TagOutputHint,
				CaseSensitive,
				tagMatchingRules,
				attributes,
				allowedChildTags,
				new Dictionary<string, string>( _metadata ),
				diagnostics.ToArray() );

			return descriptor;
		}

		public override void Reset()
		{
			Documentation = null;
			TagOutputHint = null;
			_allowedChildTags?.Clear();
			_attributeBuilders?.Clear();
			_tagMatchingRuleBuilders?.Clear();
			_metadata.Clear();
			_diagnostics?.Clear();
		}

		public string GetDisplayName()
		{
			if ( DisplayName != null )
			{
				return DisplayName;
			}

			return this.GetTypeName() ?? Name;
		}

		private void EnsureAllowedChildTags()
		{
			if ( _allowedChildTags == null )
			{
				_allowedChildTags = new List<DefaultAllowedChildTagDescriptorBuilder>();
			}
		}

		private void EnsureAttributeBuilders()
		{
			if ( _attributeBuilders == null )
			{
				_attributeBuilders = new List<DefaultBoundAttributeDescriptorBuilder>();
			}
		}

		private void EnsureTagMatchingRuleBuilders()
		{
			if ( _tagMatchingRuleBuilders == null )
			{
				_tagMatchingRuleBuilders = new List<DefaultTagMatchingRuleDescriptorBuilder>();
			}
		}
	}
}
