// 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 Microsoft.AspNetCore.Razor.Language.Intermediate;

namespace Microsoft.AspNetCore.Razor.Language.Components
{
	internal sealed class ComponentMarkupDiagnosticPass : ComponentIntermediateNodePassBase, IRazorOptimizationPass
	{
		public const int DefaultOrder = 10000;

		public override int Order => DefaultOrder;

		protected override void ExecuteCore( RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode )
		{
			var visitor = new Visitor();
			visitor.Visit( documentNode );
		}

		private class Visitor : IntermediateNodeWalker
		{
			private readonly Dictionary<string, (string name, IntermediateNode node)> _attributes = new Dictionary<string, (string, IntermediateNode)>( StringComparer.OrdinalIgnoreCase );

			public override void VisitMarkupElement( MarkupElementIntermediateNode node )
			{
				for ( var i = 0; i < node.Children.Count; i++ )
				{
					if ( node.Children[i] is HtmlAttributeIntermediateNode attribute && attribute.AttributeName != null )
					{
						if ( _attributes.TryGetValue( attribute.AttributeName, out var other ) )
						{
							// As a special case we want to point it out explicitly where a directive or other construct
							// has emitted an attribute that causes a conflict. We're already looking at the lowered version
							// of this construct, so it's easy to detect. We just need the original name to report the issue.
							//
							// Example: `bind-value` will set `value` and `onchange`.

							var originalAttributeName =
								attribute.Annotations[ComponentMetadata.Common.OriginalAttributeName] as string ??
								other.node.Annotations[ComponentMetadata.Common.OriginalAttributeName] as string;
							if ( originalAttributeName != null )
							{
								other.node.Diagnostics.Add( ComponentDiagnosticFactory.Create_DuplicateMarkupAttributeDirective(
									other.name,
									originalAttributeName,
									other.node.Source ?? node.Source ) );
							}
							else
							{
								// This is a conflict in the code the user wrote.
								other.node.Diagnostics.Add( ComponentDiagnosticFactory.Create_DuplicateMarkupAttribute(
									other.name,
									other.node.Source ?? node.Source ) );
							}
						}

						// Replace the attribute we were previously tracking. Then if you have three, the two on the left will have
						// diagnostics.
						_attributes[attribute.AttributeName] = (attribute.AttributeName, attribute);
					}
				}

				_attributes.Clear();
				base.VisitMarkupElement( node );
			}

			public override void VisitComponent( ComponentIntermediateNode node )
			{
				for ( var i = 0; i < node.Children.Count; i++ )
				{
					// Note that we don't handle ChildContent cases here. Those have their own pass for diagnostics.
					if ( node.Children[i] is ComponentAttributeIntermediateNode attribute && attribute.AttributeName != null )
					{
						if ( _attributes.TryGetValue( attribute.AttributeName, out var other ) )
						{
							// As a special case we want to point it out explicitly where a directive or other construct
							// has emitted an attribute that causes a conflict. We're already looking at the lowered version
							// of this construct, so it's easy to detect. We just need the original name to report the issue.
							//
							// Example: `bind-Value` will set `Value` and `ValueChanged`.
							var originalAttributeName =
								attribute.Annotations[ComponentMetadata.Common.OriginalAttributeName] as string ??
								other.node.Annotations[ComponentMetadata.Common.OriginalAttributeName] as string;
							if ( originalAttributeName != null )
							{
								other.node.Diagnostics.Add( ComponentDiagnosticFactory.Create_DuplicateComponentParameterDirective(
									other.name,
									originalAttributeName,
									other.node.Source ?? node.Source ) );
							}
							else
							{
								// This is a conflict in the code the user wrote.
								other.node.Diagnostics.Add( ComponentDiagnosticFactory.Create_DuplicateComponentParameter(
									other.name,
									other.node.Source ?? node.Source ) );
							}
						}

						// Replace the attribute we were previously tracking. Then if you have three, the two on the left will have
						// diagnostics.
						_attributes[attribute.AttributeName] = (attribute.AttributeName, attribute);
					}
				}

				_attributes.Clear();
				base.VisitComponent( node );
			}
		}
	}
}
