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 (string, int) GetDirectoryNameAndIndex(int resultAllInOneSubdirectoryLength, string fileNameWithoutExtension)
    {
        int converted;
        string result;
        string fileNameBeforeFirst = fileNameWithoutExtension.Split('.')[0];
        string check = fileNameBeforeFirst.Length < resultAllInOneSubdirectoryLength ? new('-', resultAllInOneSubdirectoryLength) : fileNameBeforeFirst[^resultAllInOneSubdirectoryLength..];
        if (check.Any(l => !char.IsNumber(l)))
        {
            result = new('-', resultAllInOneSubdirectoryLength);
            converted = int.Parse($"1{new string('0', resultAllInOneSubdirectoryLength)}");
        }
        else
        {
            result = check;
            converted = int.Parse(check);
        }
        return (result, converted);
    }

    internal static (string, int) GetDirectoryNameAndIndex(ResultConfiguration resultConfiguration, int id)
    {
        (string result, int converted) = GetDirectoryNameAndIndex(resultConfiguration.ResultAllInOneSubdirectoryLength, id.ToString());
        return (result, converted);
    }

    internal static (string, int) GetDirectoryNameAndIndex(ResultConfiguration resultConfiguration, FileHolder fileHolder)
    {
        (string result, int converted) = GetDirectoryNameAndIndex(resultConfiguration.ResultAllInOneSubdirectoryLength, fileHolder.NameWithoutExtension);
        return (result, converted);
    }

    internal static (string, int) GetDirectoryNameAndIndex(ResultConfiguration resultConfiguration, FilePath filePath)
    {
        string result;
        int converted;
        if (filePath.Id is not null)
            (result, converted) = GetDirectoryNameAndIndex(resultConfiguration.ResultAllInOneSubdirectoryLength, filePath.Id.Value.ToString());
        else
            (result, converted) = GetDirectoryNameAndIndex(resultConfiguration.ResultAllInOneSubdirectoryLength, filePath.NameWithoutExtension);
        return (result, converted);
    }

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

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

    internal static ReadOnlyDictionary<int, ReadOnlyDictionary<string, string[]>> GetKeyValuePairs(ResultConfiguration resultConfiguration, string? resultsFullGroupDirectory, string[]? jsonGroups)
    {
        Dictionary<int, Dictionary<string, string[]>> results = [];
        string directory;
        string checkDirectory;
        Dictionary<string, string[]>? keyValuePairs;
        ReadOnlyCollection<int> years = GetYears(resultConfiguration);
        int converted = int.Parse($"1{new string('0', resultConfiguration.ResultAllInOneSubdirectoryLength)}");
        int plusOne = converted + 1;
        List<string> collection = [];
        foreach (int year in years)
        {
            results.Add(year, []);
            if (!results.TryGetValue(year, out keyValuePairs))
                throw new NullReferenceException(nameof(keyValuePairs));
            if (jsonGroups is not null)
            {
                foreach (string jsonGroup in jsonGroups)
                {
                    if (resultsFullGroupDirectory is null)
                        continue;
                    collection.Clear();
                    for (int i = 0; i < plusOne; i++)
                    {
                        if (string.IsNullOrEmpty(jsonGroup))
                        {
                            if (i == converted)
                                checkDirectory = Path.GetFullPath(Path.Combine(resultsFullGroupDirectory, new('-', resultConfiguration.ResultAllInOneSubdirectoryLength)));
                            else
                                checkDirectory = Path.GetFullPath(Path.Combine(resultsFullGroupDirectory, i.ToString().PadLeft(resultConfiguration.ResultAllInOneSubdirectoryLength, '0')));
                        }
                        else
                        {
                            directory = Path.Combine(resultsFullGroupDirectory, jsonGroup);
                            if (i == converted)
                                checkDirectory = Path.GetFullPath(Path.Combine(directory, new('-', resultConfiguration.ResultAllInOneSubdirectoryLength)));
                            else
                                checkDirectory = Path.GetFullPath(Path.Combine(directory, i.ToString().PadLeft(resultConfiguration.ResultAllInOneSubdirectoryLength, '0')));
                        }
                        if (!Directory.Exists(checkDirectory))
                            _ = Directory.CreateDirectory(checkDirectory);
                        collection.Add(checkDirectory);
                    }
                    if (!string.IsNullOrEmpty(jsonGroup))
                        keyValuePairs.Add(jsonGroup, collection.ToArray());
                    else
                        keyValuePairs.Add(year.ToString(), collection.ToArray());
                }
            }
        }
        return Convert(results);
    }

}