<?php
/**
 * Base HTTP client for the plugin's API wrappers.
 *
 * Centralises the wp_remote_request + response-decoding boilerplate so concrete
 * clients (GoogleOAuthClient, GoogleSheetsClient, and Pro API clients) only
 * supply auth, URLs, request bodies, and their own error mapping.
 *
 * @package StoreSheet
 */

declare( strict_types=1 );

namespace StoreSheet\API;

defined( 'ABSPATH' ) || exit;

abstract class ApiClient {

	protected int $timeout = 30;

	/**
	 * Perform an HTTP request and return the decoded JSON body (array) on a 2xx,
	 * or a WP_Error on transport failure / non-2xx response.
	 *
	 * @param string               $method HTTP method.
	 * @param string               $url    Absolute URL.
	 * @param array<string, mixed> $args   wp_remote_request args (headers, body, ...).
	 * @return array<mixed>|\WP_Error
	 */
	protected function http_request( string $method, string $url, array $args = array() ) {
		$args['method']  = $method;
		$args['timeout'] = $args['timeout'] ?? $this->timeout;

		return $this->parse_response( wp_remote_request( $url, $args ) );
	}

	/**
	 * @param string               $url  Absolute URL.
	 * @param array<string, mixed> $args wp_remote_request args (headers, body, ...).
	 * @return array<mixed>|\WP_Error
	 */
	protected function http_get( string $url, array $args = array() ) {
		return $this->http_request( 'GET', $url, $args );
	}

	/**
	 * @param string               $url  Absolute URL.
	 * @param array<string, mixed> $args wp_remote_request args (headers, body, ...).
	 * @return array<mixed>|\WP_Error
	 */
	protected function http_post( string $url, array $args = array() ) {
		return $this->http_request( 'POST', $url, $args );
	}

	/**
	 * Decode a wp_remote_request result. WP_Error passes through; a non-2xx
	 * status is mapped by the concrete client; a 2xx returns the decoded array
	 * (empty array when the body is not a JSON object).
	 *
	 * @param array<string, mixed>|\WP_Error $response
	 * @return array<mixed>|\WP_Error
	 */
	protected function parse_response( $response ) {
		if ( is_wp_error( $response ) ) {
			return $response;
		}

		$code = (int) wp_remote_retrieve_response_code( $response );
		$data = json_decode( (string) wp_remote_retrieve_body( $response ), true );
		$data = is_array( $data ) ? $data : array();

		if ( $code < 200 || $code >= 300 ) {
			return $this->error_from_response( $code, $data );
		}

		return $data;
	}

	/**
	 * Map a non-2xx response to a WP_Error. Concrete clients customise the error
	 * code, message extraction, and any special-casing (e.g. 429).
	 *
	 * @param int          $code HTTP status code.
	 * @param array<mixed> $data Decoded response body.
	 */
	abstract protected function error_from_response( int $code, array $data ): \WP_Error;
}
