import {
  forwardRef,
  useEffect,
  useLayoutEffect,
  useMemo,
  useRef,
  useState,
  type ReactNode,
} from 'react';
import {
  KeyboardAvoidingView,
  Platform,
  StyleSheet,
  Text,
  View,
  type TextProps,
  type ViewProps,
} from 'react-native';
import { GestureDetector } from 'react-native-gesture-handler';
import Animated, { useSharedValue } from 'react-native-reanimated';
import { CheckCircle, Close as CloseIcon, EmergencyHome, Warning } from '@cdx-ui/icons';
import {
  DialogClose as DialogClosePrimitive,
  DialogContent as DialogContentPrimitive,
  DialogOverlay as DialogOverlayPrimitive,
  DialogPortal as DialogPortalPrimitive,
  DialogRoot as DialogRootPrimitive,
  DialogTrigger as DialogTriggerPrimitive,
  FullWindowOverlay,
  Slot,
  dataAttributes,
  mergeDataAttributes,
  useDialog,
  type IDialogCloseProps,
  type IDialogContentProps,
  type IDialogRootProps,
  type IDialogTriggerProps,
} from '@cdx-ui/primitives';
import { cn, mergeRefs, ParentContext, useParentContext, useStyleContext } from '@cdx-ui/utils';
import {
  usePopupDialogContentAnimation,
  usePopupOverlayAnimation,
  usePopupRootAnimation,
  type AnimationRootDisableAll,
} from '../../animation';
import {
  AnimationSettingsProvider,
  useAnimationSettings,
} from '../../providers/animation-settings';
import { Icon } from '../Icon';
import { DialogAnimationProvider, useDialogAnimation } from './animation';
import {
  type DialogTitleVariantProps,
  type DialogVariantProps,
  dialogCloseIconVariants,
  dialogCloseVariants,
  dialogContentVariants,
  dialogDescriptionVariants,
  dialogFooterVariants,
  dialogHeaderVariants,
  dialogOverlayVariants,
  dialogPopupVariants,
  dialogPortalVariants,
  dialogTextVariants,
  dialogTitleIconVariants,
  dialogTitleVariants,
} from './styles';

export { useDialog } from '@cdx-ui/primitives';

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

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

// =============================================================================
// STYLE CONTEXT
// =============================================================================

const SCOPE = 'DIALOG';

const useDialogStyleContext = () => useStyleContext(SCOPE) as DialogVariantProps;

// =============================================================================
// ANIMATED PRIMITIVES
// =============================================================================

const AnimatedOverlay = Animated.createAnimatedComponent(DialogOverlayPrimitive);

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

export interface DialogRootProps
  extends Omit<ViewProps, 'children' | 'role'>, IDialogRootProps, DialogVariantProps {
  className?: string;
  animation?: AnimationRootDisableAll;
}

const DialogRoot = forwardRef<View, DialogRootProps>(
  ({ size = 'md', fullBleed = false, animation, className, children, style, ...props }, ref) => {
    const parent = useParentContext();
    const ctx = useMemo(
      () => ({ ...parent, [SCOPE]: { size, fullBleed } }),
      [parent, size, fullBleed],
    );

    const { progress, isDragging, isGestureReleaseAnimationRunning, isAllAnimationsDisabled } =
      usePopupRootAnimation({ animation });

    const animationContextValue = useMemo(
      () => ({ progress, isDragging, isGestureReleaseAnimationRunning }),
      [progress, isDragging, isGestureReleaseAnimationRunning],
    );

    const animationSettingsContextValue = useMemo(
      () => ({ isAllAnimationsDisabled }),
      [isAllAnimationsDisabled],
    );

    return (
      <AnimationSettingsProvider value={animationSettingsContextValue}>
        <DialogAnimationProvider value={animationContextValue}>
          <ParentContext.Provider value={ctx}>
            <DialogRootPrimitive
              ref={ref}
              className={className}
              style={style}
              {...props}
              {...mergeDataAttributes(props, dataAttributes({ slot: 'dialog' }))}
            >
              {children}
            </DialogRootPrimitive>
          </ParentContext.Provider>
        </DialogAnimationProvider>
      </AnimationSettingsProvider>
    );
  },
);

