// SPDX-License-Identifier: AGPL-3.0-or-later

const {existsSync} = require('node:fs');
const {join, sep} = require('node:path');
const {createNativeLoadError, loadNativeBinding} = require('./loader-diagnostics.cjs');
const MODULE_NAME = '@fluxer/linux-notifications';
const SKIP_NATIVE_PROBE_ENV = 'FLUXER_LINUX_NOTIFICATIONS_SKIP_NATIVE_PROBE';
const REQUIRED_FREEDESKTOP_METHODS = ['notify', 'closeNotification', 'getServerCapabilities', 'close'];

function resolveNativeRoot() {
	const asarSegment = `${sep}app.asar${sep}`;
	if (!__dirname.includes(asarSegment)) return __dirname;
	const unpackedDir = __dirname.replace(asarSegment, `${sep}app.asar.unpacked${sep}`);
	return existsSync(unpackedDir) ? unpackedDir : __dirname;
}

function nativeFileName() {
	if (process.platform !== 'linux') {
		throw new Error(`@fluxer/linux-notifications is only supported on Linux, got ${process.platform}`);
	}
	switch (process.arch) {
		case 'x64':
			return 'linux-notifications.linux-x64-gnu.node';
		case 'arm64':
			return 'linux-notifications.linux-arm64-gnu.node';
		default:
			throw new Error(`Unsupported Linux architecture: ${process.arch}`);
	}
}

function ownPropertyNames(value) {
	if ((typeof value !== 'object' && typeof value !== 'function') || value == null) return [];
	return Object.getOwnPropertyNames(value).sort();
}

function bindingSurface(bindingValue) {
	const freedesktopNotifications = bindingValue ? bindingValue.FreedesktopNotifications : null;
	const prototype = freedesktopNotifications ? freedesktopNotifications.prototype : null;
	return {
		bindingKeys: ownPropertyNames(bindingValue),
		freedesktopNotificationsType: typeof freedesktopNotifications,
		freedesktopNotificationsName: freedesktopNotifications ? freedesktopNotifications.name : null,
		freedesktopNotificationsPrototypeKeys: ownPropertyNames(prototype),
		getServerInformationType: typeof (bindingValue ? bindingValue.getServerInformation : null),
	};
}

function validateBindingSurface(bindingValue, nativeRoot, nativePath) {
	const surface = bindingSurface(bindingValue);
	const missing = [];
	if (typeof bindingValue?.FreedesktopNotifications !== 'function') {
		missing.push('FreedesktopNotifications constructor');
	} else {
		for (const method of REQUIRED_FREEDESKTOP_METHODS) {
			if (typeof bindingValue.FreedesktopNotifications.prototype?.[method] !== 'function') {
				missing.push(`FreedesktopNotifications.prototype.${method}`);
			}
		}
	}
	if (typeof bindingValue?.getServerInformation !== 'function') {
		missing.push('getServerInformation');
	}
	if (missing.length === 0) return;
	throw createNativeLoadError({
		moduleName: MODULE_NAME,
		nativePath,
		nativeRoot,
		packageDir: __dirname,
		reason: `native binding surface mismatch: missing ${missing.join(', ')}`,
		skipNativeProbeEnv: SKIP_NATIVE_PROBE_ENV,
		extraDiagnostics: [{name: 'bindingSurface', text: JSON.stringify(surface, null, 2)}],
	});
}

let binding = null;
let loadError = null;

if (process.platform === 'linux') {
	try {
		const nativeRoot = resolveNativeRoot();
		const nativePath = join(nativeRoot, nativeFileName());
		const loaded = loadNativeBinding({
			moduleName: MODULE_NAME,
			nativePath,
			nativeRoot,
			packageDir: __dirname,
			skipNativeProbeEnv: SKIP_NATIVE_PROBE_ENV,
		});
		binding = loaded.binding;
		loadError = loaded.loadError;
		if (loadError) throw loadError;
		validateBindingSurface(binding, nativeRoot, nativePath);
	} catch (error) {
		loadError = createNativeLoadError({
			moduleName: MODULE_NAME,
			nativeRoot: resolveNativeRoot(),
			packageDir: __dirname,
			reason: 'native loader threw before binding load completed',
			cause: error,
			skipNativeProbeEnv: SKIP_NATIVE_PROBE_ENV,
		});
		throw loadError;
	}
}

module.exports = {
	FreedesktopNotifications: binding ? binding.FreedesktopNotifications : null,
	getServerInformation: binding ? binding.getServerInformation : null,
	loadError,
};
