import ExpensiMark from 'expensify-common/lib/ExpensiMark';
import type {ImageContentFit} from 'expo-image';
import type {ForwardedRef, ReactNode} from 'react';
import React, {forwardRef, useContext, useEffect, useMemo, useRef, useState} from 'react';
import type {GestureResponderEvent, StyleProp, TextStyle, ViewStyle} from 'react-native';
import {View} from 'react-native';
import type {AnimatedStyle} from 'react-native-reanimated';
import type {ValueOf} from 'type-fest';
import useStyleUtils from '@hooks/useStyleUtils';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import useWindowDimensions from '@hooks/useWindowDimensions';
import ControlSelection from '@libs/ControlSelection';
import convertToLTR from '@libs/convertToLTR';
import * as DeviceCapabilities from '@libs/DeviceCapabilities';
import getButtonState from '@libs/getButtonState';
import type {MaybePhraseKey} from '@libs/Localize';
import type {AvatarSource} from '@libs/UserUtils';
import variables from '@styles/variables';
import * as Session from '@userActions/Session';
import CONST from '@src/CONST';
import type {Icon as IconType} from '@src/types/onyx/OnyxCommon';
import type IconAsset from '@src/types/utils/IconAsset';
import Avatar from './Avatar';
import Badge from './Badge';
import DisplayNames from './DisplayNames';
import type {DisplayNameWithTooltip} from './DisplayNames/types';
import FormHelpMessage from './FormHelpMessage';
import Hoverable from './Hoverable';
import Icon from './Icon';
import * as Expensicons from './Icon/Expensicons';
import * as defaultWorkspaceAvatars from './Icon/WorkspaceDefaultAvatars';
import {MenuItemGroupContext} from './MenuItemGroup';
import MultipleAvatars from './MultipleAvatars';
import PressableWithSecondaryInteraction from './PressableWithSecondaryInteraction';
import RenderHTML from './RenderHTML';
import SelectCircle from './SelectCircle';
import Text from './Text';

type IconProps = {
    /** Flag to choose between avatar image or an icon */
    iconType?: typeof CONST.ICON_TYPE_ICON;

    /** Icon to display on the left side of component */
    icon: IconAsset | IconType[];
};

type AvatarProps = {
    iconType?: typeof CONST.ICON_TYPE_AVATAR | typeof CONST.ICON_TYPE_WORKSPACE;

    icon: AvatarSource | IconType[];
};

type NoIcon = {
    iconType?: undefined;

    icon?: undefined;
};

