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

namespace Microsoft.AspNetCore.Razor.Language.Intermediate;

internal class IntermediateNodeFormatterBase : IntermediateNodeFormatter
{
    private string _content;
    private readonly Dictionary<string, string> _properties = new Dictionary<string, string>(StringComparer.Ordinal);

    protected FormatterContentMode ContentMode { get; set; }

    protected bool IncludeSource { get; set; }

    protected TextWriter Writer { get; set; }

    public override void WriteChildren(IntermediateNodeCollection children)
    {
        if (children == null)
        {
            throw new ArgumentNullException(nameof(children));
        }

        Writer.Write(" ");
        Writer.Write("\"");
        for (var i = 0; i < children.Count; i++)
        {
            var child = children[i] as IntermediateToken;
            if (child != null)
            {
                Writer.Write(EscapeNewlines(child.Content));
            }
        }
        Writer.Write("\"");
    }

    public override void WriteContent(string content)
    {
        if (content == null)
        {
            return;
        }

        _content = EscapeNewlines(content);
    }

    public override void WriteProperty(string key, string value)
    {
        if (key == null)
        {
            throw new ArgumentNullException(nameof(key));
        }

        if (value == null)
        {
            return;
        }

        _properties.Add(key, EscapeNewlines(value));
    }

    public void FormatNode(IntermediateNode node)
    {
        if (node == null)
        {
            throw new ArgumentNullException(nameof(node));
        }

        BeginNode(node);
        node.FormatNode(this);
        EndNode(node);
    }

    public void FormatTree(IntermediateNode node)
    {
        if (node == null)
        {
            throw new ArgumentNullException(nameof(node));
        }

        var visitor = new FormatterVisitor(this);
        visitor.Visit(node);
    }

    private void BeginNode(IntermediateNode node)
    {
        Writer.Write(GetShortName(node));

        if (IncludeSource)
        {
            Writer.Write(" ");
            Writer.Write(node.Source?.ToString() ?? "(n/a)");
        }

        static string GetShortName(IntermediateNode node)
        {
            var typeName = node.GetType().Name;
            var suffix = nameof(IntermediateNode);

            return typeName.EndsWith(suffix, StringComparison.Ordinal)
                ? typeName[..^suffix.Length].ToString()
                : typeName;
        }
    }

    private void EndNode(IntermediateNode node)
    {
        if (_content != null && (_properties.Count == 0 || ContentMode == FormatterContentMode.PreferContent))
        {
            Writer.Write(" ");
            Writer.Write("\"");
            Writer.Write(EscapeNewlines(_content));
            Writer.Write("\"");
        }

        if (_properties.Count > 0 && (_content == null || ContentMode == FormatterContentMode.PreferProperties))
        {
            Writer.Write(" ");
            Writer.Write("{ ");
            Writer.Write(string.Join(", ", _properties.Select(kvp => $"{kvp.Key}: \"{kvp.Value}\"")));
            Writer.Write(" }");
        }

        _content = null;
        _properties.Clear();
    }

    private string EscapeNewlines(string content)
    {
        return content.Replace("\r", "\\r").Replace("\n", "\\n").Replace("\t", "\\t");
    }

    // Depending on the usage of the formatter we might prefer thoroughness (properties)
    // or brevity (content). Generally if a node has a single string that provides value
    // it has content.
    //
    // Some nodes have neither: TagHelperBody
    // Some nodes have content: HtmlContent
    // Some nodes have properties: Document
    // Some nodes have both: TagHelperProperty
    protected enum FormatterContentMode
    {
        PreferContent,
        PreferProperties,
    }

    protected class FormatterVisitor : IntermediateNodeWalker
    {
        private const int IndentSize = 2;

        private readonly IntermediateNodeFormatterBase _formatter;
        private int _indent;

        public FormatterVisitor(IntermediateNodeFormatterBase formatter)
        {
            _formatter = formatter;
        }

        public override void VisitDefault(IntermediateNode node)
        {
            // Indent
            for (var i = 0; i < _indent; i++)
            {
                _formatter.Writer.Write(' ');
            }
            _formatter.FormatNode(node);
            _formatter.Writer.WriteLine();

            // Process children
            _indent += IndentSize;
            base.VisitDefault(node);
            _indent -= IndentSize;
        }
    }
}
