// Used in React hooks to indicate empty deps are intentional.
import { MaybePromise, Nullable, WritableProps } from 'tsdef';

import { FileAction, FileActionEffect } from '../types/action.types';
import { Logger } from './logger';

// Used in contexts that need to provide some default value for a function.
// eslint-disable-next-line no-unused-vars,@typescript-eslint/no-unused-vars
export const NOOP_FUNCTION = (...args: any[]) => {
  Logger.warn(
    `The "NOOP_FUNCTION" from the constants module was called. ` +
      `This can indicate a bug in one of the components. Supplied args:`,
    args,
  );
};

export const isPromise = <T>(value: MaybePromise<T> | any): value is Promise<T> => {
  if (typeof value !== 'object' || !value) return false;
  const then = (value as Promise<T>).then;
  return then && typeof then === 'function';
};

export const defineFileAction = <Action extends FileAction>(
  action: Action,
  effect?: FileActionEffect<FileAction>,
): WritableProps<Action> => {
  if (action.__payloadType !== undefined && (action.hotkeys || action.button)) {
    const errorMessage =
      `Invalid definition was provided for file action "${action.id}". Actions ` +
      `that specify hotkeys or buttons cannot define a payload type. If ` +
      `your application requires this functionality, define two actions ` +
      `and chain them using effects.`;
    Logger.error(errorMessage);
    throw new Error(errorMessage);
  }

  action.effect = effect;
  return action;
};

/**
 * Recursively check the current element and the parent elements, going bottom-up.
 * Returns the first element to match the predicate, otherwise returns null if such
 * element is not found.
 */
export const findElementAmongAncestors = (
  maybeElement: HTMLElement | any,
  predicate: (maybeElement: HTMLElement | any) => boolean,
): Nullable<HTMLElement> => {
  if (!maybeElement) return maybeElement;

  if (predicate(maybeElement)) return maybeElement;

  if (maybeElement.parentElement) {
    return findElementAmongAncestors(maybeElement.parentElement, predicate);
  }

  return null;
};

export const elementIsInsideButton = (buttonCandidate: HTMLElement | any): boolean => {
  return !!findElementAmongAncestors(
    buttonCandidate,
    (element: any) => element.tagName && element.tagName.toLowerCase() === 'button',
  );
};

export const getValueOrFallback = <T extends any>(
  value: T | undefined,
  fallback: T,
  desiredType?: 'boolean' | 'string' | 'number',
): NonNullable<T> => {
  if (desiredType) {
    return (typeof value === desiredType ? value : fallback) as NonNullable<T>;
  }
  return (value !== undefined ? value : fallback) as NonNullable<T>;
};