type MenuItemBaseProps = {
    /** Function to fire when component is pressed */
    onPress?: (event: GestureResponderEvent | KeyboardEvent) => void | Promise<void>;

    /** Whether the menu item should be interactive at all */
    interactive?: boolean;

    /** Text to be shown as badge near the right end. */
    badgeText?: string;

    /** Used to apply offline styles to child text components */
    style?: StyleProp<ViewStyle>;

    /** Any additional styles to apply */
    wrapperStyle?: StyleProp<ViewStyle>;

    /** Any additional styles to apply on the outer element */
    containerStyle?: StyleProp<ViewStyle>;

    /** Used to apply styles specifically to the title */
    titleStyle?: ViewStyle;

    /** Any additional styles to apply on the badge element */
    badgeStyle?: ViewStyle;

    /** Any adjustments to style when menu item is hovered or pressed */
    hoverAndPressStyle?: StyleProp<AnimatedStyle<ViewStyle>>;

    /** Additional styles to style the description text below the title */
    descriptionTextStyle?: StyleProp<TextStyle>;

    /** The fill color to pass into the icon. */
    iconFill?: string;

    /** Secondary icon to display on the left side of component, right of the icon */
    secondaryIcon?: IconAsset;

    /** The fill color to pass into the secondary icon. */
    secondaryIconFill?: string;

    /** Icon Width */
    iconWidth?: number;

    /** Icon Height */
    iconHeight?: number;

    /** Any additional styles to pass to the icon container. */
    iconStyles?: StyleProp<ViewStyle>;

    /** A fallback avatar icon to display when there is an error on loading avatar from remote URL. */
    fallbackIcon?: IconAsset;

    /** An icon to display under the main item */
    furtherDetailsIcon?: IconAsset;

    /** Boolean whether to display the title right icon */
    shouldShowTitleIcon?: boolean;

    /** Icon to display at right side of title */
    titleIcon?: IconAsset;

    /** Boolean whether to display the right icon */
    shouldShowRightIcon?: boolean;

    /** Overrides the icon for shouldShowRightIcon */
    iconRight?: IconAsset;

    /** Should render component on the right */
    shouldShowRightComponent?: boolean;

    /** Component to be displayed on the right */
    rightComponent?: ReactNode;

    /** A description text to show under the title */
    description?: string;

    /** Should the description be shown above the title (instead of the other way around) */
    shouldShowDescriptionOnTop?: boolean;

    /** Error to display below the title */
    error?: string;

    /** Error to display at the bottom of the component */
    errorText?: MaybePhraseKey;

    /** A boolean flag that gives the icon a green fill if true */
    success?: boolean;

    /** Whether item is focused or active */
    focused?: boolean;

    /** Should we disable this menu item? */
    disabled?: boolean;

    /** Text that appears above the title */
    label?: string;

    /** Label to be displayed on the right */
    rightLabel?: string;

    /** Text to display for the item */
    title?: string;

    /** A right-aligned subtitle for this menu option */
    subtitle?: string | number;

    /** Should the title show with normal font weight (not bold) */
    shouldShowBasicTitle?: boolean;

    /** Should we make this selectable with a checkbox */
    shouldShowSelectedState?: boolean;

    /** Whether this item is selected */
    isSelected?: boolean;

    /** Prop to identify if we should load avatars vertically instead of diagonally */
    shouldStackHorizontally?: boolean;

    /** Prop to represent the size of the avatar images to be shown */
    avatarSize?: (typeof CONST.AVATAR_SIZE)[keyof typeof CONST.AVATAR_SIZE];

    /** Avatars to show on the right of the menu item */
    floatRightAvatars?: IconType[];

    /** Prop to represent the size of the float right avatar images to be shown */
    floatRightAvatarSize?: ValueOf<typeof CONST.AVATAR_SIZE>;

    /** Affects avatar size  */
    viewMode?: ValueOf<typeof CONST.OPTION_MODE>;

    /** Used to truncate the text with an ellipsis after computing the text layout */
    numberOfLinesTitle?: number;

    /**  Whether we should use small avatar subscript sizing the for menu item */
    isSmallAvatarSubscriptMenu?: boolean;

    /** The type of brick road indicator to show. */
    brickRoadIndicator?: ValueOf<typeof CONST.BRICK_ROAD_INDICATOR_STATUS>;

    /** Should render the content in HTML format */
    shouldRenderAsHTML?: boolean;

    /** Should we grey out the menu item when it is disabled? */
    shouldGreyOutWhenDisabled?: boolean;

    /** Should we use default cursor for disabled content */
    shouldUseDefaultCursorWhenDisabled?: boolean;

    /** The action accept for anonymous user or not */
    isAnonymousAction?: boolean;

    /** Flag to indicate whether or not text selection should be disabled from long-pressing the menu item. */
    shouldBlockSelection?: boolean;

    /** Whether should render title as HTML or as Text */
    shouldParseTitle?: boolean;

    /** Should check anonymous user in onPress function */
    shouldCheckActionAllowedOnPress?: boolean;

    /** Text to display under the main item */
    furtherDetails?: string;

    /** The function that should be called when this component is LongPressed or right-clicked. */
    onSecondaryInteraction?: (event: GestureResponderEvent | MouseEvent) => void;

    /** Array of objects that map display names to their corresponding tooltip */
    titleWithTooltips?: DisplayNameWithTooltip[];

    /** Icon should be displayed in its own color */
    displayInDefaultIconColor?: boolean;

    /** Determines how the icon should be resized to fit its container */
    contentFit?: ImageContentFit;

    /** Is this in the Pane */
    isPaneMenu?: boolean;

    /** Adds padding to the left of the text when there is no icon. */
    shouldPutLeftPaddingWhenNoIcon?: boolean;
};

