<?php

/**
 * Centralized output-token budget resolver for heavy LLM generation calls.
 *
 * @package LolaCore\LLM
 * @license GPL-2.0-or-later
 */

declare(strict_types=1);

namespace LolaCore\LLM;

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

/**
 * Single source of truth for the max output tokens on heavy generation calls.
 *
 * The binding constraint for plugin generation is our practical budget, not the
 * model's hard ceiling — every supported provider (DeepSeek, OpenAI, Anthropic)
 * offers far more output than a realistic plugin needs. So the practical limit
 * drives the value, and the per-provider ceilings act only as a defensive clamp.
 *
 * Design rule — the clamp must fail LOW, never HIGH: an unknown provider gets a
 * conservative ceiling so a stale map truncates the output (recoverable via the
 * generator's continuation pass) instead of exceeding the provider's real limit,
 * which would be a hard request rejection.
 */
final class ModelTokenBudget {

	/** Practical output budget for a full plugin generation (PHP + JS + CSS). */
	public const PRACTICAL_GENERATION_LIMIT = 32768;

	/** Conservative ceiling for unknown providers — fails low (truncate, not reject). */
	public const DEFAULT_CEILING = 16384;

	/**
	 * Defensive per-provider output ceilings.
	 *
	 * These are NOT the driver — the practical limit is. They are kept well below
	 * each provider's documented maximum so the practical limit can be raised later
	 * without risking a rejection on a model with a lower real ceiling.
	 *
	 * @var array<string, int>
	 */
	private const PROVIDER_CEILINGS = array(
		'deepseek'  => 128000,
		'openai'    => 128000,
		'anthropic' => 64000,
	);

	/**
	 * Max output tokens for a heavy generation call (plugin code and its continuation).
	 *
	 * @param string $provider Provider id (e.g. 'deepseek', 'openai', 'anthropic').
	 */
	public static function forGeneration( string $provider ): int {
		return min( self::PRACTICAL_GENERATION_LIMIT, self::ceilingFor( $provider ) );
	}

	private static function ceilingFor( string $provider ): int {
		return self::PROVIDER_CEILINGS[ $provider ] ?? self::DEFAULT_CEILING;
	}
}
