import {
  NavigationState,
  PartialState,
  ParamListBase,
  NavigationProp,
  RouteProp,
} from '@react-navigation/native';
import * as helpers from './helpers';
import { CompatNavigationProp } from './types';

type EventName =
  | 'action'
  | 'willFocus'
  | 'willBlur'
  | 'didFocus'
  | 'didBlur'
  | 'refocus';

// const focusSubscriptions = new WeakMap<() => void, () => void>();
// const blurSubscriptions = new WeakMap<() => void, () => void>();
// const refocusSubscriptions = new WeakMap<() => void, () => void>();

export default function createCompatNavigationProp<
  NavigationPropType extends NavigationProp<ParamListBase>,
  ParamList extends ParamListBase = NavigationPropType extends NavigationProp<
    infer P
  >
    ? P
    : ParamListBase
>(
  navigation: NavigationPropType,
  state:
    | (RouteProp<ParamList, keyof ParamList> & {
        state?: NavigationState | PartialState<NavigationState>;
      })
    | NavigationState
    | PartialState<NavigationState>,
  context: Record<string, any>,
  isFirstRouteInParent?: boolean
): CompatNavigationProp<NavigationPropType> {
  context.parent = context.parent || {};
  context.subscriptions = context.subscriptions || {
    didFocus: new Map<() => void, () => void>(),
    didBlur: new Map<() => void, () => void>(),
    refocus: new Map<() => void, () => void>(),
  };

  return {
    ...navigation,
    ...Object.entries(helpers).reduce<{
      [key: string]: (...args: any[]) => void;
    }>((acc, [name, method]: [string, Function]) => {
      if (name in navigation) {
        acc[name] = (...args: any[]) => navigation.dispatch(method(...args));
      }

      return acc;
    }, {}),
    original: navigation,
    addListener(type: EventName, callback: () => void) {
      let unsubscribe: () => void;

      switch (type) {
        case 'willFocus':
          unsubscribe = navigation.addListener('focus', callback);
          break;
        case 'willBlur':
          unsubscribe = navigation.addListener('blur', callback);
          break;
        case 'didFocus': {
          const listener = () => {
            if (navigation.isFocused()) {
              callback();
            }
          };

          // @ts-ignore
          unsubscribe = navigation.addListener('transitionEnd', listener);
          context.subscriptions.didFocus.set(callback, unsubscribe);
          break;
        }
        case 'didBlur': {
          const listener = () => {
            if (!navigation.isFocused()) {
              callback();
            }
          };

          // @ts-ignore
          unsubscribe = navigation.addListener('transitionEnd', listener);
          context.subscriptions.didBlur.set(callback, unsubscribe);
          break;
        }
        case 'refocus': {
          const listener = () => {
            if (navigation.isFocused()) {
              callback();
            }
          };

          // @ts-ignore
          unsubscribe = navigation.addListener('tabPress', listener);
          context.subscriptions.refocus.set(callback, unsubscribe);
          break;
        }
        case 'action':
          throw new Error("Listening to 'action' events is not supported.");
        default:
          // @ts-ignore
          unsubscribe = navigation.addListener(type, callback);
      }

      const subscription = () => unsubscribe();

      subscription.remove = unsubscribe;

      return subscription;
    },
    removeListener(type: EventName, callback: () => void) {
      context.subscriptions = context.subscriptions || {};

      switch (type) {
        case 'willFocus':
          navigation.removeListener('focus', callback);
          break;
        case 'willBlur':
          navigation.removeListener('blur', callback);
          break;
        case 'didFocus': {
          const unsubscribe = context.subscriptions.didFocus.get(callback);
          unsubscribe?.();
          break;
        }
        case 'didBlur': {
          const unsubscribe = context.subscriptions.didBlur.get(callback);
          unsubscribe?.();
          break;
        }
        case 'refocus': {
          const unsubscribe = context.subscriptions.refocus.get(callback);
          unsubscribe?.();
          break;
        }
        case 'action':
          throw new Error("Listening to 'action' events is not supported.");
        default:
          // @ts-ignore
          navigation.removeListener(type, callback);
      }
    },
    state: {
      ...state,
      // @ts-ignore
      routeName: state.name,
      get index() {
        // @ts-ignore
        if (state.index !== undefined) {
          // @ts-ignore
          return state.index;
        }

        console.warn(
          "Accessing child navigation state for a route is not safe and won't work correctly."
        );

        // @ts-ignore
        return state.state ? state.state.index : undefined;
      },
      get routes() {
        // @ts-ignore
        if (state.routes !== undefined) {
          // @ts-ignore
          return state.routes;
        }

        console.warn(
          "Accessing child navigation state for a route is not safe and won't work correctly."
        );

        // @ts-ignore
        return state.state ? state.state.routes : undefined;
      },
    },
    getParam<T extends keyof ParamList>(
      paramName: T,
      defaultValue: ParamList[T]
    ): ParamList[T] {
      // @ts-ignore
      const params = state.params;

      if (params && paramName in params) {
        return params[paramName];
      }

      return defaultValue;
    },
    isFirstRouteInParent(): boolean {
      if (typeof isFirstRouteInParent === 'boolean') {
        return isFirstRouteInParent;
      }

      const { routes } = navigation.dangerouslyGetState();

      // @ts-ignore
      return routes[0].key === state.key;
    },
    dangerouslyGetParent() {
      const parent = navigation.dangerouslyGetParent();

      if (parent) {
        return createCompatNavigationProp(
          parent,
          navigation.dangerouslyGetState(),
          context.parent
        );
      }

      return undefined;
    },
  } as any;
}