type MenuItemProps = (IconProps | AvatarProps | NoIcon) & MenuItemBaseProps;

function MenuItem(
    {
        interactive = true,
        onPress,
        badgeText,
        style,
        wrapperStyle,
        containerStyle,
        titleStyle,
        hoverAndPressStyle,
        descriptionTextStyle,
        badgeStyle,
        viewMode = CONST.OPTION_MODE.DEFAULT,
        numberOfLinesTitle = 1,
        icon,
        iconFill,
        secondaryIcon,
        secondaryIconFill,
        iconType = CONST.ICON_TYPE_ICON,
        iconWidth,
        iconHeight,
        iconStyles,
        fallbackIcon = Expensicons.FallbackAvatar,
        shouldShowTitleIcon = false,
        titleIcon,
        shouldShowRightIcon = false,
        iconRight = Expensicons.ArrowRight,
        furtherDetailsIcon,
        furtherDetails,
        description,
        error,
        errorText,
        success = false,
        focused = false,
        disabled = false,
        title,
        subtitle,
        shouldShowBasicTitle,
        label,
        rightLabel,
        shouldShowSelectedState = false,
        isSelected = false,
        shouldStackHorizontally = false,
        shouldShowDescriptionOnTop = false,
        shouldShowRightComponent = false,
        rightComponent,
        floatRightAvatars = [],
        floatRightAvatarSize,
        avatarSize = CONST.AVATAR_SIZE.DEFAULT,
        isSmallAvatarSubscriptMenu = false,
        brickRoadIndicator,
        shouldRenderAsHTML = false,
        shouldGreyOutWhenDisabled = true,
        shouldUseDefaultCursorWhenDisabled = false,
        isAnonymousAction = false,
        shouldBlockSelection = false,
        shouldParseTitle = false,
        shouldCheckActionAllowedOnPress = true,
        onSecondaryInteraction,
        titleWithTooltips,
        displayInDefaultIconColor = false,
        contentFit = 'cover',
        isPaneMenu = false,
        shouldPutLeftPaddingWhenNoIcon = false,
    }: MenuItemProps,
    ref: ForwardedRef<View>,
) {
    const theme = useTheme();
    const styles = useThemeStyles();
    const StyleUtils = useStyleUtils();
    const combinedStyle = [style, styles.popoverMenuItem];
    const {isSmallScreenWidth} = useWindowDimensions();
    const [html, setHtml] = useState('');
    const titleRef = useRef('');
    const {isExecuting, singleExecution, waitForNavigate} = useContext(MenuItemGroupContext) ?? {};

    const isDeleted = style && Array.isArray(style) ? style.includes(styles.offlineFeedback.deleted) : false;
    const descriptionVerticalMargin = shouldShowDescriptionOnTop ? styles.mb1 : styles.mt1;
    const fallbackAvatarSize = viewMode === CONST.OPTION_MODE.COMPACT ? CONST.AVATAR_SIZE.SMALL : CONST.AVATAR_SIZE.DEFAULT;
    const combinedTitleTextStyle = StyleUtils.combineStyles(
        [
            styles.flexShrink1,
            styles.popoverMenuText,
            // eslint-disable-next-line no-nested-ternary
            shouldPutLeftPaddingWhenNoIcon || (icon && !Array.isArray(icon)) ? (avatarSize === CONST.AVATAR_SIZE.SMALL ? styles.ml2 : styles.ml3) : {},
            shouldShowBasicTitle ? {} : styles.textStrong,
            numberOfLinesTitle !== 1 ? styles.preWrap : styles.pre,
            interactive && disabled ? {...styles.userSelectNone} : {},
            styles.ltr,
            isDeleted ? styles.offlineFeedback.deleted : {},
        ],
        titleStyle ?? {},
    );
    const descriptionTextStyles = StyleUtils.combineStyles<TextStyle>([
        styles.textLabelSupporting,
        icon && !Array.isArray(icon) ? styles.ml3 : {},
        title ? descriptionVerticalMargin : StyleUtils.getFontSizeStyle(variables.fontSizeNormal),
        (descriptionTextStyle as TextStyle) || styles.breakWord,
        isDeleted ? styles.offlineFeedback.deleted : {},
    ]);

    useEffect(() => {
        if (!title || (titleRef.current.length && titleRef.current === title) || !shouldParseTitle) {
            return;
        }
        const parser = new ExpensiMark();
        setHtml(parser.replace(title));
        titleRef.current = title;
    }, [title, shouldParseTitle]);

    const getProcessedTitle = useMemo(() => {
        let processedTitle = '';
        if (shouldRenderAsHTML) {
            processedTitle = title ? convertToLTR(title) : '';
        }

        if (shouldParseTitle) {
            processedTitle = html;
        }

        return processedTitle ? `<comment>${processedTitle}</comment>` : '';
    }, [title, shouldRenderAsHTML, shouldParseTitle, html]);

    const hasPressableRightComponent = iconRight || (shouldShowRightComponent && rightComponent);

    const renderTitleContent = () => {
        if (title && titleWithTooltips && Array.isArray(titleWithTooltips) && titleWithTooltips.length > 0) {
            return (
                <DisplayNames
                    fullTitle={title}
                    displayNamesWithTooltips={titleWithTooltips}
                    tooltipEnabled
                    numberOfLines={1}
                />
            );
        }

        return title ? convertToLTR(title) : '';
    };

    const onPressAction = (event: GestureResponderEvent | KeyboardEvent | undefined) => {
        if (disabled || !interactive) {
            return;
        }

        if (event?.type === 'click') {
            (event.currentTarget as HTMLElement).blur();
        }

        if (onPress && event) {
            if (!singleExecution || !waitForNavigate) {
                onPress(event);
                return;
            }
            singleExecution(
                waitForNavigate(() => {
                    onPress(event);
                }),
            )();
        }
    };

    return (
        <Hoverable>
            {(isHovered) => (
                <PressableWithSecondaryInteraction
                    onPress={shouldCheckActionAllowedOnPress ? Session.checkIfActionIsAllowed(onPressAction, isAnonymousAction) : onPressAction}
                    onPressIn={() => shouldBlockSelection && isSmallScreenWidth && DeviceCapabilities.canUseTouchScreen() && ControlSelection.block()}
                    onPressOut={ControlSelection.unblock}
                    onSecondaryInteraction={onSecondaryInteraction}
                    style={({pressed}) =>
                        [
                            containerStyle,
                            errorText ? styles.pb5 : {},
                            combinedStyle,
                            !interactive && styles.cursorDefault,
                            StyleUtils.getButtonBackgroundColorStyle(getButtonState(focused || isHovered, pressed, success, disabled, interactive), true),
                            !focused && (isHovered || pressed) && hoverAndPressStyle,
                            ...(Array.isArray(wrapperStyle) ? wrapperStyle : [wrapperStyle]),
                            shouldGreyOutWhenDisabled && disabled && styles.buttonOpacityDisabled,
                        ] as StyleProp<ViewStyle>
                    }
                    disabledStyle={shouldUseDefaultCursorWhenDisabled && [styles.cursorDefault]}
                    disabled={disabled || isExecuting}
                    ref={ref}
                    role={CONST.ROLE.MENUITEM}
                    accessibilityLabel={title ? title.toString() : ''}
                    accessible
                >
                    {({pressed}) => (
                        <>
                            <View style={[styles.flexColumn, styles.flex1]}>
                                {!!label && (
                                    <View style={icon ? styles.mb2 : null}>
                                        <Text style={StyleUtils.combineStyles([styles.sidebarLinkText, styles.optionAlternateText, styles.textLabelSupporting, styles.pre])}>{label}</Text>
                                    </View>
                                )}
                                <View style={[styles.flexRow, styles.pointerEventsAuto, disabled && !shouldUseDefaultCursorWhenDisabled && styles.cursorDisabled]}>
                                    {!!icon && Array.isArray(icon) && (
                                        <MultipleAvatars
                                            isHovered={isHovered}
                                            isPressed={pressed}
                                            icons={icon as IconType[]}
                                            size={avatarSize}
                                            secondAvatarStyle={[
                                                StyleUtils.getBackgroundAndBorderStyle(theme.sidebar),
                                                pressed && interactive ? StyleUtils.getBackgroundAndBorderStyle(theme.buttonPressedBG) : undefined,
                                                isHovered && !pressed && interactive ? StyleUtils.getBackgroundAndBorderStyle(theme.border) : undefined,
                                            ]}
                                        />
                                    )}
                                    {!icon && shouldPutLeftPaddingWhenNoIcon && <View style={[styles.popoverMenuIcon, iconStyles, StyleUtils.getAvatarWidthStyle(avatarSize)]} />}
                                    {icon && !Array.isArray(icon) && (
                                        <View style={[styles.popoverMenuIcon, iconStyles, StyleUtils.getAvatarWidthStyle(avatarSize)]}>
                                            {typeof icon !== 'string' && iconType === CONST.ICON_TYPE_ICON && (
                                                <Icon
                                                    contentFit={contentFit}
                                                    hovered={isHovered}
                                                    pressed={pressed}
                                                    src={icon}
                                                    width={iconWidth}
                                                    height={iconHeight}
                                                    fill={
                                                        displayInDefaultIconColor
                                                            ? undefined
                                                            : iconFill ??
                                                              StyleUtils.getIconFillColor(getButtonState(focused || isHovered, pressed, success, disabled, interactive), true, isPaneMenu)
                                                    }
                                                />
                                            )}
                                            {icon && iconType === CONST.ICON_TYPE_WORKSPACE && (
                                                <Avatar
                                                    imageStyles={[styles.alignSelfCenter]}
                                                    size={CONST.AVATAR_SIZE.DEFAULT}
                                                    source={icon as AvatarSource}
                                                    fallbackIcon={fallbackIcon}
                                                    name={title}
                                                    type={CONST.ICON_TYPE_WORKSPACE}
                                                />
                                            )}
                                            {iconType === CONST.ICON_TYPE_AVATAR && (
                                                <Avatar
                                                    imageStyles={[styles.alignSelfCenter]}
                                                    source={icon as AvatarSource}
                                                    fallbackIcon={fallbackIcon}
                                                    size={avatarSize}
                                                />
                                            )}
                                        </View>
                                    )}
                                    {secondaryIcon && (
                                        <View style={[styles.popoverMenuIcon, iconStyles]}>
                                            <Icon
                                                contentFit={contentFit}
                                                src={secondaryIcon}
                                                width={iconWidth}
                                                height={iconHeight}
                                                fill={secondaryIconFill ?? StyleUtils.getIconFillColor(getButtonState(focused || isHovered, pressed, success, disabled, interactive), true)}
                                            />
                                        </View>
                                    )}
                                    <View style={[styles.justifyContentCenter, styles.flex1, StyleUtils.getMenuItemTextContainerStyle(isSmallAvatarSubscriptMenu)]}>
                                        {!!description && shouldShowDescriptionOnTop && (
                                            <Text
                                                style={descriptionTextStyles}
                                                numberOfLines={2}
                                            >
                                                {description}
                                            </Text>
                                        )}
                                        <View style={[styles.flexRow, styles.alignItemsCenter]}>
                                            {!!title && (shouldRenderAsHTML || (shouldParseTitle && !!html.length)) && (
                                                <View style={styles.renderHTMLTitle}>
                                                    <RenderHTML html={getProcessedTitle} />
                                                </View>
                                            )}
                                            {!shouldRenderAsHTML && !shouldParseTitle && !!title && (
                                                <Text
                                                    style={combinedTitleTextStyle}
                                                    numberOfLines={numberOfLinesTitle || undefined}
                                                    dataSet={{[CONST.SELECTION_SCRAPER_HIDDEN_ELEMENT]: interactive && disabled}}
                                                >
                                                    {renderTitleContent()}
                                                </Text>
                                            )}
                                            {shouldShowTitleIcon && titleIcon && (
                                                <View style={[styles.ml2]}>
                                                    <Icon
                                                        src={titleIcon}
                                                        fill={theme.iconSuccessFill}
                                                    />
                                                </View>
                                            )}
                                        </View>
                                        {!!description && !shouldShowDescriptionOnTop && (
                                            <Text
                                                style={descriptionTextStyles}
                                                numberOfLines={2}
                                            >
                                                {description}
                                            </Text>
                                        )}
                                        {!!error && (
                                            <View style={[styles.mt1]}>
                                                <Text style={[styles.textLabelError]}>{error}</Text>
                                            </View>
                                        )}
                                        {!!furtherDetails && (
                                            <View style={[styles.flexRow, styles.mt1, styles.alignItemsCenter]}>
                                                {!!furtherDetailsIcon && (
                                                    <Icon
                                                        src={furtherDetailsIcon}
                                                        height={variables.iconSizeNormal}
                                                        width={variables.iconSizeNormal}
                                                        inline
                                                    />
                                                )}
                                                <Text
                                                    style={furtherDetailsIcon ? [styles.furtherDetailsText, styles.ph2, styles.pt1] : styles.textLabelSupporting}
                                                    numberOfLines={2}
                                                >
                                                    {furtherDetails}
                                                </Text>
                                            </View>
                                        )}
                                    </View>
                                </View>
                            </View>
                            <View style={[styles.flexRow, styles.menuItemTextContainer, !hasPressableRightComponent && styles.pointerEventsNone]}>
                                {badgeText && (
                                    <Badge
                                        text={badgeText}
                                        textStyles={styles.textStrong}
                                        badgeStyles={[
                                            styles.alignSelfCenter,
                                            styles.badgeBordered,
                                            brickRoadIndicator ? styles.mr2 : undefined,
                                            focused || isHovered || pressed ? styles.activeItemBadge : {},
                                            badgeStyle,
                                        ]}
                                    />
                                )}
                                {/* Since subtitle can be of type number, we should allow 0 to be shown */}
                                {(subtitle === 0 || subtitle) && (
                                    <View style={[styles.justifyContentCenter, styles.mr1]}>
                                        <Text style={[styles.textLabelSupporting, ...(combinedStyle as TextStyle[])]}>{subtitle}</Text>
                                    </View>
                                )}
                                {floatRightAvatars?.length > 0 && (
                                    <View style={[styles.justifyContentCenter, brickRoadIndicator ? styles.mr2 : undefined]}>
                                        <MultipleAvatars
                                            isHovered={isHovered}
                                            isPressed={pressed}
                                            icons={floatRightAvatars}
                                            size={floatRightAvatarSize ?? fallbackAvatarSize}
                                            fallbackIcon={defaultWorkspaceAvatars.WorkspaceBuilding}
                                            shouldStackHorizontally={shouldStackHorizontally}
                                        />
                                    </View>
                                )}
                                {!!brickRoadIndicator && (
                                    <View style={[styles.alignItemsCenter, styles.justifyContentCenter, styles.ml1, styles.mr2]}>
                                        <Icon
                                            src={Expensicons.DotIndicator}
                                            fill={brickRoadIndicator === 'error' ? theme.danger : theme.success}
                                        />
                                    </View>
                                )}
                                {!title && !!rightLabel && (
                                    <View style={styles.justifyContentCenter}>
                                        <Text style={styles.rightLabelMenuItem}>{rightLabel}</Text>
                                    </View>
                                )}
                                {shouldShowRightIcon && (
                                    <View style={[styles.popoverMenuIcon, styles.pointerEventsAuto, disabled && !shouldUseDefaultCursorWhenDisabled && styles.cursorDisabled]}>
                                        <Icon
                                            src={iconRight}
                                            fill={StyleUtils.getIconFillColor(getButtonState(focused || isHovered, pressed, success, disabled, interactive))}
                                        />
                                    </View>
                                )}
                                {shouldShowRightComponent && rightComponent}
                                {shouldShowSelectedState && <SelectCircle isChecked={isSelected} />}
                            </View>
                            {!!errorText && (
                                <FormHelpMessage
                                    isError
                                    shouldShowRedDotIndicator={false}
                                    message={errorText}
                                    style={styles.menuItemError}
                                />
                            )}
                        </>
                    )}
                </PressableWithSecondaryInteraction>
            )}
        </Hoverable>
    );
}

MenuItem.displayName = 'MenuItem';

export type {IconProps, AvatarProps, NoIcon, MenuItemBaseProps, MenuItemProps};
export default forwardRef(MenuItem);