import { useHasuraClientInner } from './create-client';

export { type HasuraClient, useHasuraClientInner } from './create-client';
export * from './gqty';
export { GQtyError } from 'gqty';

import type { LegacyResolveOptions } from 'gqty';
import type { Mutation, Query, Subscription } from './gqty';
import type { GqlAuthParam } from './types';

export type ResolveOptionsWithSession<T> = LegacyResolveOptions<T>;

export function useHasuraClient(args?: GqlAuthParam) {
  const { client, subscriptionsClient } = useHasuraClientInner(args);
  const retries = args.retries || 3;
  return {
    client,
    subscriptionsClient,
    async mutate<T = unknown>(
      resolved: (sub: Mutation) => T,
      options?: ResolveOptionsWithSession<T>
    ) {
      if (!options) options = {};
      if (options.noCache === undefined) options.noCache = true;

      let err: any;
      for (let tries = 1; tries <= retries; tries++) {
        try {
          const result = await client.resolve(
            ({ mutation }) => resolved(mutation),
            options
          );
          return result;
        } catch (error: any) {
          err = error;
          if (error.message !== 'database query error') break;
          await new Promise((resolve) =>
            setTimeout(resolve, 1_000 * tries + 1_000)
          );
        }
      }
      if (args.onError) throw args.onError(err) || err;
      throw err;
    },
    async query<T = unknown>(
      resolved: (sub: Query) => T,
      options?: ResolveOptionsWithSession<T>
    ) {
      let err: any;
      for (let tries = 1; tries <= retries; tries++) {
        try {
          const result = await client.resolve(
            ({ query }) => resolved(query),
            options
          );
          return result;
        } catch (error: any) {
          err = error;
          if (error.message !== 'database query error') break;
          await new Promise((resolve) =>
            setTimeout(resolve, 1_000 * tries + 1_000)
          );
        }
      }
      if (args.onError) throw args.onError(err) || err;
      throw err;
    },
    subscribe<T = unknown>(
      resolved: (sub: Subscription) => T,
      onData: (data: T, unsubscribeFn: () => Promise<void>) => void,
      onError?: (err: Error, unsubscribeFn: () => Promise<void>) => void
    ) {
      if (!subscriptionsClient) throw new Error('Subscriptions not enabled');
      let unsub: undefined | (() => Promise<void>);

      const stop = async () => {
        if (!unsub) return;
        await unsub().catch(() => undefined);
      };

      client.resolved(() => resolved(client.subscription), {
        onSubscription(event) {
          if (event.unsubscribe) unsub = event.unsubscribe;

          if (event.type === 'data') onData(event.data, stop);
          else if (event.type === 'with-errors' && onError)
            onError(event.error, stop);
        },
      });
      return stop;
    },
  };
}
