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

namespace File_Folder_Helper.Helpers;

internal static partial class HelperGenealogicalDataCommunication
{

    private record Input(string GenealogicalDataCommunicationDirectory,
                         string? GenealogicalDataCommunicationFile,
                         string? SingletonDirectory,
                         string? Destination);

    private record GenealogicalDataCommunicationCollections(ReadOnlyCollection<string> HeaderLines,
                                                            ReadOnlyDictionary<long, ReadOnlyCollection<string>> IndividualsToLines,
                                                            ReadOnlyCollection<ReadOnlyCollection<string>> FamilyGroupLines,
                                                            ReadOnlyCollection<string> FooterLines);

    private record GenealogicalDataCommunicationRelation(int FamilyIndex,
                                                         string Relation,
                                                         int Id,
                                                         string? LineTwo);

    private record PersonHour(char Status,
                              char Sex,
                              char First);

    private record Collections(ReadOnlyDictionary<long, long> IdToPersonKey,
                               ReadOnlyDictionary<long, string> IdToName,
                               ReadOnlyDictionary<long, string> IdToGivenName,
                               ReadOnlyCollection<PersonExport> PersonExportCollection);

    private record PersonExport(long Id,
                                ReadOnlyCollection<string> Lines,
                                string PersonKeyFormatted,
                                char[] AgeCollection,
                                DateTime DateTime,
                                long PersonKey);

    private record Family(string? Title,
                         string? Id,
                         string? Index,
                         string PersonName,
                         Person Person,
                         long PersonKey,
                         string? LineTwo);

    [JsonSourceGenerationOptions(WriteIndented = true)]
    [JsonSerializable(typeof(ReadOnlyCollection<ReadOnlyCollection<string>>))]
    internal partial class CollectionSourceGenerationContext : JsonSerializerContext
    {
    }

    [JsonSourceGenerationOptions(WriteIndented = true)]
    [JsonSerializable(typeof(ReadOnlyDictionary<string, ReadOnlyCollection<ReadOnlyCollection<string>>>))]
    internal partial class DictionarySourceGenerationContext : JsonSerializerContext
    {
    }

    private static string? GetFaceBook(Person person) =>
        person.Birth?.Continue.Where(l => !l.Contains("profile.php?id=") && l.StartsWith("https://www.facebook.com/")).Select(l => l[25..].Split('/')[0]).FirstOrDefault();

    private static string? GetFaceBookId(Person person) =>
        person.Birth?.Continue.Where(l => l.StartsWith("https://www.facebook.com/profile.php?id=")).Select(l => l[40..].Split('&')[0]).FirstOrDefault();

    private static string GetKey(Family family) =>
       $"{family.Id}-{family.Index}".Trim('-');

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

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

    private static ReadOnlyCollection<string> GetObjectCollection(Person? person)
    {
        List<string> results;
        if (person is null)
            results = ["Id", "First-Name", "Last-Name", "Birth-Date", "Sex", "Address", "City", "State", "Zip", "Phone", "E-mail", "Facebook", "Facebook-Id", "Comment", "U-Id"];
        else
        {
            string? facebook = GetFaceBook(person);
            string? facebookId = GetFaceBookId(person);
            results =
            [
                person.Id.ToString(),
                string.Concat(person.Name?.Given),
                string.Concat(person.Name?.Sur),
                string.Concat(person.Birth?.Date.ToString()),
                string.Concat(person.Sex),
                string.Empty,
                string.Empty,
                "NM",
                string.Empty,
                string.Empty,
                string.Empty,
                string.Concat(facebook),
                string.Concat(facebookId),
                string.Empty,
                string.Concat(person.UId)
            ];
        }
        return new(results);
    }

    private static ReadOnlyCollection<string> GetDistinctSortedKeys(List<Family> familyCollection, char personTitleFilter)
    {
        string[] results;
        string key;
        List<(string? Index, string Key)> collection = [];
        foreach (Family family in familyCollection)
        {
            if (family.Id is null)
                continue;
            if (string.IsNullOrEmpty(family.Title) || family.Title[0] != personTitleFilter)
                continue;
            key = GetKey(family);
            collection.Add((family.Index, key));
        }
        results = (from l in collection orderby l.Key, l.Index?.Length descending select l.Key).Distinct().ToArray();
        return new(results);
    }

    private static Dictionary<string, List<ReadOnlyCollection<string>>> Convert(ReadOnlyCollection<string> distinctSortedKKeys)
    {
        Dictionary<string, List<ReadOnlyCollection<string>>> results = [];
        ReadOnlyCollection<string> collection;
        List<ReadOnlyCollection<string>>? objectCollection;
        foreach (string key in distinctSortedKKeys)
        {
            if (results.ContainsKey(key))
                continue;
            if (!results.TryGetValue(key, out objectCollection))
            {
                results.Add(key, []);
                if (!results.TryGetValue(key, out objectCollection))
                    throw new NotSupportedException();
            }
            collection = GetObjectCollection(person: null);
            objectCollection.Add(collection);
        }
        return results;
    }

