using System.Collections.ObjectModel;

namespace View_by_Distance.Shared.Models.Stateless;

internal abstract class XPath
{

    internal static string GetRelativePath(string path, int length, bool forceExtensionToLower)
    {
        string result;
        if (forceExtensionToLower)
        {
            string extension = Path.GetExtension(path);
            string extensionLowered = Path.GetExtension(path).ToLower();
            if (extension != extensionLowered)
            {
                string? directoryName = Path.GetDirectoryName(path);
                if (string.IsNullOrEmpty(directoryName))
                    throw new NullReferenceException(directoryName);
                string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(path);
                if (string.IsNullOrEmpty(fileNameWithoutExtension))
                    throw new NullReferenceException(fileNameWithoutExtension);
                path = Path.Combine(directoryName, $"{fileNameWithoutExtension}{extensionLowered}");
            }
        }
        result = path[length..].Replace(@"\", "/");
        return result;
    }

    internal static bool DeleteEmptyDirectories(string rootDirectory)
    {
        bool result;
        List<string> results = [];
        DeleteEmptyDirectories(rootDirectory, results);
        result = results.Count > 0;
        return result;
    }

    internal static void DeleteEmptyDirectories(string rootDirectory, List<string> deletedDirectories)
    {
        if (Directory.Exists(rootDirectory))
        {
            string[] files;
            string[] directories = Directory.GetDirectories(rootDirectory, "*", SearchOption.TopDirectoryOnly);
            if (directories.Length > 0)
                files = [];
            else
                files = Directory.GetFiles(rootDirectory, "*", SearchOption.AllDirectories);
            if (directories.Length == 0 && files.Length == 0)
            {
                deletedDirectories.Add(rootDirectory);
                try
                { Directory.Delete(rootDirectory); }
                catch (UnauthorizedAccessException)
                {
                    new DirectoryInfo(rootDirectory).Attributes = FileAttributes.Normal;
                    Directory.Delete(rootDirectory);
                }
            }
            else
            {
                List<string> check = [];
                foreach (string directory in directories)
                {
                    DeleteEmptyDirectories(directory, check);
                    deletedDirectories.AddRange(check);
                    if (check.Count > 0)
                        DeleteEmptyDirectories(directory, deletedDirectories);
                }
            }
        }
    }

    internal static bool WriteAllText(string path, string contents, bool updateDateWhenMatches, bool compareBeforeWrite, DateTime? updateToWhenMatches)
    {
        bool result;
        string text;
        if (!compareBeforeWrite)
            result = true;
        else
        {
            if (!File.Exists(path))
                text = string.Empty;
            else
                text = File.ReadAllText(path);
            result = text != contents;
            if (!result && updateDateWhenMatches)
            {
                if (updateToWhenMatches is null)
                    File.SetLastWriteTime(path, DateTime.Now);
                else
                    File.SetLastWriteTime(path, updateToWhenMatches.Value);
            }
        }
        if (result)
        {
            if (path.Contains("()"))
                File.WriteAllText(path, contents);
            else if (path.Contains("{}") && !path.EndsWith(".json"))
                File.WriteAllText(path, contents);
            else if (path.Contains("[]") && !path.EndsWith(".json"))
                File.WriteAllText(path, contents);
            else if (path.Contains("{}") && path.EndsWith(".json") && contents[0] == '{')
                File.WriteAllText(path, contents);
            else if (path.Contains("[]") && path.EndsWith(".json") && contents[0] == '[')
                File.WriteAllText(path, contents);
            else
                File.WriteAllText(path, contents);
        }
        return result;
    }

    internal static List<string> GetDirectoryNames(string directory)
    {
        List<string> results = [];
        string? fileName;
        string? checkDirectory = directory;
        string? pathRoot = Path.GetPathRoot(directory);
        string extension = Path.GetExtension(directory);
        if (string.IsNullOrEmpty(pathRoot))
            throw new NullReferenceException(nameof(pathRoot));
        if (Directory.Exists(directory))
        {
            fileName = Path.GetFileName(directory);
            if (!string.IsNullOrEmpty(fileName))
                results.Add(fileName);
        }
        else if ((string.IsNullOrEmpty(extension) || extension.Length > 3) && !File.Exists(directory))
        {
            fileName = Path.GetFileName(directory);
            if (!string.IsNullOrEmpty(fileName))
                results.Add(fileName);
        }
        for (int i = 0; i < int.MaxValue; i++)
        {
            checkDirectory = Path.GetDirectoryName(checkDirectory);
            if (string.IsNullOrEmpty(checkDirectory) || checkDirectory == pathRoot)
                break;
            fileName = Path.GetFileName(checkDirectory);
            if (string.IsNullOrEmpty(fileName))
                continue;
            results.Add(fileName);
        }
        results.Add(pathRoot);
        results.Reverse();
        return results;
    }

    internal static List<string> GetDirectories(string directory)
    {
        List<string> results = [];
        string? checkDirectory = directory;
        string? pathRoot = Path.GetPathRoot(directory);
        if (string.IsNullOrEmpty(pathRoot))
            throw new NullReferenceException(nameof(pathRoot));
        if (Directory.Exists(directory))
            results.Add(directory);
        for (int i = 0; i < int.MaxValue; i++)
        {
            checkDirectory = Path.GetDirectoryName(checkDirectory);
            if (string.IsNullOrEmpty(checkDirectory) || checkDirectory == pathRoot)
                break;
            results.Add(checkDirectory);
        }
        results.Add(pathRoot);
        results.Reverse();
        return results;
    }

    internal static (int level, List<string> directories) Get(string rootDirectory, string sourceDirectory)
    {
        int result = 0;
        string? directory;
        string? checkDirectory;
        List<string> results = [];
        checkDirectory = sourceDirectory;
        for (int i = 0; i < int.MaxValue; i++)
        {
            result += 1;
            directory = Path.GetFileName(checkDirectory);
            if (string.IsNullOrEmpty(directory))
                break;
            results.Add(directory);
            checkDirectory = Path.GetDirectoryName(checkDirectory);
            if (checkDirectory == rootDirectory)
                break;
        }
        results.Reverse();
        return new(result, results);
    }

    internal static string GetDirectory(string sourceDirectory, int level, string directoryName)
    {
        string result;
        string? checkDirectory;
        checkDirectory = Path.GetDirectoryName(sourceDirectory);
        for (int i = 0; i < level; i++)
            checkDirectory = Path.GetDirectoryName(checkDirectory);
        if (string.IsNullOrEmpty(checkDirectory))
            throw new Exception();
        checkDirectory = Path.Combine(checkDirectory, directoryName);
        if (!Directory.Exists(checkDirectory))
            _ = Directory.CreateDirectory(checkDirectory);
        result = checkDirectory;
        return result;
    }

    internal static void ChangeDateForEmptyDirectories(string rootDirectory, long ticks)
    {
        DateTime dateTime = new(ticks);
        IEnumerable<string> fileSystemEntries;
        string[] directories;
        if (!Directory.Exists(rootDirectory))
            directories = [];
        else
            directories = Directory.GetDirectories(rootDirectory, "*", SearchOption.AllDirectories);
        foreach (string directory in directories)
        {
            fileSystemEntries = Directory.EnumerateFileSystemEntries(directory, "*", SearchOption.TopDirectoryOnly);
            if (fileSystemEntries.Any())
                continue;
            Directory.SetLastWriteTime(directory, dateTime);
        }
    }

    internal static void MakeHiddenIfAllItemsAreHidden(string rootDirectory)
    {
        bool check;
        FileInfo fileInfo;
        IEnumerable<string> files;
        DirectoryInfo directoryInfo;
        IEnumerable<string> subDirectories;
        string[] directories = Directory.GetDirectories(rootDirectory, "*", SearchOption.AllDirectories);
        foreach (string directory in directories)
        {
            directoryInfo = new(directory);
            if (directoryInfo.Attributes.HasFlag(FileAttributes.Hidden))
                continue;
            check = true;
            subDirectories = Directory.EnumerateDirectories(directory, "*", SearchOption.TopDirectoryOnly);
            foreach (string subDirectory in subDirectories)
            {
                directoryInfo = new(subDirectory);
                if (!directoryInfo.Attributes.HasFlag(FileAttributes.Hidden))
                {
                    check = false;
                    break;
                }
            }
            if (!check)
                continue;
            files = Directory.EnumerateFiles(directory, "*", SearchOption.TopDirectoryOnly);
            foreach (string file in files)
            {
                fileInfo = new(file);
                if (!fileInfo.Attributes.HasFlag(FileAttributes.Hidden))
                {
                    check = false;
                    break;
                }
            }
            if (!check)
                continue;
            directoryInfo.Attributes |= FileAttributes.Hidden;
        }
    }

    private static byte GetEnum(bool? ik, bool? dto)
    {
        byte result;
        if (ik is not null && ik.Value && dto is not null && dto.Value)
            result = 11;
        else if (ik is not null && ik.Value && dto is not null && !dto.Value)
            result = 15;
        else if (ik is not null && ik.Value && dto is null)
            result = 19;
        else if (ik is not null && !ik.Value && dto is not null && dto.Value)
            result = 51;
        else if (ik is not null && !ik.Value && dto is not null && !dto.Value)
            result = 55;
        else if (ik is not null && !ik.Value && dto is null)
            result = 59;
        else if (ik is null && dto is not null && dto.Value)
            result = 91;
        else if (ik is null && dto is not null && !dto.Value)
            result = 95;
        else if (ik is null && dto is null)
            result = 99;
        else
            throw new Exception();
        return result;
    }

    internal static byte GetEnum(FilePath filePath) =>
        GetEnum(filePath.HasIgnoreKeyword, filePath.HasDateTimeOriginal);

    private static CombinedEnumAndIndex GetCombinedEnumAndIndex(int resultAllInOneSubdirectoryLength, FilePath filePath, string fileNameWithoutExtension)
    {
        CombinedEnumAndIndex result;
        int converted;
        string combined;
        string fileNameBeforeFirst = fileNameWithoutExtension.Split('.')[0];
        byte @enum = GetEnum(filePath.HasIgnoreKeyword, filePath.HasDateTimeOriginal);
        string check = fileNameBeforeFirst.Length < resultAllInOneSubdirectoryLength ?
            new('-', resultAllInOneSubdirectoryLength) :
            fileNameBeforeFirst[^resultAllInOneSubdirectoryLength..];
        if (check.Any(l => !char.IsNumber(l)))
        {
            combined = $"{@enum}{new('-', resultAllInOneSubdirectoryLength)}";
            converted = int.Parse($"1{new string('0', resultAllInOneSubdirectoryLength)}");
        }
        else
        {
            combined = $"{@enum}{check}";
            converted = int.Parse(check);
        }
        result = new(combined, @enum, converted);
        return result;
    }

    internal static CombinedEnumAndIndex GetCombinedEnumAndIndex(ResultSettings resultSettings, FilePath filePath)
    {
        CombinedEnumAndIndex result;
        if (filePath.Id is not null)
            result = GetCombinedEnumAndIndex(resultSettings.ResultAllInOneSubdirectoryLength, filePath, filePath.Id.Value.ToString());
        else
            result = GetCombinedEnumAndIndex(resultSettings.ResultAllInOneSubdirectoryLength, filePath, filePath.NameWithoutExtension);
        return result;
    }

    private static ReadOnlyCollection<int> GetYears(ResultSettings resultSettings)
    {
        List<int> results = [];
        int currentYear = DateTime.Now.Year;
        for (int i = resultSettings.EpicYear; i < currentYear + 1; i++)
            results.Add(i);
        return results.AsReadOnly();
    }

    private static byte[] GetBytes() =>
    [
        11,
        15,
        19,
        51,
        55,
        59,
        91,
        95,
        99
    ];

    private static ReadOnlyDictionary<byte, ReadOnlyCollection<string>> Convert(Dictionary<byte, List<string>> keyValuePairs)
    {
        Dictionary<byte, ReadOnlyCollection<string>> results = [];
        foreach (KeyValuePair<byte, List<string>> keyValuePair in keyValuePairs)
            results.Add(keyValuePair.Key, new(keyValuePair.Value));
        return results.AsReadOnly();
    }

    private static ReadOnlyDictionary<byte, ReadOnlyCollection<string>> Convert(List<CombinedEnumAndIndex> collection)
    {
        Dictionary<byte, List<string>> results = [];
        List<string>? c;
        foreach (CombinedEnumAndIndex cei in collection)
        {
            if (!results.TryGetValue(cei.Enum, out c))
            {
                results.Add(cei.Enum, []);
                if (!results.TryGetValue(cei.Enum, out c))
                    throw new Exception();
            }
            c.Add(cei.Combined);
        }
        return Convert(results);
    }

    private static ReadOnlyDictionary<string, ReadOnlyDictionary<byte, ReadOnlyCollection<string>>> GetKeyValuePairs(ResultSettings resultSettings, string? resultsFullGroupDirectory, string[]? jsonGroups, DateTime dateTime)
    {
        Dictionary<string, ReadOnlyDictionary<byte, ReadOnlyCollection<string>>> results = [];
        int plusOne;
        string directory;
        string checkDirectory;
        CombinedEnumAndIndex cei;
        byte[] bytes = GetBytes();
        List<CombinedEnumAndIndex> collection = [];
        ReadOnlyDictionary<byte, ReadOnlyCollection<string>> keyValuePairs;
        int converted = int.Parse($"1{new string('0', resultSettings.ResultAllInOneSubdirectoryLength)}");
        if (jsonGroups is not null)
        {
            plusOne = converted + 1;
            foreach (string jsonGroup in jsonGroups)
            {
                if (resultsFullGroupDirectory is null)
                    continue;
                foreach (byte @enum in bytes)
                {
                    for (int i = 0; i < plusOne; i++)
                    {
                        if (string.IsNullOrEmpty(jsonGroup))
                        {
                            if (i == converted)
                                checkDirectory = Path.GetFullPath(Path.Combine(resultsFullGroupDirectory, $"{@enum}{new('-', resultSettings.ResultAllInOneSubdirectoryLength)}"));
                            else
                                checkDirectory = Path.GetFullPath(Path.Combine(resultsFullGroupDirectory, $"{@enum}{i.ToString().PadLeft(resultSettings.ResultAllInOneSubdirectoryLength, '0')}"));
                        }
                        else
                        {
                            directory = Path.Combine(resultsFullGroupDirectory, jsonGroup);
                            if (i == converted)
                                checkDirectory = Path.GetFullPath(Path.Combine(directory, $"{@enum}{new('-', resultSettings.ResultAllInOneSubdirectoryLength)}"));
                            else
                                checkDirectory = Path.GetFullPath(Path.Combine(directory, $"{@enum}{i.ToString().PadLeft(resultSettings.ResultAllInOneSubdirectoryLength, '0')}"));
                        }
                        if (!Directory.Exists(checkDirectory))
                            _ = Directory.CreateDirectory(checkDirectory);
                        cei = new(Combined: checkDirectory, Enum: @enum, Index: -1);
                        collection.Add(cei);
                    }
                }
                keyValuePairs = Convert(collection);
                if (!string.IsNullOrEmpty(jsonGroup))
                    results.Add(jsonGroup, keyValuePairs);
                else
                    results.Add(dateTime.Ticks.ToString(), keyValuePairs);
            }
        }
        return results.AsReadOnly();
    }

    private static ReadOnlyDictionary<int, ReadOnlyDictionary<string, ReadOnlyDictionary<byte, ReadOnlyCollection<string>>>> Convert(Dictionary<int, Dictionary<string, ReadOnlyDictionary<byte, ReadOnlyCollection<string>>>> keyValuePairs)
    {
        Dictionary<int, ReadOnlyDictionary<string, ReadOnlyDictionary<byte, ReadOnlyCollection<string>>>> results = [];
        foreach (KeyValuePair<int, Dictionary<string, ReadOnlyDictionary<byte, ReadOnlyCollection<string>>>> keyValuePair in keyValuePairs)
            results.Add(keyValuePair.Key, new(keyValuePair.Value));
        return results.AsReadOnly();
    }

    internal static ReadOnlyDictionary<int, ReadOnlyDictionary<string, ReadOnlyDictionary<byte, ReadOnlyCollection<string>>>> GetKeyValuePairs(ResultSettings resultSettings, string? resultsFullGroupDirectory, string[]? jsonGroups)
    {
        Dictionary<int, Dictionary<string, ReadOnlyDictionary<byte, ReadOnlyCollection<string>>>> results = [];
        if (jsonGroups is not null)
        {
            DateTime dateTime = DateTime.Now;
            ReadOnlyCollection<int> years = GetYears(resultSettings);
            Dictionary<string, ReadOnlyDictionary<byte, ReadOnlyCollection<string>>>? k;
            ReadOnlyDictionary<string, ReadOnlyDictionary<byte, ReadOnlyCollection<string>>> keyValuePairs = GetKeyValuePairs(resultSettings, resultsFullGroupDirectory, jsonGroups, dateTime);
            foreach (int year in years)
            {
                results.Add(year, []);
                if (!results.TryGetValue(year, out k))
                    throw new NullReferenceException(nameof(k));
                foreach (KeyValuePair<string, ReadOnlyDictionary<byte, ReadOnlyCollection<string>>> keyValuePair in keyValuePairs)
                    k.Add(keyValuePair.Key, keyValuePair.Value);
            }
        }
        return Convert(results);
    }

}