'use strict';

const { app, BrowserWindow, deprecate } = require('electron');
const binding = process.electronBinding('dialog');
const v8Util = process.electronBinding('v8_util');

const DialogType = {
  OPEN: 'OPEN',
  SAVE: 'SAVE'
};

const saveFileDialogProperties = {
  createDirectory: 1 << 0,
  showHiddenFiles: 1 << 1,
  treatPackageAsDirectory: 1 << 2,
  showOverwriteConfirmation: 1 << 3,
  dontAddToRecent: 1 << 4
};

const openFileDialogProperties = {
  openFile: 1 << 0,
  openDirectory: 1 << 1,
  multiSelections: 1 << 2,
  createDirectory: 1 << 3, // macOS
  showHiddenFiles: 1 << 4,
  promptToCreate: 1 << 5, // Windows
  noResolveAliases: 1 << 6, // macOS
  treatPackageAsDirectory: 1 << 7, // macOS
  dontAddToRecent: 1 << 8 // Windows
};

const normalizeAccessKey = (text) => {
  if (typeof text !== 'string') return text;

  // macOS does not have access keys so remove single ampersands
  // and replace double ampersands with a single ampersand
  if (process.platform === 'darwin') {
    return text.replace(/&(&?)/g, '$1');
  }

  // Linux uses a single underscore as an access key prefix so escape
  // existing single underscores with a second underscore, replace double
  // ampersands with a single ampersand, and replace a single ampersand with
  // a single underscore
  if (process.platform === 'linux') {
    return text.replace(/_/g, '__').replace(/&(.?)/g, (match, after) => {
      if (after === '&') return after;
      return `_${after}`;
    });
  }

  return text;
};

const checkAppInitialized = function () {
  if (!app.isReady()) {
    throw new Error('dialog module can only be used after app is ready');
  }
};

const setupDialogProperties = (type, properties) => {
  const dialogPropertiesTypes = (type === DialogType.OPEN) ? openFileDialogProperties : saveFileDialogProperties;
  let dialogProperties = 0;
  for (const prop in dialogPropertiesTypes) {
    if (properties.includes(prop)) {
      dialogProperties |= dialogPropertiesTypes[prop];
    }
  }

  return dialogProperties;
};

const saveDialog = (sync, window, options) => {
  checkAppInitialized();

  if (window && window.constructor !== BrowserWindow) {
    options = window;
    window = null;
  }

  if (options == null) options = { title: 'Save' };

  const {
    buttonLabel = '',
    defaultPath = '',
    filters = [],
    properties = [],
    title = '',
    message = '',
    securityScopedBookmarks = false,
    nameFieldLabel = '',
    showsTagField = true
  } = options;

  if (typeof title !== 'string') throw new TypeError('Title must be a string');
  if (typeof buttonLabel !== 'string') throw new TypeError('Button label must be a string');
  if (typeof defaultPath !== 'string') throw new TypeError('Default path must be a string');
  if (typeof message !== 'string') throw new TypeError('Message must be a string');
  if (typeof nameFieldLabel !== 'string') throw new TypeError('Name field label must be a string');

  const settings = { buttonLabel, defaultPath, filters, title, message, securityScopedBookmarks, nameFieldLabel, showsTagField, window };
  settings.properties = setupDialogProperties(DialogType.SAVE, properties);

  return (sync) ? binding.showSaveDialogSync(settings) : binding.showSaveDialog(settings);
};

const openDialog = (sync, window, options) => {
  checkAppInitialized();

  if (window && window.constructor !== BrowserWindow) {
    options = window;
    window = null;
  }

  if (options == null) {
    options = {
      title: 'Open',
      properties: ['openFile']
    };
  }

  const {
    buttonLabel = '',
    defaultPath = '',
    filters = [],
    properties = ['openFile'],
    title = '',
    message = '',
    securityScopedBookmarks = false
  } = options;

  if (!Array.isArray(properties)) throw new TypeError('Properties must be an array');

  if (typeof title !== 'string') throw new TypeError('Title must be a string');
  if (typeof buttonLabel !== 'string') throw new TypeError('Button label must be a string');
  if (typeof defaultPath !== 'string') throw new TypeError('Default path must be a string');
  if (typeof message !== 'string') throw new TypeError('Message must be a string');

  const settings = { title, buttonLabel, defaultPath, filters, message, securityScopedBookmarks, window };
  settings.properties = setupDialogProperties(DialogType.OPEN, properties);

  return (sync) ? binding.showOpenDialogSync(settings) : binding.showOpenDialog(settings);
};

