import {
  BaseRecord,
  CreateManyParams,
  CreateManyResponse,
  CreateParams,
  CreateResponse,
  CustomParams,
  GetListParams,
  GetListResponse,
  GetOneParams,
  GetOneResponse,
  HttpError,
} from '@refinedev/core';
import { Client, CombinedError, cacheExchange, fetchExchange, gql } from '@urql/core';
import {
  apitoConnectionFilterConditionType,
  apitoGraphQLComposedTypeName,
  apitoListCountKeyConditionType,
  apitoListCountWhereInputType,
  apitoListGraphQLTypeName,
  apitoListKeyConditionType,
  apitoMultipleResourceName,
  apitoSingularGraphQLTypeName,
  apitoModelName,
  apitoSortInputType,
  apitoWhereInputType,
  apitoWhereRelationFilterConditionType,
  buildApitoCreateMutation,
  formatApitoConnectionSubselections,
} from './apitoGraphqlNames';
import {
  ApitoGraphQLError,
  CustomResponse,
  ExtendedDataProvider,
  ResponseType,
  SingleResponseType,
} from './types';

/** Property names that must not come from user-controlled filter input (prototype pollution). */
const UNSAFE_DYNAMIC_KEYS = new Set(['__proto__', 'constructor', 'prototype']);

function isSafeDynamicKey(key: unknown): key is string {
  return typeof key === 'string' && key.length > 0 && !UNSAFE_DYNAMIC_KEYS.has(key);
}

/*
Apito Typical Graphql Error Response:
{
"data": {
  "deleteTestLabel": null
},
"errors": [
  {
    "message": "there are 1 relations that are using this document, please delete them first",
    "locations": [
      {
        "line": 2,
        "column": 3
      }
    ],
    "path": [
      "deleteTestLabel"
    ]
  }
]
}
*/

/*
Apito Typical Graphql Success Response:
{
"data": {
  "testLabelList": [
    {
      "data": {
        "description": {
          "text": null
        },
        "measure_unit": "mmol/l",
        "name": "Corres. Urine Sugar",
        "reference_range": "<7.8 mmol/l"
      },
      "id": "1ac785e3-a190-44a5-bc36-d858df8a3868",
      "meta": {
        "created_at": "2025-03-10T08:10:55Z",
        "status": true,
        "updated_at": "2025-03-10T08:10:55Z"
      }
    },
    {
      "data": {
        "description": {
          "text": null
        },
        "measure_unit": "mmol/l",
        "name": "P Glucose (F)",
        "reference_range": "3.6-5.6 mmol/l"
      },
      "id": "0c7e3a18-765c-4fed-a091-768578804fdc",
      "meta": {
        "created_at": "2025-03-10T08:10:05Z",
        "status": true,
        "updated_at": "2025-03-10T08:10:05Z"
      }
    },
    {
      "data": {
        "description": {
          "text": null
        },
        "measure_unit": "mmol/L",
        "name": "T4",
        "reference_range": "3.6-5.6 mmol/L"
      },
      "id": "13123014-8bb7-4850-9699-8eb4f0607305",
      "meta": {
        "created_at": "2025-02-17T13:32:56Z",
        "status": true,
        "updated_at": "2025-02-17T13:32:56Z"
      }
    },
    {
      "data": {
        "description": {
          "text": null
        },
        "measure_unit": "mg/dl",
        "name": "S. Creatinine",
        "reference_range": "0.6-1.2 mg/dl"
      },
      "id": "c9c9c9c9-c9c9-c9c9-c9c9-c9c9c9c9c9c9",
      "meta": {
        "created_at": "2025-02-17T13:32:56Z",
        "status": true,
        "updated_at": "2025-02-17T13:32:56Z"
      }
    }
  ],
  "testLabelListCount": {
    "total": 4
  }
}
}
*/

/**
 * Handles GraphQL errors from Apito responses
 * @param error The error object from urql client
 * @param onTokenExpired Optional callback for handling 403 token expiration
 * @returns An HttpError object with appropriate status code and message
 */
