<?php

declare(strict_types=1);

namespace Drupal\flowdrop\Controller\Api;

use Drupal\Core\Cache\CacheableJsonResponse;
use Drupal\Core\Cache\CacheableMetadata;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;

/**
 * Standardized API response helpers for FlowDrop controllers.
 *
 * Provides a consistent response envelope across all API endpoints:
 * - Success: { "success": true, "data": ... }
 * - Error: { "success": false, "error": "..." }
 * - Paginated: { "success": true, "data": [...], "pagination": { ... } }
 *
 * @internal This trait is not part of the public API and may change without notice.
 */
trait ApiResponseTrait {

  /**
   * Maximum accepted JSON request body size, in bytes.
   *
   * Bounds untrusted input before it is decoded and processed (workflow saves,
   * bundle imports). Generous enough for a max-size workflow (500 nodes /
   * 1000 edges with per-node config) while refusing pathological payloads.
   */
  protected const MAX_JSON_BYTES = 8 * 1024 * 1024;

  /**
   * Maximum accepted JSON nesting depth.
   *
   * Caps recursion before json_decode and any recursive normalization
   * (e.g. bundle canonicalization). A workflow graph is shallow; 64 levels
   * is well above any legitimate node config.
   */
  protected const MAX_JSON_DEPTH = 64;

  /**
   * Returns a standardized success response.
   *
   * @param mixed $data
   *   The response data.
   * @param int $status
   *   The HTTP status code.
   *
   * @return \Symfony\Component\HttpFoundation\JsonResponse
   *   The JSON response.
   */
  protected function successResponse(mixed $data, int $status = Response::HTTP_OK): JsonResponse {
    return new JsonResponse([
      'success' => TRUE,
      'data' => $data,
    ], $status);
  }

  /**
   * Returns a standardized error response.
   *
   * @param string $error
   *   The error message.
   * @param int $status
   *   The HTTP status code.
   *
   * @return \Symfony\Component\HttpFoundation\JsonResponse
   *   The JSON response.
   */
  protected function errorResponse(string $error, int $status = Response::HTTP_INTERNAL_SERVER_ERROR): JsonResponse {
    return new JsonResponse([
      'success' => FALSE,
      'error' => $error,
    ], $status);
  }

  /**
   * Returns a standardized paginated response.
   *
   * @param array $data
   *   The paginated data.
   * @param int $total
   *   Total count of items.
   * @param int $limit
   *   Items per page.
   * @param int $offset
   *   Current offset.
   *
   * @return \Symfony\Component\HttpFoundation\JsonResponse
   *   The JSON response.
   */
  protected function paginatedResponse(array $data, int $total, int $limit, int $offset): JsonResponse {
    return new JsonResponse([
      'success' => TRUE,
      'data' => $data,
      'pagination' => [
        'total' => $total,
        'limit' => $limit,
        'offset' => $offset,
        'has_more' => ($offset + $limit) < $total,
      ],
    ]);
  }

  /**
   * Returns a cacheable standardized success response.
   *
   * @param mixed $data
   *   The response data.
   * @param \Drupal\Core\Cache\CacheableMetadata $cacheMetadata
   *   Cache metadata.
   *
   * @return \Drupal\Core\Cache\CacheableJsonResponse
   *   The cacheable JSON response.
   */
  protected function cacheableSuccessResponse(mixed $data, CacheableMetadata $cacheMetadata): CacheableJsonResponse {
    $response = new CacheableJsonResponse([
      'success' => TRUE,
      'data' => $data,
    ]);
    $response->addCacheableDependency($cacheMetadata);
    return $response;
  }

  /**
   * Returns a cacheable standardized error response.
   *
   * @param string $error
   *   The error message.
   * @param int $status
   *   The HTTP status code.
   * @param \Drupal\Core\Cache\CacheableMetadata $cacheMetadata
   *   Cache metadata.
   *
   * @return \Drupal\Core\Cache\CacheableJsonResponse
   *   The cacheable JSON response.
   */
  protected function cacheableErrorResponse(string $error, int $status, CacheableMetadata $cacheMetadata): CacheableJsonResponse {
    $response = new CacheableJsonResponse([
      'success' => FALSE,
      'error' => $error,
    ], $status);
    $response->addCacheableDependency($cacheMetadata);
    return $response;
  }

  /**
   * Decodes and validates a JSON request body.
   *
   * @param \Symfony\Component\HttpFoundation\Request $request
   *   The HTTP request.
   *
   * @return array
   *   The decoded JSON data.
   *
   * @throws \Symfony\Component\HttpKernel\Exception\BadRequestHttpException
   *   When the request body contains invalid JSON.
   */
  protected function decodeJsonRequest(Request $request): array {
    $content = $request->getContent();

    if (empty($content)) {
      throw new BadRequestHttpException('Request body is empty');
    }

    // Bound the raw payload before decoding so a pathological body can't force
    // an expensive decode / recursive normalization pass on untrusted input.
    if (strlen($content) > self::MAX_JSON_BYTES) {
      throw new BadRequestHttpException('Request body exceeds the maximum allowed size');
    }

    // Pass an explicit, shallow depth and fail closed on JSON_ERROR_DEPTH
    // rather than relying on PHP's default depth of 512.
    $data = json_decode($content, TRUE, self::MAX_JSON_DEPTH);

    if (json_last_error() !== JSON_ERROR_NONE) {
      throw new BadRequestHttpException('Request body contains invalid JSON');
    }

    if (!is_array($data)) {
      throw new BadRequestHttpException('Request body must be a JSON object');
    }

    return $data;
  }

}
