using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.RegularExpressions;

using Microsoft.Extensions.Logging;

namespace File_Folder_Helper.ADO2025.PI5;

internal static partial class Helper20250320 {

    private record Match(string Name,
                         string Parameters,
                         string Result,
                         string Scope,
                         string Static,
                         string Value,
                         string Async,
                         string Partial);

    private record Search(string Constructor,
                          string Delegate,
                          string Name,
                          string Not,
                          string Wrap);

    private record Method(int? EndLine,
                          ReadOnlyDictionary<string, string> Parameters,
                          string FirstLine,
                          int I,
                          string Line,
                          Match Match,
                          ReadOnlyCollection<int> ReferenceToLineNumbers,
                          int? ScopeEnum,
                          Search Search,
                          int StartLine) {

        public override string ToString() {
            string result = JsonSerializer.Serialize(this, MethodCollectionCommonSourceGenerationContext.Default.Method);
            return result;
        }

    }

    [JsonSourceGenerationOptions(WriteIndented = true)]
    [JsonSerializable(typeof(Method[]))]
    private partial class MethodCollectionCommonSourceGenerationContext : JsonSerializerContext {
    }

    private record MethodWith(int? EndLine,
                              ReadOnlyDictionary<string, string> Parameters,
                              string FirstLine,
                              string Line,
                              Match Match,
                              ReadOnlyCollection<MethodWith> References,
                              ReadOnlyCollection<int> ReferenceToLineNumbers,
                              int? ScopeEnum,
                              Search Search,
                              int StartLine) {

        public override string ToString() {
            string result = JsonSerializer.Serialize(this, MethodCollectionCommonSourceGenerationContext.Default.Method);
            return result;
        }

    }

    [JsonSourceGenerationOptions(WriteIndented = true)]
    [JsonSerializable(typeof(MethodWith[]))]
    private partial class MethodWithCollectionCommonSourceGenerationContext : JsonSerializerContext {
    }

    private const string _Name = "name";
    private const string _Async = "async";
    private const string _Scope = "scope";
    private const string _Result = "result";
    private const string _Static = "static";
    private const string _Partial = "partial";
    private const string _Parameters = "parameters";

    [GeneratedRegex(@"[[\]<,>?a-zA-Z0-9_()\s]*?\s[a-z_]{1}[a-zA-Z0-9_]*?,")]
    private static partial Regex CSharpParameter();

    // VSCode Search  ^\s*\b(?<scope>public|private|internal|protected|\sI[a-zA-Z0-9_]*\.)\s?\b(?<static>static)?\s?\b(?<partial>partial)?\s?\b(?<async>async)?\s?\b(?<result>[\[\]\.\?<,>a-zA-Z0-9_()\s]*?)\s?\b(?<name>[A-Z_]{1}[a-zA-Z0-9_])+\((?<parameters>.*)\)\s?\{?$
    [GeneratedRegex(@"^\s*\b(?<scope>public|private|internal|protected|\sI[a-zA-Z0-9_]*\.)\s?\b(?<static>static)?\s?\b(?<partial>partial)?\s?\b(?<async>async)?\s?\b(?<result>[\[\]\.\?<,>a-zA-Z0-9_()\s]*?)\s?\b(?<name>[A-Z_]{1}[a-zA-Z0-9_]*)+\((?<parameters>.*)\)\s?\{?$")]
    private static partial Regex CSharpMethodLine();

    private static ReadOnlyCollection<Method> GetSortedMethods(ReadOnlyCollection<Method> methods) =>
        (from l in methods orderby l.ScopeEnum descending, l.ReferenceToLineNumbers.Count descending, l.Line.Length, l.Match.Name.Length, l.Match.Name select l).ToArray().AsReadOnly();

