import { forwardRef, useEffect, useMemo, useRef, useState } from 'react';
import { Platform, View } from 'react-native';
import { GestureDetector } from 'react-native-gesture-handler';
import Animated from 'react-native-reanimated';
import { cn, ParentContext, useParentContext, useStyleContext } from '@cdx-ui/utils';
import { CloseIcon } from '@cdx-ui/icons';
import {
  ToastRoot as ToastRootPrimitive,
  useToastContext,
  Slot,
  dataAttributes,
  mergeDataAttributes,
} from '@cdx-ui/primitives';
import { AnimationSettingsProvider } from '../../providers/animation-settings';
import type { ToastComponentProps } from '../../providers/toast';
import { useToastLifecycle } from '../../providers/toast/lifecycle.context';
import { useToastConfig } from '../../providers/toast/toast-config.context';
import { Button } from '../Button';
import { Text } from '../Text';
import { Icon } from '../Icon';
import { IconButton } from '../IconButton';
import { useToastRootAnimation } from './animation';
import { useButtonColor, useVerticalPlaceholderStyles } from './hooks';
import {
  toastRootVariants,
  toastLabelVariants,
  toastDescriptionVariants,
  toastIconVariants,
  toastStyleSheet,
  toastWrapperVariants,
} from './styles';
import type {
  DefaultToastProps,
  ToastActionProps,
  ToastCloseProps,
  ToastColorInternal,
  ToastContextValue,
  ToastDescriptionProps,
  ToastIconProps,
  ToastProps,
  ToastTitleProps,
} from './types';

const SCOPE = 'TOAST';

const useToastStyleContext = () => useStyleContext(SCOPE) as ToastContextValue;

const AnimatedToastRoot = Animated.createAnimatedComponent(ToastRootPrimitive);

const IS_WEB = Platform.OS === 'web';

let hasWarnedToastDefaultColor = false;

/**
 * Fallback timeout for web exit transitions. Covers cases where
 * `transitionend` never fires — `prefers-reduced-motion: reduce`
 * collapsing the transition, a backgrounded tab, or the element being
 * hidden before it triggers. Longer than the longest CSS transition
 * (200ms enter / 150ms exit) plus a ~100ms buffer.
 */
const WEB_TRANSITION_SAFETY_MS = 300;

type ToastDataState = 'entering' | 'open' | 'closed';

// --------------------------------------------------

