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>;
}
