import { Client, fetchExchange } from '@urql/core';
import { devtoolsExchange } from '@urql/devtools';
import { cacheExchange, type CacheExchangeOpts, type KeyingConfig } from '@urql/exchange-graphcache';
import { persistedExchange } from '@urql/exchange-persisted';
import { retryExchange } from '@urql/exchange-retry';
import { ms } from '@wener/utils';
import { batchFetchExchange } from './batchFetchExchange';

export function createUrqlClient({
	getToken,
	url,
	persisted = false,
	batch = true,
	schema,
	cache,
	resolveTypeNameFromKey,
}: {
	url: string;
	getToken?: () => string | undefined | null;
	batch?: boolean;
	persisted?: boolean;
	schema?: any;
	cache?: Partial<CacheExchangeOpts>;
	resolveTypeNameFromKey?: (key: string) => string | null | undefined;
}): Client {
	const client = new Client({
		url,
		fetchSubscriptions: true,
		// requestPolicy: 'cache-and-network',
		exchanges: [
			process.env.NODE_ENV === 'development' && devtoolsExchange,
			cacheExchange({
				...cache,
				schema,
				keys: new Proxy({} as KeyingConfig, {
					get(target, prop, receiver) {
						if (typeof prop !== 'string') {
							return null;
						}
						let preset = target[prop];
						if (preset) {
							return preset;
						}
						// console.log(`cacheExchange key: ${prop}`);
						let getKey = (data: any) => data?.id;
						if (prop.endsWith('Payload')) {
							getKey = () => null;
						}
						return (target[prop] = getKey);
					},
				}),
				resolvers: {
					Query: {
						node: (parent, args, cache, info) => {
							let __typename;
							let id = args.id;
							if (typeof id === 'string' && resolveTypeNameFromKey) {
								// const idType = id.split('_')[0];
								// __typename = (
								//   {
								//     usr: 'User',
								//   } as Record<string, string>
								// )[idType];
								__typename = resolveTypeNameFromKey(id);
							}
							return __typename ? { __typename, id } : cache.resolve(parent as any, info.parentFieldKey);
						},
					},
					...cache?.resolvers,
				},
			}),
			// scalarExchange({
			//   schema: schema as any,
			//   scalars: {
			//     JSON(value) {
			//       if (value && typeof value === 'string') return JSON.parse(value);
			//       return value;
			//     },
			//   },
			// }),
			batch
				? batchFetchExchange({
						maxBatchSize: 1000,
						// batchScheduleFn: (callback) => setTimeout(callback, 15),
					})
				: undefined,
			persisted
				? persistedExchange({
						preferGetForPersistedQueries: true,
						// enforcePersistedQueries: true,
						enableForMutation: true,
						// https://the-guild.dev/graphql/codegen/plugins/presets/preset-client#normalized-caches-urql-and-apollo-client
						generateHash: (_, document: any) => Promise.resolve(document['__meta__']?.['hash']),
					})
				: undefined,
			retryExchange({
				initialDelayMs: 1000,
				maxDelayMs: ms('5m'),
				maxNumberAttempts: Number.POSITIVE_INFINITY,
				// 默认只重试网络错误
				retryIf: (err) => Boolean(err && err.networkError),
			}),
			fetchExchange,
		].filter(Boolean),
		fetchOptions: () => {
			let headers: Record<string, string> = {};
			const token = getToken?.();
			if (token) {
				headers['Authorization'] = `Bearer ${token}`;
			}
			return {
				headers,
			};
		},
	});

	return client;
}
