#nullable enable

using System.Text;

namespace MMoneyCoderSharp.Data;

/// <summary>
/// Adds Korean initial-consonant matching without weakening the exact identities
/// used after a search result is selected.
/// </summary>
public static class HangulSearchMatcher
{
    private const int HangulSyllableStart = 0xAC00;
    private const int HangulSyllableEnd = 0xD7A3;
    private const int SyllablesPerInitial = 21 * 28;
    private const int LeadingJamoStart = 0x1100;
    private const int LeadingJamoEnd = 0x1112;
    private const string CompatibilityInitials = "ㄱㄲㄴㄷㄸㄹㅁㅂㅃㅅㅆㅇㅈㅉㅊㅋㅌㅍㅎ";

    public static bool IsMatch(string candidate, string query)
    {
        ArgumentNullException.ThrowIfNull(candidate);
        ArgumentNullException.ThrowIfNull(query);

        var normalizedCandidate = candidate.Normalize(NormalizationForm.FormC);
        var normalizedQuery = query.Trim().Normalize(NormalizationForm.FormC);
        if (normalizedQuery.Length == 0)
        {
            return true;
        }

        if (normalizedCandidate.Contains(normalizedQuery, StringComparison.OrdinalIgnoreCase))
        {
            return true;
        }

        return TryCreateInitialQuery(normalizedQuery, out var initialQuery) &&
            CreateInitialKey(normalizedCandidate).Contains(
                initialQuery,
                StringComparison.Ordinal);
    }

    public static bool IsInitialConsonantQuery(string query)
    {
        ArgumentNullException.ThrowIfNull(query);
        return TryCreateInitialQuery(
            query.Trim().Normalize(NormalizationForm.FormC),
            out _);
    }

    private static bool TryCreateInitialQuery(string query, out string initialQuery)
    {
        var builder = new StringBuilder(query.Length);
        foreach (var character in query)
        {
            if (char.IsWhiteSpace(character))
            {
                continue;
            }

            if (TryNormalizeInitial(character, out var initial))
            {
                builder.Append(initial);
                continue;
            }

            initialQuery = string.Empty;
            return false;
        }

        initialQuery = builder.ToString();
        return initialQuery.Length > 0;
    }

    private static string CreateInitialKey(string candidate)
    {
        var builder = new StringBuilder(candidate.Length);
        foreach (var character in candidate)
        {
            if (character is >= (char)HangulSyllableStart and <= (char)HangulSyllableEnd)
            {
                var initialIndex = (character - HangulSyllableStart) / SyllablesPerInitial;
                builder.Append(CompatibilityInitials[initialIndex]);
            }
            else if (TryNormalizeInitial(character, out var initial))
            {
                builder.Append(initial);
            }
        }

        return builder.ToString();
    }

    private static bool TryNormalizeInitial(char character, out char initial)
    {
        var compatibilityIndex = CompatibilityInitials.IndexOf(character);
        if (compatibilityIndex >= 0)
        {
            initial = CompatibilityInitials[compatibilityIndex];
            return true;
        }

        if (character is >= (char)LeadingJamoStart and <= (char)LeadingJamoEnd)
        {
            initial = CompatibilityInitials[character - LeadingJamoStart];
            return true;
        }

        initial = default;
        return false;
    }
}
