import {
  forwardRef,
  useCallback,
  useRef,
  type ReactElement,
  type ReactNode,
  type Ref,
  type RefObject,
} from 'react';
import type { AccessibilityRole, AccessibilityState, ViewStyle } from 'react-native';
import { useSharedValue } from 'react-native-reanimated';
import type { SharedValue } from 'react-native-reanimated';
import RNCarousel, {
  Pagination as RNCarouselPagination,
  type ICarouselInstance,
  type TCarouselProps,
} from 'react-native-reanimated-carousel';
import { useResolveClassNames } from 'uniwind';
import { cn, createContext, mergeRefs } from '@cdx-ui/utils';
import { resolveCarouselPaginationScrollAction } from './paginationNavigation';
import { carouselPaginationContainerClassName, carouselPaginationDotVariants } from './styles';

export type CarouselRef = ICarouselInstance;

/**
 * Full passthrough of `react-native-reanimated-carousel`'s own props (per `01-api-design.md`,
 * Resolved Question #1) — re-exported under a Forge-owned name so consumers never import the
 * adopted library directly. A type alias (not `interface extends`) because `TCarouselProps<T>`
 * is itself a union-containing type (vertical/mode discriminants), which interfaces cannot extend.
 */
export type CarouselProps<T> = TCarouselProps<T> & {
  className?: string;
  /** Rendered inside the carousel's progress-sharing provider — use to compose `Carousel.Pagination`. */
  children?: ReactNode;
};

/** Mirrors the adopted library's private `DotStyle` shape (not exported) without importing it. */
type CarouselDotStyle = Omit<ViewStyle, 'width' | 'height' | 'backgroundColor' | 'borderRadius'> & {
  width?: number;
  height?: number;
  backgroundColor?: string;
  borderRadius?: number;
};

interface CarouselContextValue {
  progress: SharedValue<number>;
  carouselRef: RefObject<ICarouselInstance | null>;
  loop: boolean;
}

const [CarouselProgressProvider, useCarouselProgressContext] = createContext<CarouselContextValue>(
  'CarouselProgress',
  { strict: false },
);

function isSharedValueProgress(value: unknown): value is SharedValue<number> {
  return typeof value === 'object' && value !== null && 'value' in value;
}

function CarouselInner<T>(
  {
    className,
    style,
    onProgressChange,
    children,
    loop = true,
    defaultIndex = 0,
    ...rest
  }: CarouselProps<T>,
  ref: Ref<ICarouselInstance>,
) {
  const internalRef = useRef<ICarouselInstance>(null);
  const internalProgress = useSharedValue(defaultIndex);
  const resolvedClassNameStyle = useResolveClassNames(cn(className));

  const handleProgressChange = useCallback(
    (offsetProgress: number, absoluteProgress: number) => {
      internalProgress.value = absoluteProgress;
      if (typeof onProgressChange === 'function') {
        onProgressChange(offsetProgress, absoluteProgress);
      }
    },
    [onProgressChange, internalProgress],
  );

  const consumerProgress = isSharedValueProgress(onProgressChange) ? onProgressChange : undefined;
  const contextValue: CarouselContextValue = {
    progress: consumerProgress ?? internalProgress,
    carouselRef: internalRef,
    loop,
  };

  return (
    <CarouselProgressProvider value={contextValue}>
      <RNCarousel
        ref={mergeRefs(ref, internalRef)}
        style={[resolvedClassNameStyle, style]}
        onProgressChange={consumerProgress ?? handleProgressChange}
        loop={loop}
        defaultIndex={defaultIndex}
        {...rest}
      />
      {children}
    </CarouselProgressProvider>
  );
}

const CarouselRoot = forwardRef(CarouselInner) as <T>(
  props: CarouselProps<T> & { ref?: Ref<ICarouselInstance> },
) => ReactElement | null;

// =============================================================================
// PAGINATION
// =============================================================================

export interface CarouselPaginationItemAccessibilityOverrides {
  accessibilityLabel?: string;
  accessibilityHint?: string;
  accessibilityRole?: AccessibilityRole;
  accessibilityState?: AccessibilityState;
}

