import { forwardRef, useMemo, type ReactNode } from 'react';
import { Text, type TextProps, View, type ViewProps } from 'react-native';
import { KeyboardArrowDown, KeyboardArrowUp } from '@cdx-ui/icons';
import {
  SelectContent as SelectContentPrimitive,
  SelectItem as SelectItemPrimitive,
  SelectRoot as SelectRootPrimitive,
  SelectTrigger as SelectTriggerPrimitive,
  Slot,
  dataAttributes,
  mergeDataAttributes,
  useSelectContext,
  useSelectItemContext,
  type ISelectContentProps,
  type ISelectItemLabelProps,
  type ISelectItemProps,
  type ISelectProps,
  type ISelectTriggerProps,
  type ISelectValueProps,
} from '@cdx-ui/primitives';
import { cn, ParentContext, useParentContext, useStyleContext } from '@cdx-ui/utils';
import { Icon, IconProps } from '../Icon';
import { positionToDataIcon, type IconSlotPosition } from '../../utils/positionToDataIcon';
import {
  type SelectVariantProps,
  selectContentVariants,
  selectIconVariants,
  selectItemLabelVariants,
  selectItemVariants,
  selectTriggerVariants,
  selectValueVariants,
} from './styles';

const SCOPE = 'SELECT';

const useSelectStyleContext = () => useStyleContext(SCOPE) as SelectVariantProps;

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

export interface SelectProps extends ViewProps, ISelectProps, SelectVariantProps {
  className?: string;
}