const handleGraphQLError = (
  error: CombinedError | undefined,
  onTokenExpired?: () => void
): HttpError => {
  if (!error) {
    return {
      message: 'Unknown error occurred',
      statusCode: 500,
    };
  }

  // Handle network errors
  if (error.networkError) {
    // Check for 403 status in network error
    const statusCode =
      (error.networkError as any).statusCode ||
      (error.networkError as any).status;
    if (statusCode === 403 || statusCode === 401) {
      console.log('Token expired (403/401), triggering logout...');
      onTokenExpired?.();
      return {
        message: 'Token expired. Please login again.',
        statusCode: 403,
      };
    }

    return {
      message: `Network error: ${error.networkError.message}`,
      statusCode: statusCode || 503, // Service Unavailable
    };
  }

  // Handle GraphQL errors
  if (error.graphQLErrors && error.graphQLErrors.length > 0) {
    // Check for authentication/authorization errors in GraphQL errors
    const hasAuthError = error.graphQLErrors.some(
      (err) =>
        err.message.toLowerCase().includes('unauthorized') ||
        err.message.toLowerCase().includes('forbidden') ||
        err.message.toLowerCase().includes('token') ||
        err.message.toLowerCase().includes('authentication') ||
        err.message.toLowerCase().includes('authorization')
    );

    if (hasAuthError) {
      console.log(
        'Authentication error detected in GraphQL, triggering logout...'
      );
      onTokenExpired?.();
      return {
        message: 'Authentication failed. Please login again.',
        statusCode: 403,
      };
    }

    const errorMessages = error.graphQLErrors
      .map((err) => err.message)
      .join(', ');
    return {
      message: errorMessages,
      statusCode: 400, // Bad Request for GraphQL validation errors
    };
  }

  // Fallback error
  return {
    message: error.message || 'An error occurred during the GraphQL operation',
    statusCode: 400,
  };
};

