import {
  ShopifyObjectType
} from '../models';

export type Nullable<T> = T | null;

/**
 * Generic type for whenever you need an object containing all Shopify entities.
 * 
 * I want somethign like this but it's not possible atm :(
 * https://github.com/Microsoft/TypeScript/issues/1213
 * 
 * type Record<K extends keyof any, T extends any<"generic">> = {
 *   [P in K]: T<P>;
 * };
 */
export type MappedShopifyObjectType<T extends unknown> = Record<ShopifyObjectType, T>;

export type ReadonlyPropNames<T> = Readonly<(keyof T)[]>;
export type Writable<T> = {
  -readonly [K in keyof T]: T[K];
};
type Primitive =
  | string
  | Function
  | number
  | boolean
  | Symbol
  | undefined
  | null;

type DeepOmitArray<T extends any[], K> = {
  [P in keyof T]: DeepOmit<T[P], K>;
};

export type DeepOmit<T, K> = T extends Primitive
  ? T
  : {
      [P in Exclude<keyof T, K>]: T[P] extends infer TP
        ? TP extends Primitive
          ? TP // leave primitives and functions alone
          : TP extends any[]
          ? DeepOmitArray<TP, K> // Array special handling
          : DeepOmit<TP, K>
        : never;
    };

export type NonNullableObject<T> = {
  [K in keyof T]: NonNullable<T[K]>
};

export type NullableObject<T> = {
  [K in keyof T]: Nullable<T[K]>
};

export type ValueOf<T> = T[keyof T];

export class UnreachableCaseError extends Error {
  constructor(val: never) {
    super(`Unreachable case: ${JSON.stringify(val)}`);
  }
}

// https://medium.com/terria/typescript-transforming-optional-properties-to-required-properties-that-may-be-undefined-7482cb4e1585
export type Complete<T> = {
  [P in keyof Required<T>]: Pick<T, P> extends Required<Pick<T, P>> ? T[P] : (T[P] | undefined);
};

/**
 * Source: https://stackoverflow.com/a/48244432/4744177
 */
export type AtLeastOne<T, U = {[K in keyof T]: Pick<T, K> }> = Partial<T> & U[keyof U];

export type MaybePromise<T> = Promise<T> | T;
export type DeepPartial<T> = T extends Function ? T : (
  // In TS, null does not extend object, so this works
  // Test it yourself: `type test = null extends object ? 'null extends object' : 'null does not extend object'`
  T extends object ? {
    [P in keyof T]?: DeepPartial<T[P]>;
  } : T
);

export type WithRequired<T, K extends keyof T> = T & Required<Pick<T, K>>;

/**
 * Taken from https://github.com/Microsoft/TypeScript/issues/28046#issuecomment-480516434
 */
 export type ElementType<T extends ReadonlyArray<unknown>> = T extends ReadonlyArray<infer ElementType>
 ? ElementType
 : never;

export type NonFunctionKeys<T> = {
 [Key in keyof T]-?: T[Key] extends Function ? never : Key;
}[keyof T];

export type NonNullableKeys<T> = {
 [P in keyof T]-?: null extends T[P] ? never : P;
}[keyof T];

export type PickNonNullable<T> = Pick<T, NonNullableKeys<T>>;

/**
* Great breakdown of how this works here:
* https://mariusschulz.com/blog/conditional-types-in-typescript
*/
export type NonNilKeys<T> = {
 [P in keyof T]-?: null extends T[P] ? never : undefined extends T[P] ? never : P;
}[keyof T];

export type PickNonNillable<T> = Pick<T, NonNilKeys<T>>;
export type PickNillable<T> = Omit<T, NonNilKeys<T>>;

export type PartialNullableObject<T, NullableKey extends keyof T> = {
 [P in keyof T]: P extends NullableKey ? T[P] | null : T[P];
};

export type RequiredKeys<T> = {
 [P in keyof T]-?: undefined extends T[P] ? never : P;
}[keyof T];

export type OptionalKeys<T> = {
 [P in keyof T]-?: undefined extends T[P] ? P : never;
}[keyof T];

export type PickNonOptionalProperties<T> = Pick<T, RequiredKeys<T>>;
export type PickOptionalProperties<T> = Omit<T, RequiredKeys<T>>;

export type MappedNonNullable<T> = {
 [P in keyof T]: NonNullable<T[P]>;
};

export type Nil = undefined | null;
export type NullIfUndefined<T> = undefined extends T ? Exclude<T, undefined> | null : T;

export type Nillable<T> = T | null | undefined;

export type MappedNullIfUndefined<T> = {
 [P in keyof T]: NullIfUndefined<T[P]>;
};

/**
* Source: https://github.com/microsoft/TypeScript/issues/3496#issuecomment-128553540
*/
export type JSONValue = string | number | boolean | JSONObject | JSONArray;

export interface JSONObject {
   [x: string]: JSONValue;
}

export interface JSONArray extends Array<JSONValue> { }

export type PromiseResolveType<T> = T extends PromiseLike<infer U> ? U : T;
export type ResolvedObject<T> = PromiseResolveType<{ [P in keyof T]: PromiseResolveType<T[P]> }>;