DialogRoot.displayName = 'Dialog';

// =============================================================================
// STYLED TRIGGER
// =============================================================================

export interface DialogTriggerProps extends IDialogTriggerProps {
  className?: string;
  children?: ReactNode;
}

const DialogTrigger = forwardRef<View, DialogTriggerProps>(
  ({ className, children, style, ...props }, ref) => (
    <DialogTriggerPrimitive
      ref={ref}
      className={className}
      style={style}
      {...props}
      {...mergeDataAttributes(props, dataAttributes({ slot: 'dialog-trigger' }))}
    >
      {children}
    </DialogTriggerPrimitive>
  ),
);

DialogTrigger.displayName = 'Dialog.Trigger';

// =============================================================================
// STYLED POPUP (portal + overlay + content composition)
// =============================================================================

export interface DialogPopupProps
  extends Omit<IDialogContentProps, 'asChild'>, Pick<DialogVariantProps, 'size'> {
  className?: string;
  children?: ReactNode;
  disableFullWindowOverlay?: boolean;
  isSwipeable?: boolean;
}

const DialogPopup = forwardRef<View, DialogPopupProps>(
  (
    {
      size: sizeProp,
      className,
      children,
      style,
      forceMount = false,
      avoidKeyboard = true,
      keyboardVerticalOffset = 0,
      disableFullWindowOverlay = false,
      isSwipeable = true,
      ...props
    },
    ref,
  ) => {
    const { size: contextSize } = useDialogStyleContext();
    const size = sizeProp ?? contextSize;
    const { open, onOpenChange } = useDialog();
    const parentContext = useParentContext();

    const animationContext = useDialogAnimation();
    const parentAnimationSettings = useAnimationSettings();

    const fallbackProgress = useSharedValue(0);
    const fallbackIsDragging = useSharedValue(false);
    const fallbackIsGestureReleaseAnimationRunning = useSharedValue(false);

    const progress = animationContext?.progress ?? fallbackProgress;
    const isDragging = animationContext?.isDragging ?? fallbackIsDragging;
    const isGestureReleaseAnimationRunning =
      animationContext?.isGestureReleaseAnimationRunning ??
      fallbackIsGestureReleaseAnimationRunning;

    const {
      rContainerStyle,
      entering: overlayEntering,
      exiting: overlayExiting,
    } = usePopupOverlayAnimation({
      progress,
      isDragging,
      isGestureReleaseAnimationRunning,
    });

    const {
      contentY,
      contentHeight,
      panGesture,
      rDragContainerStyle,
      entering: contentEntering,
      exiting: contentExiting,
    } = usePopupDialogContentAnimation({
      isOpen: open,
      progress,
      isDragging,
      isGestureReleaseAnimationRunning,
      onOpenChange,
      isSwipeable,
    });

    const dragContainerRef = useRef<View>(null);

    useLayoutEffect(() => {
      if (!open || IS_WEB) return;
      dragContainerRef.current?.measure((_x, _y, _width, height, _pageX, pageY) => {
        contentY.set(pageY);
        contentHeight.set(height);
      });
    }, [open, contentY, contentHeight]);

    // ---- Web enter/exit lifecycle ----
    // Mirrors the Toast pattern: mount → 'entering' → (rAF) → 'open' → (close)
    // → 'closed' → (transitionend) → unmount.
    //
    // Critical: `isExiting` is derived synchronously (via ref tracking the previous
    // `open` value) so the component stays mounted in the SAME render that sees
    // `open` go false — if we used useEffect the component would return null before
    // the effect fires.
    //
    // The transitionend listener targets the popup element (via popupRef) instead of
    // the portal wrapper. CSS exit transitions live on the overlay and popup children,
    // not the wrapper — listening on the wrapper would unmount on the first bubbled
    // event, potentially cutting the other child's animation short.
    const wrapperRef = useRef<View | null>(null);
    const popupRef = useRef<View | null>(null);
    const prevOpenRef = useRef(open);
    const [isExiting, setIsExiting] = useState(false);
    const [webState, setWebState] = useState<DialogDataState>(open ? 'entering' : 'closed');

    // Detect open → false transition synchronously during render
    if (IS_WEB && prevOpenRef.current && !open && !isExiting) {
      setIsExiting(true);
      setWebState('closed');
    }
    // Detect closed → open transition synchronously during render
    if (IS_WEB && !prevOpenRef.current && open) {
      setIsExiting(false);
      setWebState('entering');
    }
    prevOpenRef.current = open;

    // Entrance: commit 'entering' on first paint, then transition to 'open'
    useEffect(() => {
      if (!IS_WEB || !open) return;
      const raf = requestAnimationFrame(() => {
        setWebState('open');
      });
      return () => cancelAnimationFrame(raf);
    }, [open]);

    // Exit: wait for CSS transition to finish, then unmount.
    // Listens on the popup element directly — its exit transitions (opacity +
    // transform) are at least as long as the overlay's, so when the popup
    // finishes both children are done animating.
    useEffect(() => {
      if (!IS_WEB || !isExiting) return;

      const node = popupRef.current as unknown as HTMLElement | null;
      let done = false;

      const finish = () => {
        if (done) return;
        done = true;
        setIsExiting(false);
      };

      const safetyTimer = setTimeout(finish, WEB_TRANSITION_SAFETY_MS);

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

      const handler = (event: Event) => {
        if (event.target !== node) return;
        const propertyName = (event as unknown as { propertyName?: string }).propertyName ?? '';
        if (propertyName === 'opacity' || propertyName === 'transform') {
          finish();
        }
      };
      node.addEventListener('transitionend', handler);

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

    const dataState: DialogDataState = webState;
    const shouldMount = IS_WEB ? open || isExiting : open;

    if (!forceMount && !shouldMount) {
      return null;
    }

    const portalClassName = cn(dialogPortalVariants());
    const overlayClassName = cn(dialogOverlayVariants());
    const popupClassName = cn(dialogPopupVariants({ size }), className);

    const keyboardBehavior = avoidKeyboard
      ? Platform.select<'padding' | 'height'>({ ios: 'padding', default: 'height' })
      : undefined;

    return (
      <DialogPortalPrimitive forceMount>
        <ParentContext.Provider value={parentContext}>
          <AnimationSettingsProvider value={parentAnimationSettings}>
            <DialogAnimationProvider value={animationContext}>
              <FullWindowOverlay
                disableFullWindowOverlay={disableFullWindowOverlay}
                unstable_accessibilityContainerViewIsModal
              >
                <Animated.View
                  ref={wrapperRef}
                  className={portalClassName}
                  pointerEvents="box-none"
                  {...(IS_WEB ? dataAttributes({ state: dataState }) : undefined)}
                >
                  {/* Overlay */}
                  <Animated.View
                    entering={overlayEntering}
                    exiting={overlayExiting}
                    style={StyleSheet.absoluteFill}
                  >
                    <AnimatedOverlay
                      forceMount
                      className={overlayClassName}
                      style={IS_WEB ? undefined : rContainerStyle}
                      {...(IS_WEB ? dataAttributes({ state: dataState }) : undefined)}
                    />
                  </Animated.View>

                  {/* Content with gesture + animation */}
                  <GestureDetector gesture={panGesture}>
                    <Animated.View
                      ref={dragContainerRef}
                      style={internalStyles.fill}
                      pointerEvents="box-none"
                      entering={contentEntering}
                      exiting={contentExiting}
                    >
                      <Animated.View
                        style={
                          IS_WEB ? internalStyles.fill : [internalStyles.fill, rDragContainerStyle]
                        }
                        pointerEvents="box-none"
                      >
                        <KeyboardAvoidingView
                          style={internalStyles.center}
                          pointerEvents="box-none"
                          behavior={keyboardBehavior}
                          keyboardVerticalOffset={keyboardVerticalOffset}
                        >
                          <DialogContentPrimitive
                            ref={mergeRefs(ref, popupRef)}
                            forceMount
                            className={popupClassName}
                            style={style}
                            {...props}
                            {...mergeDataAttributes(
                              props,
                              dataAttributes({ slot: 'dialog-popup' }),
                              IS_WEB ? dataAttributes({ state: dataState }) : undefined,
                            )}
                          >
                            {children}
                          </DialogContentPrimitive>
                        </KeyboardAvoidingView>
                      </Animated.View>
                    </Animated.View>
                  </GestureDetector>
                </Animated.View>
              </FullWindowOverlay>
            </DialogAnimationProvider>
          </AnimationSettingsProvider>
        </ParentContext.Provider>
      </DialogPortalPrimitive>
    );
  },
);

DialogPopup.displayName = 'Dialog.Popup';

// =============================================================================
// STYLED HEADER (styled-only sub-slot)
// =============================================================================

export interface DialogHeaderProps extends ViewProps {
  asChild?: boolean;
  className?: string;
  children?: ReactNode;
}

const DialogHeader = forwardRef<View, DialogHeaderProps>(
  ({ asChild, className, children, style, ...props }, ref) => {
    const computedClassName = cn(dialogHeaderVariants(), className);
    const Comp = asChild ? Slot : View;

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

DialogHeader.displayName = 'Dialog.Header';

// =============================================================================
// STYLED TITLE (styled-only sub-slot with severity variants)
// =============================================================================

export type DialogTitleSeverity = 'neutral' | 'danger' | 'warning' | 'success';

/** @deprecated Use `severity` prop instead of `appearance`. */
export type DialogTitleAppearance = 'default' | 'danger' | 'warning' | 'success';

let hasWarnedDialogAppearanceProp = false;
let hasWarnedDialogDefaultSeverity = false;

const SEVERITY_ICON_MAP = {
  danger: EmergencyHome,
  warning: Warning,
  success: CheckCircle,
} as const;

export interface DialogTitleProps extends TextProps, Omit<DialogTitleVariantProps, 'severity'> {
  asChild?: boolean;
  /**
   * Severity level that controls the status icon and color treatment.
   * @default 'neutral'
   */
  severity?:
    | DialogTitleSeverity
    /** @deprecated Use `'neutral'` instead. */
    | 'default';
  /**
   * @deprecated Use `severity` prop instead.
   */
  appearance?: DialogTitleAppearance;
  className?: string;
  children?: ReactNode;
}

const DialogTitle = forwardRef<Text, DialogTitleProps>(
  ({ asChild, severity: severityProp, appearance, className, children, style, ...props }, ref) => {
    useEffect(() => {
      if (__DEV__ && appearance !== undefined && !hasWarnedDialogAppearanceProp) {
        hasWarnedDialogAppearanceProp = true;
        console.warn(
          '[Dialog.Title] The `appearance` prop is deprecated and will be removed in a future major release. Use `severity` instead.',
        );
      }
    }, [appearance]);

    const rawSeverity = severityProp ?? appearance ?? 'neutral';

    useEffect(() => {
      if (__DEV__ && rawSeverity === 'default' && !hasWarnedDialogDefaultSeverity) {
        hasWarnedDialogDefaultSeverity = true;
        console.warn(
          '[Dialog.Title] The `default` severity value is deprecated and will be removed in a future major release. Use `neutral` instead.',
        );
      }
    }, [rawSeverity]);

    const severity: DialogTitleSeverity = rawSeverity === 'default' ? 'neutral' : rawSeverity;

    const computedClassName = cn(dialogTitleVariants({ severity }), className);
    const IconComponent =
      severity && severity in SEVERITY_ICON_MAP
        ? SEVERITY_ICON_MAP[severity as keyof typeof SEVERITY_ICON_MAP]
        : null;
    const Comp = asChild ? Slot : Text;

    const titleElement = (
      <Comp
        ref={ref}
        className={computedClassName}
        style={style}
        {...props}
        {...mergeDataAttributes(props, dataAttributes({ slot: 'dialog-title' }))}
      >
        {children}
      </Comp>
    );

    if (IconComponent) {
      return (
        <View className="flex-row items-center gap-1">
          <Icon as={IconComponent} className={dialogTitleIconVariants({ severity })} />
          {titleElement}
        </View>
      );
    }

    return titleElement;
  },
);

DialogTitle.displayName = 'Dialog.Title';

// =============================================================================
// STYLED DESCRIPTION (styled-only sub-slot — reads dialog context for nativeID)
// =============================================================================

export interface DialogDescriptionProps extends TextProps {
  asChild?: boolean;
  className?: string;
  children?: ReactNode;
}

const DialogDescription = forwardRef<Text, DialogDescriptionProps>(
  ({ asChild, className, children, style, ...props }, ref) => {
    const { nativeID } = useDialog();
    const computedClassName = cn(dialogDescriptionVariants(), className);
    const Comp = asChild ? Slot : Text;

    return (
      <Comp
        ref={ref}
        nativeID={`${nativeID}-description`}
        className={computedClassName}
        style={style}
        {...props}
        {...mergeDataAttributes(props, dataAttributes({ slot: 'dialog-description' }))}
      >
        {children}
      </Comp>
    );
  },
);

DialogDescription.displayName = 'Dialog.Description';

// =============================================================================
// STYLED CONTENT (styled-only sub-slot — reads variant context for fullBleed)
// =============================================================================

export interface DialogContentProps extends ViewProps {
  asChild?: boolean;
  className?: string;
  children?: ReactNode;
}

const DialogContent = forwardRef<View, DialogContentProps>(
  ({ asChild, className, children, style, ...props }, ref) => {
    const { fullBleed } = useDialogStyleContext();
    const computedClassName = cn(
      dialogContentVariants({ fullBleed: fullBleed ?? false }),
      className,
    );
    const Comp = asChild ? Slot : View;

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

DialogContent.displayName = 'Dialog.Content';

// =============================================================================
// STYLED TEXT (convenience text styling, styled-only)
// =============================================================================

export interface DialogTextProps extends TextProps {
  asChild?: boolean;
  className?: string;
  children?: ReactNode;
}

const DialogText = forwardRef<Text, DialogTextProps>(
  ({ asChild, className, children, style, ...props }, ref) => {
    const computedClassName = cn(dialogTextVariants(), className);
    const Comp = asChild ? Slot : Text;

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

DialogText.displayName = 'Dialog.Text';

// =============================================================================
// STYLED FOOTER (styled-only sub-slot)
// =============================================================================

export interface DialogFooterProps extends ViewProps {
  asChild?: boolean;
  className?: string;
  children?: ReactNode;
}

const DialogFooter = forwardRef<View, DialogFooterProps>(
  ({ asChild, className, children, style, ...props }, ref) => {
    const computedClassName = cn(dialogFooterVariants(), className);
    const Comp = asChild ? Slot : View;

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

DialogFooter.displayName = 'Dialog.Footer';

// =============================================================================
// STYLED CLOSE
// =============================================================================

export interface DialogCloseProps extends IDialogCloseProps {
  className?: string;
  children?: ReactNode;
}

const DialogClose = forwardRef<View, DialogCloseProps>(
  ({ className, accessibilityLabel = 'Close', children, style, ...props }, ref) => {
    const computedClassName = cn(dialogCloseVariants(), className);

    return (
      <DialogClosePrimitive
        ref={ref}
        className={computedClassName}
        style={style}
        hitSlop={12}
        accessibilityRole="button"
        accessibilityLabel={accessibilityLabel}
        {...props}
        {...mergeDataAttributes(props, dataAttributes({ slot: 'dialog-close' }))}
      >
        {children ?? <Icon as={CloseIcon} className={dialogCloseIconVariants()} />}
      </DialogClosePrimitive>
    );
  },
);

DialogClose.displayName = 'Dialog.Close';

// =============================================================================
// COMPOUND COMPONENT
// =============================================================================

type DialogCompoundComponent = typeof DialogRoot & {
  Trigger: typeof DialogTrigger;
  Popup: typeof DialogPopup;
  Header: typeof DialogHeader;
  Title: typeof DialogTitle;
  Description: typeof DialogDescription;
  Content: typeof DialogContent;
  Text: typeof DialogText;
  Footer: typeof DialogFooter;
  Close: typeof DialogClose;
};

export const Dialog = Object.assign(DialogRoot, {
  Trigger: DialogTrigger,
  Popup: DialogPopup,
  Header: DialogHeader,
  Title: DialogTitle,
  Description: DialogDescription,
  Content: DialogContent,
  Text: DialogText,
  Footer: DialogFooter,
  Close: DialogClose,
}) as DialogCompoundComponent;

// =============================================================================
// INTERNAL STYLES
// =============================================================================

const internalStyles = StyleSheet.create({
  fill: {
    flex: 1,
  },
  center: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'center',
  },
});
