import React, { forwardRef, useEffect, useMemo, type ReactElement, type ReactNode } from 'react';
import {
  ActivityIndicator,
  Text,
  View,
  type ActivityIndicatorProps,
  type PressableProps,
  type ViewProps,
  type TextProps,
} from 'react-native';
import {
  ButtonRoot as ButtonRootPrimitive,
  Slot,
  dataAttributes,
  mergeDataAttributes,
  type IButtonProps,
} from '@cdx-ui/primitives';
import {
  cn,
  flattenChildren,
  ParentContext,
  useParentContext,
  useStyleContext,
} from '@cdx-ui/utils';
import { Icon, IconProps } from '../Icon';
import { positionToDataIcon, type IconSlotPosition } from '../../utils/positionToDataIcon';
import {
  type ButtonGroupVariantProps,
  type ButtonVariantProps,
  buttonGroupVariants,
  buttonIconVariants,
  buttonRootVariants,
  buttonSpinnerColorVariants,
  buttonSpinnerVariants,
  buttonTextVariants,
} from './styles';

const SCOPE = 'BUTTON';

const useButtonStyleContext = () => useStyleContext(SCOPE) as ButtonVariantProps;

// =============================================================================
// STYLED ROOT COMPONENT
// =============================================================================

export type ButtonVariant = 'solid' | 'outline' | 'ghost';

/** @deprecated Use {@link ButtonVariant} (`'solid'`) instead. */
export type ButtonVariantStrong = 'strong';

let hasWarnedStrongButtonVariant = false;

export interface ButtonProps extends PressableProps, IButtonProps {
  /**
   * Visual style variant.
   * @default 'solid'
   * @remarks `'solid' | 'outline' | 'ghost'`
   * @fumadocsType `'solid' | 'outline' | 'ghost'`
   */
  variant?:
    | ButtonVariant
    /**
     * @deprecated Use `'solid'` instead.
     */
    | 'strong';
  /**
   * Color theme applied to the button surface and content.
   * @default 'action'
   */
  color?: 'action' | 'danger' | 'warning' | 'success' | 'info';
  /**
   * Height and horizontal padding scale.
   * @default 'default'
   */
  size?: 'default' | 'small';
  /**
   * Stretch the button to fill the width of its container.
   * @default false
   */
  fullWidth?: boolean;
  /** Additional Uniwind/Tailwind classes, merged after the button's own styles. */
  className?: string;
  /** Button content — typically `Button.Label`, `Button.Icon`, and/or `Button.Spinner`. */
  children?: ReactNode;
}

const ButtonRoot = forwardRef<View, ButtonProps>(
  (
    {
      variant: variantProp = 'solid',
      color = 'action',
      size = 'default',
      fullWidth = false,
      className,
      children,
      style,
      ...props
    },
    ref,
  ) => {
    // TODO: Remove this once the `strong` variant is removed.
    useEffect(() => {
      if (__DEV__ && variantProp === 'strong' && !hasWarnedStrongButtonVariant) {
        hasWarnedStrongButtonVariant = true;
        console.warn(
          '[Button] The `strong` variant is deprecated and will be removed in a future major release. Use `solid` instead.',
        );
      }
    }, [variantProp]);

    const variant = variantProp === 'strong' ? 'solid' : variantProp;
    const computedClassName = cn(
      buttonRootVariants({ variant, color, size, fullWidth }),
      className,
    );

    const parent = useParentContext();
    const ctx = useMemo(
      () => ({ ...parent, [SCOPE]: { variant, color, size, fullWidth } }),
      [parent, variant, color, size, fullWidth],
    );

    return (
      <ParentContext.Provider value={ctx}>
        <ButtonRootPrimitive
          ref={ref}
          className={computedClassName}
          style={style}
          {...props}
          {...mergeDataAttributes(props, dataAttributes({ slot: 'button' }))}
        >
          {children}
        </ButtonRootPrimitive>
      </ParentContext.Provider>
    );
  },
);

ButtonRoot.displayName = 'Button';

// =============================================================================
// STYLED LABEL COMPONENT
// =============================================================================

