import {
  Children,
  cloneElement,
  forwardRef,
  isValidElement,
  useId,
  useMemo,
  type ReactElement,
  type ReactNode,
} from 'react';
import { Platform, StyleSheet, Text, View, type ViewProps, type ViewStyle } from 'react-native';
import {
  SliderRoot as SliderRootPrimitive,
  SliderRail as SliderRailPrimitive,
  SliderThumb as SliderThumbPrimitive,
  Slot,
  Portal,
  createPortalFn,
  useAnchorPosition,
  dataAttributes,
  getMarkerPositionStyle,
  getMarkerStepValues,
  mergeDataAttributes,
  percentToFillExtent,
  useSliderContext,
  valueToPercent,
  type ISliderProps,
  type ISliderRailProps,
  type ISliderThumbProps,
  type SliderOrientation,
} from '@cdx-ui/primitives';
import { cn, ParentContext, useParentContext, useStyleContext } from '@cdx-ui/utils';
import {
  sliderMarkerVariants,
  sliderPopupBaseVariants,
  sliderRootVariants,
  sliderRailVariants,
  sliderRailColorVariants,
  sliderTrackColorVariants,
  sliderTrackVariants,
  sliderThumbVariants,
  type SliderColor,
  type SliderVariantProps,
} from './styles';
import { getSliderPopupAnchorStyle, SLIDER_POPUP_Z_INDEX } from './getSliderPopupPosition';

const SCOPE = 'SLIDER';

const useSliderStyleContext = () => {
  const { color, orientation } = useStyleContext(SCOPE) as Pick<
    SliderVariantProps,
    'color' | 'orientation'
  >;

  return {
    color,
    orientation: orientation ?? 'horizontal',
  };
};

export interface SliderProps extends Omit<ISliderProps, 'orientation'> {
  className?: string;
  /**
   * Semantic color token for the track fill and thumb border.
   * @default `action`
   */
  color?: SliderColor;
  orientation?: SliderOrientation;
  children?: ReactNode;
}

const SliderRoot = forwardRef<View, SliderProps>(
  (
    {
      className,
      style,
      color = 'action',
      orientation = 'horizontal',
      children,
      isDisabled,
      ...props
    },
    ref,
  ) => {
    const parent = useParentContext();
    const ctx = useMemo(
      () => ({
        ...parent,
        [SCOPE]: { color, orientation },
      }),
      [color, orientation, parent],
    );

    const computedClassName = cn(sliderRootVariants({ orientation }), className);

    return (
      <ParentContext.Provider value={ctx}>
        <SliderRootPrimitive
          ref={ref}
          className={computedClassName}
          style={style}
          orientation={orientation}
          isDisabled={isDisabled}
          {...props}
        >
          {children}
        </SliderRootPrimitive>
      </ParentContext.Provider>
    );
  },
);

SliderRoot.displayName = 'Slider';

export interface SliderRailProps extends ISliderRailProps {
  className?: string;
  children?: ReactNode;
}

const SliderRail = forwardRef<View, SliderRailProps>(
  ({ className, style, children, ...props }, ref) => {
    const { orientation, color } = useSliderStyleContext();

    const computedClassName = cn(
      sliderRailVariants({ orientation }),
      sliderRailColorVariants({ color }),
      className,
    );

    return (
      <SliderRailPrimitive ref={ref} className={computedClassName} style={style} {...props}>
        {children}
      </SliderRailPrimitive>
    );
  },
);

SliderRail.displayName = 'Slider.Rail';

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