    private static ReadOnlyCollection<string> GetHeaderLines(string startsWith, string[] sourceLines)
    {
        List<string> results = [];
        for (int i = 0; i < sourceLines.Length; i++)
        {
            if (sourceLines[i].StartsWith(startsWith))
                break;
            results.Add(sourceLines[i]);
        }
        return new(results);
    }

    private static long? GetId(string line)
    {
        long? result;
        string[] segments = line.Split('@');
        result = segments[1].Length < 2 || !long.TryParse(segments[1][1..], out long idValue) ? null : idValue;
        return result;
    }

    private static Dictionary<string, List<string>> GetTxtFileCollection(Input input)
    {
        Dictionary<string, List<string>> results = [];
        string[] lines;
        string[] directories;
        string directoryName;
        string? sourceDirectory;
        string? parentDirectory;
        List<string>? collectionA;
        List<string>? collectionB;
        string siblingDirectoryName;
        string[] files = input.SingletonDirectory is null || !Directory.Exists(input.SingletonDirectory) ? [] : Directory.GetFiles(input.SingletonDirectory, "*.txt", SearchOption.AllDirectories);
        foreach (string file in files)
        {
            sourceDirectory = Path.GetDirectoryName(file);
            if (sourceDirectory is null)
                continue;
            parentDirectory = Path.GetDirectoryName(sourceDirectory);
            if (parentDirectory is null)
                continue;
            lines = File.ReadAllLines(file);
            if (lines.Length != 1 || lines.Length == 2 && string.IsNullOrEmpty(lines[1]))
                continue;
            directoryName = Path.GetFileName(sourceDirectory);
            if (!results.TryGetValue(directoryName, out collectionA))
            {
                results.Add(directoryName, []);
                if (!results.TryGetValue(directoryName, out collectionA))
                    throw new Exception();
            }
            collectionA.Add(lines[0]);
            directories = Directory.GetDirectories(parentDirectory, "*", SearchOption.TopDirectoryOnly);
            foreach (string directory in directories)
            {
                siblingDirectoryName = Path.GetFileName(directory);
                collectionA.Add(siblingDirectoryName);
            }
            foreach (string directory in directories)
            {
                siblingDirectoryName = Path.GetFileName(directory);
                if (!results.TryGetValue(siblingDirectoryName, out collectionB))
                {
                    results.Add(siblingDirectoryName, []);
                    if (!results.TryGetValue(siblingDirectoryName, out collectionB))
                        throw new Exception();
                }
                collectionB.AddRange(collectionA);
                collectionB.Add(lines[0]);
            }
        }
        return results;
    }

    private static (int, Name) GetName(ReadOnlyCollection<string> lines, int i)
    {
        Name result;
        string seven;
        string? surName = null;
        string? nickName = null;
        string? givenName = null;
        string? suffixName = null;
        string? forwardSlashFullName = lines[i][..7] == "1 NAME " ? lines[i][7..] : null;
        for (int j = i + 1; j < lines.Count; j++)
        {
            if (lines[j][0] == '1')
                break;
            i++;
            if (lines[j].Length < 7)
                throw new NotImplementedException();
            seven = lines[j][..7];
            if (seven == "2 GIVN ")
                givenName = lines[j][7..];
            else if (seven == "2 SURN ")
                surName = lines[j][7..];
            else if (seven == "2 NICK ")
                nickName = lines[j][7..];
            else if (seven == "2 NSFX ")
                suffixName = lines[j][7..];
            else
                throw new NotImplementedException();
        }
        result = new(forwardSlashFullName, givenName, surName, nickName, suffixName);
        return (i, result);
    }

    private static (int, Birth, bool) GetBirth(Dictionary<string, List<string>> keyValuePairs, ReadOnlyCollection<string> lines, int i)
    {
        Birth result;
        string seven;
        string? note = null;
        bool moreAdded = false;
        List<string>? collection;
        DateOnly? dateOnly = null;
        List<string> distinct = [];
        List<string> @continue = [];
        for (int j = i + 1; j < lines.Count; j++)
        {
            if (lines[j][0] == '1')
                break;
            i++;
            if (lines[j].Length < 7)
                throw new NotImplementedException();
            seven = lines[j][..7];
            if (seven == "2 DATE ")
                dateOnly = !DateOnly.TryParseExact(lines[j][7..], "d MMM yyyy", CultureInfo.InvariantCulture, DateTimeStyles.None, out DateOnly dateOnlyValue) ? null : dateOnlyValue;
            else if (seven == "2 NOTE ")
                note = lines[j][7..];
            else if (seven == "3 CONT ")
            {
                for (int k = j; k < lines.Count; k++)
                {
                    if (lines[k][0] == '1')
                        break;
                    seven = lines[k][..7];
                    if (seven != "3 CONT ")
                        break;
                    j++;
                    @continue.Add(lines[k][7..]);
                }
                if (note is not null && keyValuePairs.TryGetValue(note, out collection))
                {
                    distinct.Add(note);
                    distinct.AddRange(@continue);
                    foreach (string text in collection)
                    {
                        if (distinct.Contains(text))
                            continue;
                        distinct.Add(text);
                        if (!moreAdded)
                            moreAdded = true;
                        @continue.Add(text);
                    }
                }
            }
            else
                continue;
        }
        result = new(dateOnly, note, @continue);
        return (i, result, moreAdded);
    }

