import React, { createRef, useState, useRef } from 'react';
import { Svg, G } from 'react-native-svg';
import {
  Dimensions,
  GestureResponderEvent,
  PanResponder,
  StyleSheet,
} from 'react-native';
import { Nullable } from 'tsdef';
import { Line } from './line';
import { YAxis } from './y-axis';
import { XAxis } from './x-axis';
import { SBox } from '../../common/s-components/layout/s-box';
import { TGraphInner, TLine } from '../types';
import { fp as _ } from '../../common/utils/fp';
import { XAxisLabels } from './x-axis-labels';
import { YAxisLabels } from './y-axis-labels';
import { theme } from '../../app/theme';
import { SH6 } from '../../common/s-components/typography/s-h6';
import { SText } from '../../common/s-components/typography/s-text';
import { SDot } from '../s-components/s-dot';
import { formatStream, getWeekday } from '../../common/transducers';
import {
  getNewTotal,
  buildPath,
  formatDate,
  buildScalesDynamic,
  getLinePrimaryColorByVariant,
  getGraphPercentage,
} from '../transducers';

import { SLineDot } from '../s-components/s-line-dot';
import { SHoverLine } from '../s-components/s-hover-line';
import { GRAPH_PADDING_TOP_RATIO } from '../constants';
import { TCoordinate, TCoordinates } from '../../common/types';

type TProps = TGraphInner & {
  hideTotal?: boolean;
  hidePercentage?: boolean;
  titleY?: string;
};

type TState = {
  width: number;
  height: number;
  total: Nullable<string>;
  pointDate: Nullable<string>;
  values: Record<string, null>;
  percents: Record<string, string>;
  extraTexts: Record<string, string>;
};

type TVirtualState = {
  values?: Record<string, null>;
  percents?: Record<string, string>;
  pointDate?: Nullable<string>;
  total?: Nullable<string>;
  extraTexts?: Nullable<string>;
};

