import { Result } from ".";

/**
 * A useful guard for the our handler framework where a route function can
 * return `void` to indicate it is handling the response or a `Result` so the
 * framework can automatically handle success and failure.
 */
export function isResult<T, E>(u: unknown): u is Result<T, E> {
  return (
    u instanceof Result.Ok ||
    u instanceof Result.Err ||
    isSuccess(u) ||
    isError(u)
  );
}

function isSuccess<T>(u: unknown): u is Result.Ok<T> {
  return (
    isObject(u) &&
    "kind" in u &&
    u.kind === "ok" &&
    "_value" in u &&
    hasMethod(u, "isOk") &&
    hasCombinators(u)
  );
}

function isError<T>(u: unknown): u is Result.Ok<T> {
  return (
    isObject(u) &&
    "kind" in u &&
    u.kind === "err" &&
    "_error" in u &&
    hasMethod(u, "isErr") &&
    hasCombinators(u)
  );
}

function isObject(u: unknown): u is object {
  return u !== undefined && u !== null && typeof u === "object";
}

function hasMethod(u: object, method: string): boolean {
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  return method in u && typeof (u as any)[method] === "function";
}

function hasCombinators(u: object): boolean {
  return [
    "and",
    "andThen",
    "andThenAsync",
    "or",
    "orElse",
    "orElseAsync",
    "yield",
    "yieldAsync",
    "yieldErr",
    "yieldOr",
    "yieldOrElse",
  ].every((combinator) => hasMethod(u, combinator));
}

export const __internals = {
  isError,
  isSuccess,
};
