<?php
/**
 * Get-logs-summary use case.
 *
 * Phase 11 slice 11d Application-layer use case that drives the
 * §12.3 `GET /affilun/v1/logs/summary` REST surface documented in
 * `specifications/rest-api-spec.md`. The use case combines the
 * level-histogram half ({@see LogRepository::countByLevel()}) and
 * the recent-entries half ({@see LogRepository::findRecent()})
 * into a single {@see GetLogsSummaryResult} with the recent slice
 * already projected through the {@see LogMasker} pipeline.
 *
 * The `recent_limit` axis is bounded inclusively to [1, 50] at
 * the use-case boundary so the summary endpoint cannot be coerced
 * into materializing a full page even though the underlying
 * {@see LogRepository::findRecent()} contract permits up to 100.
 * Capping below the repository limit keeps the summary surface
 * narrow per its §12.3 "summary card" intent.
 *
 * @package Affilun\Application\Logs\UseCase
 */

declare(strict_types=1);

namespace Affilun\Application\Logs\UseCase;

use Affilun\Application\Logs\LogMasker;
use Affilun\Infrastructure\Persistence\LogRepository;
use InvalidArgumentException;

/**
 * Application-layer use case for `GET /affilun/v1/logs/summary` (§12.3).
 *
 * See the file-level docblock for the orchestration shape and the
 * `recent_limit` validation boundary.
 */
final class GetLogsSummaryUseCase {

	/**
	 * Default recent-entries limit when the caller does not supply
	 * one. Matches the §12.3 "summary card" expected size.
	 */
	public const DEFAULT_RECENT_LIMIT = 10;

	/**
	 * Maximum recent-entries limit accepted by {@see self::execute()}.
	 * Capped below {@see LogRepository::findRecent()}'s 100-row
	 * ceiling per the file-level rationale.
	 */
	public const MAX_RECENT_LIMIT = 50;

	/**
	 * Persistence-layer query surface.
	 */
	private LogRepository $logs;

	/**
	 * Row-shape masker driving the §12.3 envelope projection.
	 */
	private LogMasker $masker;

	/**
	 * Construct the use case with explicit collaborator
	 * injection.
	 *
	 * @param LogRepository $logs   Persistence-layer query surface.
	 * @param LogMasker     $masker Row-shape masker.
	 */
	public function __construct( LogRepository $logs, LogMasker $masker ) {
		$this->logs   = $logs;
		$this->masker = $masker;
	}

	/**
	 * Execute the §12.3 logs-summary query.
	 *
	 * Calls {@see LogRepository::countByLevel()} for the level
	 * histogram and {@see LogRepository::findRecent()} for the
	 * recent-entries slice, projects each returned record through
	 * {@see LogMasker::maskRecord()}, and wraps the result in a
	 * {@see GetLogsSummaryResult}.
	 *
	 * @param int $recentLimit Recent-entries limit (1..50). Default 10.
	 *
	 * @throws InvalidArgumentException When `$recentLimit` is out
	 *                                  of the [1, 50] range.
	 */
	public function execute( int $recentLimit = self::DEFAULT_RECENT_LIMIT ): GetLogsSummaryResult {
		if ( $recentLimit < 1 || $recentLimit > self::MAX_RECENT_LIMIT ) {
			$message = sprintf(
				'GetLogsSummaryUseCase: recent_limit must be between 1 and %d, got %d.',
				self::MAX_RECENT_LIMIT,
				$recentLimit
			);
			// phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Developer-facing message, never rendered to HTML.
			throw new InvalidArgumentException( $message );
		}

		$counts = $this->logs->countByLevel();

		$records = $this->logs->findRecent( $recentLimit );
		$recent  = array();
		foreach ( $records as $record ) {
			$recent[] = $this->masker->maskRecord( $record );
		}

		return new GetLogsSummaryResult( $counts, $recent );
	}
}
