import React, {forwardRef, useImperativeHandle, useRef} from 'react';
import {ImageStyle, StyleProp, ViewStyle} from 'react-native';
import {ImageProps as ExpoImageProps} from 'expo-image';
import {Image, ImageHandle} from '@components';
import {getChatImage} from '@modules/chats/utils';

export interface ProgressiveImageHandle {
  /** Force ExpoImage to re-fetch the current source (bypasses cache). */
  reload: () => Promise<void>;
}

type Props = Pick<
  ExpoImageProps,
  'contentFit' | 'onLoad' | 'onError' | 'recyclingKey'
> & {
  /** Source URL (server URL — sized variants are derived from this). */
  url?: string | null;
  /** Always-fetched placeholder variant width in px. */
  smallWidth: number;
  /**
   * Larger variant width in px, or `'original'` to fetch the unsized
   * (full original) file. Omit to render the small only.
   */
  largeWidth?: number | 'original';
  enabled?: boolean;
  /** Applied to the inner ExpoImage — carries dimensions + borderRadius. */
  style?: StyleProp<ImageStyle>;
  /**
   * Applied to the outer Pressable that wraps the ExpoImage. Use this
   * for `borderRadius` + `overflow: 'hidden'` clipping — ExpoImage's
   * own borderRadius doesn't reliably clip the image when
   * `contentFit="contain"` letterboxes the source.
   */
  containerStyle?: StyleProp<ViewStyle>;
  onPress?: () => void;
  /** Use react-native-gesture-handler Pressable (for nested gesture trees). */
  useRNGHPressable?: boolean;
};

/**
 * Two-tier image renderer that delegates the small → large swap to
 * expo-image's native `placeholder` prop. ExpoImage paints the placeholder
 * (small variant) immediately, fetches the source (large variant) in
 * parallel, then swaps in one frame when the source is decoded.
 *
 * Wraps our shared `<Image>` so we inherit auth-header injection, cacheKey
 * alignment, the auto-retry funnel (3 silent `reloadAsync` attempts before
 * `onError` propagates), and the `reload()` imperative handle.
 */
export const ProgressiveImage = forwardRef<ProgressiveImageHandle, Props>(
  function ProgressiveImage(
    {
      url,
      smallWidth,
      largeWidth,
      enabled,
      style,
      containerStyle,
      contentFit,
      onPress,
      useRNGHPressable,
      onLoad,
      onError,
      recyclingKey,
    },
    ref,
  ) {
    const {smallUrl, largeUrl} = getChatImage.progressive({
      url,
      smallWidth,
      largeWidth,
      enabled,
    });

    // When largeUrl is provided, it's the source and small is the placeholder.
    // When omitted, small is the source itself (no upgrade pass).
    const sourceUri = largeUrl || smallUrl;
    const placeholderUri = largeUrl ? smallUrl : undefined;

    const imageRef = useRef<ImageHandle>(null);
    useImperativeHandle(
      ref,
      () => ({
        reload: async () => {
          await imageRef.current?.reload();
        },
      }),
      [],
    );

    return (
      <Image
        ref={imageRef}
        source={{uri: sourceUri ?? ''}}
        placeholder={placeholderUri ? {uri: placeholderUri} : undefined}
        withAuthHeader
        enableCache
        allowDownscaling={false}
        contentFit={contentFit}
        style={style}
        containerStyle={containerStyle}
        onPress={onPress}
        useRNGHPressable={useRNGHPressable}
        onLoad={onLoad}
        onError={onError}
        // 15 s ceiling — covers slow networks for the bigger
        // (gallery / poster) variants that go through this wrapper.
        // If the fetch hasn't completed by then, falls into the
        // wrapper's auto-retry funnel (up to AUTO_RETRY_LIMIT silent
        // `reloadAsync` attempts).
        timeout={15_000}
        recyclingKey={recyclingKey}
      />
    );
  },
);