    private static (int, Death) GetDeath(ReadOnlyCollection<string> lines, int i)
    {
        Death result;
        string seven;
        string? note = null;
        DateOnly? dateOnly = null;
        List<string> @continue = [];
        bool? isDead = lines[i].Length == 8 && lines[i][..8] == "1 DEAT Y" ? true : lines[i].Length == 8 && lines[i][..8] == "1 DEAT N" ? false : null;
        for (int j = i + 1; j < lines.Count; j++)
        {
            if (lines[j][0] == '1')
                break;
            i++;
            if (lines[j].Length < 7)
                throw new NotImplementedException();
            seven = lines[j][..7];
            if (seven == "2 DATE ")
                dateOnly = !DateOnly.TryParseExact(lines[j][7..], "d MMM yyyy", CultureInfo.InvariantCulture, DateTimeStyles.None, out DateOnly dateOnlyValue) ? null : dateOnlyValue;
            else if (seven == "2 NOTE ")
                note = lines[j][7..];
            else if (seven == "3 CONT ")
            {
                for (int k = j; k < lines.Count; k++)
                {
                    if (lines[k][0] == '1')
                        break;
                    seven = lines[k][..7];
                    if (seven != "3 CONT ")
                        break;
                    j++;
                    @continue.Add(lines[k][7..]);
                }
            }
            else
                throw new NotImplementedException();
        }
        result = new(dateOnly is not null ? true : isDead, dateOnly, note, @continue);
        return (i, result);
    }

    private static (int, Change) GetChange(ReadOnlyCollection<string> lines, int i)
    {
        Change result;
        string seven;
        string? note = null;
        DateOnly? dateOnly = null;
        List<string> @continue = [];
        for (int j = i + 1; j < lines.Count; j++)
        {
            if (lines[j][0] == '1')
                break;
            i++;
            if (lines[j].Length < 7)
                throw new NotImplementedException();
            seven = lines[j][..7];
            if (seven == "2 DATE ")
                dateOnly = !DateOnly.TryParseExact(lines[j][7..], "d MMM yyyy", CultureInfo.InvariantCulture, DateTimeStyles.None, out DateOnly dateOnlyValue) ? null : dateOnlyValue;
            else if (seven == "2 NOTE ")
                note = lines[j][7..];
            else if (seven == "3 CONT ")
            {
                for (int k = j; k < lines.Count; k++)
                {
                    if (lines[k][0] == '1')
                        break;
                    seven = lines[k][..7];
                    if (seven != "3 CONT ")
                        break;
                    j++;
                    @continue.Add(lines[k][7..]);
                }
            }
            else
                throw new NotImplementedException();
        }
        result = new(dateOnly, note, @continue);
        return (i, result);
    }

    private static string[] GetNewLines(ReadOnlyCollection<string> lines, Birth? birth)
    {
        List<string> results = [];
        string six;
        string text;
        string seven;
        List<string> @continue = birth is null ? [] : birth.Continue.ToList();
        for (int i = 0; i < lines.Count; i++)
        {
            if (birth is null)
                throw new NotSupportedException();
            if (lines[i].Length < 6)
                throw new NotImplementedException();
            results.Add(lines[i]);
            six = lines[i][..6];
            if (lines[i][0] == '1')
            {
                if (six != "1 BIRT")
                    continue;
                for (int j = i + 1; j < lines.Count; j++)
                {
                    if (lines[j].Length < 7)
                        throw new NotImplementedException();
                    if (lines[j][0] == '1')
                        break;
                    i++;
                    seven = lines[j][..7];
                    if (seven != "3 CONT ")
                        results.Add(lines[j]);
                    else
                    {
                        text = lines[j][7..];
                        if (@continue.Contains(text))
                        {
                            results.Add(lines[j]);
                            _ = @continue.Remove(text);
                        }
                    }
                }
                results.AddRange(from l in @continue orderby l select $"3 CONT {l}");
            }
        }
        return results.ToArray();
    }

    [GeneratedRegex("[\\\\,\\/,\\:,\\*,\\?,\\\",\\<,\\>,\\|]")]
    private static partial Regex WindowsFileSystem();

    private static List<long> GetIdsWhenPersonHasTitle(ReadOnlyDictionary<long, Person> people)
    {
        List<long> results = [];
        foreach (KeyValuePair<long, Person> keyValuePair in people)
        {
            if (keyValuePair.Value.Title is null)
                continue;
            results.Add(keyValuePair.Key);
        }
        return results;
    }

    private static ReadOnlyCollection<GenealogicalDataCommunicationRelation> GetRelations(ReadOnlyCollection<ReadOnlyCollection<string>> familyGroupLines)
    {
        List<GenealogicalDataCommunicationRelation> results = [];
        int id;
        string relation;
        string[] segments;
        ReadOnlyCollection<string> familyLines;
        for (int i = 0; i < familyGroupLines.Count; i++)
        {
            familyLines = familyGroupLines[i];
            for (int j = 0; j < familyLines.Count; j++)
            {
                segments = familyLines[j].Split('@');
                if (segments[0].Length < 3 || segments.Length != 3)
                    continue;
                if (!int.TryParse(segments[1][1..], out id))
                    continue;
                relation = segments[0][2..].Trim();
                if (j + 1 >= familyLines.Count || familyLines[j + 1].Length < 3 || familyLines[j + 1][..3] != "2 _")
                    results.Add(new(i, relation, id, null));
                else
                    results.Add(new(i, relation, id, familyLines[j + 1][2..]));
            }
        }
        return new(results.OrderBy(l => l.FamilyIndex).ToArray());
    }