const SliderTrack = forwardRef<View, SliderTrackProps>(
  ({ asChild, className, style, children, testID, ...props }, ref) => {
    const { color, orientation } = useSliderStyleContext();
    const { percent, isDragging, trackLayout, thumbSize } = useSliderContext();

    const fillStyle = useMemo((): ViewStyle => {
      const trackSize =
        trackLayout == null
          ? 0
          : orientation === 'horizontal'
            ? trackLayout.width
            : trackLayout.height;

      if (trackSize <= 0) {
        return orientation === 'vertical'
          ? { position: 'absolute', left: 0, right: 0, bottom: 0, height: 0 }
          : { position: 'absolute', top: 0, bottom: 0, left: 0, width: 0 };
      }

      const extent = percentToFillExtent(percent, trackSize, thumbSize);

      if (orientation === 'vertical') {
        return {
          position: 'absolute',
          left: 0,
          right: 0,
          bottom: 0,
          height: extent,
        };
      }

      return {
        position: 'absolute',
        top: 0,
        bottom: 0,
        left: 0,
        width: extent,
      };
    }, [orientation, percent, thumbSize, trackLayout]);

    const computedClassName = cn(
      sliderTrackVariants({ orientation }),
      sliderTrackColorVariants({ color }),
      className,
    );
    const Comp = asChild ? Slot : View;

    return (
      <Comp
        ref={ref}
        testID={testID}
        className={computedClassName}
        pointerEvents="none"
        style={[fillStyle, style]}
        accessibilityElementsHidden
        importantForAccessibility="no-hide-descendants"
        {...mergeDataAttributes(
          props,
          dataAttributes({ slot: 'slider-track', dragging: isDragging }),
        )}
      >
        {children}
      </Comp>
    );
  },
);

SliderTrack.displayName = 'Slider.Track';

export interface SliderThumbProps extends ISliderThumbProps {
  className?: string;
  children?: ReactNode;
}

const SliderThumb = forwardRef<View, SliderThumbProps>(
  ({ className, style, children, ...props }, ref) => {
    const { color } = useSliderStyleContext();

    const computedClassName = cn(sliderThumbVariants({ color }), className);

    return (
      <SliderThumbPrimitive ref={ref} className={computedClassName} style={style} {...props}>
        {children}
      </SliderThumbPrimitive>
    );
  },
);

SliderThumb.displayName = 'Slider.Thumb';

function dataOrientation(orientation: 'horizontal' | 'vertical' | null | undefined) {
  return { 'data-orientation': orientation ?? 'horizontal' } as const;
}

function renderPopupLabel(children: ReactNode, getValueLabel: () => string) {
  if (children == null || typeof children === 'string' || typeof children === 'number') {
    return (
      <Text className="text-content-primary text-xs font-medium select-none" selectable={false}>
        {children ?? getValueLabel()}
      </Text>
    );
  }

  return children;
}

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

const SliderPopup = forwardRef<View, SliderPopupProps>(
  ({ asChild, className, style, children, ...props }, ref) => {
    const { orientation } = useSliderStyleContext();
    const { isDragging, isHovered, isFocused, getValueLabel, thumbRef, percent } =
      useSliderContext();
    const portalName = useId();

    const visible = isDragging || isHovered || isFocused;
    const anchorLayout = useAnchorPosition(thumbRef, visible, {
      remeasureKey: percent,
      trackWhileOpen: true,
    });

    if (!visible || anchorLayout == null) {
      return null;
    }

    const label = renderPopupLabel(children, getValueLabel);
    const computedClassName = cn(sliderPopupBaseVariants(), className);
    const Comp = asChild ? Slot : View;
    const anchorStyle = getSliderPopupAnchorStyle(anchorLayout, orientation);

    const popup = (
      <View pointerEvents="none" style={anchorStyle}>
        <Comp
          ref={ref}
          className={computedClassName}
          style={style}
          accessibilityElementsHidden
          importantForAccessibility="no-hide-descendants"
          {...mergeDataAttributes(
            props,
            dataAttributes({ slot: 'slider-popup', visible: true }),
            dataOrientation(orientation),
          )}
        >
          {label}
        </Comp>
      </View>
    );

    if (Platform.OS === 'web' && createPortalFn && typeof document !== 'undefined') {
      return createPortalFn(popup, document.body);
    }

    return (
      <Portal name={portalName}>
        <View
          pointerEvents="box-none"
          style={[StyleSheet.absoluteFill, { zIndex: SLIDER_POPUP_Z_INDEX }]}
        >
          {popup}
        </View>
      </Portal>
    );
  },
);

