/**
 * Federation wire-format response schemas.
 *
 * Source of truth: docs/federation/PRD.md §9 and MILESTONES.md §M3.
 *
 * DESIGN: Generic factory functions rather than z.lazy
 * ─────────────────────────────────────────────────────
 * Zod generic schemas cannot be expressed as a single re-usable `z.ZodType`
 * value because TypeScript's type system erases the generic at the call site.
 * The idiomatic Zod v4 pattern is factory functions that take an item schema
 * and return a fully-typed schema.
 *
 *   const MyListSchema = FederationListResponseSchema(z.string());
 *   type MyList = z.infer<typeof MyListSchema>;
 *   // => { items: string[]; nextCursor?: string; _partial?: boolean; _truncated?: boolean }
 *
 * Downstream consumers (M3-03..M3-07, M3-08, M3-09) should call these
 * factories once per resource type and cache the result.
 *
 * Pure — no NestJS, no DB, no Node-only APIs. Safe for browser/edge.
 */

import { z } from 'zod';

import { FEDERATION_VERBS } from './verbs.js';

// ---------------------------------------------------------------------------
// Shared envelope flags
// ---------------------------------------------------------------------------

/**
 * `_partial`: true when the response is a subset of available data (e.g. due
 * to scope intersection reducing the result set).
 */
const PartialFlag = z.boolean().optional();

/**
 * `_truncated`: true when the response was capped by max_rows_per_query and
 * additional pages exist beyond the current cursor.
 */
const TruncatedFlag = z.boolean().optional();

// ---------------------------------------------------------------------------
// FederationListResponseSchema<T> factory
// ---------------------------------------------------------------------------

/**
 * Returns a Zod schema for a paginated federation list envelope.
 *
 * @param itemSchema - Zod schema for a single item in the list.
 *
 * @example
 * ```ts
 * const TaskListSchema = FederationListResponseSchema(TaskSchema);
 * type TaskList = z.infer<typeof TaskListSchema>;
 * ```
 */
export function FederationListResponseSchema<T extends z.ZodTypeAny>(itemSchema: T) {
  return z.object({
    items: z.array(itemSchema),
    nextCursor: z.string().optional(),
    _partial: PartialFlag,
    _truncated: TruncatedFlag,
  });
}

export type FederationListResponse<T> = {
  items: T[];
  nextCursor?: string;
  _partial?: boolean;
  _truncated?: boolean;
};

// ---------------------------------------------------------------------------
// FederationGetResponseSchema<T> factory
// ---------------------------------------------------------------------------

/**
 * Returns a Zod schema for a single-item federation get envelope.
 *
 * `item` is null when the resource was not found (404 equivalent on the wire).
 *
 * @param itemSchema - Zod schema for the item (nullable is applied internally).
 *
 * @example
 * ```ts
 * const TaskGetSchema = FederationGetResponseSchema(TaskSchema);
 * type TaskGet = z.infer<typeof TaskGetSchema>;
 * ```
 */
export function FederationGetResponseSchema<T extends z.ZodTypeAny>(itemSchema: T) {
  return z.object({
    item: itemSchema.nullable(),
    _partial: PartialFlag,
  });
}

export type FederationGetResponse<T> = {
  item: T | null;
  _partial?: boolean;
};

// ---------------------------------------------------------------------------
// FederationCapabilitiesResponseSchema (fixed shape)
// ---------------------------------------------------------------------------

/**
 * Shape mirrors FederationScope (apps/gateway/src/federation/scope-schema.ts)
 * but is kept separate to avoid coupling packages/types to the gateway module.
 * The serving side populates this from the resolved grant scope at request time.
 */
export const FederationCapabilitiesResponseSchema = z.object({
  /**
   * Resources this grant is allowed to query.
   */
  resources: z.array(z.string()).nonempty(),

  /**
   * Resources explicitly blocked for this grant even if they exist.
   */
  excluded_resources: z.array(z.string()),

  /**
   * Per-resource filters (mirrors FederationScope.filters from PRD §8.1).
   * Keys are resource names; values control team/personal visibility.
   */
  filters: z
    .record(
      z.string(),
      z.object({
        include_teams: z.array(z.string()).optional(),
        include_personal: z.boolean().optional(),
      }),
    )
    .optional(),

  /**
   * Hard cap on rows returned per query for this grant.
   */
  max_rows_per_query: z.number().int().positive(),

  /**
   * Verbs currently available. Will expand in M4+ (search).
   * Closed enum — only values from FEDERATION_VERBS are accepted.
   */
  supported_verbs: z.array(z.enum(FEDERATION_VERBS)).nonempty(),

  /**
   * Rate-limit state for this grant (PRD §9.1).
   * M4 populates `remaining` and `reset_at`; M3 servers may return only
   * `limit_per_minute` or omit the field entirely.
   */
  rate_limit: z
    .object({
      limit_per_minute: z.number().int().positive(),
      remaining: z.number().int().nonnegative().optional(),
      reset_at: z.string().datetime().optional(),
    })
    .optional(),
});

export type FederationCapabilitiesResponse = z.infer<typeof FederationCapabilitiesResponseSchema>;