    private static ReadOnlyDictionary<string, ReadOnlyCollection<ReadOnlyCollection<string>>> GetKeyValuePairs(List<Family> familyCollection, char personTitleFilter)
    {
        Dictionary<string, ReadOnlyCollection<ReadOnlyCollection<string>>> results;
        Dictionary<string, List<ReadOnlyCollection<string>>> keyValuePairs;
        string id;
        string key;
        ReadOnlyCollection<string> collection;
        List<ReadOnlyCollection<string>>? objectCollection;
        ReadOnlyCollection<string> distinctSortedKeys = GetDistinctSortedKeys(familyCollection, personTitleFilter);
        keyValuePairs = Convert(distinctSortedKeys);
        foreach (Family family in familyCollection)
        {
            if (family.Id is null)
                continue;
            if (string.IsNullOrEmpty(family.Title) || family.Title[0] != personTitleFilter)
                continue;
            id = family.Person.Id.ToString();
            key = GetKey(family);
            if (!keyValuePairs.TryGetValue(key, out objectCollection))
                throw new NotSupportedException();
            collection = GetObjectCollection(family.Person);
            objectCollection.Add(collection);
        }
        results = Convert(keyValuePairs);
        return new(results);
    }

    private static string GetHourGroup(string personDisplayDirectoryName, int hour) =>
        hour == 0 ? "Unknown-Unknown-Unknown" :
        hour == 1 ? "Unknown-Unknown-Unknown" :
        hour == 2 ? "Unknown-Unknown-Unknown" :
        hour == 3 ? "Alive-Unknown-Yes" :
        hour == 4 ? "Alive-Female-Yes" :
        hour == 5 ? "Alive-Male-Yes" :
        hour == 6 ? "Alive-Female-No" :
        hour == 7 ? "Alive-Male-No" :
        hour == 13 ? "Dead-Unknown-Yes" :
        hour == 14 ? "Dead-Female-Yes" :
        hour == 15 ? "Dead-Male-Yes" :
        hour == 16 ? "Dead-Female-No" :
        hour == 17 ? "Dead-Male-No" :
        throw new NotImplementedException(personDisplayDirectoryName);

    private static (int, TimeSpan) GetAge(long minuendTicks, long subtrahendTicks)
    {
        TimeSpan result;
        int years = 0;
        DateTime check = new(subtrahendTicks);
        for (int i = 0; i < int.MaxValue; i++)
        {
            check = check.AddYears(1);
            if (check.Ticks > minuendTicks)
                break;
            years += 1;
        }
        result = new(minuendTicks - check.AddYears(-1).Ticks);
        return (years, result);
    }

    private static string? GetYearGroup(string year) =>
        !int.TryParse(year[2..], out int part) ? null : string.Concat(year[..^2], part < 50 ? "--" : "++");

    private static Input GetInput(List<string> args)
    {
        Input result;
        string? destination = null;
        string? singletonDirectory = null;
        string genealogicalDataCommunicationRootDirectory = Path.GetFullPath(args[0]);
        string fileName = Path.GetFileName(genealogicalDataCommunicationRootDirectory);
        string[] files = Directory.GetFiles(genealogicalDataCommunicationRootDirectory, $"{fileName}.ged", SearchOption.TopDirectoryOnly);
        string genealogicalDataCommunicationDirectory = Path.Combine(genealogicalDataCommunicationRootDirectory, fileName);
        if (!Directory.Exists(genealogicalDataCommunicationDirectory))
            _ = Directory.CreateDirectory(genealogicalDataCommunicationDirectory);
        for (int i = 1; i < args.Count; i++)
        {
            if (args[i].Length == 2 && i + 1 < args.Count)
            {
                if (args[i][1] == 's')
                    singletonDirectory = Path.GetFullPath(args[i + 1]);
                else if (args[i][1] == 'd')
                    destination = Path.GetFullPath(args[i + 1]);
                i++;
            }
        }
        string? genealogicalDataCommunicationFile = files.Length != 1 ? null : files[0];
        if (destination is not null)
        {
            string? root = Path.GetPathRoot(destination);
            if (root is null || !Directory.Exists(root))
                throw new NotSupportedException($"This method requires frontMatterYamlLines valid -d path <{root}>!");
            if (!Directory.Exists(destination))
                _ = Directory.CreateDirectory(destination);
        }
        result = new(genealogicalDataCommunicationDirectory, genealogicalDataCommunicationFile, singletonDirectory, destination);
        return result;
    }

