import type { ShadeBridge } from '../integration/shade-bridge.js';
import type { StandardOp } from '../protocol/kinds.js';

export type OpKind = StandardOp | `custom:${string}`;

/**
 * Per-call context handed to every server-side handler/middleware.
 *
 * Path-related ops set `path` to the (validated, normalized) primary path.
 * For `move`, `path` is `args.src`. For ops without a path (`custom`), the
 * field is the empty string.
 */
export interface OpContext<TArgs = unknown> {
  /** The op being executed. */
  op: OpKind;
  /** Validated + normalized primary path; empty string for path-less ops. */
  path: string;
  /** Zod-validated args for this op. */
  args: TArgs;
  /** Peer address (from Shade.receive). */
  sender: string;
  /** Aborted on client cancel or timeout. */
  signal: AbortSignal;
  /** Idempotency key (mutations only). */
  idempotencyKey: string | undefined;
  /** Attempt counter (1-based). */
  attemptNumber: number;
  /** Wall-clock when dispatch started (for metrics). */
  startedAt: number;
  /** Lazy fingerprint accessor — call only if needed. */
  fingerprint: () => Promise<string>;
  /** Mutable scratchpad for middleware (e.g. cache fingerprint hit). */
  metadata: Map<string, unknown>;
}

/** Build an `OpContext`. Internal — used by the dispatcher. */
export function buildOpContext<TArgs>(args: {
  op: OpKind;
  path: string;
  parsedArgs: TArgs;
  sender: string;
  signal: AbortSignal;
  idempotencyKey: string | undefined;
  attemptNumber: number;
  shade: ShadeBridge;
}): OpContext<TArgs> {
  return {
    op: args.op,
    path: args.path,
    args: args.parsedArgs,
    sender: args.sender,
    signal: args.signal,
    idempotencyKey: args.idempotencyKey,
    attemptNumber: args.attemptNumber,
    startedAt: Date.now(),
    fingerprint: () => args.shade.getFingerprintFor(args.sender),
    metadata: new Map(),
  };
}
