import { app, Menu, ipcMain, BrowserWindow, shell } from 'electron';
import * as fse from 'fs-extra';
import * as path from 'path';
import * as ChildProcess from 'child_process';

import { shellNeedsPatching, updateEnvironmentForProcess } from '@overflow/core/shell';
import { parseAppURL } from '@overflow/core/parse-app-url';
// import {
//   enableSourceMaps,
//   withSourceMappedStack,
// } from '@overflow/core/source-map-support';
import { now } from '@overflow/core/now';
import { IMenuItem } from '@overflow/core/menu-item';

import { AppWindow } from './app-window';

import { handleSquirrelEvent } from './squirrel-updater';

import { openDirectorySafe } from './shell';
import { buildDefaultMenu } from './menu/build-default';
import { MenuEvent } from '../commons/type';
import { findMenuItemByID } from './menu/find-menu-item';
import { IMenuItemState } from '../commons/model/menu-update';

import * as sqlite3 from 'sqlite3';

// enableSourceMaps();

let mainWindow: AppWindow | null = null;

const launchTime = now();

let preventQuit = false;
let readyTime: number | null = null;

type OnDidLoadFn = (window: AppWindow) => void;
/** See the `onDidLoad` function. */
let onDidLoadFns: Array<OnDidLoadFn> | null = [];

function handleUncaughtException(error: Error) {
  preventQuit = true;

  if (mainWindow) {
    mainWindow.destroy();
    mainWindow = null;
  }

  const isLaunchError = !mainWindow;
  // showUncaughtException(isLaunchError, error);
}

// process.on('uncaughtException', (error: Error) => {
//   error = withSourceMappedStack(error);

//   // reportError(error);
//   handleUncaughtException(error);
// });

const handlingSquirrelEvent = false;
// if (__WIN32__ && process.argv.length > 1) {
//   const arg = process.argv[1];

//   const promise = handleSquirrelEvent(arg);
//   if (promise) {
//     handlingSquirrelEvent = true;
//     promise
//       .catch(e => {
//         log.error(`Failed handling Squirrel event: ${arg}`, e);
//       })
//       .then(() => {
//         app.quit();
//       });
//   } else {
//     handlePossibleProtocolLauncherArgs(process.argv);
//   }
// }

// function handleAppURL(url: string) {
//   log.info('Processing protocol url');
//   const action = parseAppURL(url);
//   onDidLoad(window => {
//     // This manual focus call _shouldn't_ be necessary, but is for Chrome on
//     // macOS. See https://github.com/desktop/desktop/issues/973.
//     window.focus();
//     window.sendURLAction(action);
//   });
// }

const isDuplicateInstance = false;
// If we're handling a Squirrel event we don't want to enforce single instance.
// We want to let the updated instance launch and do its work. It will then quit
// once it's done.
// if (!handlingSquirrelEvent) {
//   isDuplicateInstance = app.makeSingleInstance((args, workingDirectory) => {
//     // Someone tried to run a second instance, we should focus our window.
//     if (mainWindow) {
//       if (mainWindow.isMinimized()) {
//         mainWindow.restore();
//       }

//       if (!mainWindow.isVisible()) {
//         mainWindow.show();
//       }

//       mainWindow.focus();
//     }

//     handlePossibleProtocolLauncherArgs(args);
//   });

//   if (isDuplicateInstance) {
//     app.quit();
//   }
// }

if (shellNeedsPatching(process)) {
  updateEnvironmentForProcess();
}

app.on('will-finish-launching', () => {
  // macOS only
  app.on('open-url', (event, url) => {
    event.preventDefault();
    // handleAppURL(url);
  });
});

/**
 * Attempt to detect and handle any protocol handler arguments passed
 * either via the command line directly to the current process or through
 * IPC from a duplicate instance (see makeSingleInstance)
 *
 * @param args Essentially process.argv, i.e. the first element is the exec
 *             path
 */
// function handlePossibleProtocolLauncherArgs(args: ReadonlyArray<string>) {
//   log.info(`Received possible protocol arguments: ${args.length}`);

//   if (__WIN32__) {
//     // We register our protocol handler callback on Windows as
//     // [executable path] --protocol-launcher -- "%1" meaning that any
//     // url data comes after we've stopped processing arguments. We check
//     // for that exact scenario here before doing any processing. If there's
//     // more than 4 args because of a malformed url then we bail out.
//     if (
//       args.length === 4 &&
//       args[1] === '--protocol-launcher' &&
//       args[2] === '--'
//     ) {
//       handleAppURL(args[3]);
//     }
//   } else if (args.length > 1) {
//     handleAppURL(args[1]);
//   }
// }