const ToastRoot = forwardRef<View, ToastProps>((props, ref) => {
  const globalConfig = useToastConfig();

  const {
    children,
    color: localColor,
    placement: localPlacement,
    index,
    total,
    heights,
    maxVisibleToasts,
    className,
    style,
    animation: localAnimation,
    isSwipeable: localIsSwipeable,
    isAnimatedStyleActive = true,
    hide,
    ...restProps
  } = props;

  /**
   * Merge global config with local props, ensuring local props take precedence
   */
  const resolvedColor = localColor ?? globalConfig?.color ?? 'neutral';
  const placement = localPlacement ?? globalConfig?.placement ?? 'bottom';
  const animation = localAnimation ?? globalConfig?.animation;
  const isSwipeable = localIsSwipeable ?? globalConfig?.isSwipeable;

  useEffect(() => {
    if (__DEV__ && resolvedColor === ('default' as string) && !hasWarnedToastDefaultColor) {
      hasWarnedToastDefaultColor = true;
      console.warn(
        '[Toast] The `default` color is deprecated and will be removed in a future major release. Use `neutral` instead.',
      );
    }
  }, [resolvedColor]);

  const color: NonNullable<ToastColorInternal> =
    (resolvedColor as string) === 'default'
      ? 'neutral'
      : (resolvedColor as NonNullable<ToastColorInternal>);

  // Access id from props (id is omitted from ToastProps type but available at runtime)
  const toastProps = props as ToastProps & Pick<ToastComponentProps, 'id'>;
  const { id } = toastProps;

  const rootClassName = cn(toastRootVariants({ color }), className);

  // Extract padding and backgroundColor for placeholder Views
  const { topStyle, bottomStyle } = useVerticalPlaceholderStyles({
    rootClassName,
    style,
  });

  const { rContainerStyle, entering, exiting, panGesture, isAllAnimationsDisabled } =
    useToastRootAnimation({
      animation,
      index,
      total,
      heights,
      placement,
      hide,
      id,
      isSwipeable,
      maxVisibleToasts,
    });

  const rootStyle = isAnimatedStyleActive
    ? [toastStyleSheet.root, rContainerStyle, style]
    : [toastStyleSheet.root, style];

  const animationSettingsContextValue = useMemo(
    () => ({
      isAllAnimationsDisabled,
    }),
    [isAllAnimationsDisabled],
  );
  const parent = useParentContext();
  const ctx = useMemo(
    () => ({ ...parent, [SCOPE]: { color, hide, id } }),
    [parent, color, hide, id],
  );

  // ---- Web enter/exit lifecycle -----------------------------------------
  // On web the CSS engine drives enter/exit via `data-state` transitions
  // (see `toastWrapperVariants`). The state machine is:
  //   mount → `entering` → (rAF) → `open` → (hide) → `closed` → (transitionend) → unmount
  // Native ignores `dataState`; Reanimated `entering` / `exiting` still handle the visuals.

  const lifecycle = useToastLifecycle();
  const isPendingRemoval = lifecycle?.pendingRemovalIds.has(id) ?? false;
  const isFrontmost = lifecycle?.frontmostId === id;

  const [localState, setLocalState] = useState<Exclude<ToastDataState, 'closed'>>('entering');

  useEffect(() => {
    if (!IS_WEB) return;
    // rAF ensures the first paint commits with `data-state="entering"`
    // (opacity 0 + slight translate), then the next frame transitions
    // to `data-state="open"` with the CSS engine driving the animation.
    const raf = requestAnimationFrame(() => {
      setLocalState('open');
    });
    return () => cancelAnimationFrame(raf);
  }, []);

  const dataState: ToastDataState = isPendingRemoval ? 'closed' : localState;

  const wrapperRef = useRef<View | null>(null);

  // Keep the latest `remove` reachable from a ref so the pending-removal effect
  // below doesn't list `lifecycle` in its dep array. The context value changes
  // whenever any toast in the stack updates, which would otherwise reset the
  // safety timeout on every unrelated re-render.
  const removeRef = useRef<((id: string) => void) | undefined>(lifecycle?.remove);
  removeRef.current = lifecycle?.remove;

  useEffect(() => {
    if (!IS_WEB) return;
    if (!isPendingRemoval) return;

    const node = wrapperRef.current as unknown as
      | (HTMLElement & {
          addEventListener: HTMLElement['addEventListener'];
          removeEventListener: HTMLElement['removeEventListener'];
        })
      | null;

    let removed = false;
    const doRemove = () => {
      if (removed) return;
      removed = true;
      removeRef.current?.(id);
    };

    const safetyTimer = setTimeout(doRemove, WEB_TRANSITION_SAFETY_MS);

    if (!node || typeof node.addEventListener !== 'function') {
      return () => {
        clearTimeout(safetyTimer);
      };
    }

    const handler = (event: Event) => {
      const propertyName = (event as unknown as { propertyName?: string }).propertyName ?? '';
      // Ignore transitions on unrelated properties (e.g. shadow tokens) so
      // we don't unmount before the exit animation actually completes.
      if (propertyName === 'opacity' || propertyName === 'transform') {
        doRemove();
      }
    };
    node.addEventListener('transitionend', handler);

    return () => {
      clearTimeout(safetyTimer);
      node.removeEventListener('transitionend', handler);
    };
  }, [isPendingRemoval, id]);

  const wrapperClassName = cn(toastWrapperVariants({ placement }));

  return (
    <AnimationSettingsProvider value={animationSettingsContextValue}>
      <ParentContext.Provider value={ctx}>
        <GestureDetector gesture={panGesture}>
          <Animated.View
            ref={wrapperRef}
            className={wrapperClassName}
            entering={entering}
            exiting={exiting}
            {...(IS_WEB && { tabIndex: isFrontmost ? 0 : -1 })}
            {...mergeDataAttributes(
              dataAttributes({ state: dataState }),
              isFrontmost ? dataAttributes({ frontmost: 'true' }) : undefined,
            )}
          >
            {/* Animated toast instance */}
            <AnimatedToastRoot
              ref={ref}
              className={rootClassName}
              style={rootStyle}
              {...restProps}
              {...mergeDataAttributes(
                restProps,
                dataAttributes({
                  slot: 'toast',
                  placement,
                  index: String(index ?? 0),
                  ...(isFrontmost && { frontmost: 'true' }),
                }),
              )}
            >
              {children}
              {/*
                When visible toasts have different heights, the toast adapts to the last visible toast height.
                In cases where a toast originally has one height and gets smaller when a new toast comes to stack,
                content might be visible behind the last toast without proper padding.
                The placeholder Views ensure that the content under active toast is hidden.
              */}
              <View className="absolute left-0 right-0 top-0" style={topStyle} />
              <View className="absolute left-0 right-0 bottom-0" style={bottomStyle} />
            </AnimatedToastRoot>
            {/*
              Hidden toast instance used to measure natural height on native.
              Web owns its own natural height (Reanimated's height-spring is disabled
              on web to avoid the enter/exit glitch), so the duplicate node is skipped
              — this halves the DOM node count per toast on web.
            */}
            {!IS_WEB && (
              <AnimatedToastRoot
                pointerEvents="none"
                className={cn(rootClassName, 'absolute opacity-0')}
                style={[toastStyleSheet.root, style]}
                onLayout={(event) => {
                  const measuredHeight = event.nativeEvent.layout.height;
                  heights.modify((value) => {
                    'worklet';
                    return { ...value, [id]: measuredHeight };
                  });
                }}
                {...restProps}
              >
                {children}
              </AnimatedToastRoot>
            )}
          </Animated.View>
        </GestureDetector>
      </ParentContext.Provider>
    </AnimationSettingsProvider>
  );
});

