import { FunctionReference, OptionalRestArgs } from "../server/api.js";
import { Id } from "../values/value.js";

/**
 * A {@link FunctionReference} that can be scheduled to run in the future.
 *
 * Schedulable functions are mutations and actions that are public or internal.
 *
 * @public
 */
export type SchedulableFunctionReference = FunctionReference<
  "mutation" | "action",
  "public" | "internal"
>;

/**
 * An interface to schedule Convex functions to run in the future.
 *
 * Available as `ctx.scheduler` in mutations and actions.
 *
 * **Execution guarantees:**
 * - **Scheduled mutations** are guaranteed to execute **exactly once**. They
 *   are automatically retried on transient errors.
 * - **Scheduled actions** execute **at most once**. They are not retried and
 *   may fail due to transient errors.
 *
 * Consider using an `internalMutation` or `internalAction` to ensure that
 * scheduled functions cannot be called directly from a client.
 *
 * @example
 * ```typescript
 * import { mutation } from "./_generated/server";
 * import { internal } from "./_generated/api";
 * import { v } from "convex/values";
 *
 * export const createOrder = mutation({
 *   args: { items: v.array(v.string()) },
 *   returns: v.null(),
 *   handler: async (ctx, args) => {
 *     const orderId = await ctx.db.insert("orders", { items: args.items });
 *
 *     // Run immediately after this mutation commits:
 *     await ctx.scheduler.runAfter(0, internal.emails.sendConfirmation, {
 *       orderId,
 *     });
 *
 *     // Run cleanup in 7 days:
 *     await ctx.scheduler.runAfter(
 *       7 * 24 * 60 * 60 * 1000,
 *       internal.orders.archiveOrder,
 *       { orderId },
 *     );
 *
 *     return null;
 *   },
 * });
 * ```
 *
 * @see https://docs.convex.dev/scheduling/scheduled-functions
 * @public
 */
export interface Scheduler {
  /**
   * Schedule a function to execute after a delay.
   *
   * @example
   * ```typescript
   * // Schedule to run as soon as possible (if this is a mutation it would be after this mutation commits):
   * await ctx.scheduler.runAfter(0, internal.tasks.process, { taskId });
   *
   * // Run after 5 seconds:
   * await ctx.scheduler.runAfter(5000, internal.tasks.process, { taskId });
   *
   * // Run after 1 hour:
   * await ctx.scheduler.runAfter(60 * 60 * 1000, internal.cleanup.run, {});
   * ```
   *
   * @param delayMs - Delay in milliseconds. Must be non-negative. If the delay
   * is zero, the scheduled function will be due to execute immediately after the
   * scheduling one completes.
   * @param functionReference - A {@link FunctionReference} for the function
   * to schedule.
   * @param args - Arguments to call the scheduled functions with.
   * @returns The ID of the scheduled function in the `_scheduled_functions`
   * system table. Use this to cancel it later if needed.
   **/
  runAfter<FuncRef extends SchedulableFunctionReference>(
    delayMs: number,
    functionReference: FuncRef,
    ...args: OptionalRestArgs<FuncRef>
  ): Promise<Id<"_scheduled_functions">>;

  /**
   * Schedule a function to execute at a specific time.
   *
   * @example
   * ```typescript
   * // Run at a specific Date:
   * await ctx.scheduler.runAt(
   *   new Date("2030-01-01T00:00:00Z"),
   *   internal.events.triggerNewYear,
   *   {},
   * );
   *
   * // Run at a timestamp (milliseconds since epoch):
   * await ctx.scheduler.runAt(Date.now() + 60000, internal.tasks.process, { taskId });
   * ```
   *
   * @param timestamp - A Date or a timestamp (milliseconds since the epoch).
   * If the timestamp is in the past, the scheduled function will be due to
   * execute immediately after the scheduling one completes. The timestamp can't
   * be more than five years in the past or more than five years in the future.
   * @param functionReference - A {@link FunctionReference} for the function
   * to schedule.
   * @param args - Arguments to call the scheduled functions with.
   * @returns The ID of the scheduled function in the `_scheduled_functions`
   * system table.
   **/
  runAt<FuncRef extends SchedulableFunctionReference>(
    timestamp: number | Date,
    functionReference: FuncRef,
    ...args: OptionalRestArgs<FuncRef>
  ): Promise<Id<"_scheduled_functions">>;

  /**
   * Cancel a previously scheduled function.
   *
   * For scheduled **actions**: if the action has not started, it will
   * not run. If it is already in progress, it will continue running but any
   * new functions it tries to schedule will be canceled.
   * If it had already completed, canceling will throw an error.
   * For scheduled **mutations**: the mutation will either show up as
   * "pending", "completed", or "failed", but never "inProgress".
   * Canceling a mutation will atomically cancel it entirely or fail to cancel
   * if it has committed. It is a transaction that will either run to
   * completion and commit or fully roll back.
   *
   * @example
   * ```typescript
   * // Cancel a scheduled function:
   * await ctx.scheduler.cancel(scheduledFunctionId);
   * ```
   *
   * @param id - The ID of the scheduled function to cancel (returned by
   * `runAfter` or `runAt`).
   */
  cancel(id: Id<"_scheduled_functions">): Promise<void>;
}
