<?php
/**
 * Shared REST controller behavior.
 *
 * @package Carticy\Conversions\Rest
 */

declare(strict_types=1);

namespace Carticy\Conversions\Rest;

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Base class for the Carticy Conversions admin REST controllers.
 *
 * Controllers never register their own routes - they declare them via
 * {@see self::routes()} and {@see RestApi} performs the single, allow-listed
 * registration pass (so the WordPress.org gate audits one file).
 * The shared permission callback gates every route on `manage_woocommerce`;
 * WordPress validates the `wp_rest` nonce from the `X-WP-Nonce` header
 * automatically once a permission callback is present.
 */
abstract class AbstractRestController {

	/**
	 * Capability required to read or write any admin contract.
	 */
	public const CAPABILITY = 'manage_woocommerce';

	/**
	 * Declare the routes this controller owns.
	 *
	 * Each entry is an associative array:
	 * - methods:  HTTP verb(s) (e.g. 'GET', 'POST').
	 * - route:    path relative to the plugin REST namespace (leading slash).
	 * - callback: callable invoked with the WP_REST_Request.
	 * - args:     optional WordPress REST args schema.
	 *
	 * @return array<int, array<string, mixed>>
	 */
	abstract public function routes(): array;

	/**
	 * Shared permission callback: gate every route on the WooCommerce
	 * management capability. WordPress enforces the REST cookie nonce on top.
	 *
	 * @return bool
	 */
	public function can_manage(): bool {
		return function_exists( 'current_user_can' ) && current_user_can( self::CAPABILITY );
	}

	/**
	 * Build a success response.
	 *
	 * @param array<string, mixed> $data Response payload.
	 * @param int                  $status HTTP status code.
	 * @return \WP_REST_Response
	 */
	protected function ok( array $data, int $status = 200 ): \WP_REST_Response {
		return new \WP_REST_Response( $data, $status );
	}

	/**
	 * Build an error response.
	 *
	 * Returned as a WP_Error so WordPress serializes `{code,message,data:{status}}`
	 * consistently and applies the HTTP status; the frontend branches on `code`.
	 *
	 * @param string               $code Machine-readable error code.
	 * @param string               $message Human-readable message.
	 * @param int                  $status HTTP status code.
	 * @param array<string, mixed> $data Extra error context merged under `data`.
	 * @return \WP_Error
	 */
	protected function fail( string $code, string $message, int $status, array $data = array() ): \WP_Error {
		return new \WP_Error( $code, $message, array_merge( array( 'status' => $status ), $data ) );
	}

	/**
	 * Read the JSON body of a write request as an array.
	 *
	 * @param \WP_REST_Request $request REST request.
	 * @return array<string, mixed>
	 */
	protected function body( \WP_REST_Request $request ): array {
		$json = $request->get_json_params();

		return is_array( $json ) ? $json : $request->get_body_params();
	}
}