    internal static void SortCodeMethods(ILogger<Worker> logger, List<string> args, CancellationToken cancellationToken) {
        bool check;
        string[] lines;
        List<string> changed = [];
        bool usePathCombine = true;
        long ticks = DateTime.Now.Ticks;
        bool logOnly = bool.Parse(args[2]);
        int scopeSpaces = int.Parse(args[3]);
        logger.LogInformation("{ticks}", ticks);
        string repositoryDirectory = Path.GetFullPath(args[0]);
        string[] cSharpFiles = Directory.GetFiles(repositoryDirectory, "*.cs", SearchOption.AllDirectories);
        ReadOnlyCollection<string> gitOthersModifiedAndDeletedExcludingStandardFiles = logOnly ? new(cSharpFiles) : Helpers.HelperGit.GetOthersModifiedAndDeletedExcludingStandardFiles(repositoryDirectory, usePathCombine, cancellationToken);
        foreach (string cSharpFile in cSharpFiles) {
            if (!gitOthersModifiedAndDeletedExcludingStandardFiles.Contains(cSharpFile))
                continue;
            for (int i = 0; i < 10; i++) {
                lines = File.ReadAllLines(cSharpFile);
                check = SortFile(logger, logOnly, scopeSpaces, cSharpFile, lines);
                if (check) {
                    Thread.Sleep(500);
                    changed.Add($"{i + 1:00}) {cSharpFile}");
                }
                if (logOnly || !check)
                    break;
            }
        }
        if (changed.Count == 0)
            logger.LogInformation("No changes :)");
        else {
            changed.Reverse();
            foreach (string c in changed)
                logger.LogInformation(c);
        }
    }

    private static bool SortFile(ILogger<Worker> logger, bool logOnly, int scopeSpaces, string cSharpFile, string[] lines) {
        bool result;
        ReadOnlyCollection<Method> methods = GetMethods(logger, scopeSpaces, cSharpFile, lines);
        if (methods.Count == 0)
            result = false;
        else if (methods.Any(l => l.EndLine is null))
            result = false;
        else if (logOnly) {
            foreach (Method method in methods)
                logger.LogInformation("{cSharpFile} - {Name} has {lines} line(s)", cSharpFile, method.Match.Name, (method.EndLine is null ? 999999 : method.EndLine.Value - method.StartLine).ToString("000000"));
            result = false;
        } else {
            ReadOnlyCollection<Method> sortedMethods = GetSortedMethods(methods);
            if (Debugger.IsAttached)
                File.WriteAllText(Path.Combine(".vscode", "helper", ".json"), JsonSerializer.Serialize(sortedMethods.ToArray(), MethodCollectionCommonSourceGenerationContext.Default.MethodArray));
            ReadOnlyCollection<MethodWith> collection = GetCollection(logger, lines, sortedMethods);
            result = WriteAllLines(cSharpFile, lines, collection);
        }
        return result;
    }