    private static GenealogicalDataCommunicationCollections GetGenealogicalDataCommunicationCollections(Input input)
    {
        GenealogicalDataCommunicationCollections result;
        long? id;
        List<string> lines = [];
        List<string> footerLines = [];
        const string startsWith = "0 @";
        Dictionary<long, List<string>> keyValuePairs = [];
        List<ReadOnlyCollection<string>> familyGroupLines = [];
        string[] sourceLines = string.IsNullOrEmpty(input.GenealogicalDataCommunicationFile) || !File.Exists(input.GenealogicalDataCommunicationFile) ? [] : File.ReadAllLines(input.GenealogicalDataCommunicationFile);
        ReadOnlyCollection<string> headerLines = GetHeaderLines(startsWith, sourceLines);
        for (int i = headerLines.Count; i < sourceLines.Length; i++)
        {
            if (!sourceLines[i].StartsWith(startsWith))
                continue;
            if (sourceLines[i].EndsWith("@ SOUR") || sourceLines[i].EndsWith("@ SUBM") || sourceLines[i].EndsWith("@ OBJE") || sourceLines[i].EndsWith("@ REPO"))
                continue;
            lines.Add(sourceLines[i]);
            if (sourceLines[i].EndsWith("@ FAM"))
            {
                lines.Clear();
                for (int j = sourceLines.Length - 1; j >= i; j--)
                {
                    lines.Add(sourceLines[j]);
                    if (sourceLines[j][0] == '0')
                    {
                        if (!sourceLines[j].EndsWith("@ FAM"))
                            footerLines.AddRange(lines);
                        else
                        {
                            lines.Reverse();
                            familyGroupLines.Add(new(lines.ToArray()));
                        }
                        lines.Clear();
                    }
                }
                familyGroupLines.Reverse();
                footerLines.Reverse();
                break;
            }
            else if (sourceLines[i].EndsWith("@ INDI"))
            {
                id = GetId(sourceLines[i]);
                for (int j = i + 1; j < sourceLines.Length; j++)
                {
                    if (sourceLines[j].StartsWith(startsWith))
                        break;
                    lines.Add(sourceLines[j]);
                }
                if (id is null)
                    throw new Exception(string.Join(Environment.NewLine, lines));
                keyValuePairs.Add(id.Value, []);
                if (lines.Count == 0)
                    continue;
                keyValuePairs[id.Value].AddRange(lines);
                lines.Clear();
            }
            else
                throw new NotSupportedException();
        }
        ReadOnlyDictionary<long, ReadOnlyCollection<string>> individualsToLines = Convert(keyValuePairs);
        result = new(headerLines, individualsToLines, new(familyGroupLines), new(footerLines));
        return result;
    }

    private static ReadOnlyDictionary<long, Person> GetPeople(Input input, GenealogicalDataCommunicationCollections genealogicalDataCommunicationCollections)
    {
        Dictionary<long, Person> results = [];
        long? id;
        char? sex;
        Name? name;
        string six;
        string? uId;
        Birth? birth;
        Death? death;
        Person person;
        string? title;
        Change? change;
        bool? moreAdded;
        ReadOnlyCollection<string> lines;
        Dictionary<string, List<string>> keyValuePairs = GetTxtFileCollection(input);
        foreach (KeyValuePair<long, ReadOnlyCollection<string>> keyValuePair in genealogicalDataCommunicationCollections.IndividualsToLines)
        {
            id = null;
            sex = null;
            name = null;
            uId = null;
            birth = null;
            death = null;
            title = null;
            change = null;
            moreAdded = null;
            lines = keyValuePair.Value;
            for (int i = 0; i < lines.Count; i++)
            {
                if (lines[i].Length < 6)
                    throw new NotImplementedException();
                six = lines[i][..6];
                if (lines[i][0] == '0')
                {
                    if (lines[i][^6..] == "@ INDI")
                        id = GetId(lines[i]);
                    else
                        throw new NotImplementedException();
                }
                else if (lines[i][0] == '1')
                {
                    if (six == "1 NAME")
                        (i, name) = GetName(lines, i);
                    else if (six == "1 SEX ")
                        sex = lines[i].Length != 7 ? null : lines[i][6];
                    else if (six is "1 UID " or "1 _UID")
                        uId = lines[i].Length == 6 ? null : lines[i][7..];
                    else if (six == "1 BIRT")
                        (i, birth, moreAdded) = GetBirth(keyValuePairs, lines, i);
                    else if (six == "1 DEAT")
                        (i, death) = GetDeath(lines, i);
                    else if (six == "1 TITL")
                        title = lines[i].Length == 6 ? null : lines[i][7..];
                    else if (six == "1 CHAN")
                        (i, change) = GetChange(lines, i);
                    else if (six is "1 FAMC" or "1 FAMS")
                        continue;
                    else
                        throw new NotImplementedException();
                }
            }
            if (id is null)
                throw new Exception(string.Join(Environment.NewLine, lines));
            if (moreAdded is null || !moreAdded.Value)
                person = new(id.Value, name, sex, uId, birth, title, death, change, lines.ToArray());
            else
                person = new(id.Value, name, sex, uId, birth, title, death, change, GetNewLines(lines, birth));
            results.Add(id.Value, person);
        }
        return new(results);
    }

