// API response envelope contracts.
//
// Every external (`/api/v1/*`) handler MUST return one of:
//   - ApiSuccessResponse<T>      → 2xx with { data: T }
//   - ApiListResponse<T>         → 2xx with { data: T[], meta: { page, limit, total } }
//   - ApiErrorResponse           → 4xx/5xx with { error, code?, details? }
//
// Internal handlers (admin, owner, demo, support tooling) SHOULD follow the same
// shape but are not bound by the same compatibility contract — see
// docs/foundation/api-contract.md for the line.
//
// These types are also the source of truth that the OpenAPI v1 spec mirrors
// in components.schemas.{Envelope,ListEnvelope,ErrorEnvelope}.

import { NextResponse } from 'next/server'

export interface ApiSuccessResponse<T> {
  data: T
}

export interface ApiListMeta {
  page: number
  limit: number
  total: number
}

export interface ApiListResponse<T> {
  data: T[]
  meta: ApiListMeta
}

export type ApiErrorCode =
  | 'unauthorized'
  | 'forbidden'
  | 'not_found'
  | 'invalid_request'
  | 'tenant_disabled'
  | 'plan_missing'
  | 'role_denied'
  | 'kill_switch'
  | 'service_misconfigured'
  | 'internal_error'

export interface ApiErrorResponse {
  error: string
  code?: ApiErrorCode
  details?: Record<string, unknown>
}

// ─── Helpers ──────────────────────────────────────────────────────────────────

export function ok<T>(data: T, init?: ResponseInit): NextResponse<ApiSuccessResponse<T>> {
  return NextResponse.json({ data }, init)
}

export function list<T>(
  data: T[],
  meta: ApiListMeta,
  init?: ResponseInit,
): NextResponse<ApiListResponse<T>> {
  return NextResponse.json({ data, meta }, init)
}

export function err(
  error: string,
  status: number,
  code?: ApiErrorCode,
  details?: Record<string, unknown>,
): NextResponse<ApiErrorResponse> {
  const body: ApiErrorResponse = { error }
  if (code) body.code = code
  if (details) body.details = details
  return NextResponse.json(body, { status })
}

// Convenience for common error cases.
export const unauthorized = (msg = 'Unauthorized') => err(msg, 401, 'unauthorized')
export const forbidden = (msg = 'Forbidden') => err(msg, 403, 'forbidden')
export const notFound = (msg = 'Not found') => err(msg, 404, 'not_found')
export const badRequest = (msg: string, details?: Record<string, unknown>) =>
  err(msg, 400, 'invalid_request', details)

// Server error: never echoes raw DB / SDK error text to the client.
// Pass the raw error as `detail` (logged server-side via lib/api/error.ts);
// the response carries a generic message only.
//
// Migration note: many existing routes call `serverError(error.message)`.
// That pattern is now equivalent to `serverError()` from a client-visibility
// perspective — the supplied string is suppressed in favour of the generic
// message and forwarded to the logger. To preserve client-visible
// detail (e.g. validation issues), use `badRequest(msg, details)` instead.
export const serverError = (detail?: unknown) => {
  if (detail !== undefined) {
    // Lazy import to avoid circular dep with lib/logger -> lib/api.
    import('@/lib/logger').then(({ logger }) => {
      logger.error('api.server_error', detail)
    }).catch(() => {})
  }
  return err('A server error occurred. The incident has been logged.', 500, 'internal_error')
}

// Pagination helpers — read query params with sane defaults and clamp.
const DEFAULT_LIMIT = 25
const MAX_LIMIT = 100

export function readPagination(searchParams: URLSearchParams): { page: number; limit: number; offset: number } {
  const page = Math.max(1, parseInt(searchParams.get('page') ?? '1', 10) || 1)
  const limit = Math.min(
    MAX_LIMIT,
    Math.max(1, parseInt(searchParams.get('limit') ?? String(DEFAULT_LIMIT), 10) || DEFAULT_LIMIT),
  )
  return { page, limit, offset: (page - 1) * limit }
}
