pragma Singleton

import QtQuick
import Quickshell

Singleton {
	id: root

	property list<DesktopEntry> entryList: []
	property var preppedIcons: []
	property var preppedIds: []
	property var preppedNames: []

	// Dynamic fixups
	property var regexSubstitutions: [
		{
			"regex": /^steam_app_(\d+)$/,
			"replace": "steam_icon_$1"
		},
		{
			"regex": /Minecraft.*/,
			"replace": "minecraft-launcher"
		},
		{
			"regex": /.*polkit.*/,
			"replace": "system-lock-screen"
		},
		{
			"regex": /gcr.prompter/,
			"replace": "system-lock-screen"
		}
	]
	property real scoreThreshold: 0.2

	// Manual overrides for tricky apps
	property var substitutions: ({
			"code-url-handler": "visual-studio-code",
			"Code": "visual-studio-code",
			"gnome-tweaks": "org.gnome.tweaks",
			"pavucontrol-qt": "pavucontrol",
			"wps": "wps-office2019-kprometheus",
			"wpsoffice": "wps-office2019-kprometheus",
			"footclient": "foot"
		})

	function checkCleanMatch(str) {
		if (!str || str.length <= 3)
			return null;
		if (typeof DesktopEntries === 'undefined' || !DesktopEntries.byId)
			return null;

		// Aggressive fallback: strip all separators
		const cleanStr = str.toLowerCase().replace(/[\.\-_]/g, '');
		const list = Array.from(entryList);

		for (let i = 0; i < list.length; i++) {
			const entry = list[i];
			const cleanId = (entry.id || "").toLowerCase().replace(/[\.\-_]/g, '');
			if (cleanId.includes(cleanStr) || cleanStr.includes(cleanId)) {
				return entry;
			}
		}
		return null;
	}

	function checkFuzzySearch(str) {
		if (typeof FuzzySort === 'undefined')
			return null;

		// Check filenames (IDs) first
		if (preppedIds.length > 0) {
			let results = fuzzyQuery(str, preppedIds);
			if (results.length === 0) {
				const underscored = str.replace(/-/g, '_').toLowerCase();
				if (underscored !== str)
					results = fuzzyQuery(underscored, preppedIds);
			}
			if (results.length > 0)
				return results[0];
		}

		// Then icons
		if (preppedIcons.length > 0) {
			const results = fuzzyQuery(str, preppedIcons);
			if (results.length > 0)
				return results[0];
		}

		// Then names
		if (preppedNames.length > 0) {
			const results = fuzzyQuery(str, preppedNames);
			if (results.length > 0)
				return results[0];
		}

		return null;
	}

	// --- Lookup Helpers ---

	function checkHeuristic(str) {
		if (typeof DesktopEntries !== 'undefined' && DesktopEntries.heuristicLookup) {
			const entry = DesktopEntries.heuristicLookup(str);
			if (entry)
				return entry;
		}
		return null;
	}

	function checkRegex(str) {
		for (let i = 0; i < regexSubstitutions.length; i++) {
			const sub = regexSubstitutions[i];
			const replaced = str.replace(sub.regex, sub.replace);
			if (replaced !== str) {
				return findAppEntry(replaced);
			}
		}
		return null;
	}

	function checkSimpleTransforms(str) {
		if (typeof DesktopEntries === 'undefined' || !DesktopEntries.byId)
			return null;

		const lower = str.toLowerCase();

		const variants = [str, lower, getFromReverseDomain(str), getFromReverseDomain(str)?.toLowerCase(), normalizeWithHyphens(str), str.replace(/_/g, '-').toLowerCase(), str.replace(/-/g, '_').toLowerCase()];

		for (let i = 0; i < variants.length; i++) {
			const variant = variants[i];
			if (variant) {
				const entry = DesktopEntries.byId(variant);
				if (entry)
					return entry;
			}
		}
		return null;
	}

	function checkSubstitutions(str) {
		let effectiveStr = substitutions[str];
		if (!effectiveStr)
			effectiveStr = substitutions[str.toLowerCase()];

		if (effectiveStr && effectiveStr !== str) {
			return findAppEntry(effectiveStr);
		}
		return null;
	}

	function distroLogoPath() {
		try {
			return (typeof OSInfo !== 'undefined' && OSInfo.distroIconPath) ? OSInfo.distroIconPath : "";
		} catch (e) {
			return "";
		}
	}

	// Robust lookup strategy
	function findAppEntry(str) {
		if (!str || str.length === 0)
			return null;

		let result = null;

		if (result = checkHeuristic(str))
			return result;
		if (result = checkSubstitutions(str))
			return result;
		if (result = checkRegex(str))
			return result;
		if (result = checkSimpleTransforms(str))
			return result;
		if (result = checkFuzzySearch(str))
			return result;
		if (result = checkCleanMatch(str))
			return result;

		return null;
	}

	function fuzzyQuery(search, preppedData) {
		if (!search || !preppedData || preppedData.length === 0)
			return [];
		return FuzzySort.go(search, preppedData, {
			all: true,
			key: "name"
		}).map(r => r.obj.entry);
	}

	function getFromReverseDomain(str) {
		if (!str)
			return "";
		return str.split('.').slice(-1)[0];
	}

	// Deprecated shim
	function guessIcon(str) {
		const entry = findAppEntry(str);
		return entry ? entry.icon : "image-missing";
	}

	function iconExists(iconName) {
		if (!iconName || iconName.length === 0)
			return false;
		if (iconName.startsWith("/"))
			return true;

		const path = Quickshell.iconPath(iconName, true);
		return path && path.length > 0 && !path.includes("image-missing");
	}

	function iconForAppId(appId, fallbackName) {
		const fallback = fallbackName || "application-x-executable";
		if (!appId)
			return iconFromName(fallback, fallback);

		const entry = findAppEntry(appId);
		if (entry) {
			return iconFromName(entry.icon, fallback);
		}

		return iconFromName(appId, fallback);
	}

	function iconFromName(iconName, fallbackName) {
		const fallback = fallbackName || "application-x-executable";
		try {
			if (iconName && typeof Quickshell !== 'undefined' && Quickshell.iconPath) {
				const p = Quickshell.iconPath(iconName, fallback);
				if (p && p !== "")
					return p;
			}
		} catch (e) {}

		try {
			return Quickshell.iconPath ? (Quickshell.iconPath(fallback, true) || "") : "";
		} catch (e2) {
			return "";
		}
	}

	function normalizeWithHyphens(str) {
		if (!str)
			return "";
		return str.toLowerCase().replace(/\s+/g, "-");
	}

	function refreshEntries() {
		if (typeof DesktopEntries === 'undefined')
			return;

		const values = Array.from(DesktopEntries.applications.values);
		if (values) {
			entryList = values.sort((a, b) => a.name.localeCompare(b.name));
			updatePreppedData();
		}
	}

	function updatePreppedData() {
		if (typeof FuzzySort === 'undefined')
			return;

		const list = Array.from(entryList);
		preppedNames = list.map(a => ({
					name: FuzzySort.prepare(`${a.name} `),
					entry: a
				}));
		preppedIcons = list.map(a => ({
					name: FuzzySort.prepare(`${a.icon} `),
					entry: a
				}));
		preppedIds = list.map(a => ({
					name: FuzzySort.prepare(`${a.id} `),
					entry: a
				}));
	}

	Component.onCompleted: refreshEntries()

	Connections {
		function onValuesChanged() {
			refreshEntries();
		}

		target: DesktopEntries.applications
	}
}
