import * as React from 'react';
import { DirectionalHint } from '../../common/DirectionalHint';
import type { IFocusZoneProps } from '../../FocusZone';
import type { IIconProps } from '../../Icon';
import type { ICalloutProps, ICalloutContentStyleProps } from '../../Callout';
import type { ITheme, IStyle } from '../../Styling';
import type { IButtonStyles } from '../../Button';
import type {
  IRefObject,
  IBaseProps,
  IRectangle,
  IRenderFunction,
  IStyleFunctionOrObject,
  IComponentAs,
} from '../../Utilities';
import type { IWithResponsiveModeState } from '../../ResponsiveMode';
import type { IContextualMenuClassNames, IMenuItemClassNames } from './ContextualMenu.classNames';
import type { IVerticalDividerClassNames } from '../Divider/VerticalDivider.types';
import type {
  IContextualMenuItemProps,
  IContextualMenuRenderItem,
  IContextualMenuItemStyleProps,
  IContextualMenuItemRenderFunctions,
} from './ContextualMenuItem.types';
import type { IKeytipProps } from '../../Keytip';
import type { Target } from '@fluentui/react-hooks';
import type { IPopupRestoreFocusParams } from '../../Popup';
import { IContextualMenuItemWrapperProps } from './ContextualMenuItemWrapper/ContextualMenuItemWrapper.types';

export { DirectionalHint } from '../../common/DirectionalHint';

/**
 * {@docCategory ContextualMenu}
 */
export enum ContextualMenuItemType {
  Normal = 0,
  Divider = 1,
  Header = 2,
  Section = 3,
}

/**
 * {@docCategory ContextualMenu}
 */
export interface IContextualMenu {}

/**
 * {@docCategory ContextualMenu}
 */
