import { forwardRef, useEffect, useState } from 'react';
import {
  Platform,
  type TextInput,
  type TextInputProps,
  type TextStyle,
  type ViewProps,
} from 'react-native';
import {
  InputField as InputFieldPrimitive,
  InputRoot as InputRootPrimitive,
  dataAttributes,
  type IInputFieldProps,
} from '@cdx-ui/primitives';
import { cn, composeEventHandlers } from '@cdx-ui/utils';
import {
  type TextareaVariantProps,
  textareaFieldPlaceholderVariants,
  textareaFieldVariants,
  textareaRootVariants,
} from './styles';

// Initial/minimum visible rows. Web textareas render compactly, so a smaller default
// reads better there; native has no intrinsic multiline height, so it needs more.
const DEFAULT_MIN_ROWS = Platform.select({ web: 2, default: 8 });

// Explicit per-size line height (px) set on the field so `rows * lineHeight` maps to an
// exact pixel height on native. Values track the field font sizes in `styles.ts`.
const LINE_HEIGHT_BY_SIZE = { default: 20, small: 16 } as const;

// Module-scoped latch so the dev conflict warning fires at most once per session.
let hasWarnedResizeAutoGrowConflict = false;

type ContentSizeChangeEvent = Parameters<NonNullable<TextInputProps['onContentSizeChange']>>[0];

export type TextareaResize = 'none' | 'vertical';

export interface TextareaProps
  extends
    Omit<TextInputProps, 'multiline' | 'children' | 'style'>,
    IInputFieldProps,
    TextareaVariantProps {
  className?: string;
  placeholderTextColorClassName?: string;
  /** Initial / minimum visible rows. Defaults to 2 on web and 8 on native. */
  numberOfLines?: number;
  /**
   * Grow the field height to fit its content. Defaults to `false`.
   * Web uses `field-sizing: content`; native grows intrinsically (`scrollEnabled`
   * off), using `onContentSizeChange` only to enable scrolling past `maxRows`.
   */
  autoGrow?: boolean;
  /** Caps `autoGrow` at this many rows, after which the field scrolls. */
  maxRows?: number;
  /**
   * Web-only user resize affordance. Defaults to `'none'`. `'vertical'` exposes the
   * browser resize handle and takes precedence over `autoGrow` (they are mutually
   * exclusive). No-op on native.
   */
  resize?: TextareaResize;
  style?: ViewProps['style'];
}

export const Textarea = forwardRef<TextInput, TextareaProps>(
  (
    {
      variant = 'outline',
      size = 'default',
      isDisabled,
      isInvalid,
      isReadOnly,
      isRequired,
      isFocused,
      isHovered,
      isFullWidth,
      className,
      placeholderTextColorClassName,
      style,
      numberOfLines,
      autoGrow = false,
      maxRows,
      resize = 'none',
      onContentSizeChange,
      ...rest
    },
    ref,
  ) => {
    const minRows = numberOfLines ?? DEFAULT_MIN_ROWS;
    const lineHeight = LINE_HEIGHT_BY_SIZE[size ?? 'default'];
    const minHeight = minRows * lineHeight;
    const maxHeight = maxRows != null ? maxRows * lineHeight : undefined;

    const isWeb = Platform.OS === 'web';
    // Web user-resize wins over auto-grow; the two are mutually exclusive.
    const userResizable = isWeb && resize === 'vertical';
    const effectiveAutoGrow = autoGrow && !userResizable;

    useEffect(() => {
      if (__DEV__ && autoGrow && resize === 'vertical' && !hasWarnedResizeAutoGrowConflict) {
        hasWarnedResizeAutoGrowConflict = true;
        console.warn(
          '[Textarea] `autoGrow` and `resize="vertical"` are mutually exclusive. ' +
            '`resize` takes precedence and `autoGrow` is ignored.',
        );
      }
    }, [autoGrow, resize]);

    // Native auto-grow: track the measured content height so we can enable internal
    // scrolling once it passes the `maxRows` cap. Growth itself is handled natively
    // (see `nativeScrollEnabled`), not by pinning a height here.
    const [measuredHeight, setMeasuredHeight] = useState<number | undefined>(undefined);

    const handleContentSizeChange = (event: ContentSizeChangeEvent) => {
      if (!isWeb && effectiveAutoGrow) {
        setMeasuredHeight(event.nativeEvent.contentSize.height);
      }
    };

    // On native, a multiline TextInput only grows to fit its content when scrolling is
    // disabled — an explicit height or an enabled scroll view pins it. So for auto-grow we
    // keep `scrollEnabled` off (letting the view size itself between min/max) and only turn
    // scrolling back on once the content exceeds the `maxRows` cap. Undefined elsewhere
    // (web + fixed height) leaves React Native's default (`true`) so overflow scrolls.
    const nativeScrollEnabled =
      !isWeb && effectiveAutoGrow
        ? maxHeight != null && measuredHeight != null && measuredHeight > maxHeight
        : undefined;

    // Auto-grow (web + native) and fixed sizing all bound the field with min/max height and
    // never pin an explicit `height`: web grows via `field-sizing: content`, native grows via
    // the disabled scroll view above, and the fixed case simply floors at `minHeight`.
    const heightStyle: TextStyle =
      !isWeb && !effectiveAutoGrow ? { minHeight } : { minHeight, maxHeight };

    // `textAlignVertical` is set in style (not just the prop) so it overrides the Android
    // primitive's `center` default — style wins over the deprecated prop.
    const fieldStyle: TextStyle = { lineHeight, textAlignVertical: 'top', ...heightStyle };

    // `className` applies to the root (the visible bordered box), matching the flat
    // CurrencyInput pattern. Field-level utilities below are internal sizing concerns.
    const fieldClassName = cn(
      textareaFieldVariants({ size }),
      effectiveAutoGrow && 'web:[field-sizing:content]',
      userResizable ? 'web:[resize:vertical]' : 'web:[resize:none]',
    );

    return (
      <InputRootPrimitive
        isDisabled={isDisabled}
        isInvalid={isInvalid}
        isReadOnly={isReadOnly}
        isRequired={isRequired}
        isFocused={isFocused}
        isHovered={isHovered}
        isFullWidth={isFullWidth}
        className={cn(textareaRootVariants({ variant, size }), className)}
        style={style}
        {...dataAttributes({ slot: 'textarea' })}
      >
        <InputFieldPrimitive
          ref={ref}
          placeholderTextColorClassName={cn(
            textareaFieldPlaceholderVariants(),
            placeholderTextColorClassName,
          )}
          {...rest}
          multiline
          numberOfLines={isWeb ? minRows : undefined}
          textAlignVertical="top"
          scrollEnabled={nativeScrollEnabled}
          className={fieldClassName}
          style={fieldStyle}
          onContentSizeChange={composeEventHandlers(onContentSizeChange, handleContentSizeChange)}
        />
      </InputRootPrimitive>
    );
  },
);

Textarea.displayName = 'Textarea';
