// 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.Globalization;
using System.IO;

#nullable enable

namespace Ignitor
{
    internal static class NodeSerializer
    {
        public static string Serialize(ElementHive hive)
        {
            using (var writer = new StringWriter())
            {
                var serializer = new Serializer(writer);
                serializer.SerializeHive(hive);
                return writer.ToString();
            }
        }

        public static string Serialize(Node node)
        {
            using (var writer = new StringWriter())
            {
                var serializer = new Serializer(writer);
                serializer.Serialize(node);
                return writer.ToString();
            }
        }

        private class Serializer
        {
            private readonly TextWriter _writer;
            private int _depth;
            private bool _atStartOfLine;
            private readonly HashSet<Node> _visited = new HashSet<Node>();

            public Serializer(TextWriter writer)
            {
                _writer = writer ?? throw new ArgumentNullException(nameof(writer));
            }

            public void SerializeHive(ElementHive hive)
            {
                foreach (var kvp in hive.Components)
                {
                    Serialize(kvp.Value);
                }
            }

            public void Serialize(Node node)
            {
                if (!_visited.Add(node))
                {
                    // This is a child component of a previously seen component. Don't repeat it
                    return;
                }

                switch (node)
                {
                    case ComponentNode componentNode:
                        {
                            SerializeComponent(componentNode);
                            break;
                        }
                    case ElementNode elementNode:
                        {
                            SerializeElement(elementNode);
                            break;
                        }
                    case TextNode textNode:
                        {
                            SerializeTextNode(textNode);
                            break;
                        }
                    case MarkupNode markupNode:
                        {
                            SerializeMarkupNode(markupNode);
                            break;
                        }
                    case ContainerNode containerNode:
                        {
                            SerializeChildren(containerNode);
                            break;
                        }
                    default:
                        {
                            Write("--- UNKNOWN (");
                            Write(node.GetType().ToString());
                            WriteLine(") ---");
                            break;
                        }
                }
            }

            private void SerializeMarkupNode(MarkupNode markupNode)
            {
                Write("M: ");
                WriteLine(markupNode.MarkupContent.Replace(Environment.NewLine, "\\r\\n"));
            }

            private void SerializeTextNode(TextNode textNode)
            {
                Write("T: ");
                WriteLine(textNode.TextContent);
            }

            private void SerializeElement(ElementNode elementNode)
            {
                Write("<");
                Write(elementNode.TagName);

                foreach (var attribute in elementNode.Attributes)
                {
                    Write(" ");
                    Write(attribute.Key);

                    if (attribute.Value != null)
                    {
                        Write("=\"");
                        Write(attribute.Value.ToString()!);
                        Write("\"");
                    }
                }

                if (elementNode.Properties.Count > 0)
                {
                    Write("  Properties: [");

                    foreach (var properties in elementNode.Properties)
                    {
                        Write(" ");
                        Write(properties.Key);

                        if (properties.Value != null)
                        {
                            Write("=\"");
                            Write(properties.Value.ToString()!);
                            Write("\"");
                        }
                    }
                    Write("]");
                }

                if (elementNode.Events.Count > 0)
                {
                    Write("  Events: [");

                    foreach (var evt in elementNode.Events)
                    {
                        Write(" ");
                        Write(evt.Value.EventName);
                        Write("(");
                        Write(evt.Value.EventId.ToString(CultureInfo.InvariantCulture));
                        Write(")");
                    }
                    Write("]");
                }

                WriteLine(">");

                _depth++;
                SerializeChildren(elementNode);
                _depth--;
                Write("</");
                Write(elementNode.TagName);
                WriteLine("/>");
            }

            private void SerializeChildren(ContainerNode containerNode)
            {
                for (var i = 0; i < containerNode.Children.Count; i++)
                {
                    Serialize(containerNode.Children[i]);
                }
            }

            private void SerializeComponent(ComponentNode component)
            {
                Write("[Component ( ");
                Write(component.ComponentId.ToString(CultureInfo.InvariantCulture));
                WriteLine(" )]");
                _depth++;
                SerializeChildren(component);
                _depth--;
            }

            private void Write(string content)
            {
                if (_atStartOfLine)
                {
                    WriteIndent();
                }

                _writer.Write(content);

                _atStartOfLine = false;
            }

            private void WriteLine(string content)
            {
                if (_atStartOfLine)
                {
                    WriteIndent();
                }

                _writer.WriteLine(content);
                _atStartOfLine = true;
            }

            private void WriteIndent()
            {
                var indent = new string(' ', _depth * 4);
                _writer.Write(indent);
            }
        }
    }
}
#nullable restore