const apitoDataProvider = (
  apiUrl: string,
  token: string,
  onTokenExpired?: () => void
): ExtendedDataProvider => {
  const client = new Client({
    url: apiUrl,
    exchanges: [cacheExchange, fetchExchange],
    fetchOptions: () => ({
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${token}`,
        'Content-Type': 'application/json',
      },
    }),
    preferGetMethod: false,
  });

  return {
    getApiUrl: () => apiUrl,
    getApiClient: () => {
      return new Client({
        url: apiUrl,
        exchanges: [cacheExchange, fetchExchange],
        fetchOptions: () => ({
          method: 'POST',
          headers: {
            'Authorization': `Bearer ${token}`,
            'Content-Type': 'application/json',
          },
        }),
        preferGetMethod: false,
      });
    },
    getToken: () => token,
    async getList<TData extends BaseRecord = BaseRecord>(
      params: GetListParams
    ): Promise<GetListResponse<TData>> {
      try {
        const { resource, filters, sorters, pagination, meta } = params;
        const connectionFields = meta?.connectionFields || {};
        const aliasFields = meta?.aliasFields || {};
        const reverseLookup = meta?.reverseLookup || {};

        let data: TData[] = [];
        let total = 0;

        let query = null;
        let variables = null;
        if (meta?.gqlQuery) {
          query = meta.gqlQuery;
          variables = meta.variables;
          const queryKey = meta.queryKey || resource;
          const response = await client
            .query<ResponseType>(query as any, variables)
            .toPromise();

          if (response.error) {
            return Promise.reject(
              handleGraphQLError(response.error, onTokenExpired)
            );
          }

          const queryResponse = response?.data?.[queryKey];
          const responseData = (
            Array.isArray(queryResponse)
              ? (queryResponse as unknown as TData[])
              : []
          ) as TData[];
          const responseTotal = responseData.length ?? 0;

          return {
            data: responseData,
            total: responseTotal,
          };
        } else {
          const fields = meta?.fields || ['id']; // Fallback to 'id' if fields are not provided
          const listPascal = apitoListGraphQLTypeName(resource);
          const pluralResource = listPascal;

          // Helper function to process filters recursively
          const processFilter = (filter: any): any => {
            const { field, operator, value } = filter;

            // Handle special case where operator is "eq" and value is an array
            if (operator === 'eq' && Array.isArray(value)) {
              // Create a nested object structure for this case (null prototype + safe keys)
              const nestedCondition = Object.create(null) as Record<
                string,
                Record<string, unknown>
              >;
              value.forEach((condition) => {
                const {
                  field: subField,
                  operator: subOperator,
                  value: subValue,
                } = condition;
                if (
                  subField &&
                  subOperator &&
                  subValue !== undefined &&
                  isSafeDynamicKey(subField) &&
                  isSafeDynamicKey(subOperator)
                ) {
                  if (!nestedCondition[subField]) {
                    nestedCondition[subField] = Object.create(null) as Record<
                      string,
                      unknown
                    >;
                  }
                  nestedCondition[subField][subOperator] = subValue;
                }
              });
              if (!isSafeDynamicKey(field)) {
                return {};
              }
              return { [field]: nestedCondition };
            }

            // Handle OR operation
            if (operator === 'or' && Array.isArray(value)) {
              const orConditions = Object.create(null) as Record<string, unknown>;
              value.forEach((condition) => {
                const { field, operator, value } = condition;
                if (field && operator && value !== undefined) {
                  // Adjust `data.name` to `name`
                  const adjustedField = field.startsWith('data.')
                    ? field.replace('data.', '')
                    : field;
                  if (isSafeDynamicKey(adjustedField) && isSafeDynamicKey(operator)) {
                    orConditions[adjustedField] = { [operator]: value };
                  }
                }
              });
              return { OR: orConditions };
            }

            // Handle AND operation
            if (operator === 'and' && Array.isArray(value)) {
              const andConditions = Object.create(null) as Record<string, unknown>;
              value.forEach((condition) => {
                const { field, operator, value } = condition;
                if (field && operator && value !== undefined) {
                  // Adjust `data.name` to `name`
                  const adjustedField = field.startsWith('data.')
                    ? field.replace('data.', '')
                    : field;
                  if (isSafeDynamicKey(adjustedField) && isSafeDynamicKey(operator)) {
                    andConditions[adjustedField] = { [operator]: value };
                  }
                }
              });
              return { AND: andConditions };
            }

            // Handle regular field filters
            if (field === '_key') {
              const keyOp = operator || 'eq';
              if (!isSafeDynamicKey(keyOp)) {
                return {};
              }
              return { _key: { [keyOp]: value } };
            }

            if (field && field.includes('relation.')) {
              const relationPath = field.replace('relation.', '').split('.');
              if (!relationPath.length || !relationPath.every(isSafeDynamicKey)) {
                return {};
              }
              const relationCondition = Object.create(null) as Record<string, any>;

              // Build nested object structure
              let current: Record<string, any> = relationCondition;
              for (let i = 0; i < relationPath.length - 1; i++) {
                const part = relationPath[i];
                if (!current[part]) {
                  current[part] = Object.create(null) as Record<string, any>;
                }
                current = current[part];
              }

              const lastPart = relationPath[relationPath.length - 1];
              if (
                operator &&
                value !== undefined &&
                isSafeDynamicKey(lastPart) &&
                isSafeDynamicKey(operator)
              ) {
                current[lastPart] = { [operator]: value };
              }

              return { relation: relationCondition };
            }

            if (operator && value !== undefined && typeof field === 'string') {
              // Adjust `data.name` to `name`
              const adjustedField = field.startsWith('data.')
                ? field.replace('data.', '')
                : field;
              if (isSafeDynamicKey(adjustedField) && isSafeDynamicKey(operator)) {
                return { [adjustedField]: { [operator]: value } };
              }
            }

            return {};
          };

          // Process filters
          let _key = null;
          let relationWhere: Record<string, any> | null = null;
          let where: Record<string, any> = {};

          if (filters && filters.length > 0) {
            filters.forEach((filter) => {
              const processed = processFilter(filter);

              // Extract _key if present
              if (processed._key) {
                _key = processed._key;
              }
              // Extract relation if present
              else if (processed.relation) {
                if (!relationWhere) {
                  relationWhere = {};
                }
                Object.assign(relationWhere, processed.relation);
              }
              // Handle OR/AND conditions
              else if (processed.OR) {
                where.OR = processed.OR;
              } else if (processed.AND) {
                where.AND = processed.AND;
              }
              // Handle regular conditions
              else {
                Object.assign(where, processed);
              }
            });
          }

          const hasKey = _key !== null;
          const hasRelationWhere = relationWhere !== null;

          const queryVariables = [
            hasKey ? `$_key: ${apitoListKeyConditionType(resource)}` : null,
            `$connection: ${apitoConnectionFilterConditionType(resource)}`,
            `$where: ${apitoWhereInputType(resource)}`,
            hasRelationWhere
              ? `$relationWhere: ${apitoWhereRelationFilterConditionType(resource)}`
              : null,
            hasKey ? `$_keyCount: ${apitoListCountKeyConditionType(resource)}` : null,
            `$whereCount: ${apitoListCountWhereInputType(resource)}`,
            hasRelationWhere
              ? `$relationWhereCount: ${apitoWhereRelationFilterConditionType(resource)}`
              : null,
            `$sort: ${apitoSortInputType(resource)}`,
            `$page: Int`,
            `$limit: Int`,
            `$local: LOCAL_TYPE_ENUM`,
          ]
            .filter(Boolean)
            .join('\n');

          const queryArguments = [
            hasKey ? '_key: $_key' : null,
            'connection: $connection',
            'where: $where',
            hasRelationWhere ? 'relation: $relationWhere' : null,
            'sort: $sort',
            'page: $page',
            'limit: $limit',
            'local: $local',
          ]
            .filter(Boolean)
            .join(', ');

          const countArguments = [
            hasKey ? '_key: $_keyCount' : null,
            'connection: $connection',
            'where: $whereCount',
            hasRelationWhere ? 'relation: $relationWhereCount' : null,
            'page: $page',
            'limit: $limit',
          ]
            .filter(Boolean)
            .join(', ');

          query = gql`
                        query Get${pluralResource}(
                            ${queryVariables}
                        ) {
                            ${apitoMultipleResourceName(resource)}(${queryArguments}) {
                                id
                                data {
                                    ${fields.join('\n')}
                                }
                                ${formatApitoConnectionSubselections(connectionFields, aliasFields)}
                                meta {
                                    created_at
                                    status
                                    updated_at
                                }
                            }
                            ${apitoMultipleResourceName(resource)}Count(${countArguments}) {
                                total
                            }
                        }
                    `;

          variables = {
            ...(hasKey && { _key: _key }),
            connection: reverseLookup || {},
            where: where || {},
            ...(hasRelationWhere && { relationWhere: relationWhere }),
            whereCount: where || {},
            ...(hasKey && { _keyCount: _key }),
            ...(hasRelationWhere && { relationWhereCount: relationWhere }),
            sort: sorters?.reduce((acc: Record<string, any>, sorter: any) => {
              const { field, order } = sorter;
              if (field && order) {
                acc[field] = order.toUpperCase(); // Convert to ASC/DESC
              }
              return acc;
            }, {}),
            page: pagination?.currentPage || 1,
            limit: pagination?.pageSize || 10,
          };

          const response = await client
            .query<ResponseType>(query as any, variables)
            .toPromise();

          if (response.error) {
            return Promise.reject(
              handleGraphQLError(response.error, onTokenExpired)
            );
          }

          const listRoot = apitoMultipleResourceName(resource);
          data = (response?.data?.[listRoot] ?? []) as unknown as TData[];
          total =
            'total' in (response?.data?.[`${listRoot}Count`] || {})
              ? (response?.data?.[`${listRoot}Count`] as SingleResponseType)
                .total
              : 0;
        }

        return {
          data: data,
          total: total,
        };
      } catch (error) {
        if ((error as any).statusCode !== undefined) {
          return Promise.reject(error);
        }

        const httpError: HttpError = {
          message: (error as Error)?.message || 'Failed to fetch list data',
          statusCode: 500,
        };
        return Promise.reject(httpError);
      }
    },

    async getOne<TData extends BaseRecord = BaseRecord>(
      params: GetOneParams
    ): Promise<GetOneResponse<TData>> {
      try {
        const { resource, id, meta } = params;
        const fields = meta?.fields || ['id']; // Fallback to 'id' if fields are not provided
        const connectionFields = meta?.connectionFields || {};
        const aliasFields = meta?.aliasFields || {};
        const singularField = apitoModelName(resource);
        const singularPascal = apitoSingularGraphQLTypeName(resource);
        const query = gql`
                  query Get${singularPascal}($id: String!) {
                      ${singularField}(_id: $id) {
                          id
                          data {
                              ${fields.join('\n')}
                          }
                          ${formatApitoConnectionSubselections(connectionFields, aliasFields)}
                          meta {
                              created_at
                              status
                              updated_at
                          }
                      }
                  }
              `;

        const response = await client
          .query<ResponseType>(query, { id })
          .toPromise();

        if (response.error) {
          return Promise.reject(
            handleGraphQLError(response.error, onTokenExpired)
          );
        }

        const data = (response?.data?.[singularField] ?? {}) as TData;

        return {
          data: data,
        };
      } catch (error) {
        if ((error as any).statusCode !== undefined) {
          return Promise.reject(error);
        }

        const httpError: HttpError = {
          message:
            (error as Error)?.message ||
            `Failed to fetch ${params.resource} with id ${params.id}`,
          statusCode: 500,
        };
        return Promise.reject(httpError);
      }
    },

    async create<TData extends BaseRecord = BaseRecord, TVariables = any>(
      params: CreateParams<TVariables>
    ): Promise<CreateResponse<TData>> {
      try {
        const { resource, variables, meta } = params;
        let query = null;
        let _variables = null;
        if (meta?.gqlMutation) {
          query = meta.gqlMutation;
          if (variables) {
            _variables = variables;
          } else {
            _variables = meta.variables;
          }
          const response = await client
            .mutation<ResponseType>(query as any, _variables)
            .toPromise();

          if (response.error) {
            return Promise.reject(handleGraphQLError(response.error));
          }

          return {
            data:
              (
                response?.data?.[
                `create${apitoSingularGraphQLTypeName(resource)}`
                ] as SingleResponseType
              )?.data ?? {},
          };
        } else {
          try {
            const { resource, variables, meta } = params;
            const fields = (meta?.fields || ['id']) as string[]; // Fallback to 'id' if fields are not provided
            const name = apitoSingularGraphQLTypeName(resource);

            const query = gql(buildApitoCreateMutation(resource, fields));

            const variableData = variables as Record<string, any>;

            const response = await client
              .mutation<ResponseType>(query, {
                payload: variableData.data,
                connect: variableData.connect,
              })
              .toPromise();

            if (response.error) {
              return Promise.reject(
                handleGraphQLError(response.error, onTokenExpired)
              );
            }

            const data = (response?.data?.[`create${name}`] ?? {}) as TData;
            return { data: data };
          } catch (error) {
            if ((error as any).statusCode !== undefined) {
              return Promise.reject(error);
            }

            const httpError: HttpError = {
              message:
                (error as Error)?.message ||
                `Failed to create ${params.resource}`,
              statusCode: 500,
            };
            return Promise.reject(httpError);
          }
        }
      } catch (error) {
        if ((error as any).statusCode !== undefined) {
          return Promise.reject(error);
        }

        const httpError: HttpError = {
          message:
            (error as Error)?.message || `Failed to create ${params.resource}`,
          statusCode: 500,
        };
        return Promise.reject(httpError);
      }
    },

    async createMany<TData extends BaseRecord = BaseRecord, TVariables = any>(
      params: CreateManyParams<TVariables>
    ): Promise<CreateManyResponse<TData>> {
      try {
        const { resource, variables, meta } = params;
        const fields = meta?.fields || ['id']; // Fallback to 'id' if fields are not provided
        const listPascal = apitoListGraphQLTypeName(resource);
        const upsertPayloadType = apitoGraphQLComposedTypeName(
          resource,
          'List_Upsert_Payload'
        );
        const listConnectType = apitoGraphQLComposedTypeName(
          resource,
          'Relation_Connect_Payload'
        );

        const mutation = gql`
                  mutation Upsert${listPascal}($payloads: [${upsertPayloadType}!]!, $connect: ${listConnectType}) {
                      upsert${listPascal}(payloads: $payloads, connect: $connect, status: published) {
                          id
                          data {
                              ${fields.join('\n')}
                          }
                          meta {
                              created_at
                              status
                              updated_at
                          }
                      }
                  }
              `;

        // Clean up the array by filtering out empty objects, null, or undefined values
        const variableData = Array.isArray(variables)
          ? (variables as any[]).filter(
            (item) =>
              item !== null &&
              item !== undefined &&
              (typeof item !== 'object' || Object.keys(item).length > 0)
          )
          : (variables as Record<string, any>);

        const response = await client
          .mutation<ResponseType>(mutation, {
            payloads: variableData,
            //connect: variableData.connect,
          })
          .toPromise();

        if (response.error) {
          return Promise.reject(
            handleGraphQLError(response.error, onTokenExpired)
          );
        }

        const data = (response?.data?.[`upsert${listPascal}`] ??
          []) as unknown as TData[];
        return { data: data };
      } catch (error) {
        if ((error as any).statusCode !== undefined) {
          return Promise.reject(error);
        }

        const httpError: HttpError = {
          message:
            (error as Error)?.message ||
            `Failed to create multiple ${params.resource} records`,
          statusCode: 500,
        };
        return Promise.reject(httpError);
      }
    },

    async update({ resource, id, variables, meta }) {
      try {
        let query = null;
        let _variables = null;
        if (meta?.gqlMutation) {
          query = meta.gqlMutation;
          if (variables) {
            _variables = variables;
          } else {
            _variables = meta.variables;
          }
          const response = await client
            .mutation<ResponseType>(query as any, _variables)
            .toPromise();

          if (response.error) {
            return Promise.reject(handleGraphQLError(response.error));
          }

          return {
            data:
              (
                response?.data?.[
                `update${apitoSingularGraphQLTypeName(resource)}`
                ] as SingleResponseType
              )?.data ?? {},
          };
        } else {
          const fields = meta?.fields || ['id']; // Fallback to 'id' if fields are not provided
          const deltaUpdate = meta?.deltaUpdate || false;
          const includeRelations = meta?.relation !== false;
          const name = apitoSingularGraphQLTypeName(resource);
          const updatePayload = apitoGraphQLComposedTypeName(resource, 'Update_Payload');
          const relConn = apitoGraphQLComposedTypeName(
            resource,
            'Relation_Connect_Payload'
          );
          const relDis = apitoGraphQLComposedTypeName(
            resource,
            'Relation_Disconnect_Payload'
          );
          const relationVarDefs = includeRelations
            ? `,
                          $connect: ${relConn},
                          $disconnect: ${relDis}`
            : '';
          const relationArgs = includeRelations
            ? `, connect: $connect, disconnect: $disconnect`
            : '';
          query = gql`
                      mutation Update${name}(
                          $id: String!,
                          $deltaUpdate: Boolean,
                          $payload: ${updatePayload}!${relationVarDefs}
                      ) {
                          update${name}(_id: $id, deltaUpdate: $deltaUpdate, payload: $payload${relationArgs}, status: published) {
                              id
                              data {
                                  ${fields.join('\n')}
                              }
                              meta {
                                  created_at
                                  status
                                  updated_at
                              }
                          }
                      }
                  `;
          _variables = {
            id: id,
            deltaUpdate: deltaUpdate,
            payload: (variables as Record<string, any>).data,
          };
          if (includeRelations) {
            (_variables as Record<string, any>).connect = (
              variables as Record<string, any>
            ).connect;
            (_variables as Record<string, any>).disconnect = (
              variables as Record<string, any>
            ).disconnect;
          }
          const response = await client
            .mutation<ResponseType>(query as any, _variables)
            .toPromise();

          if (response.error) {
            return Promise.reject(
              handleGraphQLError(response.error, onTokenExpired)
            );
          }

          return {
            data:
              (
                response?.data?.[`update${name}`] as SingleResponseType
              )?.data ?? {},
          };
        }
      } catch (error) {
        if ((error as any).statusCode !== undefined) {
          return Promise.reject(error);
        }

        const httpError: HttpError = {
          message:
            (error as Error)?.message ||
            `Failed to update ${resource} with id ${id}`,
          statusCode: 500,
        };
        return Promise.reject(httpError);
      }
    },

    async deleteOne({ resource, id }) {
      try {
        const name = apitoSingularGraphQLTypeName(resource);

        const query = gql`
                  mutation Delete${name}($ids: [String]!) {
                      delete${name}(_ids: $ids) {
                          response
                      }
                  }
              `;

        const response = await client
          .mutation<ResponseType>(query, { ids: [id] })
          .toPromise();

        // Check for GraphQL errors in the response
        if (response.error) {
          return Promise.reject(
            handleGraphQLError(response.error, onTokenExpired)
          );
        }

        // Check for errors in the data response (Apito specific error format)
        if (response.data?.errors && Array.isArray(response.data.errors)) {
          const errorMessages = (response.data.errors as ApitoGraphQLError[])
            .map((err) => err.message)
            .join(', ');

          const httpError: HttpError = {
            message: errorMessages,
            statusCode: 400,
          };
          return Promise.reject(httpError);
        }

        return {
          data:
            (
              response?.data?.[`delete${name}`] as SingleResponseType
            )?.data ?? {},
        };
      } catch (error) {
        if ((error as any).statusCode !== undefined) {
          return Promise.reject(error);
        }

        const httpError: HttpError = {
          message:
            (error as Error)?.message ||
            `Failed to delete ${resource} with id ${id}`,
          statusCode: 500,
        };
        return Promise.reject(httpError);
      }
    },

    async custom<TData extends BaseRecord = BaseRecord>(
      params: CustomParams<any, any>
    ): Promise<CustomResponse<TData>> {
      try {
        const query = params?.meta?.gqlQuery;
        const mutation = params?.meta?.gqlMutation;
        let variables = params?.meta?.gqlVariables;

        if (query && mutation) {
          const httpError: HttpError = {
            message:
              'Query and mutation cannot both be provided for custom operation',
            statusCode: 400,
          };
          return Promise.reject(httpError);
        }

        if (!query && !mutation) {
          const httpError: HttpError = {
            message: 'Query or mutation is required for custom operation',
            statusCode: 400,
          };
          return Promise.reject(httpError);
        }

        const { filters } = params;
        // Transform filters into a `where` object
        const where = filters?.reduce(
          (acc: Record<string, any>, filter: any) => {
            const { field, operator, value } = filter;
            if (operator && value !== undefined) {
              // Adjust `data.name` to `name`
              const adjustedField = field.startsWith('data.')
                ? field.replace('data.', '')
                : field;
              acc[adjustedField] = { [operator || 'eq']: value };
            }
            return acc;
          },
          {}
        );

        if (where) {
          variables = {
            ...variables,
            where: where || {},
          };
        }

        // Convert payloads object with numeric keys to array
        if (
          variables?.payloads &&
          typeof variables.payloads === 'object' &&
          !Array.isArray(variables.payloads)
        ) {
          variables = {
            ...variables,
            payloads: Object.values(variables.payloads),
          };
        }

        //debugger;

        let response = null;
        if (query) {
          response = await client
            .query<ResponseType>(query as any, variables)
            .toPromise();
        } else if (mutation) {
          response = await client
            .mutation<ResponseType>(mutation as any, variables)
            .toPromise();
        } else {
          throw new Error('No query or mutation provided');
        }

        //debugger;

        if (response.error) {
          return Promise.reject(
            handleGraphQLError(response.error, onTokenExpired)
          );
        }

        // Check for errors in the data response (Apito specific error format)
        if (response.data?.errors && Array.isArray(response.data.errors)) {
          const errorMessages = (response.data.errors as ApitoGraphQLError[])
            .map((err) => err.message)
            .join(', ');

          const httpError: HttpError = {
            message: errorMessages,
            statusCode: 400,
          };
          return Promise.reject(httpError);
        }

        //debugger;

        return {
          data: response?.data as TData,
        };
      } catch (error) {
        if ((error as any).statusCode !== undefined) {
          return Promise.reject(error);
        }

        const httpError: HttpError = {
          message:
            (error as Error)?.message || 'Failed to execute custom operation',
          statusCode: 500,
        };
        return Promise.reject(httpError);
      }
    },
  };
};

export default apitoDataProvider;