    private static ReadOnlyCollection<Method> GetMethods(ILogger<Worker> logger, int scopeSpaces, string cSharpFile, string[] lines) {
        List<Method> results = [];
        int check;
        int blocks;
        bool isLinq;
        Match match;
        string line;
        int? endLine;
        int startLine;
        Method method;
        Search search;
        int? scopeEnum;
        string firstLine;
        string innerLine;
        string lineSegmentFirst;
        List<int> referenceToLineNumbers;
        Regex parameterRegex = CSharpParameter();
        ReadOnlyDictionary<string, string> parameters;
        System.Text.RegularExpressions.Match regularExpressionsMatch;
        for (int i = 0; i < lines.Length; i++) {
            check = GetNumberOfStartSpaces(lines, i);
            if (check != scopeSpaces)
                continue;
            line = lines[i].Trim();
            if (string.IsNullOrEmpty(line))
                continue;
            if (line.Length < 5)
                continue;
            if (line.EndsWith(','))
                continue;
            regularExpressionsMatch = CSharpMethodLine().Match(line);
            if (!regularExpressionsMatch.Success)
                continue;
            match = new(Async: regularExpressionsMatch.Groups[_Async].Value,
                        Name: regularExpressionsMatch.Groups[_Name].Value,
                        Parameters: regularExpressionsMatch.Groups[_Parameters].Value,
                        Partial: regularExpressionsMatch.Groups[_Partial].Value,
                        Result: regularExpressionsMatch.Groups[_Result].Value,
                        Scope: regularExpressionsMatch.Groups[_Scope].Value,
                        Static: regularExpressionsMatch.Groups[_Static].Value,
                        Value: regularExpressionsMatch.Value);
            scopeEnum = GetScopeEnum(match);
            parameters = GetParameters(parameterRegex, match);
            search = new(Constructor: $"{match.Name.ToLower()} = new(",
                         Delegate: $" += {match.Name};",
                         Name: $" {match.Name}(",
                         Not: $"!{match.Name}(",
                         Wrap: $"({match.Name}(");
            logger.LogInformation("{line} {a} // {results}", line.Split(" =>")[0], "{ }", results.Count);
            if (string.IsNullOrEmpty(match.Name))
                continue;
            blocks = 0;
            startLine = GetStartLine(lines, i);
            if (!lines[startLine].StartsWith("#pragma") && !lines[startLine].StartsWith("#nullable"))
                firstLine = lines[startLine].Trim();
            else
                firstLine = lines[startLine + 1].Trim();
            isLinq = !lines[i + 1].StartsWith("#pragma") && !lines[i + 1].StartsWith("#nullable") && lines[i].Trim()[^1] != '{' && lines[i + 1].Trim() != "{";
            if (isLinq)
                blocks++;
            endLine = null;
            if (lines[i].Trim()[^1] == '{')
                blocks++;
            for (int j = i + 1; j < lines.Length; j++) {
                innerLine = lines[j].Trim();
                if (innerLine.StartsWith("#pragma") || innerLine.StartsWith("#nullable"))
                    continue;
                if (isLinq && string.IsNullOrEmpty(innerLine)) {
                    if (line.EndsWith(';'))
                        blocks--;
                }
                blocks += GetLineBlockCount(innerLine, isLinq);
                if (blocks != 0)
                    continue;
                endLine = j;
                if (lines.Length > j + 1 && string.IsNullOrEmpty(lines[j + 1].Trim()))
                    endLine++;
                if (j > lines.Length - 2)
                    throw new Exception();
                break;
            }
            referenceToLineNumbers = GetReferenceToLineNumbers(lines: lines, start: 0, end: lines.Length, i: i, search: search, parameters: parameters);
            if (referenceToLineNumbers.Count == 0) {
                lineSegmentFirst = line.Split(match.Name)[0];
                if (!lines[i - 1].Trim().StartsWith("[Obsolete")) {
                    if (lineSegmentFirst.StartsWith("private"))
                        logger.LogWarning("// <{cSharpFileName}> {name} with {parameters} parameter(s) <{line}>", Path.GetFileName(cSharpFile), match.Name, parameters, lineSegmentFirst);
                    else
                        logger.LogInformation("// <{cSharpFileName}> {name} with {parameters} parameter(s) <{line}>", Path.GetFileName(cSharpFile), match.Name, parameters, lineSegmentFirst);
                }
            }
            if (referenceToLineNumbers.Count == 0)
                referenceToLineNumbers.Add(-1);
            logger.LogInformation("{line} {a} // {results} ~~~ {startLine} => {firstUsedLine}", line.Split(" =>")[0], "{ }", results.Count, startLine, referenceToLineNumbers.First());
            method = new(EndLine: endLine,
                         FirstLine: firstLine,
                         I: i,
                         Line: line,
                         Match: match,
                         Parameters: parameters,
                         ReferenceToLineNumbers: referenceToLineNumbers.AsReadOnly(),
                         Search: search,
                         ScopeEnum: scopeEnum,
                         StartLine: startLine);
            results.Add(method);
        }
        return results.AsReadOnly();
    }

    private static int GetNumberOfStartSpaces(string[] lines, int i) {
        int result = 0;
        foreach (char @char in lines[i]) {
            if (@char != ' ')
                break;
            result += 1;
        }
        return result;
    }

    private static int GetScopeEnum(Match match) {
        int result;
        int value = match.Scope switch {
            "public" => 8000,
            "internal" => 7000,
            "protected" => 6000,
            "private" => 5000,
            _ => match.Scope.Length > 2
            && match.Scope[..2] == " I"
            && match.Scope[^1] == '.' ? 9000 : throw new NotImplementedException()
        };
        result = value
            + (string.IsNullOrEmpty(match.Result) ? 100 : 0)
            + (string.IsNullOrEmpty(match.Static) ? 0 : 10)
            + (string.IsNullOrEmpty(match.Async) ? 0 : 1);
        return result;
    }

