import type { Context } from "./context.ts";

export interface CachePolicy {
  ttlMs?: number;
  staleWhileRevalidateMs?: number;
  tags?: string[] | ((ctx: Context) => string[]);
}

export interface LoaderDefinition<T> {
  cache?: CachePolicy;
  load(ctx: Context): T | Promise<T>;
}

export interface ActionDefinition<I, O> {
  csrf?: boolean;
  run(input: I, ctx: Context): O | Promise<O>;
  invalidate?: string[] | ((output: O, ctx: Context) => string[]);
}

export interface DefinedLoader<T> {
  readonly definition: LoaderDefinition<T>;
  (ctx: Context): Promise<T>;
}

export interface DefinedAction<I, O> {
  readonly definition: ActionDefinition<I, O>;
  (input: I, ctx: Context): Promise<O>;
}

export function defineLoader<T>(definition: LoaderDefinition<T>): DefinedLoader<T> {
  return Object.assign(async (ctx: Context) => definition.load(ctx), { definition });
}

export function defineAction<I, O>(definition: ActionDefinition<I, O>): DefinedAction<I, O> {
  return Object.assign(async (input: I, ctx: Context) => definition.run(input, ctx), {
    definition,
  });
}

/** Request-local fetch deduplication keyed by a stable string. */
export async function dedupe<T>(ctx: Context, key: string, load: () => T | Promise<T>): Promise<T> {
  const bucket = (ctx.locals.__wrnexusData ??= new Map<string, Promise<unknown>>()) as Map<
    string,
    Promise<unknown>
  >;
  const existing = bucket.get(key);
  if (existing) return existing as Promise<T>;
  const pending = Promise.resolve().then(load);
  bucket.set(key, pending);
  try {
    return await pending;
  } catch (error) {
    bucket.delete(key);
    throw error;
  }
}