export interface IContextualMenuProps
  extends IBaseProps<IContextualMenu>,
    React.RefAttributes<HTMLDivElement>,
    // eslint-disable-next-line deprecation/deprecation
    IWithResponsiveModeState {
  /**
   * Optional callback to access the IContextualMenu interface. Use this instead of ref for accessing
   * the public methods and properties of the component.
   * @deprecated ContextualMenu has no imperative methods, so componentRef no longer returns a ref
   */
  componentRef?: IRefObject<IContextualMenu>;

  /**
   * Call to provide customized styling that will layer on top of the variant rules.
   */
  styles?: IStyleFunctionOrObject<IContextualMenuStyleProps, IContextualMenuStyles>;

  /**
   * Theme provided by higher-order component.
   */
  theme?: ITheme;

  /**
   * Additional CSS class to apply to the ContextualMenu.
   */
  className?: string;

  /**
   * The target that the ContextualMenu should try to position itself based on.
   * It can be either an element, a query selector string resolving to a valid element,
   * or a MouseEvent. If a MouseEvent is given, the origin point of the event will be used.
   */
  target?: Target;

  /**
   * How the menu should be positioned
   * @defaultvalue DirectionalHint.bottomAutoEdge
   */
  directionalHint?: DirectionalHint;

  /**
   * How the menu should be positioned in RTL layouts.
   * If not specified, a mirror of `directionalHint` will be used.
   */
  directionalHintForRTL?: DirectionalHint;

  /**
   * The gap between the ContextualMenu and the target
   * @defaultvalue 0
   */
  gapSpace?: number;

  /**
   * The width of the beak.
   * @defaultvalue 16
   */
  beakWidth?: number;

  /**
   * If true the context menu will render as the same width as the target element
   * @defaultvalue false
   */
  useTargetWidth?: boolean;

  /**
   * If true the context menu will have a minimum width equal to the width of the target element
   * @defaultvalue false
   */
  useTargetAsMinWidth?: boolean;

  /**
   * The bounding rectangle (or callback that returns a rectangle) which the contextual menu can appear in.
   */
  bounds?: IRectangle | ((target?: Target, targetWindow?: Window) => IRectangle | undefined);

  /**
   * If true then the beak is visible. If false it will not be shown.
   */
  isBeakVisible?: boolean;

  /**
   * If true, the menu will be positioned to cover the target.
   * If false, it will be positioned next to the target.
   * @defaultvalue false
   */
  coverTarget?: boolean;

  /**
   * If true the positioning logic will prefer to flip edges rather than to nudge the rectangle to fit within bounds,
   * thus making sure the element aligns perfectly with target's alignment edge
   */
  alignTargetEdge?: boolean;

  /**
   * Menu items to display.
   */
  items: IContextualMenuItem[];

  /**
   * Used as `aria-labelledby` for the menu element inside the callout.
   */
  labelElementId?: string;

  /**
   * Whether to focus on the menu when mounted.
   * @defaultvalue true
   */
  shouldFocusOnMount?: boolean;

  /**
   * Whether to focus on the contextual menu container (as opposed to the first menu item).
   *
   * Avoid using as it breaks the default focus behaviour when using assistive technologies.
   */
  shouldFocusOnContainer?: boolean;

  /**
   * Callback when the ContextualMenu tries to close. If `dismissAll` is true then all
   * submenus will be dismissed.
   */
  onDismiss?: (ev?: Event | React.MouseEvent | React.KeyboardEvent, dismissAll?: boolean) => void;

  /**
   * Click handler which is invoked if `onClick` is not passed for individual contextual
   * menu item.
   * Returning true will dismiss the menu even if `ev.preventDefault()` was called.
   */
  onItemClick?: (
    ev?: React.MouseEvent<HTMLElement> | React.KeyboardEvent<HTMLElement>,
    item?: IContextualMenuItem,
  ) => boolean | void;

  /**
   * Whether this menu is a submenu of another menu.
   */
  isSubMenu?: boolean;

  /**
   * ID for the ContextualMenu's root element (inside the callout).
   * Should be used for `aria-owns` and other such uses, rather than direct reference for programmatic purposes.
   */
  id?: string;

  /**
   * Accessible label for the ContextualMenu's root element (inside the callout).
   */
  ariaLabel?: string;

  /**
   * If true do not render on a new layer. If false render on a new layer.
   * @defaultvalue false
   */
  doNotLayer?: boolean;

  /**
   * If true the position will not change sides in an attempt to fit the ContextualMenu within bounds.
   * It will still attempt to align it to whatever bounds are given.
   * @defaultvalue false
   */
  directionalHintFixed?: boolean;

  /**
   * Callback for when the menu has been opened.
   */
  onMenuOpened?: (contextualMenu?: IContextualMenuProps) => void;

  /**
   * Callback for when the menu is being closed (removing from the DOM).
   */
  onMenuDismissed?: (contextualMenu?: IContextualMenuProps) => void;

  /**
   * Additional custom props for the Callout.
   */
  calloutProps?: ICalloutProps;

  /**
   * Title to be displayed at the top of the menu, above the items.
   */
  title?: string;

  /**
   * Method to provide the classnames to style the contextual menu.
   * @deprecated Use `styles` instead to leverage mergeStyles API.
   */
  // eslint-disable-next-line deprecation/deprecation
  getMenuClassNames?: (theme: ITheme, className?: string) => IContextualMenuClassNames;

  /** Custom render function for a submenu. */
  onRenderSubMenu?: IRenderFunction<IContextualMenuProps>;

  /**
   * Method to override the render of the list of menu items.
   */
  onRenderMenuList?: IRenderFunction<IContextualMenuListProps>;

  /**
   * Method to wrap menu items. Gives the ability to wrap a custom
   * tooltip to each menu item button.
   */
  onRenderContextualMenuItem?: IRenderFunction<IContextualMenuItem>;

  /**
   * Delay (in milliseconds) to wait before expanding / dismissing a submenu on mouseEnter or mouseLeave
   */
  subMenuHoverDelay?: number;

  /**
   * Custom component to use for rendering individual menu items.
   * @defaultvalue ContextualMenuItem
   */
  contextualMenuItemAs?: IComponentAs<IContextualMenuItemProps>;

  /**
   * Props to pass down to the FocusZone.
   * NOTE: the default FocusZoneDirection will be used unless a direction
   * is specified in the focusZoneProps (even if other focusZoneProps are defined)
   * @defaultvalue \{ direction: FocusZoneDirection.vertical \}
   */
  focusZoneProps?: IFocusZoneProps;

  /**
   * Custom component to use for rendering the focus zone (the root).
   * @defaultValue FocusZone
   */
  focusZoneAs?: IComponentAs<IFocusZoneProps>;

  /**
   * If true, renders the ContextualMenu in a hidden state.
   * Use this flag, rather than rendering a ContextualMenu conditionally based on visibility,
   * to improve rendering performance when it becomes visible.
   * Note: When ContextualMenu is hidden its content will not be rendered. It will only render
   * once the ContextualMenu is visible.
   */
  hidden?: boolean;

  /**
   * If true, the menu will be updated even when `hidden=true`. Note that this will consume
   * resources to update even when nothing is being shown to the user. This might be helpful if
   * your updates are small and you want the menu to display quickly when `hidden` is set to false.
   */
  shouldUpdateWhenHidden?: boolean;

  /**
   * If true, the contextual menu will not be updated until focus enters the menu via other means.
   * This will only result in different behavior when `shouldFocusOnMount = false`.
   */
  delayUpdateFocusOnHover?: boolean;

  /**
   * Called when the component is unmounting, and focus needs to be restored. If this is provided,
   * focus will not be restored automatically, and you'll need to call `params.originalElement.focus()`.
   */
  onRestoreFocus?: (params: IPopupRestoreFocusParams) => void;
}