export interface CarouselPaginationProps<T = unknown> {
  /** Carousel items — must be the same array (or same length) passed to `Carousel`'s `data`. */
  data: T[];
  /** Lay dots out vertically instead of horizontally. @default true (horizontal) */
  horizontal?: boolean;
  /** Custom content per dot; defaults to the library's built-in dot rendering. */
  renderItem?: (item: T, index: number) => ReactNode;
  /** Dot size in pixels (both width and height). */
  size?: number;
  /** Included in the default accessibility label, e.g. `"Slide 1 of 4 - {carouselName}"`. */
  carouselName?: string;
  /** Per-dot accessibility overrides. Defaults to `"Slide N of M"` / `"Go to Slide N of M"`. */
  paginationItemAccessibility?: (
    index: number,
    length: number,
  ) => CarouselPaginationItemAccessibilityOverrides;
  className?: string;
  /** Explicit progress value — only required when rendered outside `<Carousel>` (sibling composition). */
  progress?: SharedValue<number>;
  /** Explicit carousel ref for the default press-to-navigate behavior — only required outside `<Carousel>`. */
  carouselRef?: RefObject<ICarouselInstance | null>;
  /**
   * Mirrors `Carousel`'s `loop` prop for shortest-path dot navigation.
   * Defaults to `true` (same as `Carousel`) when omitted outside context.
   */
  loop?: boolean;
  /** Overrides the default press-to-navigate behavior (`scrollTo({ index })`). */
  onPress?: (index: number) => void;
}

function CarouselPaginationComponent<T extends object>({
  className,
  progress: progressProp,
  carouselRef: carouselRefProp,
  loop: loopProp,
  onPress,
  ...rest
}: CarouselPaginationProps<T>) {
  // `strict: false` with no `defaultValue` means this returns `undefined` outside a
  // `<Carousel>` provider at runtime; cast to communicate that to TypeScript, since
  // `createContext`'s helper type always reports `T` (see `@cdx-ui/utils`).
  const context = useCarouselProgressContext() as CarouselContextValue | undefined;
  const progress = progressProp ?? context?.progress;
  const carouselRef = carouselRefProp ?? context?.carouselRef;
  const loop = loopProp ?? context?.loop ?? true;

  if (!progress) {
    throw new Error(
      'Carousel.Pagination must be rendered inside <Carousel> or receive an explicit `progress` prop.',
    );
  }

  if (!onPress && !carouselRef) {
    throw new Error(
      'Carousel.Pagination must be rendered inside <Carousel> or receive an explicit `carouselRef` prop (or a custom `onPress`).',
    );
  }

  const handlePress = useCallback(
    (index: number) => {
      if (onPress) {
        onPress(index);
        return;
      }

      const carousel = carouselRef?.current;
      if (!carousel) return;

      const scrollAction = resolveCarouselPaginationScrollAction(
        carousel.getCurrentIndex(),
        index,
        rest.data.length,
        loop,
      );

      if (scrollAction) {
        carousel.scrollTo(scrollAction);
      }
    },
    [onPress, carouselRef, loop, rest.data.length],
  );

  const containerStyle = useResolveClassNames(cn(carouselPaginationContainerClassName, className));
  const dotStyle = useResolveClassNames(carouselPaginationDotVariants({ active: false }));
  const activeDotStyle = useResolveClassNames(carouselPaginationDotVariants({ active: true }));

  return (
    <RNCarouselPagination.Custom
      {...rest}
      progress={progress}
      onPress={handlePress}
      containerStyle={containerStyle}
      dotStyle={{
        width: 6,
        height: 6,
        ...(dotStyle as CarouselDotStyle),
      }}
      activeDotStyle={{
        width: 18,
        height: 6,
        ...(activeDotStyle as CarouselDotStyle),
      }}
    />
  );
}

CarouselPaginationComponent.displayName = 'Carousel.Pagination';

const CarouselPagination = CarouselPaginationComponent as <T>(
  props: CarouselPaginationProps<T>,
) => ReactElement | null;

type CarouselCompoundComponent = typeof CarouselRoot & {
  Pagination: typeof CarouselPagination;
};

export const Carousel = Object.assign(CarouselRoot, {
  Pagination: CarouselPagination,
}) as CarouselCompoundComponent;

// =============================================================================
// HOOK
// =============================================================================

export interface UseCarouselOptions {
  /** Seed `progress` to match `Carousel`'s `defaultIndex` in sibling composition. @default 0 */
  defaultIndex?: number;
}

export function useCarousel(options?: UseCarouselOptions) {
  const ref = useRef<ICarouselInstance>(null);
  const progress = useSharedValue(options?.defaultIndex ?? 0);
  return {
    ref,
    /** Bind to `Carousel`'s `onProgressChange` and sibling `Carousel.Pagination`'s `progress`. */
    progress,
    next: (opts?: Parameters<ICarouselInstance['next']>[0]) => ref.current?.next(opts),
    prev: (opts?: Parameters<ICarouselInstance['prev']>[0]) => ref.current?.prev(opts),
    scrollTo: (opts?: Parameters<ICarouselInstance['scrollTo']>[0]) => ref.current?.scrollTo(opts),
  };
}
