/*
 Copyright (C) 2016 - 2026 3NSoft Inc.
 
 This program is free software: you can redistribute it and/or modify it under
 the terms of the GNU General Public License as published by the Free Software
 Foundation, either version 3 of the License, or (at your option) any later
 version.
 
 This program is distributed in the hope that it will be useful, but
 WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 See the GNU General Public License for more details.
 
 You should have received a copy of the GNU General Public License along with
 this program. If not, see <http://www.gnu.org/licenses/>.
*/

// injecting global's expected to be on node, before everything else
import { makePlatformDeviceFS } from 'core-3nweb-client-lib/build/lib-common-on-node/fs-on-node';
globalThis.platform = {
	device_fs: makePlatformDeviceFS()
};

import { SKIP_APP_ERR_DIALOG_FLAG, MULTI_INSTANCE_FLAG, TEST_STAND_CONF, devToolsFromARGs, cmdTokenFromCli, SOCKS5_PROXY, urlFromArgs } from './runner-in-electron/process-args';
import { app, dialog, net, powerMonitor } from 'electron';
import { InitProc } from './runner-in-electron/init-proc';
import { registerAllProtocolShemas } from "./runner-in-electron/electron/protocols";
import { fromEvent, lastValueFrom } from 'rxjs';
import { appDir, dohURLs, logError, recordUnhandledRejectionsInProcess, SIGNUP_URL, utilDir } from './runner-in-electron/confs';
import { take } from 'rxjs/operators';
import { makeCoreDriver } from './platform/core';
import { clearDefaultWindowMenu } from './runner-in-electron/window-utils/window-menu';
import { mkdirSync } from 'fs';
import { sleep } from './platform/lib-common/processes/sleep';
import { EventEmitter } from 'events';
import { processOfUtilityArgsIfGiven } from './runner-in-electron/main-for-util-invocations';
import { ensure3NWebProtocolsAreSetInOS } from './runner-in-electron/electron/custom-url-schemas';
import { makePlatformResources } from './runner-in-electron/platform-resources';

// XXX make it so that OS secure thingy isn't triggered, if there is no autologin file

const utilityInvocation = processOfUtilityArgsIfGiven();
if (utilityInvocation) {

	// Utility args like help, version, are not supposed to start main processes.
	utilityInvocation.then(
		() => process.exit(0),
		err => {
			console.error(`\n❌ Error occured:\n`, err);
			process.exit(-1);
		}
	);

} else {

	const { signupParams, appCallViaURL, systemCmd } = urlFromArgs(process.argv);

	EventEmitter.defaultMaxListeners = 100;

	// The reason for changing this process' work directory is that down the line
	// unix sockets are used, but these are limited in length for historic reasons.
	// Work around this limitation is in having all processes working directories
	// in our own data and use relative paths whenever it is necessary.
	mkdirSync(utilDir, { recursive: true });
	process.chdir(utilDir);

	if (SOCKS5_PROXY) {
		app.commandLine.appendSwitch('proxy-server', `socks5://${SOCKS5_PROXY}`);
	}

	function setupAndStartMainInstance(): InitProc {

		registerAllProtocolShemas();

		const init = new InitProc(
			makeCoreDriver,
			{
				signUpUrl: SIGNUP_URL,
				dataDir: appDir
			},
			devToolsFromARGs(),
			TEST_STAND_CONF,
			makePlatformResources()
		);

		// Removing default menu
		clearDefaultWindowMenu();

		// Opening process
		lastValueFrom(fromEvent(app, 'ready').pipe(take(1)))
		.then(async () => {

			app.configureHostResolver({
				enableBuiltInResolver: true,
				secureDnsServers: dohURLs,
				secureDnsMode: 'automatic'
			});

			powerMonitor.on('shutdown', () => init.exit());

			ensure3NWebProtocolsAreSetInOS();

			recordUnhandledRejectionsInProcess();

			// Prevent closing when all windows are closed by setting listener
			app.on('window-all-closed', () => {});

			try {
				await init.boot(signupParams);

				// XXX testing bits
				// (async function checkOfResolutionFromHereAndDebugLogout() {
				// 	for (const d of [ 't1.3nweb.net' ]) {
				// 		try {
				// 			const info = await net.resolveHost(d);
				// 			console.log(`\n${d} resolved to`, info);
				// 		} catch (err) {
				// 			console.error(`\nDebug check of resolving ${d} from electron's main process fails with`, err);
				// 		}
				// 	}
				// })();

			} catch (err) {
				await logError(err);
				if (!SKIP_APP_ERR_DIALOG_FLAG) {
					dialog.showErrorBox(
						`Restart 3NWeb application`,
						`Error occured on 3NWeb core's initialization. Please restart application.`
					);
				}
				await init.exit(1);
				return;
			}

			process.on('SIGINT', () => init.exit());
			process.on('SIGTERM', () => init.exit());

			if (appCallViaURL) {
				init.handleAppUrlCallFromOS(appCallViaURL);
			} else if (systemCmd) {
				init.handleSystemCmdCall(systemCmd);
			}

		});

		return init;
	}

	try {

		// XXX
		// Use if OPEN_APP_CMD to open only single app, talking via socket.
		// Note that caller should give --data-dir (always?).

		if (MULTI_INSTANCE_FLAG) {
			setupAndStartMainInstance();
		} else {
			const isFstInstance = app.requestSingleInstanceLock();
			if (isFstInstance) {
				app.on('second-instance', async (event, argv, workDir) => {
					const { appCallViaURL, signupParams, systemCmd } = urlFromArgs(argv);
					if (appCallViaURL) {
						init.handleAppUrlCallFromOS(appCallViaURL);
					} else if (signupParams) {
						init.handleSignupURL(signupParams)
					} else if (systemCmd) {
						init.handleSystemCmdCall(systemCmd);
					} else {
						const cmdToken = cmdTokenFromCli(argv);
						const cmdRan = (cmdToken && init.runCmd(cmdToken));
						if (!cmdRan) {
							await init.openAllLaunchers();
						}
					}
				});
				const init = setupAndStartMainInstance();
			} else {
				sleep(300).then(() => app.quit());
			}
		}

	} catch (err) {
		console.error(`\n❌ Error occured during loading`);
		console.error(err);
		process.exit(-1);
	}

}