using File_Folder_Helper.Models;
using Microsoft.Extensions.Logging;
using System.Collections.ObjectModel;
using System.Text;
using System.Text.RegularExpressions;

namespace File_Folder_Helper.Helpers;

internal static partial class HelperKanbanMetadata
{

    [GeneratedRegex("([A-Z]+(.))")]
    private static partial Regex UpperCase();

    [GeneratedRegex("[\\s!?.,@:;|\\\\/\"'`£$%\\^&*{}[\\]()<>~#+\\-=_¬]+")]
    private static partial Regex InvalidCharacter();

    private record Record(FileInfo FileInfo,
                          string Group,
                          int GroupCount,
                          int ItemLineNumber);

    private static string GetParamCase(string value)
    {
        string result;
        StringBuilder stringBuilder = new(value);
        Match[] matches = UpperCase().Matches(value).ToArray();
        for (int i = matches.Length - 1; i > -1; i--)
            _ = stringBuilder.Insert(matches[i].Index, '-');
        string[] segments = InvalidCharacter().Split(stringBuilder.ToString().ToLower());
        result = string.Join('-', segments).Trim('-');
        return result;
    }

    private static void TestParamCases()
    { // cSpell:disable
        if (GetParamCase("PascalCase") != "pascal-case")
            throw new Exception("PascalCase");
        if (GetParamCase("camelCase") != "camel-case")
            throw new Exception("camelCase");
        if (GetParamCase("snake_case") != "snake-case")
            throw new Exception("snake_case");
        if (GetParamCase("No Case") != "no-case")
            throw new Exception("No Case");
        if (GetParamCase("With 2 numbers 3") != "with-2-numbers-3")
            throw new Exception("With 2 numbers 3");
        if (GetParamCase("Multiple  spaces") != "multiple-spaces")
            throw new Exception("Multiple  spaces");
        if (GetParamCase("Tab\tCharacter") != "tab-character")
            throw new Exception("Tab\tCharacter");
        if (GetParamCase("New\nLine") != "new-line")
            throw new Exception("New\nLine");
        if (GetParamCase("Punctuation, Characters") != "punctuation-characters")
            throw new Exception("Punctuation, Characters");
        if (GetParamCase("M!o?r.e, @p:u;n|c\\t/u\"a\'t`i£o$n% ^c&h*a{r}a[c]t(e)r<s> ~l#i+k-e= _t¬hese") != "m-o-r-e-p-u-n-c-t-u-a-t-i-o-n-c-h-a-r-a-c-t-e-r-s-l-i-k-e-t-hese")
            throw new Exception("M!o?r.e, @p:u;n|c\\t/u\"a\'t`i£o$n% ^c&h*a{r}a[c]t(e)r<s> ~l#i+k-e= _t¬hese");
        if (GetParamCase("This string ends with punctuation!") != "this-string-ends-with-punctuation")
            throw new Exception("This string ends with punctuation!");
        if (GetParamCase("?This string starts with punctuation") != "this-string-starts-with-punctuation")
            throw new Exception("?This string starts with punctuation");
        if (GetParamCase("#This string has punctuation at both ends&") != "this-string-has-punctuation-at-both-ends")
            throw new Exception("#This string has punctuation at both ends&");
        if (GetParamCase("軟件 測試") != "軟件-測試")
            throw new Exception("軟件 測試");
        if (GetParamCase("実験 試し") != "実験-試し")
            throw new Exception("実験 試し");
        if (GetParamCase("יקספּערמענאַל פּרובירן") != "יקספּערמענאַל-פּרובירן")
            throw new Exception("יקספּערמענאַל פּרובירן");
        if (GetParamCase("я надеюсь, что это сработает") != "я-надеюсь-что-это-сработает")
            throw new Exception("я надеюсь, что это сработает");
    } // cSpell:restore

    private static List<Record> GetCollectionFromIndex(string sourceDirectory, ReadOnlyCollection<string> lines)
    {
        List<Record> results = [];
        string line;
        FileInfo fileInfo;
        string[] segments;
        int groupCount = 0;
        string? group = null;
        for (int i = 0; i < lines.Count; i++)
        {
            line = lines[i];
            if (line.Length < 4)
                continue;
            if (line[..3] == "## ")
            {
                group = line[3..];
                groupCount += 1;
                continue;
            }
            if (group is null || line[..3] != "- [" || line[^1] != ')')
                continue;
            segments = line.Split("](");
            if (segments.Length != 2)
                continue;
            fileInfo = new(Path.Combine(sourceDirectory, segments[1][..^1]));
            if (!fileInfo.Exists)
                continue;
            results.Add(new(fileInfo, group, groupCount, i));
        }
        return results;
    }

    private static void WriteKanbanBoardFile(string directory, List<Record> records, string h1)
    {
        string? last = null;
        List<string> results = [h1];
        foreach (Record record in records)
        {
            if (last is null || record.Group != last)
            {
                results.Add(string.Empty);
                results.Add($"## {record.Group}");
                results.Add(string.Empty);
            }
            results.Add($"- [ ] {Path.GetFileNameWithoutExtension(record.FileInfo.Name)}");
            last = record.Group;
        }
        string file = Path.Combine(directory, "index.knb.md");
        if (File.Exists(file))
        {
            string allText = File.ReadAllText(file);
            if (string.Join(Environment.NewLine, results) == allText)
                results.Clear();
        }
        if (results.Count > 0)
            File.WriteAllText(file, string.Join(Environment.NewLine, results));
    }

