// Utils/Bangs.cs
// Utilities for handling !bangs via Kagi.

using System.Text.Json;
using System.Text.Json.Serialization;
using System.Web;
using GLib;
using OuchBrowser.Types;
using Uri = System.Uri;

namespace OuchBrowser.Utils;

internal class Bangs
{
	private readonly Dictionary<string, Bang> bangs = new();

	public Bangs()
	{
		var options = new JsonSerializerOptions
		{
			Converters = { new JsonStringEnumConverter() }
		};

		EmbeddedResource.Load("Bangs.json", out string bangsRaw);
		EmbeddedResource.Load("Bangs.Kagi.json", out string bangsKagiRaw);
		List<Bang> bangsKagi = JsonSerializer.Deserialize<List<Bang>>(bangsKagiRaw, options)!;
		List<Bang> bangsList = JsonSerializer.Deserialize<List<Bang>>(bangsRaw, options)!;
		bangsList.AddRange(bangsKagi);
		bangsList = bangsList.Where(n => n.Category != "Region search").ToList();

		foreach (Bang bang in bangsList)
		{
			bangs.Add(bang.Trigger, bang);

			if (bang.AdditionalTriggers != null)
				foreach (string trigger in bang.AdditionalTriggers) bangs.Add(trigger, bang);
		}
	}

	public string ExpandBang(string text)
	{
		string bangString = text.Split(' ')[0];
		string trigger = bangString.StartsWith('!') ? bangString.Substring(1) : bangString;
		string defaultSearch = settings.GetString("search-engine");

		bangs.TryGetValue(trigger, out Bang? bang);
		if (bang == null) return string.Format(defaultSearch, Uri.EscapeDataString(text));

		string templateUrl = bang.TemplateUrl;

		if (bang.TemplateUrl.StartsWith('/') && bang.Domain != "kagi.com" || bang.TemplateUrl.Contains("site:"))
		{
			var query_params = HttpUtility.ParseQueryString(templateUrl.Replace("/search", ""));
			templateUrl = string.Format(defaultSearch, query_params["q"]!);
		}
		else if (bang.TemplateUrl.StartsWith('/') && bang.Domain == "kagi.com")
		{
			templateUrl = "https://kagi.com" + templateUrl;
		}

		string query = string.Join(" ", text.Split(' ').Skip(1));
		return templateUrl.Replace("{{{s}}}", Uri.EscapeDataString(query));

	}

	public Bang[] AutocompleteBang(string text)
	{
		string bangString = text.Split(' ')[0];
		string trigger = bangString.StartsWith('!') ? bangString.Substring(1) : bangString;

		if (trigger == "") return []; // there are OVER 1000 BANGS, without this, the app will crash
		return bangs
			.Where(pair =>
				pair.Key.StartsWith(
					trigger,
					StringComparison.OrdinalIgnoreCase
				)
			)
			.Select(pair => pair.Value)
			.DistinctBy(bang => bang.Trigger)
			.OrderByDescending(bang => GetRankings().TryGetValue(bang.Trigger, out int rank) ? rank : 0)
			.ToArray();
	}

	public Bang? GetBang(string text)
	{
		string bangString = text.Split(' ')[0];
		string trigger = bangString.StartsWith('!') ? bangString.Substring(1) : bangString;

		bangs.TryGetValue(trigger, out Bang? bang);
		return bang;
	}

	private static Dictionary<string, int> GetRankings()
	{
		Dictionary<string, int> dict = new();
		Variant ranks = settings.GetValue("bang-rankings");
		VariantIter iter = ranks.IterNew();
		Variant currentValue;

		for (int i = 0; i < (int)ranks.NChildren(); i++)
		{
			currentValue = iter.NextValue()!;
			dict.Add(currentValue.GetChildValue(0).GetString(out _), currentValue.GetChildValue(1).GetInt32());
		}

		return dict;
	}

	public static void IncrementRanking(string bang)
	{
		Variant ranks = settings.GetValue("bang-rankings");
		VariantIter iter = ranks.IterNew();
		VariantBuilder builder = VariantBuilder.New(VariantType.New("a{si}"));
		Variant currentValue;
		bool found = false;

		// since there isn't a clean way to modify the dictionary of ranks,
		// we instead rebuild the dictionary and increment the rank by 1 to
		// push the !bang higher in autocompletion
		for (int i = 0; i < (int)ranks.NChildren(); i++)
		{
			currentValue = iter.NextValue()!;

			if (currentValue.GetChildValue(0).GetString(out _) == bang)
			{
				builder.AddValue(
					Variant.NewDictEntry(
						currentValue.GetChildValue(0),
						Variant.NewInt32(currentValue.GetChildValue(1).GetInt32() + 1)
					)
				);
				found = true;
			}
			else builder.AddValue(Variant.NewDictEntry(currentValue.GetChildValue(0), currentValue.GetChildValue(1)));
		}

		if (found != true) builder.AddValue(Variant.NewDictEntry(Variant.NewString(bang), Variant.NewInt32(1)));

		settings.SetValue("bang-rankings", builder.End());
	}
}
