using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Microsoft.CodeAnalysis;

namespace AscNet.Table
{
    [Generator]
    public class TableGenerator : ISourceGenerator
    {
        public void Execute(GeneratorExecutionContext context)
        {
            List<object> fails = new();
            foreach (var table in context.AdditionalFiles.Where(x => x.Path.EndsWith(".tsv")))
            {
                try
                {
                    string ns = string.Join("", Path.GetDirectoryName(table.Path)?.Split(new string[] { "table" }, StringSplitOptions.None).Skip(1)!)
                        .Replace('\\', '/').Replace('/', '.');

                    string fileTsv = table.GetText()?.ToString() ?? string.Empty;

                    IEnumerable<string> tsvLines = fileTsv.Split(new string[] { "\r\n", "\n" }, StringSplitOptions.RemoveEmptyEntries);

                    string[] nameList = tsvLines.First().Split('\t');
                    List<string[]> resolvedTypes = new();

                    foreach (var items in tsvLines.Skip(1).Select(x => x.Split('\t')))
                    {
                        List<string> types = new();
                        foreach (var item in items)
                        {
                            if (string.IsNullOrEmpty(item))
                            {
                                types.Add("global::System.String");
                                continue;
                            }
                            else if (int.TryParse(item, out var res))
                            {
                                types.Add("global::System.Int32");
                                continue;
                            }
                            types.Add("global::System.String");
                        }
                        resolvedTypes.Add([.. types]);
                    }

                    Dictionary<string, string> properties = new();
                    Dictionary<string, int> listCount = new();

                    for (int i = 0; i < nameList.Length; i++)
                    {
                        string headName = nameList[i];
                        if (string.IsNullOrEmpty(headName))
                            continue;

                        string[] types = resolvedTypes.Select(x => x[i]).ToArray();
                        string propName = headName.Split('[').First();


                        string resolvedType = types.All(x => x.Equals(types.First())) ? types.First() : "global::System.String";
                        if (headName.Contains('['))
                        {
                            if (listCount.ContainsKey(propName))
                                listCount[propName] += 1;
                            else
                                listCount.Add(propName, 1);
                            resolvedType = $"global::System.Collections.Generic.List<{resolvedType}>";
                        }

                        if (!properties.ContainsKey(propName))
                            properties.Add(propName, $"public {resolvedType} {propName} {{ get; set; }}");
                    }

                    string file = $@"// <auto-generated/>
namespace AscNet.Table{ns}
{{
    #nullable enable
    #pragma warning disable CS8618, CS8602 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
    public class {Path.GetFileName(table.Path).Split('.').First()}Table
    {{
        {string.Join("\r\n\t\t", properties.Values)}
    }}

    public class {Path.GetFileName(table.Path).Split('.').First()}TableReader : global::AscNet.Common.Util.TableReader<{Path.GetFileName(table.Path).Split('.').First()}TableReader, {Path.GetFileName(table.Path).Split('.').First()}Table>
    {{
        protected override string FilePath {{ get {{ return ""{string.Join("", table.Path.Replace("\\", "/").Split(new string[] {"/Resources/"}, StringSplitOptions.None).Skip(1))}""; }} }}

        public override void Load() 
        {{
            string tsvStr = global::System.IO.File.ReadAllText(FilePath);
            string[] tsvValues = tsvStr.Split('\t');
            string[] csvValues = new string[tsvValues.Length];
            for (int i = 0; i < tsvValues.Length; i++)
            {{
                if (tsvValues[i].Contains("",""))
                {{
                    csvValues[i] = ""\"""" + tsvValues[i].Replace(""\"""", ""\""\"""") + ""\"""";
                }}
                else
                {{
                    csvValues[i] = tsvValues[i];
                }}
            }}
            using var reader = new global::System.IO.StringReader(string.Join("","", csvValues));
            using var csv = new global::CsvHelper.CsvReader(reader, new global::CsvHelper.Configuration.CsvConfiguration(global::System.Globalization.CultureInfo.InvariantCulture) {{ BadDataFound = null, HeaderValidated = null, MissingFieldFound = null }});
            csv.Context.RegisterClassMap<{Path.GetFileName(table.Path).Split('.').First()}TableMap>();
            All = csv.GetRecords<{Path.GetFileName(table.Path).Split('.').First()}Table>().ToList();
        }}
{string.Join("\r\n", properties.Values.Select(x => x.Split(' ')).Where(x => !x[1].Contains("<")).Select(property => $@"
        public {Path.GetFileName(table.Path).Split('.').First()}Table? From{property[2]}({property[1]} val)
        {{
            return All.FirstOrDefault(x => x.{property[2]} == val);
        }}
"))}
    }}

    public sealed class {Path.GetFileName(table.Path).Split('.').First()}TableMap : global::CsvHelper.Configuration.ClassMap<{Path.GetFileName(table.Path).Split('.').First()}Table>
    {{
        public {Path.GetFileName(table.Path).Split('.').First()}TableMap()
        {{
            {string.Join("\r\n\t\t\t", properties.Keys.Where(x => !listCount.ContainsKey(x)).Select(x => $"Map(m => m.{x}).Name(\"{x}\");"))}
{string.Join("\r\n\t\t\t", listCount.Keys.Select(x => $@"
            Map(m => m.{x}).Convert(args =>
            {{
                {properties[x].Split(' ')[1]} tags = new {properties[x].Split(' ')[1]}();
                {(nameList.Contains($"{x}[0]") ? $"for (int i = 0; i < {listCount[x]}; i++)" : $"for (int i = 1; i <= {listCount[x]}; i++)")}
                {{
                    string? tagValue = args.Row.GetField<string>($""{x}[{{i}}]"");
                    if (!string.IsNullOrEmpty(tagValue))
                    {{
{(properties[x].Split('<')[1].StartsWith("global::System.Int32") ? @"
                        if (int.TryParse(tagValue, out int tag))
                        {
                            tags.Add(tag);
                        }": @"
                        tags.Add(tagValue);
")}
                    }}
                }}

                return tags;
            }});
"))}
        }}
    }}
}}";
                    context.AddSource($"AscNet.Table{ns}.{Path.GetFileName(table.Path).Split('.').First()}.g.cs", file);
                }
                catch (Exception ex)
                {
                    fails.Add(new
                    {
                        msg = ex.ToString(),
                        file = table.Path
                    });
                }
            }

           //  File.WriteAllText("./generator_fails.json", JsonConvert.SerializeObject(fails, Formatting.Indented));
        }

        public void Initialize(GeneratorInitializationContext context) { }
    }
}