const SelectRoot = forwardRef<View, SelectProps>(
  ({ variant = 'outline', size = 'default', className, children, style, ...props }, ref) => {
    const parent = useParentContext();
    const ctx = useMemo(() => ({ ...parent, [SCOPE]: { variant, size } }), [parent, variant, size]);

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

SelectRoot.displayName = 'Select';

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

export interface SelectTriggerProps extends ISelectTriggerProps {
  /**
   * Substitute the rendered trigger element with the single child. The child must
   * be another Forge primitive (IconButton, Chip, Button) — anything whose
   * underlying Pressable accepts Pressable-shaped handlers. When set, the
   * auto-injected arrow indicator is skipped and `selectTriggerVariants` is not
   * applied so the substituted component's own appearance wins.
   *
   * On web, `<label htmlFor>` association is preserved via a `focus()` bridge
   * registered by the primitive.
   *
   * NOTE — `data-slot` overlap: the substituted component's own `data-slot`
   * (e.g. `button`, `icon-button`, `chip`) may win on the merged element.
   * Selectors that need guaranteed `select-trigger` access should wrap the
   * trigger instead of relying on `data-slot`.
   */
  asChild?: boolean;
  className?: string;
  children?: ReactNode;
}

const SelectTrigger = forwardRef<View, SelectTriggerProps>(
  ({ asChild, className, children, style, ...props }, ref) => {
    const { open } = useSelectContext();
    const { variant, size } = useSelectStyleContext();

    const computedClassName = asChild
      ? className
      : cn(selectTriggerVariants({ variant, size }), className);

    return (
      <SelectTriggerPrimitive
        ref={ref}
        asChild={asChild}
        className={computedClassName}
        style={style}
        {...props}
        {...mergeDataAttributes(props, dataAttributes({ slot: 'select-trigger' }))}
      >
        {asChild ? (
          children
        ) : (
          <>
            {children}
            <SelectIcon as={open ? KeyboardArrowUp : KeyboardArrowDown} />
          </>
        )}
      </SelectTriggerPrimitive>
    );
  },
);

SelectTrigger.displayName = 'Select.Trigger';

// =============================================================================
// STYLED VALUE (styled-only)
// =============================================================================

export interface SelectValueProps extends TextProps, ISelectValueProps {
  asChild?: boolean;
  className?: string;
}

const SelectValue = forwardRef<Text, SelectValueProps>(
  ({ asChild, className, style, placeholder, children, ...props }, ref) => {
    const { size } = useSelectStyleContext();
    const { value, items, invalid } = useSelectContext();

    const hasValue = value !== undefined && value !== '';
    const showPlaceholder = !hasValue;
    const displayText = hasValue ? items.find((item) => item.value === value)?.label : placeholder;

    const computedClassName = cn(selectValueVariants({ size }), className);
    const Comp = asChild ? Slot : Text;

    return (
      <Comp
        ref={ref}
        className={computedClassName}
        style={style}
        {...props}
        {...mergeDataAttributes(
          props,
          dataAttributes({
            slot: 'select-value',
            placeholder: showPlaceholder,
            invalid,
          }),
        )}
      >
        {children ?? displayText}
      </Comp>
    );
  },
);

SelectValue.displayName = 'Select.Value';

// =============================================================================
// STYLED ICON (styled-only, reads context for state=open / invalid)
// =============================================================================

export interface SelectIconProps extends Omit<IconProps, 'children' | 'as'> {
  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 SelectIcon = ({ asChild, className, style, as, position, ...props }: SelectIconProps) => {
  const { size } = useSelectStyleContext();
  const { invalid } = useSelectContext();

  const computedClassName = cn(selectIconVariants({ size }), className);

  const slotData = mergeDataAttributes(
    props,
    dataAttributes({ slot: 'select-icon', invalid }),
    positionToDataIcon(position),
  );

  if (asChild) {
    return <Slot className={computedClassName} style={style} {...props} {...slotData} />;
  }

  if (!as) {
    return null;
  }

  return <Icon as={as} className={computedClassName} style={style} {...props} {...slotData} />;
};

SelectIcon.displayName = 'Select.Icon';

// =============================================================================
// STYLED CONTENT
// =============================================================================

export interface SelectContentProps extends ViewProps, ISelectContentProps {
  asChild?: boolean;
  className?: string;
}

const SelectContent = forwardRef<View, SelectContentProps>(
  ({ asChild, className, style, ...props }, ref) => {
    const computedClassName = cn(selectContentVariants(), className);

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

SelectContent.displayName = 'Select.Content';

// =============================================================================
// STYLED ITEM
// =============================================================================

export interface SelectItemProps extends ISelectItemProps {
  asChild?: boolean;
  className?: string;
  children?: ReactNode;
}

const SelectItem = forwardRef<View, SelectItemProps>(
  ({ asChild, className, children, style, ...props }, ref) => {
    const { size } = useSelectStyleContext();

    const computedClassName = cn(selectItemVariants({ size }), className);

    return (
      <SelectItemPrimitive
        ref={ref}
        asChild={asChild}
        className={computedClassName}
        style={style}
        {...props}
        {...mergeDataAttributes(props, dataAttributes({ slot: 'select-item' }))}
      >
        {children}
      </SelectItemPrimitive>
    );
  },
);

SelectItem.displayName = 'Select.Item';

// =============================================================================
// STYLED ITEM LABEL (styled-only, reads item context for checked state)
// =============================================================================

export interface SelectItemLabelProps extends TextProps, ISelectItemLabelProps {
  asChild?: boolean;
  className?: string;
}

const SelectItemLabel = forwardRef<Text, SelectItemLabelProps>(
  ({ asChild, className, style, children, ...props }, ref) => {
    const { size } = useSelectStyleContext();
    const { checked } = useSelectItemContext();

    const computedClassName = cn(selectItemLabelVariants({ size }), className);
    const Comp = asChild ? Slot : Text;

    return (
      <Comp
        ref={ref}
        className={computedClassName}
        style={style}
        {...props}
        {...mergeDataAttributes(
          props,
          dataAttributes({
            slot: 'select-item-label',
            state: checked ? 'checked' : 'unchecked',
          }),
        )}
      >
        {children}
      </Comp>
    );
  },
);

SelectItemLabel.displayName = 'Select.ItemLabel';

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

type SelectCompoundComponent = typeof SelectRoot & {
  Trigger: typeof SelectTrigger;
  Value: typeof SelectValue;
  Icon: typeof SelectIcon;
  Content: typeof SelectContent;
  Item: typeof SelectItem;
  ItemLabel: typeof SelectItemLabel;
};

export const Select = Object.assign(SelectRoot, {
  Trigger: SelectTrigger,
  Value: SelectValue,
  Icon: SelectIcon,
  Content: SelectContent,
  Item: SelectItem,
  ItemLabel: SelectItemLabel,
}) as SelectCompoundComponent;
