import { Menu, ipcMain, shell, app } from 'electron';
import { ensureDir } from 'fs-extra';

import { ensureItemIds } from './ensure-item-ids';
import { MenuEvent } from '../../commons/type/menu-event';
import { openDirectorySafe } from '../shell';


export function buildDefaultMenu(
): Electron.Menu {
  const template = new Array<Electron.MenuItemConstructorOptions>();
  const separator: Electron.MenuItemConstructorOptions = { type: 'separator' };

  if (__DARWIN__) {
    template.push({
      label: 'overFlow Scanner',
      submenu: [
        {
          label: 'About overFlow Scanner',
          click: emit('show-about'),
          id: 'about',
        },
        separator,
        {
          label: 'Preferences…',
          id: 'preferences',
          accelerator: 'CmdOrCtrl+,',
          click: emit('show-preferences'),
        },
        separator,
        {
          role: 'services',
          submenu: [],
        },
        separator,
        { role: 'hide' },
        { role: 'hideothers' },
        { role: 'unhide' },
        separator,
        { role: 'quit' },
      ],
    });
  }

  const fileMenu: Electron.MenuItemConstructorOptions = {
    label: __DARWIN__ ? 'File' : '&File',
    submenu: [
      {
        label: __DARWIN__ ? 'Save…' : 'Save…',
        id: 'save',
        click: emit('save'),
        accelerator: 'CmdOrCtrl+S',
      },
      separator,
      {
        label: __DARWIN__ ? 'Export as…' : 'Export as…',
        submenu: [
          {
            label: __DARWIN__ ? 'CSV…' : 'CSV…',
            id: 'export-csv',
            click: emit('show-export-csv'),
          },
        ],
      },
      separator,
      {
        label: __DARWIN__ ? 'Print…' : 'Print…',
        id: 'print',
        click: emit('show-print'),
      },
    ],
  };
  template.push(fileMenu);

  template.push({
    label: __DARWIN__ ? 'View' : '&View',
    submenu: [
      {
        label: __DARWIN__ ? 'Toggle Full Screen' : 'Toggle &full screen',
        role: 'togglefullscreen',
      },
      separator,
      {
        label: __DARWIN__ ? 'Reset Zoom' : 'Reset zoom',
        accelerator: 'CmdOrCtrl+0',
        click: zoom(ZoomDirection.Reset),
      },
      {
        label: __DARWIN__ ? 'Zoom In' : 'Zoom in',
        accelerator: 'CmdOrCtrl+=',
        click: zoom(ZoomDirection.In),
      },
      {
        label: __DARWIN__ ? 'Zoom Out' : 'Zoom out',
        accelerator: 'CmdOrCtrl+-',
        click: zoom(ZoomDirection.Out),
      },
      separator,
      {
        label: '&Reload',
        id: 'reload-window',
        // Ctrl+Alt is interpreted as AltGr on international keyboards and this
        // can clash with other shortcuts. We should always use Ctrl+Shift for
        // chorded shortcuts, but this menu item is not a user-facing feature
        // so we are going to keep this one around and save Ctrl+Shift+R for
        // a different shortcut in the future...
        accelerator: 'CmdOrCtrl+Alt+R',
        click(item: any, focusedWindow: Electron.BrowserWindow) {
          if (focusedWindow) {
            focusedWindow.reload();
          }
        },
        visible: __RELEASE_CHANNEL__ === 'development',
      },
      {
        id: 'show-devtools',
        label: __DARWIN__
          ? 'Toggle Developer Tools'
          : '&Toggle developer tools',
        accelerator: (() => {
          return __DARWIN__ ? 'Alt+Command+I' : 'Ctrl+Shift+I';
        })(),
        click(item: any, focusedWindow: Electron.BrowserWindow) {
          if (focusedWindow) {
            focusedWindow.webContents.toggleDevTools();
          }
        },
      },
    ],
  });

  if (__DARWIN__) {
    template.push({
      role: 'window',
      submenu: [
        { role: 'minimize' },
        { role: 'zoom' },
        { role: 'close' },
        separator,
        { role: 'front' },
      ],
    });
  }

  const settingMenu: Electron.MenuItemConstructorOptions = {
    label: __DARWIN__ ? 'Discovery' : '&Discovery',
    submenu: [
      {
        label: __DARWIN__ ? 'Setting…' : 'Setting…',
        id: 'preferences',
        accelerator: 'CmdOrCtrl+,',
        click: emit('show-preferences'),
      },
      {
        label: __DARWIN__ ? 'Language…' : 'Language…',
        submenu: [
          {
            label: __DARWIN__ ? 'English…' : 'English…',
            id: 'language-english',
            click: emit('change-language-english'),
          },
          {
            label: __DARWIN__ ? 'Korean…' : 'Korean…',
            id: 'language-korean',
            click: emit('change-language-korean'),
          },
        ],
      },
    ],
  };
  template.push(settingMenu);

  const submitIssueItem: Electron.MenuItemConstructorOptions = {
    label: __DARWIN__ ? 'Report Issue…' : 'Report issue…',
    click() {
      shell.openExternal('https://github.com/desktop/desktop/issues/new/choose');
    },
  };

  const contactSupportItem: Electron.MenuItemConstructorOptions = {
    label: __DARWIN__ ? 'Contact overFlow Scanner Support…' : '&Contact overFlow Scanner support…',
    click() {
      shell.openExternal(
        `https://github.com/contact?from_desktop_app=1&app_version=${app.getVersion()}`
      );
    },
  };

  const showUserGuides: Electron.MenuItemConstructorOptions = {
    label: 'Show User Guides',
    click() {
      shell.openExternal('https://help.github.com/desktop/guides/');
    },
  };

  const helpItems = [
    submitIssueItem,
    contactSupportItem,
    showUserGuides,
  ];

  if (__DARWIN__) {
    template.push({
      role: 'help',
      submenu: helpItems,
    });
  } else {
    template.push({
      label: '&Help',
      submenu: [
        ...helpItems,
        separator,
        {
          label: '&About overFlow Scanner',
          click: emit('show-about'),
          id: 'about',
        },
      ],
    });
  }

  ensureItemIds(template);

  return Menu.buildFromTemplate(template);
}

