import { isMacOS } from "../components/helpers/dom_helpers";
import { Color } from "../types/misc";
import { SpreadsheetChildEnv } from "../types/spreadsheet_env";

export type MenuItemOrSeparator = Action | "separator";

/*
 * An Action represent a menu item for the menus of the top bar
 * and the context menu in the grid. It can also represent a button
 * used in the toolbar to trigger an action too.
 */
export interface ActionSpec {
  /**
   * String or a function to compute the name
   */
  name: string | ((env: SpreadsheetChildEnv) => string);
  description?: string | ((env: SpreadsheetChildEnv) => string);
  shortcut?: string;
  /**
   * which represents its position inside the
   * menus (the lower sequence it has, the upper it is in the menu)
   */
  sequence?: number;
  /**
   * used for example to add child
   */
  id?: string;
  /**
   * Can be defined to compute the visibility of the item
   */
  isVisible?: (env: SpreadsheetChildEnv) => boolean;
  /**
   * Can be defined to compute if the user can click on the action
   */
  isEnabled?: (env: SpreadsheetChildEnv) => boolean;
  /**
   * Can be defined to compute if the action is active
   */
  isActive?: (env: SpreadsheetChildEnv) => boolean;
  /**
   * Can be defined to display an icon
   */
  icon?: string | ((env: SpreadsheetChildEnv) => string);
  iconColor?: Color;
  /**
   * is the action allowed when running spreadsheet in readonly mode
   */
  isReadonlyAllowed?: boolean;
  /**
   * is the action allowed when the active sheet is locked
   */
  isEnabledOnLockedSheet?: boolean;
  /**
   *
   * Execute the action. The action can return a result.
   * The result will be carried by a `menu-clicked` event to the menu parent component.
   */
  execute?: (env: SpreadsheetChildEnv, isMiddleClick?: boolean) => unknown;
  /**
   * subitems associated to this item
   * NB: an action without an execute function or children is not displayed !
   */
  children?: ActionChildren;
  /**
   * whether it should add a separator below the item in menus
   * NB: a separator defined on the last item is not displayed !
   */
  separator?: boolean;
  textColor?: Color;
  onStartHover?: (env: SpreadsheetChildEnv) => void;
  onStopHover?: (env: SpreadsheetChildEnv) => void;
}

export interface Action {
  name: (env: SpreadsheetChildEnv) => string;
  description: (env: SpreadsheetChildEnv) => string;
  shortcut: string;
  sequence: number;
  id: string;
  isVisible: (env: SpreadsheetChildEnv) => boolean;
  isEnabled: (env: SpreadsheetChildEnv) => boolean;
  isActive?: (env: SpreadsheetChildEnv) => boolean;
  icon: (env: SpreadsheetChildEnv) => string;
  iconColor?: Color;
  isReadonlyAllowed: boolean;
  isEnabledOnLockedSheet: boolean;
  execute?: (env: SpreadsheetChildEnv, isMiddleClick?: boolean) => unknown;
  children: (env: SpreadsheetChildEnv) => Action[];
  separator: boolean;
  textColor?: Color;
  onStartHover?: (env: SpreadsheetChildEnv) => void;
  onStopHover?: (env: SpreadsheetChildEnv) => void;
}

export interface ComputedAction extends Omit<Action, "name" | "description" | "icon" | "children"> {
  name: string;
  description: string;
  icon: string;
}

export type ActionBuilder = (env: SpreadsheetChildEnv) => ActionSpec[];
type ActionChildren = (ActionSpec | ActionBuilder)[];

export function createActions(menuItems: ActionSpec[]): Action[] {
  return menuItems.map(createAction).sort((a, b) => a.sequence - b.sequence);
}

export function adaptShortcutStringToMacOs(shortcut: string | undefined) {
  return shortcut && isMacOS() ? shortcut.replace("Ctrl", "⌘").replace("Alt", "⌃") : shortcut;
}

