/**
 * Custom op registry — typed via TypeScript declaration merging.
 *
 * Consumers register a custom op by:
 *   1. Adding to `CustomOpsMap` via declaration merging (compile-time typing).
 *   2. Passing a Zod-backed handler in `FileHandlerConfig.custom`.
 *
 * Server validates wire args against the registered Zod schema before
 * invoking the user handler, then validates the user's response against
 * the schema before shipping it.
 *
 * @example
 * ```ts
 * // In your app, once globally:
 * declare module '@shade/files' {
 *   interface CustomOpsMap {
 *     'app.deploy-mod': CustomOpDef<{ jarPath: string }, { deploymentId: string }>;
 *   }
 * }
 *
 * // Server registers a handler:
 * shade.files.serve({
 *   custom: {
 *     'app.deploy-mod': {
 *       args: z.object({ jarPath: z.string() }),
 *       response: z.object({ deploymentId: z.string() }),
 *       handler: async (args, ctx) => ({ deploymentId: '...' }),
 *     },
 *   },
 * });
 *
 * // Client gets typed I/O:
 * const result = await fs.custom('app.deploy-mod', { jarPath: '...' });
 * //    ^? { deploymentId: string }
 * ```
 */
import type { ZodType } from 'zod';
import type { OpContext } from './handler-context.js';

// ─── Public typing scaffold (declaration-merged by consumers) ─

/** Marker shape for one custom op entry — consumers extend `CustomOpsMap`. */
export interface CustomOpDef<A = unknown, R = unknown> {
  args: A;
  response: R;
}

/**
 * Registry of all custom ops known at compile time. Empty by default —
 * consumers extend via TypeScript declaration merging:
 *
 * ```ts
 * declare module '@shade/files' {
 *   interface CustomOpsMap {
 *     'app.foo': CustomOpDef<{ x: number }, { y: string }>;
 *   }
 * }
 * ```
 */
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
export interface CustomOpsMap {}

// ─── Server-side handler config ──────────────────────────────

/**
 * Server-side definition for one custom op: the args schema (validated
 * inbound), the response schema (validated outbound), and the handler.
 */
export interface CustomOpRegistration<TArgs = unknown, TResponse = unknown> {
  args: ZodType<TArgs>;
  response: ZodType<TResponse>;
  handler: (args: TArgs, ctx: OpContext<{ name: string; payload: TArgs }>) => Promise<TResponse>;
  /**
   * Optional cost override for the rate limiter. Default: same as
   * `opCost.custom` (1 unless overridden in `RateLimitConfig`).
   */
  cost?: number;
}

/**
 * Map from custom-op name to registration. The keys are the same names
 * declared in `CustomOpsMap`. Required typing via declaration merging is
 * not strict here — runtime validation is enforced by the registered Zod
 * schemas, so consumers using untyped names still get safety.
 */
export type CustomOpRegistrations = Record<string, CustomOpRegistration<any, any>>;