/**
 * {@docCategory ContextualMenu}
 */
export interface IContextualMenuItemRenderProps extends IContextualMenuItem {
  index: number;
  focusableElementIndex: number;
  totalItemCount: number;
  hasCheckmarks: boolean;
  hasIcons: boolean;
}

/**
 * {@docCategory ContextualMenu}
 */
export interface IContextualMenuListProps {
  items: IContextualMenuItem[];
  totalItemCount: number;
  hasCheckmarks: boolean;
  hasIcons: boolean;
  defaultMenuItemRenderer: (item: IContextualMenuItemRenderProps) => React.ReactNode;
  ariaLabel?: string;
  labelElementId?: string;
  role?: string;
}

/**
 * {@docCategory ContextualMenu}
 */
export interface IContextualMenuItem {
  /**
   * Optional callback to access the IContextualMenuRenderItem interface.
   * This will get passed down to ContextualMenuItem.
   */
  componentRef?: IRefObject<IContextualMenuRenderItem>;

  /**
   * Unique id to identify the item
   */
  key: string;

  /**
   * Text of the menu item.
   * If a standard hyphen (-) is passed in, then the item will be rendered as a divider.
   * If a dash must appear as text, use an emdash (—), figuredash (‒), or minus symbol (−) instead.
   */
  text?: string;

  /**
   * Secondary description for the menu item to display
   */
  secondaryText?: string;

  itemType?: ContextualMenuItemType;

  /**
   * Props for an icon to display next to the item.
   */
  iconProps?: IIconProps;

  /**
   * Custom render function for the menu item icon.
   * iconProps must be present on at least one item for onRenderIcon to be called.
   */
  onRenderIcon?: IRenderFunction<IContextualMenuItemProps>;

  /**
   * Props for the Icon used for the chevron.
   */
  submenuIconProps?: IIconProps;

  /**
   * Whether the menu item is disabled
   * @defaultvalue false
   */
  disabled?: boolean;

  /**
   * If the menu item is a split button, this prop disables purely the primary action of the button.
   * @defaultvalue false
   */
  primaryDisabled?: boolean;

  /**
   * @deprecated Not used
   */
  shortCut?: string;

  /**
   * Whether or not this menu item can be checked
   * @defaultvalue false
   */
  canCheck?: boolean;

  /**
   * Whether or not this menu item is currently checked.
   * @defaultvalue false
   */
  checked?: boolean;

  /**
   * Whether or not this menu item is a splitButton.
   * @defaultvalue false
   */
  split?: boolean;

  /**
   * Any custom data the developer wishes to associate with the menu item.
   */
  data?: any;

  /**
   * Callback for when the menu item is invoked. If `ev.preventDefault()` is called in `onClick`,
   * the click will not close the menu.
   *
   * Only for ContextualMenu items, returning true will dismiss the menu even if `ev.preventDefault()`
   * was called (does not apply for button or CommandBar sub-menu items).
   */
  onClick?: (
    ev?: React.MouseEvent<HTMLElement> | React.KeyboardEvent<HTMLElement>,
    item?: IContextualMenuItem,
  ) => boolean | void;