/**
 * Wrapper around app.setAsDefaultProtocolClient that adds our
 * custom prefix command line switches on Windows that prevents
 * command line argument parsing after the `--`.
 */
function setAsDefaultProtocolClient(protocol: string) {
  if (__WIN32__) {
    app.setAsDefaultProtocolClient(protocol, process.execPath, [
      '--protocol-launcher',
      '--',
    ]);
  } else {
    app.setAsDefaultProtocolClient(protocol);
  }
}

// if (process.env.GITHUB_DESKTOP_DISABLE_HARDWARE_ACCELERATION) {
//   log.info(
//     `GITHUB_DESKTOP_DISABLE_HARDWARE_ACCELERATION environment variable set, disabling hardware acceleration`
//   );
//   app.disableHardwareAcceleration();
// }

app.on('ready', () => {
  if (isDuplicateInstance || handlingSquirrelEvent) {
    return;
  }

  const dbPath = __DEV__ ?
    path.join(__dirname, '..', '..', 'config', '_database.sqlite')
    : path.join(__dirname, '..', '..', 'bin', 'database.sqlite');

  const db = new sqlite3.Database(dbPath, sqlite3.OPEN_READONLY, (err: Error) => {
    if (err) {
      console.error(err.message);
    }
    console.log('Connected to the database.');
  });

  if (!__DEV__) {
    const probePath = path.join(__dirname, '..', '..', 'bin', 'probe');
    ChildProcess.spawn(probePath);
  }

  readyTime = now() - launchTime;

  // setAsDefaultProtocolClient('x-github-client');

  // if (__DEV__) {
  //   setAsDefaultProtocolClient('x-github-desktop-dev-auth');
  // } else {
  //   setAsDefaultProtocolClient('x-github-desktop-auth');
  // }

  // // Also support Desktop Classic's protocols.
  // if (__DARWIN__) {
  //   setAsDefaultProtocolClient('github-mac');
  // } else if (__WIN32__) {
  //   setAsDefaultProtocolClient('github-windows');
  // }

  createWindow();

  let menu = buildDefaultMenu();
  Menu.setApplicationMenu(menu);

  ipcMain.on(
    'update-preferred-app-menu-item-labels',
    (
      event: Electron.IpcMessageEvent,
    ) => {
      menu = buildDefaultMenu();
      Menu.setApplicationMenu(menu);
      if (mainWindow) {
        mainWindow.sendAppMenu();
      }
    }
  );

  ipcMain.on('menu-event', (event: Electron.IpcMessageEvent, args: any[]) => {
    const { name }: { name: MenuEvent } = event as any;
    if (mainWindow) {
      mainWindow.sendMenuEvent(name);
    }
  });

  /**
   * An event sent by the renderer asking that the menu item with the given id
   * is executed (ie clicked).
   */
  ipcMain.on(
    'execute-menu-item',
    (event: Electron.IpcMessageEvent, { id }: { id: string }) => {
      const menuItem = findMenuItemByID(menu, id);
      if (menuItem) {
        const window = BrowserWindow.fromWebContents(event.sender);
        const fakeEvent = { preventDefault: () => { }, sender: event.sender };
        menuItem.click(fakeEvent, window, event.sender);
      }
    }
  );

  ipcMain.on(
    'update-menu-state',
    (
      event: Electron.IpcMessageEvent,
      items: Array<{ id: string; state: IMenuItemState }>
    ) => {
      let sendMenuChangedEvent = false;

      for (const item of items) {
        const { id, state } = item;
        const menuItem = findMenuItemByID(menu, id);

        if (menuItem) {
          // Only send the updated app menu when the state actually changes
          // or we might end up introducing a never ending loop between
          // the renderer and the main process
          if (
            state.enabled !== undefined &&
            menuItem.enabled !== state.enabled
          ) {
            menuItem.enabled = state.enabled;
            sendMenuChangedEvent = true;
          }
        } else {
          // fatalError(`Unknown menu id: ${id}`);
        }
      }

      if (sendMenuChangedEvent && mainWindow) {
        mainWindow.sendAppMenu();
      }
    }
  );

  // ipcMain.on(
  //   'show-contextual-menu',
  //   (event: Electron.IpcMessageEvent, items: ReadonlyArray<IMenuItem>) => {
  //     const menu = buildContextMenu(items, ix =>
  //       event.sender.send('contextual-menu-action', ix)
  //     );

  //     const window = BrowserWindow.fromWebContents(event.sender);
  //     menu.popup({ window });
  //   }
  // );

  /**
   * An event sent by the renderer asking for a copy of the current
   * application menu.
   */
  ipcMain.on('get-app-menu', () => {
    if (mainWindow) {
      mainWindow.sendAppMenu();
    }
  });

  // ipcMain.on(
  //   'show-certificate-trust-dialog',
  //   (
  //     event: Electron.IpcMessageEvent,
  //     {
  //       certificate,
  //       message,
  //     }: { certificate: Electron.Certificate; message: string }
  //   ) => {
  //     // This API is only implemented for macOS and Windows right now.
  //     if (__DARWIN__ || __WIN32__) {
  //       onDidLoad(window => {
  //         window.showCertificateTrustDialog(certificate, message);
  //       });
  //     }
  //   }
  // );

  // ipcMain.on(
  //   'log',
  //   (event: Electron.IpcMessageEvent, level: LogLevel, message: string) => {
  //     writeLog(level, message);
  //   }
  // );

  // ipcMain.on(
  //   'uncaught-exception',
  //   (event: Electron.IpcMessageEvent, error: Error) => {
  //     handleUncaughtException(error);
  //   }
  // );

  // ipcMain.on(
  //   'send-error-report',
  //   (
  //     event: Electron.IpcMessageEvent,
  //     { error, extra }: { error: Error; extra: { [key: string]: string } }
  //   ) => {
  //     reportError(error, extra);
  //   }
  // );

  // ipcMain.on(
  //   'open-external',
  //   (event: Electron.IpcMessageEvent, { path }: { path: string }) => {
  //     const pathLowerCase = path.toLowerCase();
  //     if (
  //       pathLowerCase.startsWith('http://') ||
  //       pathLowerCase.startsWith('https://')
  //     ) {
  //       log.info(`opening in browser: ${path}`);
  //     }

  //     const result = shell.openExternal(path);
  //     event.sender.send('open-external-result', { result });
  //   }
  // );

  // ipcMain.on(
  //   'show-item-in-folder',
  //   (event: Electron.IpcMessageEvent, { path }: { path: string }) => {
  //     Fs.stat(path, (err, stats) => {
  //       if (err) {
  //         log.error(`Unable to find file at '${path}'`, err);
  //         return;
  //       }

  //       if (stats.isDirectory()) {
  //         openDirectorySafe(path);
  //       } else {
  //         shell.showItemInFolder(path);
  //       }
  //     });
  //   }
  // );
});

