export class ExtendedError extends Error {
  constructor(message: string) {
    super(message);

    this.name = "Error";
    this.message = message;
    this.stack = new Error().stack;
  }

  reason: unknown;
}

export type ToError = [Error, null];
export type ToValue<T> = [null, T];

export type To<T = any> = [Error | null, T | null];
export type Fn<T> = () => T;

const handleError = (error: unknown): ToError => {
  if (error instanceof Error) {
    return [error, null];
  } else {
    const extendedError = new ExtendedError(new String(error) as string);
    extendedError.reason = error;

    return [extendedError, null];
  }
};

const toAsync = async <T>(call: Promise<T>): Promise<ToValue<T> | ToError> => {
  let result;
  try {
    result = await call;
    return [null, result];
  } catch (error) {
    return handleError(error);
  }
};

export const to = <T = any>(
  call: Promise<T> | Fn<T>
): ToError | ToValue<T> | Promise<ToValue<T> | ToError> => {
  if (typeof call === "function") {
    try {
      const value = call();
      return [null, value];
    } catch (error) {
      return handleError(error);
    }
  }

  return toAsync(call);
};
