import { assertWorkletCreator } from '@gallery-toolkit/common';
import {
  ImageTransformer,
  ImageTransformerProps,
  InteractionType,
  RenderImageProps,
} from '@gallery-toolkit/image-transformer';
import {
  Pager,
  PagerProps,
  RenderPageProps,
} from '@gallery-toolkit/pager';
import React from 'react';
import { Dimensions } from 'react-native';
import { runOnJS } from 'react-native-reanimated';

const dimensions = Dimensions.get('window');

const assertWorklet = assertWorkletCreator(
  '@gallery-toolkit/simple-gallery',
);

export interface SimpleGalleryItemType {
  width: number;
  height: number;
  uri: string;
}

interface Handlers<T> {
  onTap?: ImageTransformerProps['onTap'];
  onDoubleTap?: ImageTransformerProps['onDoubleTap'];
  onInteraction?: ImageTransformerProps['onInteraction'];
  onPagerTranslateChange?: (translateX: number) => void;
  onGesture?: PagerProps<T, any>['onGesture'];
  onPagerEnabledGesture?: PagerProps<T, any>['onEnabledGesture'];
  shouldPagerHandleGestureEvent?: PagerProps<
    T,
    any
  >['shouldHandleGestureEvent'];
  onShouldHideControls?: (shouldHide: boolean) => void;
}

export interface SimpleGalleryHandler {
  goNext: () => void;
  goBack: () => void;
  setIndex: (nextIndex: number) => void;
}

export interface ImageRendererProps<T> extends Handlers<T> {
  item: RenderPageProps<T>['item'];
  pagerProps: RenderPageProps<T>;
  width: number;
  height: number;
  renderImage?: (
    props: RenderImageProps,
    index?: number,
  ) => JSX.Element;
  ImageComponent?: React.ComponentType<any>;
}

type UnpackItemT<T> = T extends (infer ItemT)[]
  ? ItemT
  : T extends ReadonlyArray<infer ItemT>
  ? ItemT
  : T extends Map<any, infer ItemT>
  ? ItemT
  : T extends Set<infer ItemT>
  ? ItemT
  : T extends {
      [key: string]: infer ItemT;
    }
  ? ItemT
  : any;

export interface SimpleGalleryProps<T, ItemT>
  extends Handlers<ItemT> {
  items: T;
  renderPage?: (
    props: ImageRendererProps<ItemT>,
    index: number,
  ) => JSX.Element;
  renderImage?: (
    props: RenderImageProps,
    item: ItemT,
    index: number,
  ) => JSX.Element;
  keyExtractor?: (item: ItemT, index: number) => string;
  initialIndex?: number;
  width?: number;
  height?: number;
  numToRender?: number;
  gutterWidth?: number;
  onIndexChange?: (nextIndex: number) => void;
  getItem?: (data: T, index: number) => ItemT | undefined;
  getTotalCount?: (data: T) => number;
  ImageComponent?: React.ComponentType;
}

function isImageItemType(type: any): type is SimpleGalleryItemType {
  return (
    typeof type === 'object' &&
    'width' in type &&
    'height' in type &&
    'uri' in type
  );
}

export function ImageRenderer<T = unknown>({
  item,
  pagerProps,
  width,
  height,
  onDoubleTap,
  onTap,
  onInteraction,
  renderImage,
  ImageComponent,
}: ImageRendererProps<T>) {
  if (!isImageItemType(item)) {
    throw new Error(
      'ImageRenderer: item should have both width and height',
    );
  }

  return (
    <ImageTransformer
      outerGestureHandlerActive={pagerProps.isPagerInProgress}
      isActive={pagerProps.isPageActive}
      windowDimensions={{ width, height }}
      height={item.height}
      renderImage={renderImage}
      onStateChange={pagerProps.onPageStateChange}
      outerGestureHandlerRefs={pagerProps.pagerRefs}
      source={item.uri}
      width={item.width}
      onDoubleTap={onDoubleTap}
      onTap={onTap}
      onInteraction={onInteraction}
      ImageComponent={ImageComponent}
    />
  );
}

export class SimpleGallery<
  T,
  ItemT = UnpackItemT<T>
> extends React.PureComponent<
  SimpleGalleryProps<T, ItemT>,
  {
    localIndex: number;
  }