  /**
   * Navigate to this URL when the menu item is clicked.
   */
  href?: string;

  /**
   * Target window when using `href`.
   */
  target?: string;

  /**
   * Link relation setting when using `href`.
   * If `target` is `_blank`, `rel` is defaulted to a value to prevent clickjacking.
   */
  rel?: string;

  /**
   * Properties to apply to a submenu to this item.
   *
   * The ContextualMenu will provide default values for `target`, `onDismiss`, `isSubMenu`,
   * `id`, `shouldFocusOnMount`, `directionalHint`, `className`, and `gapSpace`, all of which
   * can be overridden.
   */
  subMenuProps?: IContextualMenuProps;

  /**
   * Method to provide the classnames to style the individual items inside a menu.
   * @deprecated Use `styles` prop of `IContextualMenuItemProps` to leverage mergeStyles API.
   */
  getItemClassNames?: (
    theme: ITheme,
    disabled: boolean,
    expanded: boolean,
    checked: boolean,
    isAnchorLink: boolean,
    knownIcon: boolean,
    itemClassName?: string,
    dividerClassName?: string,
    iconClassName?: string,
    subMenuClassName?: string,
    primaryDisabled?: boolean,
  ) => // eslint-disable-next-line deprecation/deprecation
  IMenuItemClassNames;

  /**
   * Optional IContextualMenuItemProps overrides to customize behaviors such as item styling via `styles`.
   */
  itemProps?: Partial<IContextualMenuItemProps>;

  /**
   * Method to provide the classnames to style the Vertical Divider of a split button inside a menu.
   * Default value is the `getSplitButtonVerticalDividerClassNames` func defined in `ContextualMenu.classnames.ts`.
   * @defaultvalue getSplitButtonVerticalDividerClassNames
   */
  // eslint-disable-next-line deprecation/deprecation
  getSplitButtonVerticalDividerClassNames?: (theme: ITheme) => IVerticalDividerClassNames;

  /**
   * Properties to apply to render this item as a section.
   * This prop is mutually exclusive with `subMenuProps`.
   */
  sectionProps?: IContextualMenuSection;

  /**
   * Additional CSS class to apply to the menu item.
   */
  className?: string;

  /**
   * Additional styles to apply to the menu item
   * @deprecated Use `styles` instead to leverage mergeStyles API.
   */
  style?: React.CSSProperties;

  /**
   * Custom accessible label for the element.
   * If no override is specified, the `aria-label` attribute will contain the item name.
   */
  ariaLabel?: string;

  /**
   * Title (tooltip) text displayed when hovering over an item.
   */
  title?: string;

  /**
   * Method to custom render this menu item.
   * For keyboard accessibility, the top-level rendered item should be a focusable element
   * (like an anchor or a button) or have the `data-is-focusable` property set to true.
   *
   * @param item - Item to render. Will typically be of type `IContextualMenuItem`.
   * (When rendering a command bar item, will be `ICommandBarItemProps`.)
   * @param dismissMenu - Function to dismiss the menu. Can be used to ensure that a custom menu
   * item click dismisses the menu. (Will be undefined if rendering a command bar item.)
   */
  onRender?: (item: any, dismissMenu: (ev?: any, dismissAll?: boolean) => void) => React.ReactNode;

  /**
   * An override for the child content of the contextual menu item.
   */
  contextualMenuItemAs?: IComponentAs<IContextualMenuItemProps>;

  /**
   * An override for the entire component used to render the contextal menu item.
   */
  contextualMenuItemWrapperAs?: IComponentAs<IContextualMenuItemWrapperProps>;

  /**
   * Method to customize sub-components rendering of this menu item.
   *
   * @param props - Props used to pass into render functions
   * @param defaultRenders - Default render functions that renders default sub-components
   */
  onRenderContent?: (
    props: IContextualMenuItemProps,
    defaultRenders: IContextualMenuItemRenderFunctions,
  ) => React.ReactNode;

