<?php
/**
 * REST API endpoint for sparkline chart data.
 *
 * GET /crtoday/v1/chart?source=FEX&deal=STANDARD&from=EUR&to=USD&period=24h
 *
 * Returns a JSON array of {ts, rate} points for the requested pair and period.
 * Cross-rates are computed on the fly from the stored base→code snapshots.
 *
 * @package CRTodayCurrencyBlocks
 */

declare(strict_types=1);

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

final class CRToday_Chart_Api {

	private const NAMESPACE = 'crtoday/v1';
	private const ROUTE     = '/chart';

	/**
	 * Period definitions: key → seconds.
	 *
	 * @var array<string,int>
	 */
	private const PERIODS = [
		'24h' => 86400,
		'7d'  => 604800,
		'30d' => 2592000,
		'90d' => 7776000,
	];

	public static function register(): void {
		add_action('rest_api_init', [self::class, 'register_routes']);
	}

	public static function register_routes(): void {
		register_rest_route(self::NAMESPACE, self::ROUTE, [
			'methods'             => \WP_REST_Server::READABLE,
			'callback'            => [self::class, 'handle'],
			'permission_callback' => '__return_true',
			'args'                => [
				'source' => [
					'required'          => true,
					'type'              => 'string',
					'sanitize_callback' => 'sanitize_text_field',
				],
				'deal' => [
					'required'          => true,
					'type'              => 'string',
					'sanitize_callback' => 'sanitize_text_field',
				],
				'from' => [
					'required'          => true,
					'type'              => 'string',
					'sanitize_callback' => 'sanitize_text_field',
					'validate_callback' => [self::class, 'validate_currency_code'],
				],
				'to' => [
					'required'          => true,
					'type'              => 'string',
					'sanitize_callback' => 'sanitize_text_field',
					'validate_callback' => [self::class, 'validate_currency_code'],
				],
				'period' => [
					'required'          => false,
					'type'              => 'string',
					'default'           => '24h',
					'sanitize_callback' => 'sanitize_text_field',
					'validate_callback' => [self::class, 'validate_period'],
				],
			],
		]);
	}

	public static function validate_currency_code($value): bool {
		return is_string($value) && preg_match('/^[A-Z]{3}$/i', $value) === 1;
	}

	public static function validate_period($value): bool {
		return is_string($value) && isset(self::PERIODS[$value]);
	}

	/**
	 * @return \WP_REST_Response|\WP_Error
	 */
	public static function handle(\WP_REST_Request $request) {
		$source = strtoupper((string) $request->get_param('source'));
		$deal   = strtoupper((string) $request->get_param('deal'));
		$from   = strtoupper((string) $request->get_param('from'));
		$to     = strtoupper((string) $request->get_param('to'));
		$period = (string) $request->get_param('period');

		$resolved = CRToday_Plugin_Settings::resolve_source($source, $deal);
		if ($resolved === null || ($resolved['type'] ?? '') !== 'builtin') {
			return new \WP_Error('invalid_source', 'Unknown data source.', ['status' => 400]);
		}

		$base       = strtoupper((string) ($resolved['base'] ?? 'USD'));
		$window     = self::PERIODS[$period] ?? self::PERIODS['24h'];
		$now        = time();
		$from_ts    = $now - $window;

		$points = self::build_cross_rate_series($source, $deal, $base, $from, $to, $from_ts, $now);

		$response = new \WP_REST_Response($points, 200);
		$response->header('Cache-Control', 'public, max-age=120');

		return $response;
	}

	/**
	 * Build a cross-rate time series by merging two base→code series.
	 *
	 * For pair FROM→TO with source base B:
	 *   cross_rate = rate(B→TO) / rate(B→FROM)
	 *
	 * When FROM or TO equals the source base, only one series is needed.
	 *
	 * @return list<array{ts:int,rate:float}>
	 */
	public static function build_cross_rate_series(
		string $source,
		string $deal,
		string $base,
		string $from,
		string $to,
		int $from_ts,
		int $to_ts
	): array {
		/* Direct: FROM is the source base */
		if ($from === $base) {
			$to_series = CRToday_Raw_Snapshot_Repository::get_time_series($source, $deal, $to, $from_ts, $to_ts);
			$points = [];
			foreach ($to_series as $p) {
				$points[] = ['ts' => $p['ts'], 'rate' => round($p['mid'], 10)];
			}
			return $points;
		}

		/* Inverse: TO is the source base */
		if ($to === $base) {
			$from_series = CRToday_Raw_Snapshot_Repository::get_time_series($source, $deal, $from, $from_ts, $to_ts);
			$points = [];
			foreach ($from_series as $p) {
				if ($p['mid'] > 0) {
					$points[] = ['ts' => $p['ts'], 'rate' => round(1.0 / $p['mid'], 10)];
				}
			}
			return $points;
		}

		/* Cross-rate: merge both series by matching timestamps */
		$from_series = CRToday_Raw_Snapshot_Repository::get_time_series($source, $deal, $from, $from_ts, $to_ts);
		$to_series   = CRToday_Raw_Snapshot_Repository::get_time_series($source, $deal, $to, $from_ts, $to_ts);

		/* Index by timestamp for O(n) merge */
		$from_by_ts = [];
		foreach ($from_series as $p) {
			$from_by_ts[$p['ts']] = $p['mid'];
		}

		$points = [];
		foreach ($to_series as $p) {
			$ts = $p['ts'];
			if (isset($from_by_ts[$ts]) && $from_by_ts[$ts] > 0) {
				$points[] = ['ts' => $ts, 'rate' => round($p['mid'] / $from_by_ts[$ts], 10)];
			}
		}

		return $points;
	}
}
