import { getHttpStatusText } from '@wener/utils';

export function resolveErrorMessage(error: Error | any) {
  if (!error) {
    return;
  }

  if (!(error instanceof Error)) {
    if (error && typeof error === 'object') {
      if ('message' in error) {
        return error.message;
      }
      if ('detail' in error) {
        return error.detail;
      }
    }
    return String(error);
  }

  try {
    if (isZodError(error)) {
      return error.issues.map((v) => `${v.path.join('.')}: ${v.message}`).join(';');
    }
    if (isTypeBoxError(error)) {
      const { path, type, message } = error.error;
      return `${path}: ${message} (${type})`;
    }
    if (URQLError.is(error)) {
      return error.response.errors.map((v: any) => v.message).join(';');
    }

    const buildGraphQLError = (errors: GraphQLError[] = []) => {
      return errors
        .map((v) => {
          return `${v.message}`;
        })
        .join(';');
    };

    const buildGraphQLResponseError = ({
      response,
    }: {
      response: {
        status: number;
        errors?: GraphQLError[];
      };
    }) => {
      let s = [''];
      let status = response.status;
      if (status !== 200) {
        s.push(`${status} ${getHttpStatusText(status)}`);
      }
      if (response.errors) {
        s.push(buildGraphQLError(response.errors));
      }
      return s.join(' ');
    };

    if (GraphQL.isClientError(error)) {
      return buildGraphQLResponseError(error);
    }

    if (typeof error === 'object' && 'message' in error) {
      return error.message;
    }
  } catch (e) {
    console.error('resolveErrorMessage', e);
  }
  return String(error);
}

namespace GraphQL {
  interface GraphQLResponse<T = unknown> {
    data?: T;
    errors?: GraphQLError[];
    extensions?: unknown;
    status: number;
    headers?: Headers;

    [key: string]: unknown;
  }

  interface GraphQLRequestContext<V extends {} = {}> {
    query: string | string[];
    variables?: V;
  }

  type ClientError = Error & {
    response: GraphQLResponse;
    request: GraphQLRequestContext;
  };

  export function isClientError(error: any): error is ClientError {
    // graphql-client _ClientError
    // https://github.com/graffle-js/graffle/blob/graphql-request/src/legacy/classes/ClientError.ts
    return (
      typeof error.response === 'object' &&
      typeof error.request === 'object' &&
      typeof error.response.status === 'number' &&
      typeof error.request.query === 'string' &&
      (error.response.errors === undefined || error.response.errors === null || Array.isArray(error.response.errors))
    );
  }
}

function isTypeBoxError(error: any): error is TransformDecodeCheckError {
  return error.error?.path && error.error.type && error.error.message && error instanceof Error;
}

interface TransformDecodeCheckError extends Error {
  error: {
    path: string;
    type: string;
    message: string;
  };
}

function isZodError(error: any): error is ZodError {
  return error instanceof Error && Array.isArray((error as any).issues);
}

interface ZodIssue {
  fatal?: boolean;
  code: any;
  path: (string | number)[];
  message: string;
}

interface ZodError {
  issues: ZodIssue[];

  get errors(): ZodIssue[];

  get message(): string;

  get isEmpty(): boolean;
}

// https://commerce.nearform.com/open-source/urql/docs/basics/errors/
// https://github.com/urql-graphql/urql/blob/main/packages/core/src/utils/error.ts
// urql CombinedError
namespace URQLError {
  // import { GraphQLError } from '@0no-co/graphql.web';

  /** An abstracted `Error` that provides either a `networkError` or `graphQLErrors`.
   *
   * @remarks
   * During a GraphQL request, either the request can fail entirely, causing a network error,
   * or the GraphQL execution or fields can fail, which will cause an {@link ExecutionResult}
   * to contain an array of GraphQL errors.
   *
   * The `CombinedError` abstracts and normalizes both failure cases. When {@link OperationResult.error}
   * is set to this error, the `CombinedError` abstracts all errors, making it easier to handle only
   * a subset of error cases.
   *
   * @see {@link https://urql.dev/goto/docs/basics/errors} for more information on handling
   * GraphQL errors and the `CombinedError`.
   */
  export interface CombinedError extends Error {
    name: string;
    message: string;

    /** A list of GraphQL errors rehydrated from a {@link ExecutionResult}.
     *
     * @remarks
     * If an {@link ExecutionResult} received from the API contains a list of errors,
     * the `CombinedError` will rehydrate them, normalize them to
     * {@link GraphQLError | GraphQLErrors} and list them here.
     * An empty list indicates that no GraphQL error has been sent by the API.
     */
    graphQLErrors: GraphQLError[];

    /** Set to an error, if a GraphQL request has failed outright.
     *
     * @remarks
     * A GraphQL over HTTP request may fail and not reach the API. Any error that
     * prevents a GraphQl request outright, will be considered a “network error” and
     * set here.
     */
    networkError?: Error;

    /** Set to the {@link Response} object a fetch exchange received.
     *
     * @remarks
     * If a built-in fetch {@link Exchange} is used in `urql`, this may
     * be set to the {@link Response} object of the Fetch API response.
     * However, since `urql` doesn’t assume that all users will use HTTP
     * as the only or exclusive transport for GraphQL this property is
     * neither typed nor guaranteed and may be re-used for other purposes
     * by non-fetch exchanges.
     *
     * Hint: It can be useful to use `response.status` here, however, if
     * you plan on relying on this being a {@link Response} in your app,
     * which it is by default, then make sure you add some extra checks
     * before blindly assuming so!
     */
    response?: any;
  }

  export function is(error: any): error is CombinedError {
    return Array.isArray(error?.graphQLErrors) && error.name === 'CombinedError' && error instanceof Error;
  }
}

interface CombinedError extends Error {
  name: string;
  message: string;
  graphQLErrors: GraphQLError[];
  networkError?: Error;
  response?: any;
}

// import type { GraphQLError } from 'graphql'

interface GraphQLError extends Error {
  readonly locations: ReadonlyArray<any> | undefined;
  readonly path: ReadonlyArray<string | number> | undefined;
  readonly nodes: ReadonlyArray<any> | undefined;
  readonly source:
    | {
        body: string;
        name: string;
        locationOffset: {
          line: number;
          column: number;
        };
      }
    | undefined;
  readonly positions: ReadonlyArray<number> | undefined;
  readonly originalError: Error | undefined;
  readonly extensions: Record<string, any>;
}
