/**
 * Common shared type definitions.
 *
 * Generic pagination wrapper and shared utility types used across all EP data domains.
 *
 * @module types/ep/common
 * @see https://data.europarl.europa.eu/api/v2/
 */

/**
 * Generic paginated response wrapper for API results.
 *
 * Standard pagination format used across all European Parliament MCP Server
 * endpoints. Wraps arrays of data with pagination metadata enabling
 * efficient iteration through large datasets. Implements offset-based
 * pagination pattern.
 *
 * **Pagination Strategy:** Offset-based (not cursor-based)
 * - Predictable page jumps
 * - Total count available
 * - Direct page access
 * - May miss/duplicate items if data changes during pagination
 *
 * **Performance Considerations:**
 * - Cached responses (15 min TTL) for better performance
 * - Large offsets may have slower query performance
 * - Recommended limit: 50-100 items per page
 * - Maximum limit: 100 items per page
 *
 * @template T The type of items in the data array
 * @interface PaginatedResponse
 *
 * @example
 * ```typescript
 * // Basic pagination usage
 * const response: PaginatedResponse<MEP> = {
 *   data: [
 *     { id: "person/124936", name: "Jane Andersson", country: "SE", ... },
 *     { id: "person/198765", name: "Hans Schmidt", country: "DE", ... }
 *   ],
 *   total: 705,
 *   limit: 50,
 *   offset: 0,
 *   hasMore: true
 * };
 *
 * console.log(`Showing ${response.data.length} of ${response.total} MEPs`);
 * console.log(`Current page: ${Math.floor(response.offset / response.limit) + 1}`);
 * ```
 *
 * @example
 * ```typescript
 * // Iterating through all pages
 * async function getAllMEPs(): Promise<MEP[]> {
 *   const allMEPs: MEP[] = [];
 *   let offset = 0;
 *   const limit = 50;
 *
 *   while (true) {
 *     const response: PaginatedResponse<MEP> = await getMEPs({
 *       limit,
 *       offset
 *     });
 *
 *     allMEPs.push(...response.data);
 *
 *     if (!response.hasMore) {
 *       break;
 *     }
 *
 *     offset += limit;
 *   }
 *
 *   return allMEPs;
 * }
 * ```
 *
 * @example
 * ```typescript
 * // Calculating pagination metadata
 * function getPaginationInfo<T>(response: PaginatedResponse<T>) {
 *   const currentPage = Math.floor(response.offset / response.limit) + 1;
 *   const totalPages = Math.ceil(response.total / response.limit);
 *   const itemsOnPage = response.data.length;
 *   const startItem = response.offset + 1;
 *   const endItem = response.offset + itemsOnPage;
 *
 *   return {
 *     currentPage,      // e.g., 3
 *     totalPages,       // e.g., 15
 *     startItem,        // e.g., 101
 *     endItem,          // e.g., 150
 *     hasNext: response.hasMore,
 *     hasPrevious: response.offset > 0
 *   };
 * }
 * ```
 *
 * @example
 * ```typescript
 * // Empty result set
 * const emptyResponse: PaginatedResponse<MEP> = {
 *   data: [],
 *   total: 0,
 *   limit: 50,
 *   offset: 0,
 *   hasMore: false
 * };
 * ```
 *
 * @example
 * ```typescript
 * // Last page (partial page)
 * const lastPageResponse: PaginatedResponse<MEP> = {
 *   data: [
 *     // 5 items only
 *   ],
 *   total: 705,
 *   limit: 50,
 *   offset: 700,  // Last page
 *   hasMore: false
 * };
 * ```
 *
 * @see {@link MEP} for MEP data example
 * @see {@link VotingRecord} for voting record pagination
 * @see {@link LegislativeDocument} for document pagination
 */
export interface PaginatedResponse<T> {
  /**
   * Array of items for current page.
   *
   * Contains the actual data items for the current page/offset.
   * Array length may be less than limit on last page or when
   * fewer items match the query.
   *
   * **Type:** Array of generic type T
   * **Min Length:** 0 (empty result set)
   * **Max Length:** limit value (typically 50-100)
   *
   * @example
   * ```typescript
   * // Full page
   * data: [
   *   { id: "person/1", name: "MEP 1", ... },
   *   { id: "person/2", name: "MEP 2", ... },
   *   // ... 48 more items for limit=50
   * ]
   * ```
   *
   * @example
   * ```typescript
   * // Partial page (last page)
   * data: [
   *   { id: "person/701", name: "MEP 701", ... },
   *   { id: "person/702", name: "MEP 702", ... },
   *   // Only 5 items on last page
   * ]
   * ```
   *
   * @example
   * ```typescript
   * // Empty result
   * data: []
   * ```
   */
  data: T[];