function filterActionShortcutForMacOs(shortcut: string | undefined) {
  // we filter out the alt key from shortcuts because on macOs, it's used for special characters input and it creates conflicts with o-spreadsheet shortcuts
  return shortcut && isMacOS() && shortcut.match(/\+?Alt\+?/) ? "" : shortcut;
}

let nextItemId = 1;

export function createAction(item: ActionSpec): Action {
  const name = item.name;
  const children = item.children;
  const description = item.description;
  const shortcut = filterActionShortcutForMacOs(item.shortcut);
  const icon = item.icon;
  const itemId = item.id || nextItemId++;
  const isEnabled = item.isEnabled ? item.isEnabled : () => true;
  return {
    id: itemId.toString(),
    name: typeof name === "function" ? name : () => name,
    isVisible: item.isVisible ? item.isVisible : () => true,
    isEnabled: isEnabled,
    isActive: item.isActive,
    execute: item.execute
      ? (env, isMiddleClick) => {
          if (isEnabled(env)) {
            return item.execute!(env, isMiddleClick);
          }
          return undefined;
        }
      : undefined,
    children: children
      ? (env) => {
          const uniqueChildren: Record<string, Action> = {};
          children
            .flatMap((child) => (typeof child === "function" ? child(env) : child))
            .forEach((childSpec) => {
              const childAction = createAction(childSpec);
              if (uniqueChildren[childAction.id]) {
                throw new Error(
                  `Duplicate child action id "${childAction.id}" in action "${itemId}".`
                );
              }
              uniqueChildren[childAction.id] = childAction;
            });
          return Object.values(uniqueChildren).sort((a, b) => a.sequence - b.sequence);
        }
      : () => [],
    isReadonlyAllowed: item.isReadonlyAllowed || false,
    isEnabledOnLockedSheet: item.isEnabledOnLockedSheet || false,
    separator: item.separator || false,
    icon: typeof icon === "function" ? icon : () => icon || "",
    iconColor: item.iconColor,
    description: typeof description === "function" ? description : () => description || "",
    shortcut: shortcut || "",
    textColor: item.textColor,
    sequence: item.sequence || 0,
    onStartHover: item.onStartHover,
    onStopHover: item.onStopHover,
  };
}

export function getMenuItemsAndSeparators(
  env: SpreadsheetChildEnv,
  actions: Action[]
): MenuItemOrSeparator[] {
  const menuItemsAndSeparators: MenuItemOrSeparator[] = [];
  for (let i = 0; i < actions.length; i++) {
    const menuItem = actions[i];
    if (menuItem.isVisible(env) && (!isRootMenu(menuItem) || hasVisibleChildren(env, menuItem))) {
      menuItemsAndSeparators.push(menuItem);
    }
    if (
      menuItem.separator &&
      i !== actions.length - 1 && // no separator at the end
      menuItemsAndSeparators[menuItemsAndSeparators.length - 1] !== "separator" // no double separator
    ) {
      menuItemsAndSeparators.push("separator");
    }
  }
  if (menuItemsAndSeparators[menuItemsAndSeparators.length - 1] === "separator") {
    menuItemsAndSeparators.pop();
  }
  if (menuItemsAndSeparators.length === 1 && menuItemsAndSeparators[0] === "separator") {
    return [];
  }
  return menuItemsAndSeparators;
}

export function isRootMenu(menu: Action) {
  return !menu.execute;
}

export function hasVisibleChildren(env: SpreadsheetChildEnv, menu: Action) {
  return menu.children(env).some((child) => child.isVisible(env));
}

export function isMenuItemEnabled(env: SpreadsheetChildEnv, menu: Action): boolean {
  const children = menu.children?.(env);
  if (children.length) {
    return children.some((child) => isMenuItemEnabled(env, child));
  } else {
    if (menu.isEnabled(env)) {
      return env.model.getters.isReadonly() ? menu.isReadonlyAllowed : true;
    }
    return false;
  }
}