// --------------------------------------------------

const ToastTitle = forwardRef<View, ToastTitleProps>((props, ref) => {
  const { children, className, ...restProps } = props;

  const { nativeID } = useToastContext();
  const { color } = useToastStyleContext();

  const labelClassName = cn(toastLabelVariants({ color }), className);

  return (
    <Text
      ref={ref}
      className={labelClassName}
      nativeID={`${nativeID}_label`}
      role="heading"
      aria-level={3}
      {...restProps}
    >
      {children}
    </Text>
  );
});

// --------------------------------------------------

const ToastDescription = forwardRef<View, ToastDescriptionProps>((props, ref) => {
  const { children, className, ...restProps } = props;

  const { nativeID } = useToastContext();
  const { color } = useToastStyleContext();
  const descriptionClassName = cn(toastDescriptionVariants({ color }), className);

  return (
    <Text
      ref={ref}
      className={descriptionClassName}
      nativeID={`${nativeID}_description`}
      {...restProps}
    >
      {children}
    </Text>
  );
});

// --------------------------------------------------

const ToastIcon = ({ asChild, className, style, as, ...props }: ToastIconProps) => {
  const { color } = useToastStyleContext();
  const computedClassName = cn(toastIconVariants({ color }), className);

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

  if (!as) {
    return null;
  }

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

ToastIcon.displayName = 'Toast.Icon';

// --------------------------------------------------

const ToastAction = forwardRef<View, ToastActionProps>((props, ref) => {
  const { children, variant, color, size = 'small', className, ...restProps } = props;

  const { color: toastColor } = useToastStyleContext();

  // TODO: Add toastActionVariants to styles.ts to restore color-aware action styling

  const buttonColor = useButtonColor({ color, toastColor });

  return (
    <Button
      ref={ref}
      variant={variant ?? 'outline'}
      color={buttonColor}
      size={size}
      className={className}
      {...restProps}
    >
      <Button.Label>{children}</Button.Label>
    </Button>
  );
});

// --------------------------------------------------

const ToastClose = forwardRef<View, ToastCloseProps>((props, ref) => {
  const { children, size = 'small', className, onPress, ...restProps } = props;
  const { hide, id } = useToastStyleContext();

  /**
   * Handle close button press
   * If hide and id are available from context, use them to hide the toast
   * Otherwise, use the provided onPress handler
   */
  const handlePress = (event: any) => {
    if (hide && id) {
      hide(id);
    }
    if (onPress && typeof onPress === 'function') {
      onPress(event);
    }
  };

  return (
    <IconButton
      ref={ref}
      variant="ghost"
      size={size}
      aria-label="Close"
      className={className}
      onPress={handlePress}
      as={CloseIcon}
      {...restProps}
    />
  );
});

// --------------------------------------------------

/**
 * Default styled toast component for simplified toast.show() API
 * Used internally when showing toasts with string or config object (without component)
 */
export function DefaultToast(props: DefaultToastProps) {
  const globalConfig = useToastConfig();

  const {
    id,
    color: localColor,
    placement: localPlacement,
    isSwipeable: localIsSwipeable,
    animation: localAnimation,
    label,
    description,
    actionLabel,
    onActionPress,
    icon,
    hide,
    show,
    ...toastComponentProps
  } = props;

  /**
   * Merge global config with local props, ensuring local props take precedence
   */
  const rawDefaultColor = localColor ?? globalConfig?.color ?? 'neutral';
  const placement = localPlacement ?? globalConfig?.placement ?? 'bottom';
  const isSwipeable = localIsSwipeable ?? globalConfig?.isSwipeable;
  const animation = localAnimation ?? globalConfig?.animation;

  useEffect(() => {
    if (__DEV__ && rawDefaultColor === ('default' as string) && !hasWarnedToastDefaultColor) {
      hasWarnedToastDefaultColor = true;
      console.warn(
        '[Toast] The `default` color is deprecated and will be removed in a future major release. Use `neutral` instead.',
      );
    }
  }, [rawDefaultColor]);

  const color: NonNullable<ToastColorInternal> =
    (rawDefaultColor as string) === 'default'
      ? 'neutral'
      : (rawDefaultColor as NonNullable<ToastColorInternal>);

  const handleActionPress = () => {
    if (onActionPress) {
      onActionPress({ show, hide });
    }
  };

  return (
    <ToastRoot
      id={id}
      color={color}
      placement={placement}
      isSwipeable={isSwipeable}
      animation={animation}
      className="flex-row gap-3"
      hide={hide}
      show={show}
      {...(color === 'danger' && { role: 'alert' as const })}
      {...toastComponentProps}
    >
      {icon ? <ToastIcon as={icon} /> : null}
      <View className="flex-1">
        {label && <ToastTitle>{label}</ToastTitle>}
        {description && <ToastDescription>{description}</ToastDescription>}
      </View>
      {actionLabel && <ToastAction onPress={handleActionPress}>{actionLabel}</ToastAction>}
    </ToastRoot>
  );
}

// --------------------------------------------------

ToastRoot.displayName = 'Toast';
ToastTitle.displayName = 'Toast.Title';
ToastDescription.displayName = 'Toast.Description';
ToastIcon.displayName = 'Toast.Icon';
ToastAction.displayName = 'Toast.Action';
ToastClose.displayName = 'Toast.Close';

type ToastCompoundComponent = typeof ToastRoot & {
  Title: typeof ToastTitle;
  Description: typeof ToastDescription;
  Icon: typeof ToastIcon;
  Action: typeof ToastAction;
  Close: typeof ToastClose;
};

export const Toast = Object.assign(ToastRoot, {
  Title: ToastTitle,
  Description: ToastDescription,
  Icon: ToastIcon,
  Action: ToastAction,
  Close: ToastClose,
}) as ToastCompoundComponent;

export type {
  ToastColor,
  ToastPlacement,
  ToastRootAnimation,
  ToastProps,
  ToastTitleProps,
  ToastDescriptionProps,
  ToastIconProps,
  ToastActionProps,
  ToastCloseProps,
  ToastContextValue,
  ToastRootRef,
  DefaultToastProps,
} from './types';