    private static Collections GetCollections(AppSettings appSettings, ReadOnlyDictionary<long, Person> people)
    {
        Collections result;
        long personKey;
        char[] ageCollection;
        List<PersonExport> collection = [];
        Dictionary<long, string> idToName = [];
        Dictionary<long, long> idToPersonKey = [];
        Dictionary<long, string> idToGivenName = [];
        int length = appSettings.PersonBirthdayFormat.Length;
        foreach (KeyValuePair<long, Person> keyValuePair in people)
        {
            if (keyValuePair.Value.Birth?.Note is null)
                continue;
            if (string.IsNullOrEmpty(keyValuePair.Value.Name?.ForwardSlashFull))
                continue;
            if (keyValuePair.Value.Birth.Note.Length != length)
                continue;
            if (!DateTime.TryParseExact(keyValuePair.Value.Birth.Note, appSettings.PersonBirthdayFormat, CultureInfo.InvariantCulture, DateTimeStyles.None, out DateTime dateTime))
                continue;
            personKey = dateTime.Ticks;
            idToPersonKey.Add(keyValuePair.Key, personKey);
            idToName.Add(keyValuePair.Key, WindowsFileSystem().Replace(keyValuePair.Value.Name.ForwardSlashFull, "_"));
            ageCollection = keyValuePair.Value.Birth.Continue.Count == 0 ? [] : keyValuePair.Value.Birth.Continue[0].ToArray();
            idToGivenName.Add(keyValuePair.Key, string.IsNullOrEmpty(keyValuePair.Value.Name.Given) ? WindowsFileSystem().Replace(keyValuePair.Value.Name.ForwardSlashFull, "_") : WindowsFileSystem().Replace(keyValuePair.Value.Name.Given, "_"));
            collection.Add(new(keyValuePair.Key, new(keyValuePair.Value.Lines), keyValuePair.Value.Birth.Note, ageCollection, dateTime, personKey));
        }
        result = new(new(idToPersonKey), new(idToName), new(idToGivenName), new(collection));
        return result;
    }

    private static List<Family> GetFamilyCollection(ReadOnlyCollection<ReadOnlyCollection<string>> familyGroupLines, ReadOnlyDictionary<long, Person> people, ReadOnlyDictionary<long, long> idToPersonKey, ReadOnlyDictionary<long, string> idToName, ReadOnlyDictionary<long, string> idToGivenName)
    {
        List<Family> results = [];
        string? name;
        long personKey;
        Person? person;
        string? givenName;
        string familyIndex;
        const string wife = "WIFE";
        string? familyTitle = null;
        const string child = "CHIL";
        const string husband = "HUSB";
        string wifeName = string.Empty;
        string? lastFamilyIndex = null;
        string husbandName = string.Empty;
        List<long> family = GetIdsWhenPersonHasTitle(people);
        ReadOnlyCollection<GenealogicalDataCommunicationRelation> genealogicalDataCommunicationRelations = GetRelations(familyGroupLines);
        foreach (GenealogicalDataCommunicationRelation genealogicalDataCommunicationRelation in genealogicalDataCommunicationRelations)
        {
            if (idToName.Count == 0 || idToGivenName.Count == 0)
                break;
            if (!idToName.TryGetValue(genealogicalDataCommunicationRelation.Id, out name))
                continue;
            if (!people.TryGetValue(genealogicalDataCommunicationRelation.Id, out person))
                continue;
            if (!idToGivenName.TryGetValue(genealogicalDataCommunicationRelation.Id, out givenName))
                continue;
            familyIndex = genealogicalDataCommunicationRelation.FamilyIndex.ToString("0000");
            if (lastFamilyIndex is not null && lastFamilyIndex != familyIndex)
            {
                familyTitle = null;
                wifeName = string.Empty;
                husbandName = string.Empty;
            }
            lastFamilyIndex = familyIndex;
            if (genealogicalDataCommunicationRelation.Relation == husband)
            {
                husbandName = givenName;
                if (person.Title is not null)
                    familyTitle = person.Title;
                continue;
            }
            if (genealogicalDataCommunicationRelation.Relation == wife)
            {
                wifeName = givenName;
                if (person.Title is not null)
                    familyTitle = person.Title;
                continue;
            }
            if (genealogicalDataCommunicationRelation.Relation != child)
                continue;
            if (!idToPersonKey.TryGetValue(genealogicalDataCommunicationRelation.Id, out personKey))
                continue;
            if (person.Title is not null)
                familyTitle = person.Title;
            _ = family.Remove(genealogicalDataCommunicationRelation.Id);
            results.Add(new(familyTitle, $"{husbandName}-{wifeName}".Trim('-'), familyIndex, name, person, personKey, genealogicalDataCommunicationRelation.LineTwo));
        }
        foreach (KeyValuePair<long, Person> keyValuePair in people)
        {
            if (!family.Contains(keyValuePair.Key))
                continue;
            if (!idToName.TryGetValue(keyValuePair.Key, out name))
                continue;
            if (!idToPersonKey.TryGetValue(keyValuePair.Key, out personKey))
                continue;
            if (!idToGivenName.TryGetValue(keyValuePair.Key, out givenName))
                continue;
            if (!family.Remove(keyValuePair.Key))
                continue;
            results.Add(new(keyValuePair.Value.Title, givenName, null, name, keyValuePair.Value, personKey, null));
        }
        if (family.Count > 0)
            throw new NotSupportedException();
        foreach (GenealogicalDataCommunicationRelation genealogicalDataCommunicationRelation in genealogicalDataCommunicationRelations)
        {
            if (idToName.Count == 0 || idToGivenName.Count == 0)
                break;
            if (!idToName.TryGetValue(genealogicalDataCommunicationRelation.Id, out name))
                continue;
            if (!people.TryGetValue(genealogicalDataCommunicationRelation.Id, out person))
                continue;
            if (!idToPersonKey.TryGetValue(genealogicalDataCommunicationRelation.Id, out personKey))
                continue;
            results.Add(new(null, null, null, name, person, personKey, genealogicalDataCommunicationRelation.LineTwo));
        }
        return results;
    }