SliderPopup.displayName = 'Slider.Popup';

export interface SliderMarkerProps extends ViewProps {
  asChild?: boolean;
  className?: string;
  value?: number;
  children?: ReactNode;
}

const SliderMarker = forwardRef<View, SliderMarkerProps>(
  ({ asChild, className, style, value, testID, ...props }, ref) => {
    const {
      value: currentValue,
      min,
      max,
      orientation,
      trackLayout,
      thumbSize,
    } = useSliderContext();

    const markerStyle = useMemo(() => {
      if (value == null) {
        return undefined;
      }
      const percent = valueToPercent(value, min, max);
      return getMarkerPositionStyle(percent, orientation, trackLayout, thumbSize);
    }, [max, min, orientation, thumbSize, trackLayout, value]);

    const isFilled = value != null && value <= currentValue;
    const computedClassName = cn(sliderMarkerVariants({ filled: isFilled }), className);
    const Comp = asChild ? Slot : View;

    return (
      <Comp
        ref={ref}
        testID={testID}
        className={computedClassName}
        style={[markerStyle, style]}
        accessibilityElementsHidden
        importantForAccessibility="no-hide-descendants"
        {...mergeDataAttributes(
          props,
          dataAttributes({
            slot: 'slider-marker',
            value: value == null ? undefined : String(value),
            filled: isFilled,
          }),
        )}
      />
    );
  },
);

SliderMarker.displayName = 'Slider.Marker';

function isSliderMarkerElement(child: unknown): child is ReactElement<SliderMarkerProps> {
  if (!isValidElement(child)) {
    return false;
  }
  const type = child.type as { displayName?: string };
  return type.displayName === 'Slider.Marker';
}

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

const SliderMarkerGroup = forwardRef<View, SliderMarkerGroupProps>(
  ({ asChild, className, style, children, testID, ...props }, ref) => {
    const { min, max, step } = useSliderContext();

    const markers = useMemo(() => {
      if (step <= 0 || max <= min) {
        return null;
      }

      const childList = Children.toArray(children);
      if (childList.length === 0) {
        return null;
      }

      const templateMarker = childList.find(
        (child): child is ReactElement<SliderMarkerProps> =>
          isSliderMarkerElement(child) && child.props.value == null,
      );

      if (templateMarker) {
        return getMarkerStepValues(min, max, step).map((markerValue) =>
          cloneElement(templateMarker, { key: markerValue, value: markerValue }),
        );
      }

      return childList;
    }, [children, max, min, step]);

    if (!markers) {
      return null;
    }

    const Comp = asChild ? Slot : View;

    return (
      <Comp
        ref={ref}
        testID={testID}
        className={cn('absolute inset-0 z-[1]', className)}
        style={style}
        pointerEvents="none"
        accessibilityElementsHidden
        importantForAccessibility="no-hide-descendants"
        {...mergeDataAttributes(props, dataAttributes({ slot: 'slider-marker-group' }))}
      >
        {markers}
      </Comp>
    );
  },
);

SliderMarkerGroup.displayName = 'Slider.MarkerGroup';

type SliderCompoundComponent = typeof SliderRoot & {
  Rail: typeof SliderRail;
  Track: typeof SliderTrack;
  Thumb: typeof SliderThumb;
  Popup: typeof SliderPopup;
  MarkerGroup: typeof SliderMarkerGroup;
  Marker: typeof SliderMarker;
};

export const Slider = Object.assign(SliderRoot, {
  Rail: SliderRail,
  Track: SliderTrack,
  Thumb: SliderThumb,
  Popup: SliderPopup,
  MarkerGroup: SliderMarkerGroup,
  Marker: SliderMarker,
}) as SliderCompoundComponent;

export type { SliderColor, SliderVariantProps };
