import { RefObject } from 'react';
import { MeasureInWindowOnSuccessCallback, StyleProp, ViewStyle, StyleSheet } from 'react-native';
import { Placement, Point, Rect, Size } from './Types';
import { DEFAULT_ARROW_SIZE, DEFAULT_BORDER_RADIUS } from './Constants';

type RefType = RefObject<{
  measureInWindow: (callback: MeasureInWindowOnSuccessCallback) => void
}>;

export function getRectForRef(ref: RefType): Promise<Rect> {
  return new Promise((resolve, reject) => {
    if (ref.current) {
      ref.current.measureInWindow(
        (x: number, y: number, width: number, height: number) =>
          resolve(new Rect(x, y, width, height))
      );
    } else {
      reject(new Error('getRectForRef - current is not set'));
    }
  });
}

export async function waitForChange(
  getFirst: () => Promise<Rect>,
  getSecond: () => Promise<Rect>
): Promise<void> {
  // Failsafe so that the interval doesn't run forever
  let count = 0;
  let first, second;
  do {
    first = await getFirst();
    second = await getSecond();
    await new Promise(resolve => {
      setTimeout(resolve, 100);
    });
    count++;
    if (count++ > 20) {
      throw new Error('waitForChange - Timed out waiting for change (waited 2 seconds)');
    }
  } while (first.equals(second));
}

export async function waitForNewRect(ref: RefType, initialRect: Rect): Promise<Rect> {
  await waitForChange(() => getRectForRef(ref), () => Promise.resolve(initialRect));
  const rect = await getRectForRef(ref);
  return rect;
}

export function sizeChanged(a: Size | null, b: Size | null): boolean {
  if (!a || !b) return false;
  return Math.round(a.width) !== Math.round(b.width) ||
    Math.round(a.height) !== Math.round(b.height);
}

export function rectChanged(a: Rect | null, b: Rect | null): boolean {
  if (!a || !b) return false;
  return Math.round(a.x) !== Math.round(b.x) ||
    Math.round(a.y) !== Math.round(b.y) ||
    Math.round(a.width) !== Math.round(b.width) ||
    Math.round(a.height) !== Math.round(b.height);
}

export function pointChanged(a: Point, b: Point): boolean {
  return (Math.round(a.x) !== Math.round(b.x) || Math.round(a.y) !== Math.round(b.y));
}

export function getArrowSize(
  placement: Placement,
  arrowStyle: StyleProp<ViewStyle>
): Size {
  let { width, height } = StyleSheet.flatten(arrowStyle);
  if (typeof width !== 'number') ({ width } = DEFAULT_ARROW_SIZE);
  if (typeof height !== 'number') ({ height } = DEFAULT_ARROW_SIZE);
  switch (placement) {
    case Placement.LEFT:
    case Placement.RIGHT:
      return new Size(height, width);
    default:
      return new Size(width, height);
  }
}

export function getBorderRadius(popoverStyle: StyleProp<ViewStyle>): number {
  if (StyleSheet.flatten(popoverStyle).borderRadius === 0) return 0;
  return StyleSheet.flatten(popoverStyle).borderRadius || DEFAULT_BORDER_RADIUS;
}

export function getChangedProps(
  props: Record<string, unknown>,
  prevProps: Record<string, unknown>,
  importantProps: string[]
): string[] {
  return importantProps.filter(key => {
    const curVal = props[key];
    const prevVal = prevProps[key];
    if (curVal instanceof Rect && prevVal instanceof Rect) {
      return !curVal.equals(prevVal);
    }
    return curVal !== prevVal;
  });
}