    private static void WriteJsonFiles(AppSettings appSettings, Input input, GenealogicalDataCommunicationCollections genealogicalDataCommunicationCollections, ReadOnlyDictionary<long, Person> people, List<Family> familyCollection)
    {
        string json;
        if (people.Count != genealogicalDataCommunicationCollections.IndividualsToLines.Count)
            throw new NotSupportedException();
        ReadOnlyDictionary<string, ReadOnlyCollection<ReadOnlyCollection<string>>> keyValuePairs;
        json = JsonSerializer.Serialize(new(people), PeopleSourceGenerationContext.Default.DictionaryInt64Person);
        File.WriteAllText(Path.Combine(input.GenealogicalDataCommunicationDirectory, "people.json"), json);
        Dictionary<long, Person>? result = JsonSerializer.Deserialize(json, PeopleSourceGenerationContext.Default.DictionaryInt64Person);
        if (result is null)
            throw new NullReferenceException(nameof(result));
        json = JsonSerializer.Serialize(genealogicalDataCommunicationCollections.FamilyGroupLines, CollectionSourceGenerationContext.Default.ReadOnlyCollectionReadOnlyCollectionString);
        File.WriteAllText(Path.Combine(input.GenealogicalDataCommunicationDirectory, "family.json"), json);
        foreach (char personTitleFilter in appSettings.PersonTitleFilters)
        {
            keyValuePairs = GetKeyValuePairs(familyCollection, personTitleFilter);
            json = JsonSerializer.Serialize(keyValuePairs, DictionarySourceGenerationContext.Default.ReadOnlyDictionaryStringReadOnlyCollectionReadOnlyCollectionString);
            File.WriteAllText(Path.Combine(input.GenealogicalDataCommunicationDirectory, $"{personTitleFilter}.json"), json);
        }
    }

    private static void WriteGenealogicalDataCommunicationCollections(ILogger<Worker> logger, Input input, GenealogicalDataCommunicationCollections genealogicalDataCommunicationCollections, ReadOnlyDictionary<long, Person> people)
    {
        List<string> lines = [];
        List<string> allLines = [];
        if (genealogicalDataCommunicationCollections.HeaderLines.Count > 0)
        {
            allLines.AddRange(genealogicalDataCommunicationCollections.HeaderLines);
            File.WriteAllLines(Path.Combine(input.GenealogicalDataCommunicationDirectory, "a.pged"), genealogicalDataCommunicationCollections.HeaderLines);
        }
        if (people.Count > 0)
        {
            lines.Clear();
            ReadOnlyCollection<string>? collection;
            foreach (KeyValuePair<long, Person> keyValuePair in people)
            {
                if (!genealogicalDataCommunicationCollections.IndividualsToLines.TryGetValue(keyValuePair.Key, out collection))
                    throw new NotSupportedException();
                if (keyValuePair.Value.Lines.Length != collection.Count)
                    logger.LogInformation("{name} has been changed", keyValuePair.Value.Name?.ForwardSlashFull);
                lines.AddRange(keyValuePair.Value.Lines);
            }
            allLines.AddRange(lines);
            File.WriteAllLines(Path.Combine(input.GenealogicalDataCommunicationDirectory, "b.pged"), lines);
        }
        if (genealogicalDataCommunicationCollections.FamilyGroupLines.Count > 0)
        {
            lines.Clear();
            foreach (ReadOnlyCollection<string> keyValuePair in genealogicalDataCommunicationCollections.FamilyGroupLines)
                lines.AddRange(keyValuePair);
            allLines.AddRange(lines);
            File.WriteAllLines(Path.Combine(input.GenealogicalDataCommunicationDirectory, "c.pged"), lines);
        }
        if (genealogicalDataCommunicationCollections.FooterLines.Count > 0)
        {
            allLines.AddRange(genealogicalDataCommunicationCollections.FooterLines);
            File.WriteAllLines(Path.Combine(input.GenealogicalDataCommunicationDirectory, "d.pged"), genealogicalDataCommunicationCollections.FooterLines);
        }
        File.WriteAllLines(Path.Combine(input.GenealogicalDataCommunicationDirectory, "e.ged"), allLines);
    }