export interface ButtonLabelProps extends TextProps {
  /** Render the consumer's child element in place of the default `Text` host. */
  asChild?: boolean;
  /** Additional Uniwind/Tailwind classes, merged after the label's own styles. */
  className?: string;
  /** Label content (or a custom element when `asChild` is set). */
  children?: ReactNode;
}

const ButtonLabel = forwardRef<Text, ButtonLabelProps>(
  ({ asChild, className, children, style, ...props }, ref) => {
    const { variant, color, size } = useButtonStyleContext();
    const computedClassName = cn(buttonTextVariants({ variant, color, size }), className);
    const Comp = asChild ? Slot : Text;

    return (
      <Comp
        ref={ref}
        className={computedClassName}
        style={style}
        {...props}
        {...mergeDataAttributes(props, dataAttributes({ slot: 'button-label' }))}
      >
        {children}
      </Comp>
    );
  },
);

ButtonLabel.displayName = 'Button.Label';

// =============================================================================
// STYLED GROUP COMPONENT
// =============================================================================

// TODO: Use ButtonGroupVariantProps
export interface ButtonGroupProps extends Omit<ViewProps, 'children'> {
  /** Render the consumer's child element in place of the default `View` host. */
  asChild?: boolean;
  /** Additional Uniwind/Tailwind classes, merged after the group's own styles. */
  className?: string;
  /** The buttons to group together. */
  children: ReactElement | ReactElement[];
  /**
   * Layout direction of the grouped buttons.
   * @default 'row'
   */
  flexDirection?: 'row' | 'column' | 'row-reverse' | 'column-reverse';
  /** Disable every button in the group. */
  isDisabled?: boolean;
  /** Remove the gap between buttons and merge their adjoining borders. */
  isAttached?: boolean;
  /** Reverse the visual order of the buttons. */
  reversed?: boolean;
  /** Alias of `reversed`. */
  isReversed?: boolean;
}

const ButtonGroup = forwardRef<View, ButtonGroupProps>(
  (
    {
      asChild,
      className,
      children,
      style,
      flexDirection = 'row',
      isAttached,
      isDisabled,
      isReversed,
      reversed,
      ...props
    },
    ref,
  ) => {
    const direction = flexDirection.includes('reverse')
      ? flexDirection === 'column-reverse'
        ? 'column'
        : 'row'
      : flexDirection;

    let childrenArray = React.Children.toArray(flattenChildren(children));
    childrenArray = isReversed || reversed ? [...childrenArray].reverse() : childrenArray;

    const computedChildren = childrenArray.map((child: any, index: number) => {
      if (typeof child === 'string' || typeof child === 'number') {
        return child;
      }

      const attachedStyles: Record<string, number> = {};

      if (index === 0) {
        if (direction === 'column') {
          attachedStyles.borderBottomLeftRadius = 0;
          attachedStyles.borderBottomRightRadius = 0;
        } else {
          attachedStyles.borderTopRightRadius = 0;
          attachedStyles.borderBottomRightRadius = 0;
        }
      } else if (index === childrenArray.length - 1) {
        if (direction === 'column') {
          attachedStyles.borderTopLeftRadius = 0;
          attachedStyles.borderTopRightRadius = 0;
        } else {
          attachedStyles.borderTopLeftRadius = 0;
          attachedStyles.borderBottomLeftRadius = 0;
        }
      } else {
        attachedStyles.borderRadius = 0;
      }

      const childProps = {
        isDisabled,
        ...child.props,
        style: {
          ...(isAttached ? attachedStyles : {}),
          ...child.props.style,
        },
      };

      return (
        <React.Fragment key={child.key ?? `spaced-child-${index.toString()}`}>
          {React.cloneElement(child, childProps)}
        </React.Fragment>
      );
    });

    const groupClassName = cn(
      buttonGroupVariants({ flexDirection, isAttached: !!isAttached }),
      className,
    );
    const Comp = asChild ? Slot : View;
    const gapProp = isAttached ? { gap: 0 } : {};

    return (
      <Comp
        ref={ref}
        className={groupClassName}
        style={[{ flexDirection }, style, gapProp]}
        {...props}
        {...mergeDataAttributes(props, dataAttributes({ slot: 'button-group' }))}
      >
        {computedChildren}
      </Comp>
    );
  },
);

