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

#nullable disable

using System.Linq;
using Microsoft.AspNetCore.Razor.Language.Intermediate;

namespace Microsoft.AspNetCore.Razor.Language.Components;

// We don't support 'complex' content for components (mixed C# and markup) right now.
// It's not clear yet if components will have a good scenario to use these constructs.
//
// This is where a lot of the complexity in the Razor/TagHelpers model creeps in and we
// might be able to avoid it if these features aren't needed.
internal class ComponentComplexAttributeContentPass : ComponentIntermediateNodePassBase, IRazorOptimizationPass
{
    // Run before other Component passes
    public override int Order => -1000;

    protected override void ExecuteCore(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode)
    {
        if (!IsComponentDocument(documentNode))
        {
            return;
        }

        var nodes = documentNode.FindDescendantNodes<TagHelperIntermediateNode>();
        for (var i = 0; i < nodes.Count; i++)
        {
            ProcessAttributes(nodes[i]);
        }
    }

    private void ProcessAttributes(TagHelperIntermediateNode node)
    {
        for (var i = node.Children.Count - 1; i >= 0; i--)
        {
            if (node.Children[i] is TagHelperPropertyIntermediateNode propertyNode)
            {
                if (!TrySimplifyContent(propertyNode) && node.TagHelpers.Any(t => t.IsComponentTagHelper()))
                {
                    node.Diagnostics.Add(ComponentDiagnosticFactory.Create_UnsupportedComplexContent(
                        propertyNode,
                        propertyNode.AttributeName));
                    node.Children.RemoveAt(i);
                    continue;
                }
            }
            else if (node.Children[i] is TagHelperHtmlAttributeIntermediateNode htmlNode)
            {
                if (!TrySimplifyContent(htmlNode) && node.TagHelpers.Any(t => t.IsComponentTagHelper()))
                {
                    node.Diagnostics.Add(ComponentDiagnosticFactory.Create_UnsupportedComplexContent(
                        htmlNode,
                        htmlNode.AttributeName));
                    node.Children.RemoveAt(i);
                    continue;
                }
            }
            else if (node.Children[i] is TagHelperDirectiveAttributeIntermediateNode directiveAttributeNode)
            {
                if (!TrySimplifyContent(directiveAttributeNode))
                {
                    node.Diagnostics.Add(ComponentDiagnosticFactory.Create_UnsupportedComplexContent(
                        directiveAttributeNode,
                        directiveAttributeNode.OriginalAttributeName));
                    node.Children.RemoveAt(i);
                    continue;
                }
            }
        }
    }

    private static bool TrySimplifyContent(IntermediateNode node)
    {
        if (node.Children.Count == 1 &&
            node.Children[0] is HtmlAttributeIntermediateNode htmlNode &&
            htmlNode.Children.Count > 1)
        {
            // This case can be hit for a 'string' attribute
            return false;
        }
        else if (node.Children.Count == 1 &&
            node.Children[0] is CSharpExpressionIntermediateNode cSharpNode &&
            cSharpNode.Children.Count > 1)
        {
            // This case can be hit when the attribute has an explicit @ inside, which
            // 'escapes' any special sugar we provide for codegen.
            //
            // There's a special case here for explicit expressions. See https://github.com/aspnet/Razor/issues/2203
            // handling this case as a tactical matter since it's important for lambdas.
            if (cSharpNode.Children.Count == 3 &&
                cSharpNode.Children[0] is IntermediateToken token0 &&
                cSharpNode.Children[2] is IntermediateToken token2 &&
                token0.Content == "(" &&
                token2.Content == ")")
            {
                cSharpNode.Children.RemoveAt(2);
                cSharpNode.Children.RemoveAt(0);

                // We were able to simplify it, all good.
                return true;
            }

            return false;
        }
        else if (node.Children.Count == 1 &&
            node.Children[0] is CSharpCodeIntermediateNode)
        {
            // This is the case when an attribute contains a code block @{ ... }
            // We don't support this.
            return false;
        }
        else if (node.Children.Count > 1)
        {
            // This is the common case for 'mixed' content
            return false;
        }

        return true;
    }
}