> {
  static ImageRenderer = React.memo(ImageRenderer);

  tempIndex: number = this.props.initialIndex ?? 0;

  controlsHidden = false;

  constructor(props: SimpleGalleryProps<T, ItemT>) {
    super(props);

    this._renderPage = this._renderPage.bind(this);
    this._keyExtractor = this._keyExtractor.bind(this);
  }

  state = {
    localIndex: this.props.initialIndex ?? 0,
  };

  get totalCount() {
    if (Array.isArray(this.props.items)) {
      return this.props.items.length;
    }

    if (typeof this.props.getTotalCount === 'function') {
      return this.props.getTotalCount(this.props.items);
    }

    throw new Error(
      'SimpleGallery: either items should be an array or getTotalCount should be defined',
    );
  }

  private setLocalIndex(nextIndex: number) {
    this.setState({
      localIndex: nextIndex,
    });
  }

  private setTempIndex(nextIndex: number) {
    this.tempIndex = nextIndex;
  }

  setIndex(nextIndex: number) {
    this.setLocalIndex(nextIndex);
  }

  goNext() {
    const nextIndex = this.tempIndex + 1;
    if (nextIndex > this.totalCount - 1) {
      console.warn(
        'SimpleGallery: Index cannot be bigger than pages count',
      );
      return;
    }

    this.setIndex(nextIndex);
  }

  goBack() {
    const nextIndex = this.tempIndex - 1;

    if (nextIndex < 0) {
      console.warn('SimpleGallery: Index cannot be negative');
      return;
    }

    this.setIndex(nextIndex);
  }

  _keyExtractor(item: ItemT, index: number) {
    if (typeof this.props.keyExtractor === 'function') {
      return this.props.keyExtractor(item, index);
    }

    return index.toString();
  }

  _renderPage(pagerProps: RenderPageProps<ItemT>, index: number) {
    const {
      onDoubleTap,
      onTap,
      onInteraction,
      width = dimensions.width,
      height = dimensions.height,
      renderPage,
      renderImage,
    } = this.props;

    const onShouldHideControls = (
      isScaled?: boolean | InteractionType,
    ) => {
      let shouldHide = true;

      if (typeof isScaled === 'boolean') {
        shouldHide = !isScaled;
      } else if (typeof isScaled === 'string') {
        shouldHide = true;
      } else {
        shouldHide = !this.controlsHidden;
      }

      this.controlsHidden = shouldHide;

      if (this.props.onShouldHideControls) {
        this.props.onShouldHideControls(shouldHide);
      }
    };

    const _onDoubleTap = (isScaled: boolean) => {
      'worklet';

      if (onDoubleTap) {
        onDoubleTap(isScaled);
      }

      runOnJS(onShouldHideControls)(isScaled);
    };

    const _onTap = (isScaled: boolean) => {
      'worklet';

      if (onTap) {
        onTap(isScaled);
      }

      runOnJS(onShouldHideControls)();
    };

    const _onInteraction = (type: InteractionType) => {
      'worklet';

      if (onInteraction) {
        onInteraction(type);
      }

      runOnJS(onShouldHideControls)(type);
    };

    const props = {
      ImageComponent: this.props.ImageComponent,
      item: pagerProps.item,
      width,
      height,
      pagerProps,

      onDoubleTap: _onDoubleTap,
      onTap: _onTap,
      onInteraction: _onInteraction,

      renderImage: renderImage
        ? (props: RenderImageProps) => {
            return renderImage(props, pagerProps.item, index);
          }
        : undefined,
    };

    if (typeof renderPage === 'function') {
      return renderPage(props, index);
    }

    return <ImageRenderer {...props} />;
  }

  render() {
    const {
      items,
      gutterWidth,
      onIndexChange,
      getItem,
      width = dimensions.width,
      onPagerTranslateChange,
      numToRender,
      onGesture,
      shouldPagerHandleGestureEvent,
      onPagerEnabledGesture,
    } = this.props;

    if (onIndexChange) {
      assertWorklet(onIndexChange);
    }

    const setTempIndex = (index: number) => {
      this.setTempIndex(index);
    };

    function onIndexChangeWorklet(nextIndex: number) {
      'worklet';

      runOnJS(setTempIndex)(nextIndex);

      if (onIndexChange) {
        onIndexChange(nextIndex);
      }
    }

    return (
      <Pager
        totalCount={this.totalCount}
        getItem={getItem}
        keyExtractor={this._keyExtractor}
        initialIndex={this.state.localIndex}
        pages={items}
        width={width}
        gutterWidth={gutterWidth}
        onIndexChange={onIndexChangeWorklet}
        onPagerTranslateChange={onPagerTranslateChange}
        shouldHandleGestureEvent={shouldPagerHandleGestureEvent}
        onGesture={onGesture}
        onEnabledGesture={onPagerEnabledGesture}
        renderPage={this._renderPage}
        numToRender={numToRender}
      />
    );
  }
}