    private static ReadOnlyDictionary<string, string> GetParameters(Regex parameterRegex, Match match) {
        Dictionary<string, string> results = [];
        string value;
        string[] segments;
        System.Text.RegularExpressions.Match[] matches = parameterRegex.Matches($"{match.Parameters},").ToArray();
        try {
            foreach (System.Text.RegularExpressions.Match m in matches) {
                if (!m.Success)
                    continue;
                value = m.Value.Trim()[..^1];
                segments = value.Split(' ');
                results.Add(segments[^1], value);
            }
        } catch (Exception) {
            results.Clear();
            System.Text.RegularExpressions.Match m;
            for (int i = 0; i < matches.Length; i++) {
                m = matches[i];
                if (!m.Success)
                    continue;
                results.Add(i.ToString(), i.ToString());
            }
        }
        return new(results);
    }

    private static int GetStartLine(string[] lines, int i) {
        int result = i;
        string line;
        for (int j = i - 1; j > -1; j--) {
            line = lines[j].Trim();
            if (!line.StartsWith('[') && !line.StartsWith('#') && !line.StartsWith("/// "))
                break;
            result--;
        }
        return result;
    }

    private static int GetLineBlockCount(string line, bool isLinq) {
        int result = 0;
        bool ignore = false;
        for (int i = 0; i < line.Length; i++) {
            if (line[i] == '\'')
                i++;
            else if (!isLinq && !ignore && line[i] == '{')
                result++;
            else if (!isLinq && !ignore && line[i] == '}')
                result--;
            else if (isLinq && !ignore && line[i] == ';')
                result--;
            else if (i > 0 && line[i] == '"' && line[i - 1] != '\\')
                ignore = !ignore;
        }
        return result;
    }

    private static List<int> GetReferenceToLineNumbers(string[] lines, int start, int end, int i, Search search, ReadOnlyDictionary<string, string> parameters) {
        List<int> results = [];
        string[] segments;
        string[] afterSegments;
        string lastSegmentBeforeDot;
        for (int j = start; j < end; j++) {
            if (j == i)
                continue;
            segments = lines[j].Split(search.Name);
            if (segments.Length == 1) {
                segments = lines[j].Split(search.Not);
                if (segments.Length == 1) {
                    segments = lines[j].Split(search.Wrap);
                    if (segments.Length == 1) {
                        if (!lines[j].EndsWith(search.Delegate)) {
                            segments = lines[j].Split(search.Constructor);
                            if (segments.Length == 1)
                                continue;
                        }
                    }
                }
            }
            if (lines[j].EndsWith(search.Delegate))
                results.Add(j);
            else {
                lastSegmentBeforeDot = segments[^1].Split(").")[0];
                if (parameters.Count == 0) {
                    if (lastSegmentBeforeDot.Contains(','))
                        continue;
                } else {
                    afterSegments = lastSegmentBeforeDot.Split(',');
                    if (afterSegments.Length != parameters.Count)
                        continue;
                }
                results.Add(j);
            }
        }
        return results;
    }

    private static ReadOnlyCollection<MethodWith> GetCollection(ILogger<Worker> logger, string[] lines, ReadOnlyCollection<Method> sortedMethods) {
        List<MethodWith> results = [];
        List<Method> check = sortedMethods.ToList();
        foreach (Method method in sortedMethods) {
            logger.LogInformation($"{method.Match.Name} => {method.Parameters.Count}");
            if (method.EndLine is null)
                continue;
            if (!check.Remove(method))
                continue;
            MethodWith methodWith = GetMethodWith(lines, sortedMethods, check, method, method.EndLine.Value);
            results.Add(methodWith);
        }
        return results.AsReadOnly();
    }