app.on('activate', () => {
  onDidLoad(window => {
    window.show();
  });
});

app.on('web-contents-created', (event, contents) => {
  contents.on('new-window', (_event, url) => {
    // Prevent links or window.open from opening new windows
    _event.preventDefault();
    log.warn(`Prevented new window to: ${url}`);
  });
});

// app.on(
//   'certificate-error',
//   (event, webContents, url, error, certificate, callback) => {
//     callback(false);

//     onDidLoad(window => {
//       window.sendCertificateError(certificate, error, url);
//     });
//   }
// );

function createWindow() {
  const window = new AppWindow();

  if (__DEV__) {
    const {
      default: installExtension,
    } = require('electron-devtools-installer');

    require('electron-debug')({ showDevTools: true });

    const ChromeLens = {
      id: 'idikgljglpfilbhaboonnpnnincjhjkd',
      electron: '>=1.2.1',
    };

    const extensions = [ChromeLens];

    for (const extension of extensions) {
      try {
        installExtension(extension);
      } catch (e) { }
    }
  }

  window.onClose(() => {
    mainWindow = null;
    if (!__DARWIN__ && !preventQuit) {
      app.quit();
    }
  });

  window.onDidLoad((data: any) => {
    window.show();
    window.sendLaunchTimingStats({
      mainReadyTime: readyTime ? readyTime : 0,
      loadTime: window.loadTime ? window.loadTime : 0,
      rendererReadyTime: window.rendererReadyTime ? window.rendererReadyTime : 0,
    });

    const fns = onDidLoadFns ? onDidLoadFns : null;
    onDidLoadFns = null;
    if (fns) {
      for (const fn of fns) {
        fn(window);
      }
    }
  });

  window.load();

  mainWindow = window;
}

/**
 * Register a function to be called once the window has been loaded. If the
 * window has already been loaded, the function will be called immediately.
 */
function onDidLoad(fn: OnDidLoadFn) {
  if (onDidLoadFns) {
    onDidLoadFns.push(fn);
  } else {
    if (mainWindow) {
      fn(mainWindow);
    }
  }
}
