<?php
/**
 * affilun Cloud product article-draft client.
 *
 * @package Affilun\Application\Cloud
 */

declare(strict_types=1);

namespace Affilun\Application\Cloud;

use Affilun\Application\Settings\CloudSettings;
use InvalidArgumentException;
use RuntimeException;

/**
 * Requests editable affiliate article drafts for selected products.
 */
final class CloudProductArticleDraftClient {

	/**
	 * Target article draft lengths by option token.
	 *
	 * @var array<string,int>
	 */
	private const ARTICLE_DRAFT_LENGTH_CHARS = array(
		'short'  => 200,
		'medium' => 400,
		'long'   => 600,
	);

	/**
	 * Fallback article lead copy by tone token.
	 *
	 * @var array<string,string>
	 */
	private const ARTICLE_DRAFT_TONE_LEADS = array(
		'professional_blog' => 'プロの商品紹介ライターの視点で、推しポイントを中心に整理します。',
		'friendly'          => '新しい物好きの友人におすすめするように、良さが伝わるポイントを紹介します。',
		'review_comparison' => 'プロのバイヤーが同ジャンル製品と比較する目線で、注目点を整理します。',
		'concise'           => 'マーケティング担当者が売り込み前に押さえたい要点として、推しポイントをまとめます。',
	);

	/**
	 * Transport callback.
	 *
	 * @var callable
	 */
	private $transport;

	/**
	 * @param CloudRequestSigner $signer    Request signer.
	 * @param callable|null      $transport Transport override.
	 *
	 * @phpstan-param (callable(string,array<string,string>,string):array<string,mixed>)|null $transport
	 */
	public function __construct(
		private CloudRequestSigner $signer = new CloudRequestSigner(),
		?callable $transport = null
	) {
		$this->transport = $transport ?? self::defaultTransport();
	}

	/**
	 * Generate an editable article draft.
	 *
	 * @param CloudSettings       $settings  Cloud settings.
	 * @param array<string,mixed> $payload   Product and article options.
	 * @param int                 $timestamp Timestamp.
	 * @param string              $nonce     Nonce.
	 *
	 * @return array<string,mixed>
	 *
	 * @throws InvalidArgumentException When Cloud settings are incomplete.
	 * @throws RuntimeException When Cloud response fails.
	 */
	public function generate( CloudSettings $settings, array $payload, int $timestamp, string $nonce ): array {
		if ( ! $settings->isConfigured() ) {
			throw new InvalidArgumentException( 'affilun Cloud settings are not configured.' );
		}

		$path                    = '/ai/product-article-draft';
		$body                    = self::jsonEncode( $payload );
		$headers                 = $this->signer->sign(
			$settings->siteId(),
			$settings->apiKey(),
			'POST',
			$path,
			$body,
			$timestamp,
			$nonce
		);
		$headers['Content-Type'] = 'application/json';

		$response = ( $this->transport )( $settings->endpoint() . $path, $headers, $body );
		$status   = isset( $response['status'] ) ? (int) $response['status'] : 0;
		$data     = isset( $response['data'] ) && is_array( $response['data'] ) ? $response['data'] : null;
		if ( $status < 200 || $status >= 300 || $data === null ) {
			return self::fallbackDraft( $payload );
		}

		return $data;
	}

	/**
	 * Build a local fallback article draft when Cloud generation is unavailable.
	 *
	 * @param array<string,mixed> $payload Request payload.
	 *
	 * @return array<string,mixed>
	 */
	private static function fallbackDraft( array $payload ): array {
		$title       = is_scalar( $payload['title'] ?? null ) ? (string) $payload['title'] : '商品';
		$brand       = is_scalar( $payload['brand'] ?? null ) ? trim( (string) $payload['brand'] ) : '';
		$description = is_scalar( $payload['description'] ?? null ) ? trim( (string) $payload['description'] ) : '';
		$tone        = is_scalar( $payload['tone'] ?? null ) ? (string) $payload['tone'] : 'professional_blog';
		$length      = is_scalar( $payload['target_length'] ?? null ) ? (string) $payload['target_length'] : 'medium';
		if ( ! array_key_exists( $tone, self::ARTICLE_DRAFT_TONE_LEADS ) ) {
			$tone = 'professional_blog';
		}
		if ( ! array_key_exists( $length, self::ARTICLE_DRAFT_LENGTH_CHARS ) ) {
			$length = 'medium';
		}

		$points = array_filter(
			array(
				$brand !== '' ? 'ブランド: ' . $brand : '',
				$description !== '' ? $description : '',
				'最新の価格・在庫・配送条件はリンク先で確認してください。',
			),
			static fn ( string $value ): bool => $value !== ''
		);

		$body = array(
			'# ' . $title . ' の紹介',
			'',
			self::ARTICLE_DRAFT_TONE_LEADS[ $tone ],
			'',
			'## 推しポイント',
		);
		foreach ( $points as $point ) {
			$body[] = '- ' . $point;
		}
		$body[] = '';
		$body[] = '## まとめ';
		$body[] = $title . ' が気になる場合は、商品リンク先で最新情報を確認してから検討してください。';

		return array(
			'title'         => $title . ' の紹介',
			'excerpt'       => $title . ' の特徴と購入前のチェックポイントをまとめました。',
			'body_markdown' => implode( "\n", $body ),
			'sources'       => array(),
			'model'         => 'plugin-fallback-article-draft',
			'usage'         => array(
				'units'         => 0,
				'provider'      => 'plugin_fallback',
				'tone'          => $tone,
				'target_length' => $length,
				'length_chars'  => self::ARTICLE_DRAFT_LENGTH_CHARS[ $length ],
			),
		);
	}

	/**
	 * JSON encode request payload.
	 *
	 * @param array<string,mixed> $payload Payload.
	 */
	private static function jsonEncode( array $payload ): string {
		if ( function_exists( 'wp_json_encode' ) ) {
			$body = wp_json_encode( $payload );
		} else {
			// phpcs:ignore WordPress.WP.AlternativeFunctions.json_encode_json_encode -- Pure-unit fallback when WordPress is unavailable.
			$body = json_encode( $payload );
		}
		if ( ! is_string( $body ) ) {
			throw new RuntimeException( 'Failed to encode affilun Cloud request.' );
		}
		return $body;
	}

	/**
	 * Default WordPress HTTP transport.
	 *
	 * @return callable
	 *
	 * @phpstan-return callable(string,array<string,string>,string):array<string,mixed>
	 */
	private static function defaultTransport(): callable {
		return static function ( string $url, array $headers, string $body ): array {
			$response = CloudHttpOptions::safePost(
				$url,
				array(
					'timeout' => 15,
					'headers' => $headers,
					'body'    => $body,
				)
			);
			if ( function_exists( 'is_wp_error' ) && is_wp_error( $response ) ) {
				return array(
					'status' => 503,
					'data'   => null,
				);
			}
			$raw     = is_array( $response ) ? (string) wp_remote_retrieve_body( $response ) : '';
			$decoded = json_decode( $raw, true );
			return array(
				'status' => is_array( $response ) ? (int) wp_remote_retrieve_response_code( $response ) : 0,
				'data'   => is_array( $decoded ) ? $decoded : null,
			);
		};
	}
}
