export type QueryVariables = { [param: string]: any };

export type ServerEndpoint = string;

export type QueryResponse = any;

export type Request = { query: Query; variables?: QueryVariables };

export enum OperationType {
  QUERY = 'query',
  MUTATION = 'mutation',
  SUBSCRIPTION = 'subscription',
}

export type Query = {
  operation: OperationType;
  endpoint: ServerEndpoint;
};

/**
 * Path where to find the data in the cache Object
 */
export type CacheDataPath = string | string[];

/**
 * endpoint => boolean (should we fetch it from the server or not)
 */
export type CacheLogicConditionForFetchType = {
  [endpoint: ServerEndpoint]: ConditionForFetchHandler;
} & { default: ConditionForFetchHandler };

export type ConditionForFetchHandler = (
  cache: CacheInterface,
  cacheKey: CacheDataPath,
  request: Request
) => boolean;

/**
 * Cache merger rule for every endpoint for every operation type
 * query => endpoint => mergerFunction
 */
export type CacheLogicMergerType = {
  [operation in OperationType]: ResponseMergerType;
};

export type ResponseMergerType = {
  [endpoint: ServerEndpoint]: ResponseMergerHandler;
} & { default: ResponseMergerHandler };

export type ResponseMergerHandler = (
  cache: CacheInterface,
  request: Request,
  response: QueryResponse
) => void;

/**
 * How to resolve the response from the cache
 */
export type CacheLogicResponseManipulatorType = {
  [operation in OperationType]: ResponseManipulatorType;
};

export type ResponseManipulatorType = {
  [endpoint: ServerEndpoint]: ResponseManipulatorHandler;
} & { default: ResponseManipulatorHandler };

export type ResponseManipulatorHandler = (
  cache: CacheInterface,
  cacheKey: CacheDataPath,
  request: Request
) => any;

export type CacheResetHandler = (cache: CacheInterface) => any;

export type CacheDataPathGeneratorType = (request: Request) => CacheDataPath;

export interface CacheLogic {
  makeCacheDataPath: CacheDataPathGeneratorType;
  conditionForFetch: CacheLogicConditionForFetchType;
  requestResponseMerger: CacheLogicMergerType;
  requestResponseManipulator: CacheLogicResponseManipulatorType;
  resetCache: CacheResetHandler;
}

export type RequestCacheStateType = {
  data: any;
  shouldFetch: boolean;
  cacheKey: CacheDataPath;
};

export interface CacheInterface {
  resetCache(): void;
  mergeRequestResult(request: Request, response: QueryResponse): void;
  getRequestCacheState(request: Request): RequestCacheStateType;
  getCacheData(cacheKey: CacheDataPath): any;
  getCacheDataByRequest(request: Request): any;
  setCacheData(cacheKey: CacheDataPath, data: any);
  setCacheDataByRequest(request: Request, data: any);
  deleteCacheData(cacheKey: CacheDataPath);
  deleteCacheDataByRequest(request: Request);
  getCacheLogic(): CacheLogic;
}
