#nullable enable

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Xml.Linq;

namespace Barotrauma
{
    public interface IImplementsVariants<T> where T : Prefab
    {
        public Identifier VariantOf { get; }

        public T? ParentPrefab { get; set; }

        public void InheritFrom(T parent);
    }

    public static class VariantExtensions
    {
        public static ContentXElement CreateVariantXML(this ContentXElement variantElement, ContentXElement baseElement)
        {
            #warning TODO: fix %ModDir% instances in the base element such that they become %ModDir:BaseMod% if necessary
            return variantElement.Element.CreateVariantXML(baseElement.Element).FromPackage(variantElement.ContentPackage);
        }

        public delegate void VariantXMLChecker(XElement originalElement, XElement? variantElement, XElement result);

        public static XElement CreateVariantXML(this XElement variantElement, XElement baseElement, VariantXMLChecker? checker = null)
        {
            XElement newElement = new XElement(variantElement.Name);
            newElement.Add(baseElement.Attributes());
            newElement.Add(baseElement.Elements());

            ReplaceElement(newElement, variantElement);

            void ReplaceElement(XElement element, XElement replacement)
            {
                XElement originalElement = new XElement(element);

                List<XElement> newElementsFromBase = new List<XElement>(element.Elements());
                List<XElement> elementsToRemove = new List<XElement>();
                foreach (XAttribute attribute in replacement.Attributes())
                {
                    ReplaceAttribute(element, attribute);
                }
                foreach (XElement replacementSubElement in replacement.Elements())
                {
                    int index = replacement.Elements().ToList().FindAll(e => e.Name.ToString().Equals(replacementSubElement.Name.ToString(), StringComparison.OrdinalIgnoreCase)).IndexOf(replacementSubElement);
                    System.Diagnostics.Debug.Assert(index > -1);

                    int i = 0;
                    bool matchingElementFound = false;
                    foreach (var subElement in element.Elements())
                    {
                        if (replacementSubElement.Name.ToString().Equals("clear", StringComparison.OrdinalIgnoreCase))
                        {
                            matchingElementFound = true;
                            newElementsFromBase.Clear();
                            elementsToRemove.AddRange(element.Elements());
                            break;
                        }
                        if (!subElement.Name.ToString().Equals(replacementSubElement.Name.ToString(), StringComparison.OrdinalIgnoreCase)) { continue; }
                        if (i == index)
                        {
                            if (!replacementSubElement.HasAttributes && !replacementSubElement.HasElements)
                            {
                                //if the replacement is empty (no attributes or child elements)
                                //remove the element from the variant
                                elementsToRemove.Add(subElement);
                            }
                            else
                            {
                                ReplaceElement(subElement, replacementSubElement);
                            }
                            matchingElementFound = true;
                            newElementsFromBase.Remove(subElement);
                            break;
                        }
                        i++;
                    }
                    if (!matchingElementFound)
                    {
                        element.Add(replacementSubElement);
                    }
                }
                elementsToRemove.ForEach(e => e.Remove());
                checker?.Invoke(originalElement, replacement, element);
                foreach (XElement newElement in newElementsFromBase)
                {
                    checker?.Invoke(newElement, null, newElement);
                }
            }

            void ReplaceAttribute(XElement element, XAttribute newAttribute)
            {
                XAttribute? existingAttribute = element.Attributes().FirstOrDefault(a => a.Name.ToString().Equals(newAttribute.Name.ToString(), StringComparison.OrdinalIgnoreCase));
                if (existingAttribute == null)
                {
                    element.Add(newAttribute);
                    return;
                }
                float.TryParse(existingAttribute.Value, NumberStyles.Any, CultureInfo.InvariantCulture, out float value);
                if (newAttribute.Value.StartsWith('*'))
                {
                    string multiplierStr = newAttribute.Value.Substring(1, newAttribute.Value.Length - 1);
                    float.TryParse(multiplierStr, NumberStyles.Any, CultureInfo.InvariantCulture, out float multiplier);
                    if (multiplierStr.Contains('.') || existingAttribute.Value.Contains('.'))
                    {
                        existingAttribute.Value = (value * multiplier).ToString("G", CultureInfo.InvariantCulture);
                    }
                    else
                    {
                        existingAttribute.Value = ((int)(value * multiplier)).ToString();
                    }
                }
                else if (newAttribute.Value.StartsWith('+'))
                {
                    string additionStr = newAttribute.Value.Substring(1, newAttribute.Value.Length - 1);
                    float.TryParse(additionStr, NumberStyles.Any, CultureInfo.InvariantCulture, out float addition);
                    if (additionStr.Contains('.') || existingAttribute.Value.Contains('.'))
                    {
                        existingAttribute.Value = (value + addition).ToString("G", CultureInfo.InvariantCulture);
                    }
                    else
                    {
                        existingAttribute.Value = ((int)(value + addition)).ToString();
                    }
                }
                else
                {
                    existingAttribute.Value = newAttribute.Value;
                }
            }

            return newElement;
        }
        
    }
}