  /**
   * Total number of items matching the query (exact or heuristic estimate).
   *
   * For **in-memory paginated** results (e.g. `getCurrentMEPs` with filters,
   * `getVotingRecords`), this is the **exact** count of all matching items.
   *
   * For **server-paginated** results where the EP API does not return a total
   * count header, this is a **heuristic sentinel**:
   * - On the **last page** (`hasMore === false`): the value is exact
   *   (`offset + data.length`), **assuming `offset` is within the actual
   *   result range**. If the caller requests an out-of-range `offset`
   *   (beyond the dataset), the EP API returns an empty page and `total`
   *   becomes `offset`, which may overestimate the real count.
   * - On **earlier pages** (`hasMore === true`): the value is
   *   `offset + data.length + 1`. This signals that more data may exist
   *   but may **overestimate by 1** when the dataset size is an exact
   *   multiple of `limit` (i.e., the last server page is exactly full).
   *
   * For **client-filtered server endpoints** (e.g. `getPlenarySessions` with
   * location, `getParliamentaryQuestions` with author/topic), `total` and
   * `hasMore` are derived from the **unfiltered server page size**, not
   * from `data.length` after client-side filtering. This means `hasMore` can
   * be `true` even when the filtered `data` array is empty, and `total` will
   * not reflect the count of filtered matches.
   *
   * **Exception — `searchDocuments`:** `total` is derived from the
   * **post-filter** `data.length`, not the unfiltered server page size.
   * Concretely, this endpoint guarantees the envelope identity
   * `total === offset + data.length + (hasMore ? 1 : 0)` while `hasMore`
   * remains pre-filter (server page fullness). Note that `total` here is a
   * **pagination-envelope sentinel**, not a count of items matching the
   * post-filter query: because `offset` is the raw server offset (not a
   * cumulative count of filtered matches across previous pages), `total`
   * may exceed the true number of matches. Its role is purely to keep the
   * envelope internally consistent. This prevents misleading responses such
   * as `data:[] total:21 hasMore:true` when a full server page is
   * eliminated by keyword/committee/date filters (the new envelope in that
   * case is `data:[] total:1 hasMore:true`). Callers should still paginate
   * until `hasMore === false` to enumerate all matches.
   *
   * **Do not** use this value for exact "X of Y" UI or page-count
   * calculations on server-paginated endpoints. Instead, iterate all
   * pages (using `hasMore`) to determine the true dataset size.
   *
   * **Min Value:** 0 (no matches)
   *
   * @example 705 // Exact total from in-memory pagination
   * @example 51  // Heuristic: offset=0, data.length=50, hasMore=true (may overestimate by 1)
   * @example 23  // Exact on last page: offset=20, data.length=3, hasMore=false
   * @example 0   // No matches found
   */
  total: number;

  /**
   * Maximum items per page (requested page size).
   *
   * The limit value that was requested for this query. Determines
   * maximum array size for data field. Actual data length may be
   * less on last page or with filtered queries.
   *
   * **EP API Field:** Query parameter `limit`
   * **Min Value:** 1
   * **Max Value:** 100 (enforced by API)
   * **Default:** 50
   * **Recommended:** 50-100 for performance
   *
   * @example 50  // Default page size
   * @example 100 // Maximum page size
   * @example 20  // Custom smaller page size
   */
  limit: number;

  /**
   * Number of items skipped (pagination offset).
   *
   * Number of items to skip from the beginning of the result set.
   * Used for offset-based pagination. To get page N, use
   * `offset = (N - 1) * limit`.
   *
   * **EP API Field:** Query parameter `offset`
   * **Min Value:** 0 (first page)
   * **Max Value:** total - 1
   * **Calculation:** `(currentPage - 1) * limit`
   *
   * @example 0    // First page
   * @example 50   // Second page (if limit=50)
   * @example 100  // Third page (if limit=50)
   * @example 700  // Page 15 (if limit=50)
   */
  offset: number;

  /**
   * Indicates if more items may exist beyond the current page.
   *
   * Boolean flag for "load more" / "next page" logic. When `true`,
   * another page may exist and the caller should fetch it. When `false`,
   * the current page is definitively the last one.
   *
   * For **server-paginated** results this is a heuristic based on the
   * underlying server page fullness, not always on the filtered
   * `data.length` returned to the caller:
   * - For ordinary server-paginated endpoints, `hasMore` is typically
   *   derived from server page fullness (effectively whether the server
   *   returned `limit` items). A full page suggests more data may follow,
   *   but can be a **false positive** when the dataset size is an exact
   *   multiple of `limit`.
   * - For **client-filtered server endpoints** (e.g. `getPlenarySessions`,
   *   `getParliamentaryQuestions`), `hasMore` is derived from the
   *   **unfiltered server page size** before client-side filtering. This
   *   means `hasMore` can be `true` even when the filtered `data` array
   *   contains fewer than `limit` items or is empty.
   * - **Exception — `searchDocuments`:** `hasMore` still reflects pre-filter
   *   server page fullness (so callers continue paginating for more matches),
   *   but `total` is derived from the post-filter `data.length` — guaranteeing
   *   the identity `total === offset + data.length + (hasMore ? 1 : 0)`.
   *
   * Callers should paginate until `hasMore` is `false`. For **in-memory
   * paginated** results, `hasMore` is exact: `(offset + data.length) < total`.
   *
   * @example true  // More pages available
   * @example false // Last page or all results shown
   *
   * @example
   * ```typescript
   * // Using hasMore for navigation
   * if (response.hasMore) {
   *   console.log("Click 'Next' to see more results");
   *   const nextOffset = response.offset + response.limit;
   *   // Fetch next page with new offset
   * } else {
   *   console.log("You've reached the end of results");
   * }
   * ```
   */
  hasMore: boolean;
}