ButtonGroup.displayName = 'Button.Group';

// =============================================================================
// STYLED ICON COMPONENT
// =============================================================================

export interface ButtonIconProps extends Omit<IconProps, 'children' | 'as'> {
  /** Render the consumer's child element in place of the default icon host. */
  asChild?: boolean;
  /**
   * Forge icon component to render. Required unless `asChild` is set, in which
   * case the consumer's substituted element is rendered instead.
   */
  as?: IconProps['as'];
  /**
   * Position hint that emits `data-icon="inline-start"` or `data-icon="inline-end"`.
   * CVA uses this attribute to apply leading/trailing padding logically (RTL-aware).
   * Omit to apply the symmetric baseline gap.
   */
  position?: IconSlotPosition;
}

const ButtonIcon = ({ asChild, className, style, as, position, ...props }: ButtonIconProps) => {
  const { variant, color, size } = useButtonStyleContext();
  const computedClassName = cn(buttonIconVariants({ variant, color, size }), className);

  if (asChild) {
    return (
      <Slot
        className={computedClassName}
        style={style}
        {...props}
        {...mergeDataAttributes(
          props,
          dataAttributes({ slot: 'button-icon' }),
          positionToDataIcon(position),
        )}
      />
    );
  }

  if (!as) {
    return null;
  }

  return (
    <Icon
      as={as}
      className={computedClassName}
      style={style}
      {...props}
      {...mergeDataAttributes(
        props,
        dataAttributes({ slot: 'button-icon' }),
        positionToDataIcon(position),
      )}
    />
  );
};

ButtonIcon.displayName = 'Button.Icon';

// =============================================================================
// STYLED SPINNER COMPONENT
// =============================================================================

export interface ButtonSpinnerProps extends ActivityIndicatorProps {
  /** Render the consumer's child element in place of the default `ActivityIndicator` host. */
  asChild?: boolean;
  /** Additional Uniwind/Tailwind classes for the spinner container. */
  className?: string;
  /** Uniwind `accent-*` color class override for the spinner. */
  colorClassName?: string;
  /**
   * Position hint that emits `data-icon="inline-start"` or `data-icon="inline-end"`.
   * CVA uses this attribute to apply leading/trailing padding logically (RTL-aware).
   * Omit to apply the symmetric baseline gap.
   */
  position?: IconSlotPosition;
}

const ButtonSpinner = forwardRef<ActivityIndicator, ButtonSpinnerProps>(
  (
    {
      asChild,
      className,
      colorClassName,
      style,
      position,
      accessibilityLabel = 'Loading',
      ...props
    },
    ref,
  ) => {
    const { variant, color } = useButtonStyleContext();
    const computedClassName = cn(buttonSpinnerVariants(), className);
    const computedColorClassName = cn(
      buttonSpinnerColorVariants({ variant, color }),
      colorClassName,
    );
    const Comp = asChild ? Slot : ActivityIndicator;

    return (
      <Comp
        ref={ref}
        className={computedClassName}
        colorClassName={computedColorClassName}
        style={style}
        accessibilityLabel={accessibilityLabel}
        {...props}
        {...mergeDataAttributes(
          props,
          dataAttributes({ slot: 'button-spinner' }),
          positionToDataIcon(position),
        )}
      />
    );
  },
);

ButtonSpinner.displayName = 'Button.Spinner';

type ButtonCompoundComponent = typeof ButtonRoot & {
  Label: typeof ButtonLabel;
  Group: typeof ButtonGroup;
  Icon: typeof ButtonIcon;
  Spinner: typeof ButtonSpinner;
};

export const Button = Object.assign(ButtonRoot, {
  Label: ButtonLabel,
  Group: ButtonGroup,
  Icon: ButtonIcon,
  Spinner: ButtonSpinner,
}) as ButtonCompoundComponent;

export type { ButtonVariantProps, ButtonGroupVariantProps };
