import { initGraphQLTada } from "gql.tada";
import { createClient, Client, fetchExchange } from "urql";
import type { OperationResult } from "urql";
// Intentional dev-time type reference: gql-tada reuses the sibling frontend app's
// generated introspection types, which are not exposed under a package name.
// oxlint-disable-next-line erp-kit/no-relative-packages
import type { introspection } from "../../../../frontend/src/graphql/generated/graphql-env.d";

export const graphql = initGraphQLTada<{
  introspection: introspection;
  scalars: {
    DateTime: string;
    Date: string;
    Time: string;
  };
}>();

export type { FragmentOf, ResultOf, VariablesOf } from "gql.tada";

export function createGraphQLClient(appUrl: string, token: string): Client {
  const endpoint = new URL("/query", appUrl).href;

  const client = createClient({
    url: endpoint,
    exchanges: [fetchExchange],
    requestPolicy: "network-only",
    preferGetMethod: false,
    fetchOptions: () => ({
      method: "POST",
      headers: {
        Authorization: `Bearer ${token}`,
        "Content-Type": "application/json",
      },
    }),
  });

  const wrapToPromise = <T extends OperationResult>(originalToPromise: () => Promise<T>) => {
    return async (): Promise<T> => {
      const result = await originalToPromise();
      if (result.error) {
        throw result.error;
      }
      return result;
    };
  };

  return {
    ...client,
    mutation: <Data = unknown, Variables extends Record<string, unknown> = Record<string, unknown>>(
      query: string,
      variables?: Variables,
    ) => {
      const original = client.mutation<Data, Variables>(query, variables!);
      return Object.assign(original, {
        toPromise: wrapToPromise(original.toPromise.bind(original)),
      });
    },
    query: <Data = unknown, Variables extends Record<string, unknown> = Record<string, unknown>>(
      query: string,
      variables?: Variables,
    ) => {
      const original = client.query<Data, Variables>(query, variables!);
      return Object.assign(original, {
        toPromise: wrapToPromise(original.toPromise.bind(original)),
      });
    },
  } as Client;
}