const messageBox = (sync, window, options) => {
  checkAppInitialized();

  if (window && window.constructor !== BrowserWindow) {
    options = window;
    window = null;
  }

  if (options == null) options = { type: 'none' };

  const messageBoxTypes = ['none', 'info', 'warning', 'error', 'question'];
  const messageBoxOptions = { noLink: 1 << 0 };

  let {
    buttons = [],
    cancelId,
    checkboxLabel = '',
    checkboxChecked,
    defaultId = -1,
    detail = '',
    icon = null,
    noLink = false,
    message = '',
    title = '',
    type = 'none'
  } = options;

  const messageBoxType = messageBoxTypes.indexOf(type);
  if (messageBoxType === -1) throw new TypeError('Invalid message box type');
  if (!Array.isArray(buttons)) throw new TypeError('Buttons must be an array');
  if (options.normalizeAccessKeys) buttons = buttons.map(normalizeAccessKey);
  if (typeof title !== 'string') throw new TypeError('Title must be a string');
  if (typeof noLink !== 'boolean') throw new TypeError('noLink must be a boolean');
  if (typeof message !== 'string') throw new TypeError('Message must be a string');
  if (typeof detail !== 'string') throw new TypeError('Detail must be a string');
  if (typeof checkboxLabel !== 'string') throw new TypeError('checkboxLabel must be a string');

  checkboxChecked = !!checkboxChecked;
  if (checkboxChecked && !checkboxLabel) {
    throw new Error('checkboxChecked requires that checkboxLabel also be passed');
  }

  // Choose a default button to get selected when dialog is cancelled.
  if (cancelId == null) {
    // If the defaultId is set to 0, ensure the cancel button is a different index (1)
    cancelId = (defaultId === 0 && buttons.length > 1) ? 1 : 0;
    for (let i = 0; i < buttons.length; i++) {
      const text = buttons[i].toLowerCase();
      if (text === 'cancel' || text === 'no') {
        cancelId = i;
        break;
      }
    }
  }

  const settings = {
    window,
    messageBoxType,
    buttons,
    defaultId,
    cancelId,
    noLink,
    title,
    message,
    detail,
    checkboxLabel,
    checkboxChecked,
    icon
  };

  if (sync) {
    return binding.showMessageBoxSync(settings);
  } else {
    return binding.showMessageBox(settings);
  }
};

module.exports = {
  showOpenDialog: function (window, options) {
    return openDialog(false, window, options);
  },

  showOpenDialogSync: function (window, options) {
    return openDialog(true, window, options);
  },

  showSaveDialog: function (window, options) {
    return saveDialog(false, window, options);
  },

  showSaveDialogSync: function (window, options) {
    return saveDialog(true, window, options);
  },

  showMessageBox: function (window, options) {
    return messageBox(false, window, options);
  },

  showMessageBoxSync: function (window, options) {
    return messageBox(true, window, options);
  },

  showErrorBox: function (...args) {
    return binding.showErrorBox(...args);
  },

  showCertificateTrustDialog: function (window, options) {
    if (window && window.constructor !== BrowserWindow) options = window;
    if (options == null || typeof options !== 'object') {
      throw new TypeError('options must be an object');
    }

    const { certificate, message = '' } = options;
    if (certificate == null || typeof certificate !== 'object') {
      throw new TypeError('certificate must be an object');
    }

    if (typeof message !== 'string') throw new TypeError('message must be a string');

    return binding.showCertificateTrustDialog(window, certificate, message);
  }
};
