/**
 * All of the utility types to describe a Convex API of queries and mutations
 */

import { Expand, PickByValue, UnionToIntersection } from "../type_utils";

/**
 * Description of the Convex functions available to an application.
 *
 * This is a generic type that expresses the shape of API types created by
 * `npx convex codegen`. It's used to make the Convex clients type-safe.
 *
 * @public
 */
export type GenericAPI = {
  queries: Record<string, (...args: any[]) => any>;
  mutations: Record<string, (...args: any[]) => any>;
};

/**
 * Helper types for interacting with the overall API type
 */

/**
 * The names of query functions in a Convex API.
 *
 * @public
 */
export type QueryNames<API extends GenericAPI> = keyof API["queries"] & string;

/**
 * The names of mutation functions in a Convex API.
 *
 * @public
 */
export type MutationNames<API extends GenericAPI> = keyof API["mutations"] &
  string;

/**
 * The type of a query function in a Convex API.
 *
 * @public
 */
export type NamedQuery<
  API extends GenericAPI,
  Name extends QueryNames<API>
> = API["queries"][Name];

/**
 * The type of a mutation function in a Convex API.
 *
 * @public
 */
export type NamedMutation<
  API extends GenericAPI,
  Name extends MutationNames<API>
> = API["mutations"][Name];

/**
 * Internal Codegen Type Helpers
 */

/**
 * Generate the fully-qualified query/mutation name of an export.
 *
 * This is `path/to/module:export` or `path/to/module` for the default export.
 */
type FunctionName<
  FilePath extends string,
  ExportName extends string
> = ExportName extends "default" ? FilePath : `${FilePath}:${ExportName}`;

/**
 * Generate a type of this module where each export is renamed to its
 * fully-qualified {@link FunctionName}.
 */
type NameModule<FilePath extends string, Module extends Record<string, any>> = {
  [ExportName in keyof Module as FunctionName<
    FilePath,
    ExportName & string
  >]: Module[ExportName];
};

/**
 * Name and merge together all of the exports in the `convex/` directory into
 * a flat object type.
 */
type MergeAllExports<Modules extends Record<string, Record<string, any>>> =
  UnionToIntersection<
    {
      [FilePath in keyof Modules]: NameModule<
        FilePath & string,
        Modules[FilePath]
      >;
    }[keyof Modules]
  >;

type UndefinedToNull<T> = T extends void ? null : T;

/**
 * Converts a map of query and mutation types into their client form.
 *
 * This is done by:
 * - Unwrapping `Promise` if it's in the output.
 * - Switching functions that output `undefined` to `null`.
 *
 */
type ConvertToClientFunctions<FunctionsByName extends Record<string, any>> = {
  [Name in keyof FunctionsByName]: (
    ...args: FunctionsByName[Name]["args"]
  ) => UndefinedToNull<Awaited<FunctionsByName[Name]["output"]>>;
};

/**
 * Create the API type from the types of all of the modules.
 *
 * Input is an object mapping file paths to the type of each module.
 *
 * For internal use by Convex code generation.
 *
 * @public
 */
export type ApiFromModules<
  Modules extends Record<string, Record<string, any>>
> = {
  queries: Expand<
    ConvertToClientFunctions<
      PickByValue<MergeAllExports<Modules>, { isQuery: true }>
    >
  >;
  mutations: Expand<
    ConvertToClientFunctions<
      PickByValue<MergeAllExports<Modules>, { isMutation: true }>
    >
  >;
};