type ClickHandler = (
  menuItem: Electron.MenuItem,
  browserWindow: Electron.BrowserWindow,
  event: Electron.Event
) => void;

/**
 * Utility function returning a Click event handler which, when invoked, emits
 * the provided menu event over IPC.
 */
function emit(name: MenuEvent): ClickHandler {
  return (menuItem, window) => {
    if (window) {
      window.webContents.send('menu-event', { name });
    } else {
      ipcMain.emit('menu-event', { name });
    }
  };
}

enum ZoomDirection {
  Reset,
  In,
  Out,
}

/** The zoom steps that we support, these factors must sorted */
const ZoomInFactors = [1, 1.1, 1.25, 1.5, 1.75, 2];
const ZoomOutFactors = ZoomInFactors.slice().reverse();

/**
 * Returns the element in the array that's closest to the value parameter. Note
 * that this function will throw if passed an empty array.
 */
function findClosestValue(arr: Array<number>, value: number) {
  return arr.reduce((previous, current) => {
    return Math.abs(current - value) < Math.abs(previous - value)
      ? current
      : previous;
  });
}

/**
 * Figure out the next zoom level for the given direction and alert the renderer
 * about a change in zoom factor if necessary.
 */
function zoom(direction: ZoomDirection): ClickHandler {
  return (menuItem, window) => {
    if (!window) {
      return;
    }

    const { webContents } = window;

    if (direction === ZoomDirection.Reset) {
      webContents.setZoomFactor(1);
      webContents.send('zoom-factor-changed', 1);
    } else {
      webContents.getZoomFactor(rawZoom => {
        const zoomFactors =
          direction === ZoomDirection.In ? ZoomInFactors : ZoomOutFactors;

        // So the values that we get from getZoomFactor are floating point
        // precision numbers from chromium that don't always round nicely so
        // we'll have to do a little trick to figure out which of our supported
        // zoom factors the value is referring to.
        const currentZoom = findClosestValue(zoomFactors, rawZoom);

        const nextZoomLevel = zoomFactors.find(
          f =>
            direction === ZoomDirection.In ? f > currentZoom : f < currentZoom
        );

        // If we couldn't find a zoom level (likely due to manual manipulation
        // of the zoom factor in devtools) we'll just snap to the closest valid
        // factor we've got.
        const newZoom =
          nextZoomLevel === undefined ? currentZoom : nextZoomLevel;

        webContents.setZoomFactor(newZoom);
        webContents.send('zoom-factor-changed', newZoom);
      });
    }
  };
}
