 'use strict';

 const electron       = require('electron');
 const console        = require('gifer-console');
 const os             = require('os');
 // const domainRequire  = require('domain').create();

 // console.SetLogLevel(-1); 
 console.SetLogLevel(console.logLevel.INFO);

 global.__dirname = __dirname;

 const app            = electron.app;
 const ipc            = electron.ipcMain;
 const BrowserWindow  = electron.BrowserWindow;
 const globalShortcut = electron.globalShortcut;
 const screen         = electron.screen;
 // const protocol       = electron.protocol;

 // Мінімальна мажорна версія Electron, потрібна цьому пакету коду.
 // Підвищувати разом зі збіркою під новий Electron.
 const MIN_ELECTRON_MAJOR = 39;

 // Гард сумісності рантайму.
 // In-app апдейт міняє лише resources/app/ (JS-код), Electron-бінарник лишається старий.
 // Якщо новий код запущено на застарілому бінарнику — renderer (Chromium) не тягне
 // новий код і даємо білий екран. Замість білого екрана — зрозумілий діалог.
 // Має виконатись ДО важких require нижче (вони можуть впасти на старому Node).
 // Тільки класичний синтаксис + API, наявне в Electron 14.
 (function electronVersionGuard() {
     var cur = parseInt(String(process.versions.electron || '0').split('.')[0], 10);

     if (!(cur < MIN_ELECTRON_MAJOR)) {
         return;
     }

     var detail = 'This MyChat client update requires a newer runtime.\n\n' +
                  'Your installed version is outdated and cannot run this update.\n\n' +
                  'Please download and run the full client installer:\n' +
                  'https://nsoft-s.com/en/downloadmychat.html';

     var url = 'https://nsoft-s.com/en/downloadmychat.html';

     app.whenReady().then(function () {
         var res = electron.dialog.showMessageBoxSync({
             type:          'error',
             title:         'Update requires full reinstall',
             message:       'Full installation required',
             detail:        detail,
             buttons:       ['Open download page', 'OK'],
             defaultId:     0,
             cancelId:      1
         });

         if (res === 0) {
             electron.shell.openExternal(url);
         }

         app.quit();
     });

     // Блокуємо подальший старт (вікно, фази апдейту тощо).
     app.on('ready', function () { app.quit(); });
 })();

 const Service         = require('./service/service.js');
 const DbWork          = require('./service/dbwork');
 const Profile         = require('./service/profile');
 const clientUpdate    = require('./service/clientUpdate').updateDB;
 const CMD             = require('./client/modules/cmd/cmd').CMD;// mcconnect.CMD;

 const sendDataToServer = require('./service/web_client_connect.js').UniversalParser;
 const mcconnect        = require('./service/mcconnect');

 // const __stack          = require('stack-trace');

 const startLocalPort  = 32019;
 const CRLF            = "\r\n";

 const WindowSizes     = "WindowSizes";
 const WindowPos       = "WindowPosition";

 let hotKeys         = {
     showWindow: {
         key: "Super+F12",
         handle: null
     }
 };
 let electronCMD     = null;
 let pluginHost      = null;
 let mainWindow      = null;
 let localSRV        = null;
 let logFileDay      = "";   // дата (yyyy-mm-dd) поточного файлу логів

 var   wndRect  = {};

 // if (os.platform() === "linux") { // try catch segfault on debian
 //     try {
 //         let segfaultHandler = require('node-segfault-handler');
 //
 //         segfaultHandler.registerHandler(Profile.getProfilePath() + "/logs/segfault.log");
 //
 //     } catch (err) {
 //         console.immediately(err.stack);
 //     }
 // }

 // ===== Debug Mod ========================================
 // ========================================================

 app.commandLine.appendSwitch('ignore-certificate-errors');
 app.commandLine.appendSwitch('inspect', '5858');
 // app.commandLine.appendSwitch('debug-brk', '5858');
 // app.commandLine.appendSwitch('--no-sandbox');
 // app.commandLine.appendSwitch('remote-debugging-port', '8315');
 // app.commandLine.appendSwitch('host-rules', 'MAP * 127.0.0.1');

 // --expose-debug-as=v8debug --trace-deprecation

 // ========================================================
 // ========================================================

 function getRootPath() {
     const path     = require('path');
     const rootPath = path.join(__dirname, '..');

     return path.resolve(rootPath) + "/";
 }

 // Перецілює файл логів на поточну добу. ConsoleFile у gifer-console кешується
 // глобально й сам не перечитує дату, тому після сну/зміни доби логи інакше
 // лишаються у вчорашній теці. Викликати на старті, на 'resume' та при зміні доби.
 function refreshLogFile() {
     let now = new Date();

     logFileDay = now.myFormat('yyyy-mm-dd');

     console.DublicateToFile(MCPathes.History + now.myFormat('yyyy') + "/" + now.myFormat('mm') + "/" + now.myFormat('dd') + "/node.log");
 }

 function showMainWindow() {
     if (mainWindow && !mainWindow.isDestroyed()){
         mainWindow.show();
         mainWindow.focus();
     }
 }

 function startClient (){
     const pidPath  = Profile.getProfilePath() + "/logs/pid";
     let   jPackage = Service.LoadFileSync(getRootPath() + 'package.json');
     var   pidFile = "";

     try {
         if (os.platform() === "linux"){
             require('fs').readFile('/etc/os-release', 'utf8', (err, data) => {
                 if (err) {
                     console.err("Load linux version name error: " + err);
                 } else {
                     const lines = data.split('\n')
                     const releaseDetails = {}

                     lines.forEach((line, index) => {
                         const words = line.split('=');

                         if (words[0] && words[0].length && words[1] && words[1].length) {
                             releaseDetails[words[0].trim().toLowerCase()] = words[1].trim();
                         }
                     });

                     global._linuxName = (releaseDetails["pretty_name"] || (os.type() + " " + os.release())).replace(/\"|\'/ig, "");

                     console.log(`OS ver: ${global._linuxName}`);
                 }
             });
         } else {
             console.log(`OS ver: ${os.type()} ${os.release()}`);
         }
     } catch (e) {
         console.log(`OS ver: ${os.type()} ${os.release()}`);
     }
     
     console.log("======================================================");
     console.log("================ START CLIENT ========================");
     console.log("======================================================");
     
     if (jPackage){
         try {
             global.jPackage = JSON.parse(jPackage);
         } catch (e) {
             global.jPackage = null;
         }
     }

     // === Try restore minimized previous copy of chat ===
     new Promise(function (resolve) {
         try {
             pidFile = Service.loadFileSync(pidPath).toString().split(CRLF);

             console.log(pidFile);
         } catch (e) {
             resolve();
         }

         if (pidFile.length >= 2) {
             let showPrev = require('net').connect(pidFile[1], "127.0.0.1", function () {
                 showPrev.write(pidFile[0]);
             });

             showPrev.on('data', function (data) {
                 console.log("data: " + data);

                 if (data.toString() === "OK"){
                     app.quit();
                 } else {
                     try {
                         showPrev.destroy();
                     } catch (e){}

                     showPrev = null;

                     resolve();
                 }
             });

             showPrev.on("error", function (err) {
                 console.log("showPrev: " + err.message);
                 showPrev = null;

                 resolve();
             });

             setTimeout(function () {
                 if (showPrev){
                     showPrev.destroy();

                     resolve();
                 }
             }, 5000);
         } else {
             console.log("No pid");

             resolve();
         }
     })
     // === Init DB ===
     .then(function (){
         // protocol.registerHttpProtocol('mychat', (request, callback) => {
         //     const url = request.url.substr(7);
         //
         //     callback({path: path.normalize(`${__dirname}/${url}`)})
         // }, (error) => {
         //     if (error) console.error('Failed to register protocol')
         // });

         console.log('Connecting DB');

         return DbWork.Init(Profile.getProfilePath() + '/db/main.db'); // second param it is enable debug log level
     })
     .then(DbWork.connect)
     .then(clientUpdate)
     // === Window Position ===
     .then(function (){
          return DbWork.do.userStorage.get(-1, -1, WindowPos);
      })
     .then(function ([res]) {
         let data = null;

         if (res && res.value) {
             try {
                 data = JSON.parse(res.value);
             } catch (e){
                 DbWork.do.userStorage.remove(-1, -1, WindowPos);
             }

         }

         if (data){
             wndRect.x = data[0];
             wndRect.y = data[1];
         }
     })
     // === Window Size ===
     .then(function (){
          return DbWork.do.userStorage.get(-1, -1, WindowSizes);
      })
     .then(function ([res]) {
         let data = null;

         if (res && res.value) {
             try {
                 data = JSON.parse(res.value);
             } catch (e){
                 DbWork.do.userStorage.remove(-1, -1, WindowSizes);
             }
         }

         if (data){
             wndRect.width  = data[0];
             wndRect.height = data[1];
         }
     })

     // === Get HardwareID ===
     .then(function () {
         return DbWork.do.system.hardwareID.get();
     })
     .then(function ([hwid]){
         if (!hwid || !hwid.hardware_id || (hwid.hardware_id && hwid.hardware_id.indexOf(".") === -1)) {
             const uuidPath = Profile.getProfilePath() + "/logs/";

             return new Promise(function (resolve) {
                 Service.getHardwareID(function (newHwid) {
                     global.myHWND = newHwid + "." + Service.RandomHash(7); // нужно иметь точно уникальную строку, чтобы можно было запустить несколько чатом на одном компе mainWindow.getNativeWindowHandle().toString("hex");             ClientSettings.hardware_id = newHwid;

                     resolve(myHWND);
                 }, uuidPath);
             });
         } else {
             global.myHWND = hwid.hardware_id;
         }
     })
     .then(function (myHWND) {
         if (myHWND) {
             DbWork.do.system.hardwareID.set(myHWND);
         }
     })

     // === Show window ===
     .then(function () {
         let opts = {
             frame          : true,
             autoHideMenuBar: true,
             height         : 600,
             width          : 1000,
             minWidth       : 640,
             minHeight      : 450,
             show           : true,
             icon           : `${__dirname}/client/modules/images/256x256.png`,
             webPreferences : {
                 // sandbox         : false,
                 nodeIntegration : true,
                 contextIsolation: false,
                 // webviewTag      : true,
                 spellcheck      : true // отключаем встроенный, так как используем свой
             }
         };

         if (process.platform === "linux") {
             // На Linux desktopName має дорівнювати імені .desktop файлу
             // (mychatclient.desktop з buildDeb.js), інакше GNOME не змапить
             // вікно на ярлик і покаже generic-іконку вгорі та в таскбарі.
             app.setDesktopName("mychatclient.desktop");
         } else {
             app.setDesktopName("MyChat Client " + global.jPackage.version + "." + global.jPackage.buildNumber);
         }

         let hasPos  = wndRect.hasOwnProperty("x")     && wndRect.hasOwnProperty("y");
         let hasSize = wndRect.hasOwnProperty("width") && wndRect.hasOwnProperty("height");

         if (hasPos) {
             let probe = {
                 x     : wndRect.x,
                 y     : wndRect.y,
                 width : hasSize ? wndRect.width  : opts.width,
                 height: hasSize ? wndRect.height : opts.height
             };
             let _display = screen.getDisplayMatching(probe);

             if (_display && _display.bounds.x <= wndRect.x && _display.bounds.y <= wndRect.y) {
                 opts.x = wndRect.x;
                 opts.y = wndRect.y;

                 if (hasSize && (_display.bounds.height >= wndRect.height || _display.bounds.width >= wndRect.width)) {
                     opts.height = wndRect.height;
                     opts.width  = wndRect.width;
                 }
             }
         } else if (hasSize) {
             opts.height = wndRect.height;
             opts.width  = wndRect.width;
         }

         mainWindow = new BrowserWindow(opts);

         // На packaged Windows constructor opts.x/y/width/height можуть ігноруватись через DPI/frame —
         // тому виставляємо bounds явно після створення вікна.
         if (opts.x !== undefined && opts.y !== undefined) {
             try {
                 mainWindow.setBounds({
                     x     : opts.x,
                     y     : opts.y,
                     width : opts.width,
                     height: opts.height
                 });
             } catch (e) {
                 console.err("setBounds: " + e.message);
             }
         }

         mainWindow.webContents.session.setPermissionRequestHandler(function (wc, permission, callback) {
             callback(true);
         });

         mainWindow.webContents.session.setDisplayMediaRequestHandler(function (request, callback) {
             electron.desktopCapturer.getSources({ types: ['screen', 'window'] }).then(function (sources) {
                 if (sources && sources.length) {
                     callback({ video: sources[0] });
                 } else {
                     callback();
                 }
             }).catch(function (err) {
                 console.error('setDisplayMediaRequestHandler: ' + err.message);
                 callback();
             });
         });

         mainWindow.loadURL(`file://${__dirname}/client/index.html`);
         // mainWindow.webContents.session.clearCache(function () { });

         // mainWindow.setContentProtection(true);

         mcconnect.tryUpdateClient(tryUpdateClient);

         // relaunch і close — локальні функції main.js, а сам main.js нізвідки не тягнеться,
         // тож обробники отримують їх сеттер-ін'єкцією. Пара обов'язкова: app.relaunch лише
         // реєструє аргументи наступного запуску й процес не завершує
         mcconnect.setRestartApp(function () {
             relaunch();
             close();
         });

         if (globalShortcut.isRegistered(hotKeys.showWindow.key)) {
             console.log('Super+F12 is busy');
         } else {
             try{
                 hotKeys.showWindow.handle = globalShortcut.register(hotKeys.showWindow.key, () => {
                     showAndFocus();
                 });

                 console.log('Super+F12 is passed!');
             } catch (e){
                 console.error(e.message);
             }
         }

         if (process.argv.indexOf('/debugWnd') >= 0) {
             mainWindow.webContents.openDevTools();
         } else {
             mainWindow.setMenuBarVisibility(false);
             mainWindow.setMenu(null);
         }

         electronCMD = require("./service/electronCMD")(electron, app, mainWindow, DbWork, Profile, shutdownPlugins);

         const PluginHost = require('./service/plugins/pluginHost');

         // Версія збірки = version + buildNumber: білд інкрементиться незалежно від calver,
         // тож на трьох компонентах гейт minHost не розрізняв би сусідні збірки одного дня.
         // global.jPackage заповнюється умовно (див. вище), тому без guard тут — TypeError.
         pluginHost = new PluginHost({
             profileRoot: Profile.getProfilePath(),
             toRender   : sendPluginEnvelopeToRenderer,
             hostVersion: global.jPackage ? global.jPackage.version + "." + global.jPackage.buildNumber : ""
         });

         electronCMD[CMD.ce_plugin_msg] = function (args) {
             pluginHost.onRenderMessage(args[0]);
         };

         setMainWindowHandlers();

         mainWindow.webContents.session.setSpellCheckerLanguages(['en-US', 'ru', 'uk']);

         if (os.platform() === "darwin") {
             //  для мака не нужно делать "повторный запуск" клиента с контролем уже запущенной версии,
             //  так как запущенный вариант всегда на панели снизу в отличие от винды и линукса....

             showMainWindow();
         } else {
             Service.isPortBusy(startLocalPort, function (freePort) {
                 Service.WriteFile(pidPath, global.myHWND.toString() + CRLF + freePort);

                 localSRV = require('net').createServer(function (socket) {
                     socket.on("data", function (data) {
                         console.log("localSRV: " + data.toString());

                         if (data.toString() === global.myHWND){
                             socket.write("OK");

                             showMainWindow();
                         } else {
                             socket.write("FAIL");
                         }

                         if (!socket.destroyed) {
                             socket.destroy();
                         }
                     });

                     socket.on('error', function (err) {
                         console.warn("localSRV.socket: " + err.message);
                     });
                 });

                 localSRV.listen(freePort, "127.0.0.1", function () {
                     // console.log("Hardware ID: " + global.myHWND);
                 });

                 localSRV.on("error", function (err) {
                     console.warn("localSRV: " + err.message);
                 });
             }, function (){
                 if (pidFile && pidFile.length >= 2 && pidFile[1] == startLocalPort){
                     console.immediately("startLocalPort === port from pidFile and busy, try use next port: " + (startLocalPort + 1 ));

                     // app.quit();
                 }
             }, 10, "127.0.0.1");
         }
     })
     .catch(function (err) {
         if (err) {
             console.error(err.stack || err.message || err);
         }
     });

     ipc.on('csPostParser', function(res, data){
         let reqData = Service.Get_CMD(data);

         sendDataToServer.apply({}, [reqData.CMD, reqData.DATA, function(outData){
             if (res){
                 res.sender.send('scPostParser', outData); // mainWindow.webContents
             }
         }]);
     });

     ipc.on('ce_CMD', function parse_CE_CMD (res, data) {
         let cmd = data.shift();

         if (electronCMD.hasOwnProperty(cmd)){
             electronCMD[cmd](data, res);
         } else {
             console.error('Unknown CMD: ' + cmd);
         }
     });

     ipc.handle('getWindowWH', function(){
         return mainWindow.getSize();
     });

     ipc.handle('get-desktop-sources', async function (e, opts) {
         const sources = await electron.desktopCapturer.getSources(opts || { types: ['screen', 'window'] });

         return sources.map(function (s) {
             return {
                 id              : s.id,
                 name            : s.name,
                 display_id      : s.display_id,
                 thumbnailDataURL: s.thumbnail ? s.thumbnail.toDataURL() : null
             };
         });
     });
 }

 function relaunch() {
     let args = [];

     for (let i = 1; i < process.argv.length; i++){
         if (process.argv[i] !== CMD.updateConsoleCMD
             && process.argv[i] !== CMD.normalStart
             && process.argv[i] !== CMD.afterCopyArg
             && process.argv[i] !== ""){
             args.push(process.argv[i]);
         }
     }

     args.push(CMD.normalStart);

     app.relaunch({args: args});
 }

 function close() {
     sendDataToServer(CMD.cs_quit, mcconnect.GetFirstSID()); // quit
     
     global.isBrodcast = false;

     app.closeNow = true;

     if (mainWindow) {
         mainWindow.close();
     } else {
         app.quit();
     }
 }

 function zipErrorAction(path, err, restart){
     console.immediately(err);

     require('rimraf')(path, function () {
         const { dialog } = electron;
         const options = {
             type     : 'warning',
             buttons  : ['Cancel', 'Relaunch', 'Open logs'],
             defaultId: 0,
             message  : 'Update error',
             detail   : err
         };

         dialog.showMessageBox(null, options, function (id) {
             if (restart && id === 1){
                 console.immediately('Restarting...');

                 relaunch();
                 close();
             } else
             if (id === 2){
                 electron.shell.openExternal(Profile.getProfilePath() + "/logs/" + (new Date()).myFormat('yyyy') + "/" + (new Date()).myFormat('mm') + "/" + (new Date()).myFormat('dd'));

                 setTimeout(close, 100);
             } else {
                 close();
             }
         });
     });
 }

 function tryUpdateClient(serverInfo, checkUpdate) {
     // if (mainWindow && !mainWindow.isDestroyed()){
     //     let updatePath = Service.mcServerURL(null, serverInfo) + Service.getUpdateFilePath();
     //
     //     require('request')({
     //         method: "HEAD",
     //         uri   : updatePath
     //     }, function (err, res) {
     //         if (err || res.statusCode !== 200){
     //             checkUpdate && checkUpdate(false);
     //         } else {
                 mainWindow.webContents.send('ec_CMD', [
                     CMD.ec_download_update,
                     serverInfo
                 ]);
             // }
         // });
     // }
 }

 function showAndFocus() {
     if (mainWindow && !mainWindow.isDestroyed()){
         mainWindow.show();
         mainWindow.focus();
     }
 }

 async function shutdownPlugins(){
     if (pluginHost) await pluginHost.shutdownAll(2000);
 }

 function sendPluginEnvelopeToRenderer(envelope){
     if (mainWindow && !mainWindow.isDestroyed()){
         mainWindow.webContents.send('ec_CMD', [ CMD.ec_plugin_msg, envelope ]);
     }
 }

 function sendDateToClient(CMD, data){
     if (ipc && mainWindow && !mainWindow.isDestroyed()){
         mainWindow.webContents.send('ec_CMD', [
             CMD,
             data
         ]);
     }
 }

 function setMainWindowHandlers(){
     var positionTimer = null;
     var chatIsLive    = true;

     console.log("Set handlers");

     electron.powerMonitor.on('suspend', () => {
         if (ipc) {
             console.log('The system is going to sleep!');

             chatIsLive = false;

             let connection = mcconnect.GetConnection();

             if (connection){
                 console.important('Send "Quit" to server');
                 console.important('Stopping FTP server');

                 mainWindow.webContents.send('ec_CMD', [
                     CMD.ec_system_suspend
                 ]);

                 connection.SendDataToServer('\u0017\u0006' + CMD.cs_quit + '00\r\n');

                 setTimeout(() => mainWindow.webContents.send('ec_CMD', [
                     CMD.ec_drop_connect
                 ]), 100);

                 electronCMD && electronCMD[CMD.ce_stop_ftp_server]();
             }
         }
     });

     electron.powerMonitor.on('resume', () => {
         if (!chatIsLive && mainWindow && !mainWindow.isDestroyed()){
             console.log('The system wake up!');

             refreshLogFile();   // після сну дата могла змінитись — перецілюємо лог одразу

             chatIsLive = true;
             
             mainWindow.webContents.send('ec_CMD', [
                 CMD.ec_system_resume
             ]);
         }
     });

     electron.powerMonitor.on('shutdown', close);

     mainWindow.on('close', function (e) {
         if (global.isBrodcast){
             e.preventDefault();
             e.returnValue = false
         } else

         if (!app.closeNow){
             mainWindow.blur();

             e.preventDefault();

             mainWindow.hide();

             sendDateToClient(
                 CMD.ec_main_window_state,
                 {
                     state: {
                         visible: mainWindow.isVisible(),
                         minimize: mainWindow.isMinimized(),
                         focus: mainWindow.isFocused()
                     }
                 }
             );
         }
     });

     mainWindow.on('restore', function (e) {
         sendDateToClient(
             CMD.ec_main_window_state,
             {
                 state: {
                     visible: mainWindow.isVisible(),
                     minimize: mainWindow.isMinimized(),
                     focus: mainWindow.isFocused()
                 }
             }
         );
     });
     
     mainWindow.on('minimize', function (e) {
         if (global.isBrodcast){
             mainWindow.show();

             e.preventDefault();
             e.returnValue = false
         } else
             sendDateToClient(
                 CMD.ec_main_window_state,
                 {
                     state: {
                         visible: mainWindow.isVisible(),
                         minimize: mainWindow.isMinimized(),
                         focus: mainWindow.isFocused()
                     }
                 }
             );
     });

     mainWindow.on('closed', function _closed () {
         if (app.closeNow){
             mainWindow = null;

             globalShortcut.unregisterAll();

             if (localSRV){
                 localSRV.close();
             }

             DbWork.close();

             console.immediately("Close app"); // + __stack.get().toString().replace(',', '\n'));

             setTimeout(function () {
                 app.quit();
             }, 200);
         }
     });

     mainWindow.on('focus', function () {
         sendDateToClient(
             CMD.ec_main_window_state,
             {
                 state: {
                     visible: mainWindow.isVisible(),
                     minimize: mainWindow.isMinimized(),
                     focus: mainWindow.isFocused()
                 },
                 notify: true
             }
         );
     });

     mainWindow.on('blur', function () {
         sendDateToClient(
             CMD.ec_main_window_state,
             {
                 state: {
                     visible: mainWindow.isVisible(),
                     minimize: mainWindow.isMinimized(),
                     focus: mainWindow.isFocused()
                 },
                 notify: true
             }
         );
     });

     mainWindow.on('show', function () {
         sendDateToClient(
             CMD.ec_main_window_state,
             {
                 state: {
                     visible: mainWindow.isVisible(),
                     minimize: mainWindow.isMinimized(),
                     focus: mainWindow.isFocused()
                 }
             }
         );
     });

     var sizeTimer = null;

     function saveBoundsValue(name, value) {
         let idSrv = -1, uin = -1;
         let json  = JSON.stringify(value);

         DbWork.do.userStorage
         .get(idSrv, uin, name)
         .then(function ([res]) {
             if (res && res.value){
                 return DbWork.do.userStorage.update(idSrv, uin, name, json);
             } else {
                 return DbWork.do.userStorage.add(idSrv, uin, name, json);
             }
         })
         .catch(function (err) {
             if (err) console.err(err.stack || err.message || err);
         });
     }

     mainWindow.on('move', function () {
         if (!mainWindow || mainWindow.isDestroyed()) return;
         if (mainWindow.isMinimized() || mainWindow.isMaximized() || mainWindow.isFullScreen()) return;

         if (positionTimer) clearTimeout(positionTimer);

         positionTimer = setTimeout(function () {
             positionTimer = null;

             if (!mainWindow || mainWindow.isDestroyed()) return;

             saveBoundsValue(WindowPos, mainWindow.getPosition());
         }, 300);
     });

     mainWindow.on('resize', function () {
         if (!mainWindow || mainWindow.isDestroyed()) return;
         if (mainWindow.isMinimized() || mainWindow.isMaximized() || mainWindow.isFullScreen()) return;

         if (sizeTimer) clearTimeout(sizeTimer);

         sizeTimer = setTimeout(function () {
             sizeTimer = null;

             if (!mainWindow || mainWindow.isDestroyed()) return;

             saveBoundsValue(WindowSizes, mainWindow.getSize());
         }, 300);
     });
 }

 // ========================================================
 // ========================================================

 // GPU-acceleration залишаємо ввімкненим. Fallback для старих GPU/VM:
 //   mcclient.exe --disable-gpu
 if (process.argv.indexOf('--disable-gpu') !== -1) {
     app.disableHardwareAcceleration();
 }

 app.on('ready', function() {
     process.env['ELECTRON_DISABLE_SECURITY_WARNINGS'] = true;

     Profile.init(__dirname);
     Profile.testProfile();

     // console.log(JSON.stringify(zip.getEntries()), console.logLevel.L_Extended);

     Service.InitMCPathes(Profile.getProfilePath());

     refreshLogFile();

     // Зміна доби без сну: щохвилини звіряємо дату, при новому дні перецілюємо лог
     setInterval(function () {
         if ((new Date()).myFormat('yyyy-mm-dd') !== logFileDay) {
             refreshLogFile();
         }
     }, 60000);

     app.closeNow = true;

     const updatePath = Profile.getProfilePath() + "/updates/";
     const updateTemp = updatePath + "temp/";

     Service.checkDir(updateTemp);

     console.log("Path to client: " + process.argv.join(','));
     console.log("ABI version: " + process.versions.modules);

     // check update
     if (process.argv.indexOf(CMD.updateConsoleCMD) !== -1 && process.argv.indexOf(CMD.normalStart) === -1){

         const fs   = require('fs');
         const copy = require('./service/tools/copy').copy;

         console.immediately("================= UPDATING ===========================");
         console.immediately("======================================================");

         let dest = "";

         switch (os.platform()) {
             case "darwin":
                 dest = Service.ExtractPath(process.argv[0]).replace("MacOS", "Resources");
             break;

             case "linux":
             case "win32":
                 dest = Service.ExtractPath(process.argv[0]) + "/resources/";
             break;
         }

         // Функція очистки: видалення app.asar, app.asar.unpacked, temp, ZIP
         function updateCleanup(arcFile) {
             process.noAsar = true;

             const asarPath       = dest + "app.asar";
             const asarUnpackPath = dest + "app.asar.unpacked";

             let cleanupTasks = [];

             try {
                 if (fs.existsSync(asarPath)) {
                     console.immediately("Delete old app.asar: " + asarPath);

                     cleanupTasks.push(new Promise(function (resolve) {
                         fs.unlink(asarPath, function (err) {
                             if (err) {
                                 console.immediately("Error deleting app.asar: " + err.message);
                             } else {
                                 console.immediately("app.asar deleted!");
                             }
                             resolve();
                         });
                     }));
                 }
             } catch (e) {
                 console.immediately("Check app.asar: " + e.message);
             }

             try {
                 if (fs.existsSync(asarUnpackPath)) {
                     console.immediately("Delete app.asar.unpacked: " + asarUnpackPath);

                     cleanupTasks.push(new Promise(function (resolve) {
                         require('rimraf')(asarUnpackPath, function (err) {
                             if (err) {
                                 console.immediately("Error deleting app.asar.unpacked: " + err.message);
                             } else {
                                 console.immediately("app.asar.unpacked deleted");
                             }
                             resolve();
                         });
                     }));
                 }
             } catch (e) {
                 console.immediately("Check app.asar.unpacked: " + e.message);
             }

             // Очистка temp
             cleanupTasks.push(new Promise(function (resolve) {
                 require('rimraf')(updateTemp, function (err) {
                     if (err) {
                         console.immediately("Error removing folder temp: " + err.message);
                     } else {
                         console.immediately("temp removed");
                     }
                     resolve();
                 });
             }));

             // Видалення ZIP-файлу оновлення
             if (arcFile) {
                 cleanupTasks.push(new Promise(function (resolve) {
                     fs.unlink(arcFile, function (err) {
                         if (err) {
                             console.immediately("Error deleting ZIP: " + err.message);
                         } else {
                             console.immediately("ZIP deleted: " + arcFile);
                         }
                         resolve();
                     });
                 }));
             }

             Promise.all(cleanupTasks).then(function () {
                 console.immediately("Clearing complete. Restart...");

                 process.noAsar = false;

                 relaunch();
                 close();
             });
         }

         // Визначаємо чи потрібна Phase 1 (розпаковка + копіювання)
         // або тільки очистка (міграція зі старого клієнта / повторний запуск після копіювання)
         if (process.argv.indexOf(CMD.afterCopyArg) !== -1) {
             // Phase 2: файли вже скопійовано новим кодом, тільки очистка
             console.immediately("Phase 2: clear after start");

             updateCleanup();

         } else {
             // Перевіряємо чи є ZIP для розпаковки
             let arc  = "";
             let zip  = null;

             switch (os.platform()) {
                 case "darwin":
                     arc = "updatemac.zip";
                 break;

                 case "linux":
                     arc = "updatelinux" + (os.arch() === "ia32" ? "32" : "") + ".zip";
                 break;

                 case "win32":
                     arc = "updatewin32next.zip";
                 break;
             }

             try {
                 zip = fs.statSync(updatePath + arc);
             } catch (e) {
                 // ZIP не знайдено — можливо, старий клієнт вже розпакував і скопіював
                 console.immediately("ZIP not found, skip unpacking: " + updatePath + arc);
             }

             if (zip){
                 try {
                     zip = new (require('adm-zip'))(updatePath + arc);
                 } catch (e) {
                     zip = null;

                     console.immediately("Open ZIP error: " + e.message);

                     zipErrorAction(updatePath + arc, "Update file is corrupted and deleted: " + updatePath + arc, true);
                 }
             }

             if (zip){
                 // Phase 1: розпаковка ZIP і копіювання app/ → resources/app/
                 console.immediately("Phase 1: updates unpacking");

                 process.noAsar = true;

                 try {
                     zip.extractAllTo(updateTemp, true);

                     copy(updateTemp + "/app/", dest + "/app/", function (err) {
                         if (err){
                             console.immediately("Update copy error: " + (err.message || err));

                             // Помилка копіювання — можливо файли зайняті (міграція зі старого клієнта)
                             // Старий клієнт вже скопіював нові файли, тому просто очищуємо
                             // console.immediately("Файли можливо вже оновлені, виконуємо очистку...");

                             updateCleanup(updatePath + arc);
                         } else {
                             console.immediately("Update: files copied, restart for Phase 2");

                             // Перезапуск з --afterCopy для Phase 2
                             let args = process.argv.slice(1).filter(function (a) {
                                 return a !== "" && a !== CMD.normalStart;
                             });

                             if (args.indexOf(CMD.afterCopyArg) === -1) {
                                 args.push(CMD.afterCopyArg);
                             }

                             app.relaunch({args: args});
                             close();
                         }
                     });
                 } catch (e) {
                     console.immediately(e.message);

                     zipErrorAction(updatePath + arc, "Update error restart app " + e.message);
                 }
             } else if (!zip) {
                 // ZIP не знайдено або пошкоджений — старий клієнт вже все зробив, тільки очистка
                 // console.immediately("Міграція: файли вже оновлені, виконуємо очистку");

                 updateCleanup();
             }
         }
     } else {
         startClient();
     }

     app.on('window-all-closed', () => {
         if (process.platform !== 'darwin' && app.closeNow) {
             app.quit()
         }
     });
 });

 app.on('window-all-closed', () => {
     //app.quit();
 });

 app.on('activate-with-no-open-windows', showAndFocus);

 app.on('activate', showAndFocus);

 process
    .on('uncaughtException', function (err) {
        console.immediately("Uncaught Exception: " + err.message + '\n');
        console.immediately(err.stack + '\n');
    })
    .on('unhandledRejection', (err) => {
        console.immediately('Unhandled Rejection at Promise: ' + err.message);
        console.immediately(err.stack + '\n');
    })
 ;

 // mainWindow.webContents.session.setPermissionRequestHandler((webContents, permission, done) => {
 //     const parsedUrl = new URL(webContents.getURL());
 //     const options = {
 //         title: 'Permission Request',
 //         message: `Allow '${parsedUrl.origin}' to access '${permission}'?`,
 //         buttons: ['OK', 'Cancel'],
 //         cancelId: 1
 //     };
 //     electron.dialog.showMessageBox(mainWindow, options).then(({ response }) => {
 //         done(response === 0);
 //     });
 // });


