/*
 * Tencent is pleased to support the open source community by making
 * Hippy available.
 *
 * Copyright (C) 2017-2019 THL A29 Limited, a Tencent company.
 * All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/* eslint-disable no-underscore-dangle */

import React from 'react';
import StyleSheet from '../modules/stylesheet';
import { callUIFunction } from '../modules/ui-manager-module';
import Element from '../dom/element-node';
import { isRTL } from '../utils/i18n';
import View from './view';

interface ScrollViewPropsIOS {

  /**
   * When `true`, shows a horizontal scroll indicator.
   * Default: true
   */
  showsHorizontalScrollIndicator?: boolean;

  /**
   * When `true`, shows a vertical scroll indicator.
   * Default: true
   */
  showsVerticalScrollIndicator?: boolean;
}

interface ScrollViewPropsAndroid {
  /**
   * When false, the scroll view will hide scroll indicator
   * @default false
   */
  showScrollIndicator?: boolean;
}

export interface ScrollEvent {
  contentInset: {
    right: number;
    top: number;
    left: number;
    bottom: number;
  };
  contentOffset: {
    x: number;
    y: number;
  };
  contentSize: {
    width: number;
    height: number;
  };
  layoutMeasurement: {
    width: number;
    height: number;
  };
  zoomScale: number;
}

export interface ScrollViewProps extends ScrollViewPropsAndroid, ScrollViewPropsIOS {
  // TODO: allow HippyTypes.Style[]
  style?: HippyTypes.Style;
  /**
   * When true, the scroll view's children are arranged horizontally in a row
   * instead of vertically in a column.
   * The default value is `false`.
   */
  horizontal?: boolean;

  /**
   * When `true`, the scroll view stops on multiples of the scroll view's size when scrolling.
   * This can be used for horizontal pagination.
   * Default: false
   */
  pagingEnabled?: boolean;

  /**
   * When `false`, the view cannot be scrolled via touch interaction.
   * Default: true
   *
   * > Note that the view can always be scrolled by calling scrollTo.
   */
  scrollEnabled?: boolean;

  /**
   * These styles will be applied to the scroll view content container which wraps all
   * of the child views.
   */
  contentContainerStyle?: HippyTypes.StyleProp;

  /**
   * This controls how often the scroll event will be fired while scrolling
   * (as a time interval in ms). A lower number yields better accuracy for code
   * that is tracking the scroll position, but can lead to scroll performance
   * problems due to the volume of information being send over the bridge.
   * You will not notice a difference between values set between 1-16 as the JS run loop
   * is synced to the screen refresh rate. If you do not need precise scroll position tracking,
   * set this value higher to limit the information being sent across the bridge.
   *
   * The default value is zero, which results in the scroll event being sent only once
   * each time the view is scrolled.
   */
  scrollEventThrottle?: number;

  /**
   * The amount by which the scroll view indicators are inset from the edges of the scroll view.
   * This should normally be set to the same value as the `contentInset`.
   *
   * Default: {top: 0, right: 0, bottom: 0, left: 0}.
   */
  scrollIndicatorInsets?: {
    top: number;
    right: number;
    bottom: number;
    left: number;
  };

  /**
   * Called when the momentum scroll starts (scroll which occurs as the ScrollView starts gliding).
   */
  onMomentumScrollBegin?: (event: ScrollEvent) => void;

  /**
   * Called when the momentum scroll ends (scroll which occurs as the ScrollView glides to a stop).
   */
  onMomentumScrollEnd?: (event: ScrollEvent) => void;

  /**
   * Fires at most once per frame during scrolling.
   * The frequency of the events can be controlled using the `scrollEventThrottle` prop.
   *
   * @param {Object} event - Scroll event data.
   * @param {number} event.contentOffset.x - Offset X of scrolling.
   * @param {number} event.contentOffset.y - Offset Y of scrolling.
   */
  onScroll?: (event: ScrollEvent) => void;

  /**
   * Called when the user begins to drag the scroll view.
   */
  onScrollBeginDrag?: (event: ScrollEvent) => void;

  /**
   * Called when the user stops dragging the scroll view and it either stops or begins to glide.
   */
  onScrollEndDrag?: (event: ScrollEvent) => void;
}

const styles = StyleSheet.create({
  baseVertical: {
    flexGrow: 1,
    flexShrink: 1,
    flexDirection: 'column',
    overflow: 'scroll',
  },
  baseHorizontal: {
    flexGrow: 1,
    flexShrink: 1,
    flexDirection: 'row',
    overflow: 'scroll',
  },
  contentContainerVertical: {
    collapsable: false,
    flexDirection: 'column',
  },
  contentContainerHorizontal: {
    collapsable: false,
    flexDirection: 'row',
  },
});

/**
 * Scrollable View without recycle feature.
 *
 * If you need to implement a long list, use `ListView`.
 * @noInheritDoc
 */
export class ScrollView extends React.Component<ScrollViewProps, {}> {
  private instance: Element | HTMLDivElement | null = null;

  /**
   * Scrolls to a given x, y offset, either immediately, with a smooth animation.
   *
   * @param {number} x - Scroll to horizon position X.
   * @param {number} y - Scroll To veritical position Y.
   * @param {boolean} animated - With smooth animation.By default is true.
   */
  public scrollTo(
    x: number | { x: number; y: number; animated: boolean; },
    y: number,
    animated = true,
  ) {
    let x_ = x;
    let y_ = y;
    let animated_ = animated;
    if (typeof x === 'object' && x) {
      ({ x: x_, y: y_, animated: animated_ } = x);
    }
    x_ = x_ || 0;
    y_ = y_ || 0;
    animated_ = !!animated_;
    callUIFunction(this.instance as Element, 'scrollTo', [x_, y_, animated_]);
  }

  /**
   * Scrolls to a given x, y offset, with specific duration of animation.
   *
   * @param {number} x - Scroll to horizon position X.
   * @param {number} y - Scroll To vertical position Y.
   * @param {number} duration - Duration of animation execution time, with ms unit.
   *                            Default is 1000ms.
   */
  public scrollToWithDuration(x = 0, y = 0, duration = 1000) {
    callUIFunction(this.instance as Element, 'scrollToWithOptions', [{ x, y, duration }]);
  }

  /**
   * @ignore
   */
  public render() {
    const {
      horizontal,
      contentContainerStyle,
      children,
      style,
    } = this.props;
    const contentContainerStyle_ = [
      horizontal ? styles.contentContainerHorizontal : styles.contentContainerVertical,
      contentContainerStyle,
    ];
    const newStyle: HippyTypes.Style = horizontal
      ? Object.assign({}, styles.baseHorizontal, style)
      : Object.assign({}, styles.baseVertical, style);

    if (horizontal) {
      newStyle.flexDirection = isRTL() ? 'row-reverse' : 'row';
    }

    return (
      // @ts-ignore
      <div
        nativeName="ScrollView"
        ref={(ref) => {
          this.instance = ref;
        }}
        {...this.props}
        // @ts-ignore
        style={newStyle}
      >
        <View
          style={contentContainerStyle_}>
          {children}
        </View>
      </div>
    );
  }
}
export default ScrollView;
