import { merge } from '@overflow/core/merge';

import { MenuIDs } from '../type';
import { IAppState } from './app-state';
import { TipState } from './tip';
import { AppMenu, MenuItem } from './app-menu';

export interface IMenuItemState {
  readonly enabled?: boolean;
}

/**
 * Utility class for coalescing updates to menu items
 */
class MenuStateBuilder {
  private readonly _state: Map<MenuIDs, IMenuItemState>;

  public constructor(state: Map<MenuIDs, IMenuItemState> = new Map()) {
    this._state = state;
  }

  /**
   * Returns an Map where each key is a MenuID and the values
   * are IMenuItemState instances containing information about
   * whether a particular menu item should be enabled/disabled or
   * visible/hidden.
   */
  public get state() {
    return new Map<MenuIDs, IMenuItemState>(this._state);
  }

  private updateMenuItem<K extends keyof IMenuItemState>(
    id: MenuIDs,
    state: Pick<IMenuItemState, K>
  ) {
    const currentState = this._state.get(id) || {};
    this._state.set(id, merge(currentState, state));
  }

  /** Set the state of the given menu item id to enabled */
  public enable(id: MenuIDs): this {
    this.updateMenuItem(id, { enabled: true });
    return this;
  }

  /** Set the state of the given menu item id to disabled */
  public disable(id: MenuIDs): this {
    this.updateMenuItem(id, { enabled: false });
    return this;
  }

  /** Set the enabledness of the given menu item id */
  public setEnabled(id: MenuIDs, enabled: boolean): this {
    this.updateMenuItem(id, { enabled });
    return this;
  }

  /**
   * Create a new state builder by merging the current state with the state from
   * the other state builder. This will replace values in `this` with values
   * from `other`.
   */
  public merge(other: MenuStateBuilder): MenuStateBuilder {
    const merged = new Map<MenuIDs, IMenuItemState>(this._state);
    for (const [key, value] of other._state) {
      merged.set(key, value);
    }
    return new MenuStateBuilder(merged);
  }
}

function menuItemStateEqual(state: IMenuItemState, menuItem: MenuItem) {
  if (
    state.enabled !== undefined &&
    menuItem.type !== 'separator' &&
    menuItem.enabled !== state.enabled
  ) {
    return false;
  }

  return true;
}

const allMenuIds: ReadonlyArray<MenuIDs> = [
  'save',
  'preferences',
  'about',
  'export-csv',
  'print',
  'language-english',
  'language-korean',
];

function getAllMenusDisabledBuilder(): MenuStateBuilder {
  const menuStateBuilder = new MenuStateBuilder();

  for (const menuId of allMenuIds) {
    menuStateBuilder.disable(menuId);
  }

  return menuStateBuilder;
}

function getMenuState(state: IAppState): Map<MenuIDs, IMenuItemState> {
  if (state.currentPopup) {
    return getAllMenusDisabledBuilder().state;
  }

  return getAllMenusEnabledBuilder()
    .merge(getInWelcomeFlowBuilder(state.showWelcomeFlow)).state;
}

function getAllMenusEnabledBuilder(): MenuStateBuilder {
  const menuStateBuilder = new MenuStateBuilder();
  for (const menuId of allMenuIds) {
    menuStateBuilder.enable(menuId);
  }
  return menuStateBuilder;
}

function getInWelcomeFlowBuilder(inWelcomeFlow: boolean): MenuStateBuilder {
  const welcomeScopedIds: ReadonlyArray<MenuIDs> = [
    'preferences',
    'about',
  ];

  const menuStateBuilder = new MenuStateBuilder();
  if (inWelcomeFlow) {
    for (const id of welcomeScopedIds) {
      menuStateBuilder.disable(id);
    }
  } else {
    for (const id of welcomeScopedIds) {
      menuStateBuilder.enable(id);
    }
  }

  return menuStateBuilder;
}

/**
 * Update the menu state in the main process.
 *
 * This function will set the enabledness and visibility of menu items
 * in the main process based on the AppState. All changes will be
 * batched together into one ipc message.
 */
export function updateMenuState(
  state: IAppState,
  currentAppMenu: AppMenu | null
) {
  const menuState = getMenuState(state);

  // Try to avoid updating sending the IPC message at all
  // if we have a current app menu that we can compare against.
  if (currentAppMenu) {
    for (const [id, menuItemState] of menuState.entries()) {
      const appMenuItem = currentAppMenu.getItemById(id);

      if (appMenuItem && menuItemStateEqual(menuItemState, appMenuItem)) {
        menuState.delete(id);
      }
    }
  }

  if (menuState.size === 0) {
    return;
  }

  // because we can't send Map over the wire, we need to convert
  // the remaining entries into an array that can be serialized
  const array = new Array<{ id: MenuIDs; state: IMenuItemState }>();
  menuState.forEach((value, key) => array.push({ id: key, state: value }));
  // ipcUpdateMenuState(array);
}