    private static void WriteKanbanBoardYmlView(string directory, List<Record> records, string kanbanIndexH1)
    {
        List<string> results = [kanbanIndexH1, string.Empty];
        string h1;
        TimeSpan timeSpan;
        List<string> lines;
        LineNumber lineNumber;
        Record[] sorted = (from l in records orderby l.GroupCount, l.FileInfo.LastWriteTime descending select l).ToArray();
        foreach (Record record in sorted)
        {
            if (record.ItemLineNumber == 0)
                throw new NotSupportedException();
            (lines, lineNumber) = HelperMarkdown.GetStatusAndFrontMatterYamlEndLineNumbers(record.FileInfo);
            if (lines.Count == 0)
                continue;
            timeSpan = new(record.FileInfo.LastWriteTime.Ticks - record.FileInfo.CreationTime.Ticks);
            h1 = lineNumber.H1 is null ? Path.GetFileNameWithoutExtension(record.FileInfo.Name) : lines[lineNumber.H1.Value];
            results.Add($"#{h1}");
            results.Add(string.Empty);
            results.Add("```yaml");
            results.Add($"CreationTime: {record.FileInfo.CreationTime:yyyy-MM-dd}");
            results.Add($"LastWriteTime: {record.FileInfo.LastWriteTime:yyyy-MM-dd}");
            results.Add($"TotalDays: {Math.Round(timeSpan.TotalDays, 2)}");
            if (lineNumber.FrontMatterYamlEnd is not null && lines.Count >= lineNumber.FrontMatterYamlEnd.Value)
            {
                for (int i = 0; i < lineNumber.FrontMatterYamlEnd; i++)
                {
                    if (lines[i] == "---")
                        continue;
                    results.Add(lines[i]);
                }
            }
            results.Add($"status: {record.GroupCount}-{record.Group}");
            results.Add("```");
            results.Add(string.Empty);
        }
        string file = Path.Combine(directory, "index.yml.md");
        if (File.Exists(file))
        {
            string allText = File.ReadAllText(file);
            if (string.Join(Environment.NewLine, results) == allText)
                results.Clear();
        }
        if (results.Count > 0)
            File.WriteAllText(file, string.Join(Environment.NewLine, results));
    }

    internal static void SetMetadata(string sourceDirectory, ReadOnlyCollection<string> kanbanIndexFileLines, LineNumber kanbanIndexFileLineNumber, ReadOnlyCollection<string> gitOthersModifiedAndDeletedExcludingStandardFiles)
    {
        bool? match;
        bool gitCheck;
        string? paramCase;
        string statusLine;
        List<string> lines;
        LineNumber lineNumber;
        string? directory = Path.GetDirectoryName(sourceDirectory);
        List<Record> records = GetCollectionFromIndex(sourceDirectory, kanbanIndexFileLines);
        if (directory is not null && kanbanIndexFileLineNumber.H1 is not null)
        {
            string checkDirectory = Path.Combine(directory, ".vscode", "helper");
            if (Directory.Exists(checkDirectory))
            {
                WriteKanbanBoardFile(checkDirectory, records, kanbanIndexFileLines[kanbanIndexFileLineNumber.H1.Value]);
                WriteKanbanBoardYmlView(checkDirectory, records, kanbanIndexFileLines[kanbanIndexFileLineNumber.H1.Value]);
            }
        }
        foreach (Record record in records)
        {
            if (record.ItemLineNumber == 0)
                throw new NotSupportedException();
            (lines, lineNumber) = HelperMarkdown.GetStatusAndFrontMatterYamlEndLineNumbers(record.FileInfo);
            if (lines.Count == 0)
                continue;
            statusLine = $"status: {record.GroupCount}-{record.Group}";
            paramCase = lineNumber.H1 is null ? null : GetParamCase(lines[lineNumber.H1.Value]);
            match = lineNumber.H1 is null || paramCase is null ? null : Path.GetFileNameWithoutExtension(record.FileInfo.Name) == paramCase;
            if (lineNumber.FrontMatterYamlEnd is null)
                throw new NotSupportedException($"{nameof(SetMetadata)} must be executed first!");
            if (lineNumber.H1 is not null && paramCase is not null && match is not null && !match.Value)
                lines[lineNumber.H1.Value] = $"# {paramCase}";
            if (lineNumber.Status is null)
                lines.Insert(lineNumber.FrontMatterYamlEnd.Value, statusLine);
            else
            {
                if ((match is null || match.Value) && lines[lineNumber.Status.Value] == statusLine)
                    continue;
                lines[lineNumber.Status.Value] = statusLine;
            }
            gitCheck = gitOthersModifiedAndDeletedExcludingStandardFiles.Contains(record.FileInfo.FullName);
            if (!gitCheck)
                continue;
            File.WriteAllLines(record.FileInfo.FullName, lines);
        }
    }

    internal static void SetMetadata(ILogger logger, string sourceDirectory)
    {
        TestParamCases();
        string fullPath = Path.GetFullPath(sourceDirectory);
        if (!Directory.Exists(fullPath))
            _ = Directory.CreateDirectory(fullPath);
        string indexFile = Path.Combine(fullPath, "index.md");
        if (!File.Exists(indexFile))
            logger.LogInformation("<{indexFile}> doesn't exist!", indexFile);
        else
        {
            FileInfo fileInfo = new(indexFile);
            (List<string> lines, LineNumber lineNumber) = HelperMarkdown.GetStatusAndFrontMatterYamlEndLineNumbers(fileInfo);
            SetMetadata(fullPath, new(lines), lineNumber, gitOthersModifiedAndDeletedExcludingStandardFiles: new([]));
        }
    }

}