/* c8 ignore start */
import * as API from '@unito/integration-api';
import Logger from './logger.js';
import { Credentials } from '../middlewares/credentials.js';
import { Secrets } from '../middlewares/secrets.js';
import { Filter } from '../middlewares/filters.js';

type Maybe<T> = T | null;
type Empty = Record<string, never>;
type Params = Record<string, string>;
type Query = {
  [key: string]: undefined | string | string[] | Query | Query[];
};
type CreateItemBody = API.CreateItemRequestPayload;
type CreateBlobBody = API.CreateBlobRequestPayload;
type UpdateItemBody = API.UpdateItemRequestPayload;

/**
 * The base context object is passed to every handler function.
 *
 * It contains the parsed request params, query string, credentials, secrets, etc.
 */
export type Context<P extends Maybe<Params> = Maybe<Params>, Q extends Maybe<Query> = Maybe<Query>> = {
  /**
   * The parsed credentials associated with the request through the X-Unito-Credentials header.
   *
   * Will contain the keys for the variables defined in the corresponding configuration's authorization.
   */
  credentials: Credentials;
  /**
   * The parsed secrets associated with the request through the X-Unito-Secrets header.
   *
   * Will contain the keys for the secrets defined in the corresponding configuration's secrets.
   */
  secrets: Secrets;
  /**
   * The logger pre decorated with the correlation ID and the additionnal metadata provided through the request headers.
   */
  logger: Logger;
  /**
   * Each request is expected to complete within a certain time frame. This signal object has been instantiated with the
   * X-Unito-Operation-Deadline header. This header contains the timestamp after which Unito will consider the operation
   * to be timed out. You can use this signal to abort any operation that would exceed this time frame.
   */
  signal: AbortSignal | undefined;
  /**
   * The request params.
   *
   * A call to `/customers/:customerId/subscriptions/:subscriptionId` => `/customers/123/subscriptions/2` will yield:
   *
   * {
   *  customerId: '123',
   *  subscriptionId: '2'
   * }
   */
  params: P;
  /**
   * The raw query string parsed as an object.
   */
  query: Q;
};

/**
 * Context received by the `GetItemHandler`.
 *
 * @see {@link Context}
 */
export type GetItemContext<P extends Maybe<Params> = Empty, Q extends Query = Empty> = Context<P, Q>;

/**
 * Context received by the `GetBlobHandler`, same as `GetItemContext`.
 *
 * @see {@link Context}
 */
export type GetBlobContext<P extends Maybe<Params> = Empty, Q extends Query = Empty> = Context<P, Q>;

/**
 * Context received by the `GetCollectionHandler`.
 *
 * @filters Array of {@link Filter} representing the filterss parsed from the query params
 * @see {@link Context} for base params
 */
export type GetCollectionContext<P extends Maybe<Params> = Empty, Q extends Query = Empty> = Context<P, Q> & {
  /**
   * Parsed filter query param yielding a list of filters.
   *
   *  Given a filter query param:
   *  `filter=name=John,department.name=Engineering`
   *
   *  Context.filters will be:
   *  [
   *    { field: 'name', operator: 'EQUAL', values: ['John'] },
   *    { field: 'department.name', operator: 'EQUAL', values: ['Engineering'] }
   *  ]
   *
   * @see {@link Filter}
   */
  filters: Filter[];
  /**
   * Parsed search query param yielding a free-text search query.
   *
   *  Given a search query param:
   *  `search=John`
   *
   *  Context.search will be:
   *  'John'
   */
  search: string | null;
  /**
   * Parsed select query param yielding a list of fields to select.
   *
   *  Given a select query param:
   *  `select=name,department.name`
   *
   *  Context.selects will be:
   *  ['name', 'department.name']
   */
  selects: string[];
  /**
   * Parsed relations query param yielding a list of relation for which the path should be returned.
   *
   *  Given a relations query param:
   *  `relations=items,subitems`
   *
   *  Context.relations will be:
   *  ['items', 'subitems']
   */
  relations: string[];
};

export type CreateItemContext<
  P extends Maybe<Params> = Empty,
  Q extends Maybe<Query> = Empty,
  B extends CreateItemBody = API.CreateItemRequestPayload,
> = Context<P, Q> & { body: B };

export type CreateBlobContext<
  P extends Maybe<Params> = Empty,
  Q extends Maybe<Query> = Empty,
  B extends CreateBlobBody = API.CreateBlobRequestPayload,
> = Context<P, Q> & { body: B };

export type UpdateItemContext<
  P extends Maybe<Params> = Empty,
  Q extends Maybe<Query> = Empty,
  B extends UpdateItemBody = API.UpdateItemRequestPayload,
> = Context<P, Q> & { body: B };

export type DeleteItemContext<P extends Maybe<Params> = Empty, Q extends Query = Empty> = Context<P, Q>;

export type GetCredentialAccountContext<P extends Maybe<Params> = Empty, Q extends Query = Empty> = Context<P, Q>;

export type ParseWebhooksContext<
  P extends Maybe<Params> = Empty,
  Q extends Maybe<Query> = Empty,
  B extends API.WebhookParseRequestPayload = API.WebhookParseRequestPayload,
> = Omit<Context<P, Q>, 'credentials'> & { body: B };

export type UpdateWebhookSubscriptionsContext<
  P extends Maybe<Params> = Empty,
  Q extends Maybe<Query> = Empty,
  B extends API.WebhookSubscriptionRequestPayload = API.WebhookSubscriptionRequestPayload,
> = Context<P, Q> & { body: B };

export type AcknowledgeWebhooksContext<
  P extends Maybe<Params> = Empty,
  Q extends Maybe<Query> = Empty,
  B extends API.WebhookParseRequestPayload = API.WebhookParseRequestPayload,
> = Omit<Context<P, Q>, 'credentials'> & { body: B };
/* c8 ignore stop */
