export interface RequestContextBase {
  peerAuthorization?: string;
  method: string;
  requestId: number;
}

export type MethodHandler<Params, Result, PeerMethods, RequestContext> = (
  params: Params,
  ctx: RequestContext,
  peer: ISocketClient<PeerMethods>,
  next: () => Promise<Result>
) => Promise<Result>;

// A  generic middleware is a method handler that can be chained with any other method handler, so it uses `any` for the params and result types
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type GenericMiddleware<PeerMethods, RequestContext> = MethodHandler<any, any, PeerMethods, RequestContext>;

type NonEmptyArray<T> = [...T[], T];

export type MethodHandlers<Methods, PeerMethods, RequestContext> = {
  [Key in keyof Methods]: Methods[Key] extends (params: infer Params) => Promise<infer Result>
    ? NonEmptyArray<MethodHandler<Params, Result, PeerMethods, RequestContext>>
    : Methods[Key] extends Record<string, unknown>
      ? MethodHandlers<Methods[Key], PeerMethods, RequestContext>
      : never;
};

type ISocketClientMethodCall<Methods> = {
  [Key in keyof Methods]: Methods[Key] extends (params: infer P) => Promise<infer R>
    ? (params: P) => Promise<R>
    : Methods[Key] extends Record<string, unknown>
      ? ISocketClientMethodCall<Methods[Key]>
      : never;
};

export type ISocketClient<Methods> = {
  close: (reason?: string) => void;
  closeAndRejectPendingRequests: (reason?: string) => void;
  call: ISocketClientMethodCall<Methods>;
  requestWithTracing: <Params, Result>(
    method: string,
    params: Params,
    tracingHeaders: { traceparent?: string; tracestate?: string },
    authorization?: string
  ) => Promise<Result>;
};

export type MethodSchema<Params, Response> = (params: Params) => Promise<Response>;

/** The exception type can be thrown to cause the default error handler to write a raw error response to the socket */
export class SocketErrorException extends Error {
  readonly code: number;
  readonly message: string;

  constructor(code: number, message: string) {
    super(message);
    this.code = code;
    this.message = message;
  }
}

export type SocketTimeouts = {
  noResponseTimeoutInSeconds?: number;
  connectionTimeoutInSeconds?: number;
};
