import { FetchResult, Operation } from '@apollo/client/core';
import { Breadcrumb as SentryBreadcrumb } from '@sentry/browser';
import dotProp from 'dot-prop';
import { print } from 'graphql';

import { extractDefinition } from './operation';
import { FullOptions, AttachBreadcrumbsOptions } from './options';

export interface BreadcrumbData {
  url?: string;
  query?: string;
  variables?: Record<string, unknown>;
  operationName?: string;
  fetchResult?: FetchResult;
  error?: Error;
  cache?: Record<string, unknown>;
  context?: Record<string, unknown>;
}

export interface GraphQLBreadcrumb extends SentryBreadcrumb {
  data: BreadcrumbData;
}

export function makeBreadcrumb(
  operation: Operation,
  options: FullOptions,
): GraphQLBreadcrumb {
  // We validated this is set before calling this function
  const attachBreadcrumbs =
    options.attachBreadcrumbs as AttachBreadcrumbsOptions;

  const definition = extractDefinition(operation);

  const data: BreadcrumbData = {};

  const uri = options.uri;
  if (uri) {
    data.url = uri;
  }

  const operationName = definition.name?.value;
  if (operationName) {
    data.operationName = operationName;
  }

  if (attachBreadcrumbs.includeQuery) {
    data.query =
      // The document might have been parsed with `noLocation: true`
      definition.loc?.source?.body ?? print(definition);
  }

  if (attachBreadcrumbs.includeVariables) {
    data.variables = operation.variables;
  }

  if (attachBreadcrumbs.includeCache) {
    data.cache =
      (operation.getContext().cache?.data?.data as Record<string, unknown>) ??
      undefined;
  }

  const contextKeys = attachBreadcrumbs.includeContext;
  if (contextKeys) {
    data.context = extractKeys(operation.getContext(), contextKeys);
  }

  return {
    type: 'http',
    category: `graphql.${definition.operation}`,
    data,
  };
}

function extractKeys(
  context: Record<string, unknown>,
  keys: Array<string>,
): Record<string, unknown> {
  const result: Record<string, unknown> = {};

  keys.forEach((key) => {
    result[key] = dotProp.get(context, key);
  });

  return result;
}