    private static MethodWith GetMethodWith(string[] lines, ReadOnlyCollection<Method> methods, List<Method> check, Method method, int methodEndLineValue) {
        MethodWith methodWith;
        List<int> referenceToLineNumbers;
        MethodWith[] sortedReferences;
        Dictionary<int, MethodWith> references = [];
        foreach (Method m in methods) {
            if (m.EndLine is null)
                continue;
            if (m == method)
                continue;
            referenceToLineNumbers = GetReferenceToLineNumbers(lines: lines, start: method.StartLine, end: methodEndLineValue, i: -1, search: m.Search, parameters: m.Parameters);
            if (referenceToLineNumbers.Count > 0) {
                if (!check.Remove(m))
                    continue;
                foreach (int i in referenceToLineNumbers) {
                    if (references.ContainsKey(i))
                        continue;
                    methodWith = GetMethodWith(lines, methods, check, m, m.EndLine.Value);
                    references.Add(i, methodWith);
                    break;
                }
            }
        }
        if (references.Count < 2)
            sortedReferences = (from l in references select l.Value).ToArray();
        else
            sortedReferences = (from l in references orderby l.Key select l.Value).ToArray();
        methodWith = new(EndLine: method.EndLine,
                         FirstLine: method.FirstLine,
                         Line: method.Line,
                         Match: method.Match,
                         Parameters: method.Parameters,
                         References: new(sortedReferences),
                         ReferenceToLineNumbers: method.ReferenceToLineNumbers,
                         ScopeEnum: method.ScopeEnum,
                         Search: method.Search,
                         StartLine: method.StartLine);
        return methodWith;
    }

    private static bool WriteAllLines(string cSharpFile, string[] lines, ReadOnlyCollection<MethodWith> collection) {
        bool result;
        if (Debugger.IsAttached)
            WriteDebug(collection);
        List<string> results = [];
        ReadOnlyCollection<int> methodLines = GetMethodLines(collection);
        int maxMethodLines = methodLines.Max();
        for (int i = 0; i < maxMethodLines; i++) {
            if (methodLines.Contains(i))
                continue;
            results.Add(lines[i]);
        }
        List<bool> nests = [true];
        foreach (MethodWith methodWith in collection) {
            if (methodWith.EndLine is null)
                continue;
            AppendLines(results, nests, lines, methodWith, methodWith.EndLine.Value);
        }
        for (int i = maxMethodLines + 1; i < lines.Length; i++)
            results.Add(lines[i]);
        string text = File.ReadAllText(cSharpFile);
        string join = string.Join(Environment.NewLine, results);
        if (join == text)
            result = false;
        else {
            result = true;
            File.WriteAllText(cSharpFile, join);
        }
        return result;
    }

    private static void WriteDebug(ReadOnlyCollection<MethodWith> collection) {
        List<string> results = [];
        List<bool> nests = [true];
        foreach (MethodWith methodWith in collection)
            AppendLines(results, nests, methodWith);
        File.WriteAllText(Path.Combine(".vscode", "helper", ".md"), string.Join(Environment.NewLine, results));
    }

    private static void AppendLines(List<string> results, List<bool> nests, MethodWith methodWith) {
        nests.Add(true);
        results.Add($" - {new string('#', nests.Count)} {methodWith.Match.Name} => {methodWith.Parameters.Count}");
        foreach (MethodWith m in methodWith.References)
            AppendLines(results, nests, m);
        nests.RemoveAt(nests.Count - 1);
    }

    private static ReadOnlyCollection<int> GetMethodLines(ReadOnlyCollection<MethodWith> collection) {
        List<int> results = [];
        List<bool> nests = [true];
        foreach (MethodWith methodWith in collection) {
            if (methodWith.EndLine is null)
                continue;
            AppendLineNumbers(results, nests, methodWith, methodWith.EndLine.Value);
        }
        int[] distinct = results.Distinct().ToArray();
        if (distinct.Length != results.Count)
            throw new Exception();
        return new(results);
    }

    private static void AppendLineNumbers(List<int> results, List<bool> nests, MethodWith methodWith, int methodWithEndLineValue) {
        nests.Add(true);
        for (int i = methodWith.StartLine; i < methodWithEndLineValue + 1; i++)
            results.Add(i);
        foreach (MethodWith m in methodWith.References) {
            if (m.EndLine is null)
                continue;
            AppendLineNumbers(results, nests, m, m.EndLine.Value);
        }
        nests.RemoveAt(nests.Count - 1);
    }

    private static void AppendLines(List<string> results, List<bool> nests, string[] lines, MethodWith methodWith, int methodWithEndLineValue) {
        nests.Add(true);
        for (int i = methodWith.StartLine; i < methodWithEndLineValue + 1; i++)
            results.Add(lines[i]);
        foreach (MethodWith m in methodWith.References) {
            if (m.EndLine is null)
                continue;
            AppendLines(results, nests, lines, m, m.EndLine.Value);
        }
        nests.RemoveAt(nests.Count - 1);
    }

}