using System.Text.RegularExpressions;
using Journal.Core.Models;

namespace Journal.Core.Services.Entries;

public static partial class JournalParser
{
    [GeneratedRegex(@"(?:\*\*Date:\*\*|\*\*Date:|Date:)\s*(.+)")]
    private static partial Regex DatePattern();
    [GeneratedRegex(@"^\#\#+\s*(.*)$")]
    private static partial Regex SectionHeaderPattern();
    [GeneratedRegex(@"^\s*[-*]\s*\[([xX ])\]\s*(.*)$")]
    private static partial Regex CheckboxPattern();
    [GeneratedRegex(@"^(!\w+)\s*((?:@\S+\s*)?)(?:\s*((?:#\S+\s*)*))?\s*$")]
    private static partial Regex FragmentHeaderPattern();
    [GeneratedRegex(@"^!\w+\s*")]
    private static partial Regex FragmentBoundaryPattern();

    public static JournalEntry ParseJournalContent(string content, string fileStem)
    {
        ArgumentNullException.ThrowIfNull(content);
        return new JournalEntry(
            date: ExtractDate(content, fileStem),
            rawContent: content,
            sections: ParseSections(content),
            fragments: ParseFragments(content));
    }

    public static string ExtractDate(string content, string fileStem)
    {
        ArgumentNullException.ThrowIfNull(content);
        if (string.IsNullOrWhiteSpace(fileStem))
            throw new ArgumentException("File stem is required", nameof(fileStem));

        var match = DatePattern().Match(content);
        if (match.Success)
        {
            var parsed = match.Groups[1].Value.Trim();
            if (!string.IsNullOrWhiteSpace(parsed))
                return parsed;
        }

        return fileStem.Trim();
    }

    public static Dictionary<string, ParsedSection> ParseSections(string content)
    {
        ArgumentNullException.ThrowIfNull(content);

        var parsedSections = new Dictionary<string, ParsedSection>();
        string? currentSectionTitle = null;
        var currentSectionContent = new List<string>();
        var currentSectionCheckboxes = new Dictionary<string, bool>();

        var lines = content.Split(["\r\n", "\n", "\r"], StringSplitOptions.None);
        foreach (var line in lines)
        {
            var sectionHeaderMatch = SectionHeaderPattern().Match(line.Trim());
            if (sectionHeaderMatch.Success)
            {
                if (currentSectionTitle is not null)
                {
                    parsedSections[currentSectionTitle] = new ParsedSection(
                        currentSectionTitle,
                        currentSectionContent,
                        currentSectionCheckboxes);
                }

                var headerText = sectionHeaderMatch.Groups[1].Value.Trim();
                var foundTitle = FindCanonicalSectionTitle(headerText);

                if (foundTitle is not null)
                {
                    currentSectionTitle = foundTitle;
                    currentSectionContent = [];
                    currentSectionCheckboxes = [];
                }
                else
                {
                    currentSectionTitle = null;
                    currentSectionContent = [];
                    currentSectionCheckboxes = [];
                }

                continue;
            }

            if (currentSectionTitle is not null)
            {
                var checkboxMatch = CheckboxPattern().Match(line);
                if (checkboxMatch.Success)
                {
                    var isChecked = checkboxMatch.Groups[1].Value.Trim().Equals("x", StringComparison.OrdinalIgnoreCase);
                    var checkboxText = checkboxMatch.Groups[2].Value.Trim();
                    currentSectionCheckboxes[checkboxText] = isChecked;
                }

                currentSectionContent.Add(line);
            }
        }

        if (currentSectionTitle is not null)
        {
            parsedSections[currentSectionTitle] = new ParsedSection(
                currentSectionTitle,
                currentSectionContent,
                currentSectionCheckboxes);
        }

        return parsedSections;
    }

    public static List<Fragment> ParseFragments(string content)
    {
        ArgumentNullException.ThrowIfNull(content);

        var fragments = new List<Fragment>();
        var lines = content.Split(["\r\n", "\n", "\r"], StringSplitOptions.None);

        for (var i = 0; i < lines.Length; i++)
        {
            var headerMatch = FragmentHeaderPattern().Match(lines[i]);
            if (!headerMatch.Success)
                continue;

            var type = headerMatch.Groups[1].Value.Trim();
            var timeToken = headerMatch.Groups[2].Value.Trim().TrimStart('@');
            var tagsToken = headerMatch.Groups[3].Value.Trim();

            var descriptionLines = new List<string>();
            var j = i + 1;
            while (j < lines.Length && !FragmentBoundaryPattern().IsMatch(lines[j]))
            {
                descriptionLines.Add(lines[j]);
                j++;
            }

            var description = string.Join("\n", descriptionLines).Trim();
            if (!string.IsNullOrWhiteSpace(description))
            {
                var fragment = new Fragment(type, description);
                if (!string.IsNullOrWhiteSpace(timeToken) && DateTimeOffset.TryParse(timeToken, out var parsedTime))
                    fragment.Time = parsedTime;

                if (!string.IsNullOrWhiteSpace(tagsToken))
                {
                    fragment.Tags =
                    [
                        .. tagsToken.Split(' ', StringSplitOptions.RemoveEmptyEntries)
                            .Where(t => t.StartsWith('#'))
                            .Select(t => t.Trim().TrimStart('#'))
                            .Where(t => !string.IsNullOrWhiteSpace(t))
                    ];
                }

                fragments.Add(fragment);
            }

            i = j - 1;
        }

        return fragments;
    }

    private static string? FindCanonicalSectionTitle(string headerText)
    {
        foreach (var title in SectionTitles.Canonical)
        {
            if (headerText.Contains(title, StringComparison.OrdinalIgnoreCase))
                return title;
        }

        return null;
    }
}
