<?php
/**
 * Answyr — AI Provider contract.
 *
 * Adding a provider: extend this class, implement the abstract methods, and
 * register an instance on the `answyr_register_ai_providers` action:
 *
 *     add_action( 'answyr_register_ai_providers', function () {
 *         Answyr_Provider_Registry::register( new My_Provider() );
 *     } );
 *
 * Providers are stateless w.r.t. credentials — the API key, model, and tuning
 * options arrive per-call via $opts, so a single instance can be enumerated in
 * settings and reused.
 *
 * @package Answyr
 */

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

abstract class Answyr_AI_Provider {

	/** Stable machine id, e.g. "anthropic". */
	abstract public function id(): string;

	/** Human label for settings, e.g. "Anthropic (Claude)". */
	abstract public function label(): string;

	/**
	 * Selectable models: [ 'model-id' => 'Display Label', ... ].
	 *
	 * @return array<string,string>
	 */
	abstract public function models(): array;

	/** Default model id (first listed unless overridden). */
	public function default_model(): string {
		$ids = array_keys( $this->models() );
		return $ids[0] ?? '';
	}

	/**
	 * Blocking completion — returns the full assistant text.
	 *
	 * @param string                                       $system   System prompt.
	 * @param array<int,array{role:string,content:string}> $messages Conversation turns.
	 * @param array<string,mixed>                          $opts     api_key, model, max_tokens, temperature.
	 * @return string
	 *
	 * @throws \RuntimeException on transport/API error.
	 */
	abstract public function complete( string $system, array $messages, array $opts ): string;


	/** Verify a key with a minimal call. */
	abstract public function test_connection( string $api_key ): bool;

	/** Whether this provider can run (has a key configured). */
	public function is_configured(): bool {
		return '' !== trim( (string) Answyr_Settings::api_key( $this->id() ) );
	}

	/**
	 * URL where a user creates an API key for this provider — surfaced in
	 * first-run onboarding so non-technical users aren't stuck at the key wall.
	 * Empty means "no public console link".
	 */
	public function key_url(): string {
		return '';
	}

	// ── Helpers for subclasses ──────────────────────────────────────────────────

	/** @param array<string,mixed> $opts */
	protected function opt( array $opts, string $key, $fallback = null ) {
		return array_key_exists( $key, $opts ) && '' !== $opts[ $key ] ? $opts[ $key ] : $fallback;
	}
}
