/**
 * Types declared in this file will be globally available throughout the project.  These should
 * be used sparsely and mainly for utility types (such as $Debug) that are likely unused outside
 * of being helper utilities.
 *
 * @note
 *  If needing to `import` in this file, the global types must all be wrapped in
 * `declare global` or they will no longer be seen as global.
 */

/**
 * When running $Debug, these values will not be mapped into.  One must be careful here as
 * interfaces do not often match with `extends` in Typescript (aka O[K] extends React.CSSProperties won't
 * work)
 */
type $DebugNoExtendValue<E> =
  | null
  | undefined
  | void
  | E
  | ((...args: any[]) => any);

type $DebugDepth = 1 | 2 | 3 | 4 | 5;
type $DebugDepthDefault = 5;

/**
 * $Debug<Type> allows you to expand out types in a way that will allow you to see
 * exact properties of a type that will be the result of the final calculations
 * such as One & Two & { three: string } --> { one: string, two: string, three: string }
 *
 * @example
 *  type PropsLink = CommonLinkProps & {
 *   href: string;
 *   target?: string | undefined;
 *   className?: string | undefined;
 *   style?: React.CSSProperties | undefined;
 *  } & {
 *   icon?: string | undefined;
 *  }
 *
 *  type CheckProps = $Debug<PropsLink>
 *
 *  type CheckProps = {
 *   color?: string | undefined;
 *   type?: "secondary" | "primary" | "dark" | "error" | "success" | "light" | undefined;
 *   children: React.ReactNode;
 *   href: string;
 *   target?: string | undefined;
 *   className?: string | undefined;
 *   style?: React.CSSProperties | undefined;
 *   icon?: string | undefined;
 *  }
 */
type $Debug<T, E = never> = T extends $DebugNoExtendValue<E>
  ? T
  : T extends object
  ? T extends infer O
    ? {
        [K in keyof O]: O[K];
      }
    : never
  : T;

/**
 * $DebugDeep<Type, Exclude, Depth> allows you to expand out types in a way that will allow you to see
 * exact properties of a type that will be the result of the final calculations. Depth will default to the
 * maximum levels (currently 5).
 *
 * You may exclude expansion of certain values by providing the `Exclude` option.  If you want to define
 * Depth and not Exclude just provide `never`.
 * such as One & Two & { three: string } --> { one: string, two: string, three: string }
 *
 * @example
 *  $DebugDeep<One & Two, SomeType>; // Don't expand values of SomeType if encountered
 *  $DebugDeep<One & Two, never, 1>; // Expand one level into (One & Two)
 *  $DebugDeep<One & Two, never, 4>; // Expand two levels into (One & Two)
 *  $DebugDeep<One & Two>; // Expand maximum levels into (One & Two)
 */
type $DebugDeep<
  T,
  E = never,
  L extends $DebugDepth = $DebugDepthDefault
> = T extends $DebugNoExtendValue<E>
  ? T
  : T extends object
  ? T extends infer O
    ? {
        [K in keyof O]: L extends 1
          ? $DebugDeep1<O[K], E>
          : L extends 2
          ? $DebugDeep2<O[K], E>
          : L extends 3
          ? $DebugDeep3<O[K], E>
          : L extends 4
          ? $DebugDeep4<O[K], E>
          : L extends 5
          ? $DebugDeep5<O[K], E>
          : $DebugDeep<T, E, $DebugDepthDefault>;
      }
    : never
  : T;

type $DebugDeep1<T, E = never> = T extends $DebugNoExtendValue<E>
  ? T
  : T extends object
  ? T extends infer O
    ? {
        [K in keyof O]: O[K];
      }
    : never
  : T;

type $DebugDeep2<T, E = never> = T extends $DebugNoExtendValue<E>
  ? T
  : T extends object
  ? T extends infer O
    ? {
        [K in keyof O]: $DebugDeep1<O[K], E>;
      }
    : never
  : T;

type $DebugDeep3<T, E = never> = T extends $DebugNoExtendValue<E>
  ? T
  : T extends object
  ? T extends infer O
    ? {
        [K in keyof O]: $DebugDeep2<O[K], E>;
      }
    : never
  : T;

type $DebugDeep4<T, E = never> = T extends $DebugNoExtendValue<E>
  ? T
  : T extends object
  ? T extends infer O
    ? {
        [K in keyof O]: $DebugDeep3<O[K], E>;
      }
    : never
  : T;

type $DebugDeep5<T, E = never> = T extends $DebugNoExtendValue<E>
  ? T
  : T extends object
  ? T extends infer O
    ? {
        [K in keyof O]: $DebugDeep4<O[K], E>;
      }
    : never
  : T;