import { convexToJson, jsonToConvex } from "@convex-dev/common";
import { GenericDataModel } from "../data_model.js";
import {
  MutationCtx,
  PublicMutation,
  PublicQuery,
  QueryCtx,
} from "../registration.js";
import { setupAuth } from "./authentication_impl.js";
import { setupReader, setupWriter } from "./database_impl.js";

async function invokeMutation<
  F extends (ctx: MutationCtx<GenericDataModel>, ...args: any) => any
>(func: F, argsStr: string) {
  const args = jsonToConvex(JSON.parse(argsStr));
  const mutationCtx = {
    db: setupWriter(),
    auth: setupAuth(),
  };
  const result = await Promise.resolve(func(mutationCtx, ...(args as any)));
  return JSON.stringify(convexToJson(result === undefined ? null : result));
}

/**
 * Define a mutation in this Convex app's public API.
 *
 * This function will be allowed to modify your Convex database and will be accessible from the client.
 *
 * If you're using code generation, use the `mutation` function in
 * `convex/_generated/server.d.ts` which is typed for your data model.
 *
 * @param func - The mutation function. It receives a {@link MutationCtx} as its first argument.
 * @returns The wrapped mutation. Include this as an `export` to name it and make it accessible.
 *
 * @public
 */
export const mutationGeneric = <
  DataModel extends GenericDataModel,
  Args extends any[],
  Output
>(
  func: (ctx: MutationCtx<DataModel>, ...args: Args) => Output
): PublicMutation<DataModel, Args, Output> => {
  const m = func as unknown as PublicMutation<DataModel, Args, Output>;
  // Helpful runtime check that functions are only be registered once
  if (m.isRegistered) {
    throw new Error("Function registered twice " + func);
  }
  m.isRegistered = true;
  m.isMutation = true;
  m.invokeMutation = argsStr => invokeMutation(func as any, argsStr);
  return m;
};

async function invokeQuery<
  F extends (ctx: QueryCtx<GenericDataModel>, ...args: any) => any
>(func: F, argsStr: string) {
  const args = jsonToConvex(JSON.parse(argsStr));
  const queryCtx = {
    db: setupReader(),
    auth: setupAuth(),
  };
  const result = await Promise.resolve(func(queryCtx, ...(args as any)));
  return JSON.stringify(convexToJson(result === undefined ? null : result));
}

/**
 * Define a query in this Convex app's public API.
 *
 * This function will be allowed to read your Convex database and will be accessible from the client.
 *
 * If you're using code generation, use the `query` function in
 * `convex/_generated/server.d.ts` which is typed for your data model.
 *
 * @param func - The query function. It receives a {@link QueryCtx} as its first argument.
 * @returns The wrapped query. Include this as an `export` to name it and make it accessible.
 *
 * @public
 */
export const queryGeneric = <
  DataModel extends GenericDataModel,
  Args extends any[],
  Output
>(
  func: (ctx: QueryCtx<DataModel>, ...args: Args) => Output
): PublicQuery<DataModel, Args, Output> => {
  const q = func as unknown as PublicQuery<DataModel, Args, Output>;
  // Helpful runtime check that functions are only be registered once
  if (q.isRegistered) {
    throw new Error("Function registered twice " + func);
  }
  q.isRegistered = true;
  q.isQuery = true;
  q.invokeQuery = argsStr => invokeQuery(func as any, argsStr);
  return q;
};