  /**
   * A function to be executed on mouse down. This is executed before an `onClick` event and can
   * be used to interrupt native on click events as well. The click event should still handle
   * the commands. This should only be used in special cases when react and non-react are mixed.
   */
  onMouseDown?: (item: IContextualMenuItem, event: React.MouseEvent<HTMLElement>) => void;

  /**
   * Optional override for the menu button's role.
   * @default `menuitem` or `menuitemcheckbox`
   */
  role?: string;

  /**
   * When rendering a custom menu component that is passed in, the component might also be a list of
   * elements. We want to keep track of the correct index our menu is using based off of
   * the length of the custom list. It is up to the user to increment the count for their list.
   */
  customOnRenderListLength?: number;

  /**
   * Keytip for this contextual menu item
   */
  keytipProps?: IKeytipProps;

  /**
   * @deprecated Use subMenuProps.items instead.
   */
  items?: IContextualMenuItem[];

  /**
   * Any additional properties to use when custom rendering menu items.
   */
  [propertyName: string]: any;

  /**
   * Detailed description of the menu item for the benefit of screen readers.
   */
  ariaDescription?: string;

  /**
   * ID of the element that contains additional detailed descriptive information for screen readers
   */
  ariaDescribedBy?: string;

  /**
   * @deprecated No longer used. All contextual menu items are now focusable when disabled.
   */
  inactive?: boolean;

  /**
   * Text of the menu item.
   * @deprecated Use `text` instead.
   */
  name?: string;

  /**
   * Flag which indicates that, when the item is clicked, the 'target' for the click event should be
   * overridden to reflect the launch target from the root menu.
   * This avoids a situation where the 'target' of the event may wind up detached from the DOM
   * when the menu is dismissed in response to the click.
   */
  preferMenuTargetAsEventTarget?: boolean;
}

/**
 * {@docCategory ContextualMenu}
 */
export interface IContextualMenuSection extends React.ClassAttributes<any> {
  /**
   * The items to include inside the section.
   */
  items: IContextualMenuItem[];

  /**
   * The optional section title.
   */
  title?: string | IContextualMenuItem;

  /**
   * If set to true, the section will display a divider at the top of the section.
   */
  topDivider?: boolean;

  /**
   * If set to true, the section will display a divider at the bottom of the section.
   */
  bottomDivider?: boolean;
}

/**
 * {@docCategory ContextualMenu}
 */
export interface IMenuItemStyles extends IButtonStyles {
  /**
   * Styles for a menu item that is an anchor link.
   */
  item?: IStyle;

  /**
   * Styles for the content inside the button/link of the menuItem.
   */
  linkContent?: IStyle;

  /**
   * Styles for a menu item that is an anchor link.
   */
  anchorLink?: IStyle;

  /**
   * Default icon color style for known icons.
   */
  iconColor?: IStyle;

  /**
   * Default style for checkmark icons.
   */
  checkmarkIcon?: IStyle;

  /**
   * Styles for the submenu icon of a menu item.
   */
  subMenuIcon?: IStyle;

  /**
   * Styles for a divider item of a ContextualMenu.
   */
  divider?: IStyle;
}

/**
 * {@docCategory ContextualMenu}
 */
export interface IContextualMenuStyleProps {
  theme: ITheme;

  className?: string;

  // Insert ContextualMenu style props below
}

/**
 * {@docCategory ContextualMenu}
 */
export interface IContextualMenuStyles {
  /**
   * Style override for the contextual menu title.
   */
  title: IStyle;

  /**
   * Style for the container which parents all menu items.
   */
  container: IStyle;

  /**
   * Base styles for the root element of all ContextualMenus.
   */
  root: IStyle;

  /**
   * Styles for the header item of a ContextualMenu
   */
  header: IStyle;

  /**
   * Styles for the list that contains all menuItems.
   */
  list: IStyle;

  /**
   * SubComponent styles.
   */
  subComponentStyles: IContextualMenuSubComponentStyles;
}

/**
 * {@docCategory ContextualMenu}
 */
export interface IContextualMenuSubComponentStyles {
  /** Styles for the callout that hosts the ContextualMenu options. */
  callout: IStyleFunctionOrObject<ICalloutContentStyleProps, any>;

  /** Styles for each menu item. */
  menuItem: IStyleFunctionOrObject<IContextualMenuItemStyleProps, any>;
}