    private static void ExportFamilies(AppSettings appSettings, Input input, List<Family> familyCollection)
    {
        string directory;
        DateTime dateTime;
        string? destinationRoot;
        string destinationDirectory;
        foreach (Family family in familyCollection)
        {
            destinationRoot = Path.GetDirectoryName(input.GenealogicalDataCommunicationFile);
            if (string.IsNullOrEmpty(destinationRoot))
                continue;
            dateTime = new(family.PersonKey);
            directory = family.Id is null ? "A-A-0000" : $"{family.Title ?? "O"}-{family.Id}-{family.Index}".Trim('-');
            destinationDirectory = Path.Combine(destinationRoot, directory, family.PersonName, dateTime.ToString(appSettings.PersonBirthdayFormat));
            if (!Directory.Exists(destinationDirectory))
                _ = Directory.CreateDirectory(destinationDirectory);
            File.WriteAllText(Path.Combine(destinationDirectory, $"{family.PersonName}.txt"), family.LineTwo);
        }
    }

    private static void Export(Input input, long ticks, ReadOnlyDictionary<long, string> idToName, ReadOnlyCollection<PersonExport> personExportCollection)
    {
        int age;
        string text;
        string? name;
        string directory;
        string hourGroup;
        string? yearGroup;
        long count = ticks;
        string rootDirectory;
        string approximateYears;
        List<string> distinct = [];
        List<string> duplicates = [];
        string personDisplayDirectoryName;
        foreach (PersonExport personExport in personExportCollection)
        {
            if (input.Destination is null)
                break;
            if (!idToName.TryGetValue(personExport.Id, out name))
                continue;
            hourGroup = GetHourGroup(name, personExport.DateTime.Hour);
            (age, _) = GetAge(DateTime.Now.Ticks, personExport.PersonKey);
            for (int i = 1; i < 3; i++)
            {
                if (i == 2)
                {
                    yearGroup = GetYearGroup(personExport.DateTime.Year.ToString());
                    personDisplayDirectoryName = name;
                    if (string.IsNullOrEmpty(yearGroup))
                        continue;
                }
                else if (i == 1)
                {
                    yearGroup = personExport.AgeCollection[0].ToString();
                    approximateYears = yearGroup[0] == '^' ? $"^{age}" : new string(personExport.AgeCollection);
                    personDisplayDirectoryName = $"{name}{approximateYears}";
                    if (distinct.Contains(personDisplayDirectoryName))
                    {
                        duplicates.Add(personDisplayDirectoryName);
                        continue;
                    }
                    distinct.Add(personDisplayDirectoryName);
                }
                else
                    throw new NotSupportedException();
                rootDirectory = i == 1 ? input.Destination : i == 2 ? input.GenealogicalDataCommunicationDirectory : throw new NotSupportedException();
                directory = Path.Combine(rootDirectory, yearGroup, hourGroup, personDisplayDirectoryName, personExport.PersonKeyFormatted);
                if (!Directory.Exists(directory))
                    _ = Directory.CreateDirectory(directory);
                if (i == 2)
                {
                    text = string.Join(Environment.NewLine, personExport.Lines);
                    count += 1;
                    File.WriteAllText(Path.Combine(directory, $"{count}-A.pged"), text);
                }
                text = string.Join(Environment.NewLine, personExport.Lines);
                if (!string.IsNullOrEmpty(text))
                {
                    count += 1;
                    if (i == 2)
                        File.WriteAllText(Path.Combine(directory, $"{count}-B.pged"), text);
                    else
                        File.WriteAllText(Path.Combine(directory, $"{personExport.PersonKeyFormatted}.pged"), text);
                }
            }
        }
        if (duplicates.Count > 0)
            throw new NotSupportedException();
    }

    internal static void FileSystemToGenealogicalDataCommunication(AppSettings appSettings, ILogger<Worker> logger, List<string> args)
    {
        Input input = GetInput(args);
        long ticks = DateTime.Now.Ticks;
        logger.LogInformation("{ticks}", ticks);
        logger.LogInformation("{old} {days} day(s) => {new}", 638258293638438812, 4, new DateTime(638258293638438812).AddDays(4.0001).Ticks);
        GenealogicalDataCommunicationCollections genealogicalDataCommunicationCollections = GetGenealogicalDataCommunicationCollections(input);
        ReadOnlyDictionary<long, Person> people = GetPeople(input, genealogicalDataCommunicationCollections);
        Collections collections = GetCollections(appSettings, people);
        if (collections.IdToPersonKey.Count != people.Count || collections.IdToPersonKey.Count != collections.IdToName.Count || collections.IdToPersonKey.Count != collections.IdToGivenName.Count)
            throw new NotSupportedException();
        List<Family> familyCollection = GetFamilyCollection(genealogicalDataCommunicationCollections.FamilyGroupLines, people, collections.IdToPersonKey, collections.IdToName, collections.IdToGivenName);
        WriteJsonFiles(appSettings, input, genealogicalDataCommunicationCollections, people, familyCollection);
        WriteGenealogicalDataCommunicationCollections(logger, input, genealogicalDataCommunicationCollections, people);
        if (input.Destination is not null)
            ExportFamilies(appSettings, input, familyCollection);
        if (input.Destination is not null)
            Export(input, ticks, collections.IdToName, collections.PersonExportCollection);
        if (string.IsNullOrEmpty(input.GenealogicalDataCommunicationFile))
            logger.LogInformation("{file} is null?", input.GenealogicalDataCommunicationDirectory);
    }

}