export const MultilineGraphAnimated: React.FC<TProps> = props => {
  const [state, setState] = useState<TState>({
    width: Dimensions.get('window').width,
    height: Dimensions.get('window').height,
    total: null,
    pointDate: null,
    values: {},
    percents: {},
    extraTexts: {},
  });

  const virtualState = useRef<TVirtualState>({
    values: undefined,
    percents: undefined,
    extraTexts: undefined,
    pointDate: null,
    total: null,
  });

  const cursors = useRef<Record<string, React.RefObject<any>>>({});

  const line = useRef<React.RefObject<any>>();

  const bgLayer = useRef();

  const getSizes = (state: TState) => {
    const { width, height } = state;
    const padding = 56;
    const paddingTop = 27;
    const paddingBottom = 123;
    const svgHeight = height - 38;
    const innerWidth = width - padding * 2 - 171;
    const innerHeight = svgHeight - paddingBottom;

    return {
      height: svgHeight,
      width,
      padding,
      paddingTop,
      paddingBottom,
      innerHeight,
      innerWidth,
      xAxisOffset: padding + 61,
      xAxisOffsetRight: innerWidth,
      offsetTop: 15,
    };
  };

  const getScales = (props: TProps, state: TState) => {
    const {
      maxes: { xMin, xMax, yMax },
      lines,
    } = props;

    return buildScalesDynamic({
      ...getSizes(state),
      xMin,
      xMax,
      yMax,
      lines,
    });
  };

  const getTotal = (pageX: number) => {
    const { scalesQuant } = getScales(props, state);
    const { lines } = props;
    return getNewTotal(lines, scalesQuant, pageX);
  };

  const setLinePosition = (anyTarget: TCoordinate) => {
    const { xAxisOffset, offsetTop } = getSizes(state);

    if (anyTarget) {
      const targetX = anyTarget.x;
      // @ts-ignore
      line.current.setNativeProps({
        style: {
          transform: [
            { translateX: targetX - 1 - xAxisOffset },
            { translateY: offsetTop - 24 },
          ],
          opacity: 1,
        },
      });
    }
  };

  const setStateVirtual = (nextState: Partial<TVirtualState>): void => {
    virtualState.current = _.updateI(
      [_.$merge(nextState)],
      virtualState.current
    );
  };

  const setLabelTotal = (pageX: number) => {
    const { scalesQuant } = getScales(props, state);
    const { lines } = props;
    const { total } = virtualState.current;
    const newTotal = getNewTotal(lines, scalesQuant, pageX);

    const value = newTotal === 0 ? '' : formatStream(newTotal);

    if (total !== value) {
      setStateVirtual({ total: value });
    }
  };

  const setDotPosition = (ref: React.RefObject<any>, target: TCoordinate) => {
    const { xAxisOffset, offsetTop } = getSizes(state);

    ref.current.setNativeProps({
      style: {
        transform: [
          { translateX: target.x - 12 / 2 - xAxisOffset },
          { translateY: target.y! - 12 / 2 + offsetTop + 1 },
        ],
        opacity: 1,
      },
    });
  };

  const setLabelDate = (pageX: number) => {
    const { scalesQuant } = getScales(props, state);
    const { lines } = props;
    const { pointDate } = virtualState.current;
    let match = null;

    for (let i = 0, { length } = lines; i < length; i++) {
      const scale = scalesQuant[i];
      const target = scale(pageX);

      if (target) {
        match = formatDate(_.path('x', target));
      }

      if (match) {
        break;
      }
    }

    if (pointDate !== match) {
      setStateVirtual({ pointDate: match });
    }
  };

  const removeZeroOpacity = (ref: React.RefObject<any>) => {
    ref.current.setNativeProps({
      style: {
        opacity: 1,
      },
    });
  };

  const updateStateFromVirtual = () => {
    setState({ ...state, ...virtualState.current } as TState);
    virtualState.current = {};
  };

  const init = () => {
    const { lines } = props;
    let values: Record<string, null> = {};
    _.forEachIndexed((o, index) => {
      values[index] = null;
    }, lines);

    const params = { values, total: null, pointDate: null };

    setState(params as any);
    setTimeout(() => {
      virtualState.current = params;
    }, 0);
  };

  const setZeroOpacity = (ref: React.RefObject<any>) => {
    if (ref.current) {
      ref.current.setNativeProps({
        style: {
          opacity: 0,
        },
      });
    }
  };

  const setZeroOpacityAll = () => {
    _.forEach(setZeroOpacity as any, cursors);
    setZeroOpacity(line);
    setZeroOpacity(bgLayer);
    init();
  };

  const setLabel = (pageX: number, index: number) => {
    const { scalesQuant } = getScales(props, state);
    const scale = scalesQuant[index];
    const {
      values = {},
      percents = {},
      extraTexts = {},
    } = virtualState.current;
    const valueRaw = (scale(pageX) || {}).y;
    const extraTextRaw = (scale(pageX) || {}).extraText;
    const value = formatStream(valueRaw);
    const oldValue = _.path([index], values);

    if (oldValue !== value) {
      const total = getTotal(pageX);
      const percent = getGraphPercentage(total, valueRaw);
      setStateVirtual({
        values: _.updateI(
          [index, _.isNotNil(valueRaw) ? _.$set(value) : _.$none],
          values
        ),
        percents: _.updateI(
          [index, _.isNotNil(valueRaw) ? _.$set(percent) : _.$none],
          percents
        ),
        extraTexts: _.updateI(
          [index, _.isNotNil(extraTextRaw) ? _.$set(extraTextRaw) : _.$none],
          extraTexts
        ),
      });
    }
  };

  const updateEntityIfExist = (
    entity: TCoordinates,
    target: TCoordinate,
    pageX: number,
    index: number
  ) => {
    if (entity) {
      if (target) {
        // @ts-ignore
        setDotPosition(cursors[index], target);
        setLabel(pageX, index);
      } else {
        // @ts-ignore
        setZeroOpacity(cursors[index]);
        // @ts-ignore
        setLabel('N/A', index);
      }
    }
  };

  const onInteraction = (evt: GestureResponderEvent) => {
    const { pageX } = evt.nativeEvent;
    const { scalesQuantRaw } = getScales(props, state);
    const { lines } = props;
    const targets: TCoordinates = [];

    _.forEachIndexed<any, any>((line: TLine, index: number) => {
      const target = scalesQuantRaw[index](pageX);
      updateEntityIfExist(line.dotted, target, pageX, index);
      targets.push(target);
    }, lines);

    if (_.any(_.isNotNil, targets)) {
      setLinePosition(_.find(_.isNotNil, targets)!);
      setLabelDate(pageX);
      setLabelTotal(pageX);
      removeZeroOpacity(bgLayer);
    } else {
      setZeroOpacityAll();
    }

    updateStateFromVirtual();
  };

  const getPath = (data: TCoordinates): string => {
    const { xScaleMinus1, yScale } = getScales(props, state);
    return buildPath({ xScale: xScaleMinus1, yScale, data });
  };

  const panResponder = PanResponder.create({
    onStartShouldSetPanResponder: _.T,
    onStartShouldSetPanResponderCapture: _.T,
    onMoveShouldSetPanResponder: _.T,
    onMoveShouldSetPanResponderCapture: _.T,
    onPanResponderGrant: e => {
      onInteraction(e);
    },
    onPanResponderMove: e => {
      onInteraction(e);
    },
    onPanResponderTerminationRequest: _.T,
    // onPanResponderRelease: () => {
    //   this.setZeroOpacityAll();
    // },
    onShouldBlockNativeResponder: _.T,
  });

  const {
    maxes: { xMin, xMax, yMax },
    ticks,
    lines,
    hideTotal,
    hidePercentage,
    titleY,
  } = props;
  const {
    height,
    innerWidth,
    innerHeight,
    padding,
    width,
    xAxisOffset,
    offsetTop,
  } = getSizes(state);
  const { xScale, yScale } = getScales(props, state);
  const yTicks = [0, yMax / 2, yMax];
  const hideGraph = _.any(_.isNil)([yMax, xMax, xMin]);
  const { values, total, pointDate, percents, extraTexts } = state;
  const graphMax = yMax / GRAPH_PADDING_TOP_RATIO;

  return (
    <SBox bg="fiord" position="relative">
      <SBox position="absolute" left={56} top={2}>
        <SH6 color={theme.colors.periwinkleGray} lineHeight="15px">
          {titleY || 'Streams'}
        </SH6>
      </SBox>

      <SBox
        position="absolute"
        width={251}
        right={0}
        top={-11}
        mr={16}
        px={16}
        pt={13}
        pb={!hideTotal ? 7 : 13}
        borderRadius={4}
        overflow="hidden"
      >
        <SBox
          style={StyleSheet.absoluteFill}
          opacity={0}
          bg="ebonyClay"
          ref={bgLayer}
        />
        <SBox
          color="periwinkleGray"
          flexDirection="row"
          justifyContent="space-between"
          alignItems="baseline"
          height={24}
        >
          <SText
            fontSize={10}
            bold
            lineHeight="15px"
            letterSpacing={1}
            color="periwinkleGray"
          >
            {pointDate ? `${getWeekday(pointDate!)}, ${pointDate}:` : null}
          </SText>
          {!hideTotal ? (
            <SText
              fontSize={12}
              bold
              lineHeight="15px"
              testID="multiline-graph-total"
            >
              {total ? `${total}` : null}
            </SText>
          ) : null}
        </SBox>
        <SBox flexDirection="column">
          {lines.map((line, i) => {
            const { variant, sourceName, title, empty } = line;
            const label = sourceName || title;
            const color = getLinePrimaryColorByVariant(variant);

            return label && !empty ? (
              <SBox key={variant}>
                <SBox
                  flexDirection="row"
                  justifyContent="space-between"
                  width={1}
                  mb={!hideTotal ? 5.3 : 0}
                  height={14.7}
                >
                  <SBox flexDirection="row" alignItems="center">
                    <SDot size={10} bg={color} mr={10} />
                    <SText sm>{label}</SText>
                  </SBox>
                  {_.isNotNil(_.path(i, values)) ? (
                    <SText testID="multiline-graph-value">
                      <SText bold sm>
                        {_.path(i, values)}
                      </SText>
                      {!hidePercentage && (
                        <SText normal sm>
                          {' '}
                          {_.path(i, percents)}
                        </SText>
                      )}
                    </SText>
                  ) : _.isEmpty(values) ? null : (
                    <SText bold sm>
                      N/A
                    </SText>
                  )}
                </SBox>
                {_.path(i, extraTexts) ? (
                  <SBox alignItems="flex-end" mt={1}>
                    <SText fontSize={12}>{_.path(i, extraTexts)}</SText>
                  </SBox>
                ) : null}
              </SBox>
            ) : null;
          })}
        </SBox>
      </SBox>

      <XAxisLabels
        ticks={ticks}
        scale={xScale}
        yOffset={innerHeight + offsetTop}
        variant="large"
      />

      <YAxisLabels
        ticks={yTicks}
        scale={yScale}
        offsetLeft={padding}
        offsetTop={1}
        variant="big"
      />

      <SBox pt={offsetTop}>
        <Svg height={height - 4} width={innerWidth}>
          {!hideGraph ? (
            <>
              <YAxis
                ticks={yTicks}
                scale={yScale}
                xOffsetRight={width - padding}
                xOffset={padding}
              />

              <XAxis ticks={ticks} scale={xScale} yOffset={innerHeight} />

              {lines.map(line => {
                const { dotted, variant, empty } = line;

                if (empty) {
                  return null;
                }

                return dotted ? (
                  <G transform="translate(0, 0)" key={variant}>
                    <Line
                      // testID="multiline-graph-line"
                      graphMax={graphMax}
                      variant={variant}
                      data={dotted}
                      path={getPath(dotted)}
                    />
                  </G>
                ) : null;
              }, lines)}
            </>
          ) : null}
        </Svg>
      </SBox>

      <SBox
        style={StyleSheet.absoluteFill}
        ml={xAxisOffset - 1}
        mr={padding - 1}
        {...panResponder.panHandlers}
        // Pan responder doesn't work with views without color on android
        // https://stackoverflow.com/a/51833007/6190198
        bg="transparent"
      >
        {lines.map((value, index) => {
          const { variant, empty } = lines[index];

          // @ts-ignore
          if (!cursors[index]) {
            // @ts-ignore
            cursors[index] = createRef();
          }
          // @ts-ignore
          if (empty) return <SBox ref={cursors[index]} key={variant} />;

          const color = getLinePrimaryColorByVariant(variant);

          return (
            <SLineDot
              testID="multiline-graph-line-dots"
              // @ts-ignore
              ref={cursors[index]}
              bg={color}
              key={variant}
            />
          );
        }, lines)}

        <SBox pt={0} mt={2}>
          <SHoverLine ref={line} height={innerHeight - 27} />
        </SBox>
      </SBox>
    </SBox>
  );
};
