using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Xml.Linq;
using GalaxyBudsClient.Generators.Utils;
using Microsoft.CodeAnalysis;

namespace GalaxyBudsClient.Generators.Localization;

[Generator]
public class LocalizationKeySourceGenerator : ISourceGenerator
{
    public void Initialize(GeneratorInitializationContext context)
    {
    }

    private readonly struct Language(string langCode, string langName)
    {
        public string LangCode { get; } = langCode;
        public string LangName { get; } = langName;
    }

    public void Execute(GeneratorExecutionContext context)
    {
        var languages = new List<Language>();
        
        foreach (var additionalFile in context.AdditionalFiles)
        {
            if (additionalFile == null)
                continue;

            // Check if the file name is the specific file that we expect.
            if (!additionalFile.Path.EndsWith(".axaml") || !additionalFile.Path.Contains("i18n"))
                continue;

            var xaml = additionalFile.GetText();
            if (xaml == null)
                break;

            var doc = XDocument.Parse(xaml.ToString());
            var nodes = doc.Root?.Nodes();
            if(nodes == null)
                break;

            var langCode = Path.GetFileNameWithoutExtension(additionalFile.Path);
            string? englishLangName = null;
            
            var keyClassMembers = new List<string>();
            var stringClassMembers = new List<string>();
            
            var dictionarySource = new CodeGenerator();
            dictionarySource.AppendLines("""
                                         // <auto-generated/>
                                         namespace GalaxyBudsClient.Generated.I18N;
                                         """);
            dictionarySource.EnterScope("internal static partial class LocalizationDictionaries");
            dictionarySource.EnterScope($"private static global::System.Collections.Generic.Dictionary<global::System.String, global::System.String> @{langCode} => new()");
            
            foreach (var node in nodes)
            {
                if (node is not XElement element) 
                    continue;
                
                // Example for xmlNamespace: clr-namespace:System;assembly=mscorlib
                var xmlNamespace = element.Name.NamespaceName.Split(';');
                var namespaceName = xmlNamespace.First().Split(':').Last();
                var assemblyName = xmlNamespace.Last().Split('=').Last();
                var typeName = element.Name.LocalName;
                var englishString = element.Value;

                var key = element.Attributes().First(x => x.Name.LocalName == "Key");
                if (key == null)
                    keyClassMembers.Add($"#warning {additionalFile.Path}: x:Key attribute not found for XAML element of type {namespaceName}.{typeName}");
                else
                {
                    // Generate the key/string class members based on the english translation
                    if (additionalFile.Path.EndsWith("en.axaml"))
                    {
                        // Snake case to Pascal case
                        var memberName = key.Value.Split(["_"], StringSplitOptions.RemoveEmptyEntries)
                            .Select(s => char.ToUpperInvariant(s[0]) + s.Substring(1, s.Length - 1))
                            .Aggregate(string.Empty, (s1, s2) => s1 + s2);

                        if (memberName == null)
                            keyClassMembers.Add(
                                $"#warning {additionalFile.Path}: Failed to convert key to Pascal case for XAML element of type {namespaceName}.{typeName}");
                        else
                        {
                            keyClassMembers.Add($"/** <summary> Resolves to: '{englishString}' </summary> */");
                            keyClassMembers.Add($"public const global::System.String {memberName} = \"{key.Value}\";");
                            stringClassMembers.Add($"/** <summary> Resolves to: '{englishString}' </summary> */");
                            stringClassMembers.Add(
                                $"public static global::System.String {memberName} => Loc.Resolve(Keys.{memberName});");
                            
                            if (englishLangName == null && memberName == "language_name_en")
                                englishLangName = englishString;
                        }
                    }

                    var sanitized = element.Value
                        .Replace(@"\", @"\\")
                        .Replace("\"", "\\\"")
                        .Replace("\r\n", "\n")
                        .Replace('\r', '\n')
                        .Split('\n')
                        .Select(x => x.Trim());
                    dictionarySource.AppendLine($$"""{"{{key.Value}}", "{{string.Join("\n", sanitized).Replace("\n", "\\n")}}"},""");
                }
            }
            
            languages.Add(new Language(langCode, englishLangName ?? langCode));
            
            // Build up the source code.
            if (additionalFile.Path.EndsWith("en.axaml"))
            {
                var keysSource = new CodeGenerator();
                keysSource.AppendLines("""
                                       // <auto-generated/>
                                       namespace GalaxyBudsClient.Generated.I18N;
                                       """);
                keysSource.EnterScope("public static class Keys");
                keyClassMembers.ForEach(keysSource.AppendLine);
                keysSource.LeaveScope();

                var stringsSource = new CodeGenerator();
                stringsSource.AppendLines("""
                                          // <auto-generated/>
                                          using GalaxyBudsClient.Utils.Interface;

                                          namespace GalaxyBudsClient.Generated.I18N;
                                          """);
                stringsSource.EnterScope("public static class Strings");
                stringClassMembers.ForEach(stringsSource.AppendLine);
                stringsSource.LeaveScope();

                // Add the source code to the compilation.
                context.AddSource($"LocalizationKeys.g.cs", keysSource.ToString());
                context.AddSource($"LocalizationStrings.g.cs", stringsSource.ToString());
            }
            
            dictionarySource.LeaveScope(";");
            dictionarySource.LeaveScope();
            
            context.AddSource("LocalizationDictionary_" + langCode + ".g.cs", dictionarySource.ToString());
        }
        
        // Add language dict lookup function
        var lookupSource = new CodeGenerator();
        lookupSource.AppendLines("""
                                 // <auto-generated/>
                                 #nullable enable
                                 namespace GalaxyBudsClient.Generated.I18N;
                                 """);
        lookupSource.EnterScope("internal static partial class LocalizationDictionaries");
        lookupSource.EnterScope("public static global::System.Collections.Generic.Dictionary<global::System.String, global::System.String>? GetByLangCode(global::System.String langCode)");
        lookupSource.EnterScope("return langCode switch");
        foreach (var lang in languages)
        {
            lookupSource.AppendLine($"\"{lang.LangCode}\" => @{lang.LangCode},");
        }
        lookupSource.AppendLine("_ => null,");
        lookupSource.LeaveScope(";");
        lookupSource.LeaveScope();
        lookupSource.LeaveScope();
        
        context.AddSource($"LocalizationDictionaries.g.cs", lookupSource.ToString());
        
                
        // Add locales enum
        // FIXME: Roslyn doesn't support source generation chaining yet. This enum could not be read by the JSON source generator.
        /* var localesSource = new CodeGenerator();
        localesSource.AppendLines("""
                                  // <auto-generated/>
                                  #nullable enable
                                  namespace GalaxyBudsClient.Generated.I18N;

                                  """);
        localesSource.AppendLine("[GalaxyBudsClient.Generated.Model.Attributes.CompiledEnum]");
        localesSource.EnterScope("public enum Locales");
        foreach (var lang in languages)
        {
            localesSource.AppendLine($"[System.ComponentModel.DescriptionAttribute(\"{lang.LangName}\")]");
            localesSource.AppendLine($"@{lang.LangCode},");
        }
        localesSource.AppendLine($"[System.Runtime.Serialization.IgnoreDataMemberAttribute, System.ComponentModel.DescriptionAttribute(\"custom_language.xaml\")]");
        localesSource.AppendLine("@custom,");
        
        localesSource.LeaveScope();
        
        context.AddSource($"Locales.g.cs", localesSource.ToString()); */
    }
}