<?php
/**
 * Amazon discount shortcode configurator card.
 *
 * @package Affilun\Presentation\Admin\Page
 */

declare(strict_types=1);

namespace Affilun\Presentation\Admin\Page;

use Affilun\Application\AmazonDiscount\AmazonDiscountBrand;
use Affilun\Application\AmazonDiscount\AmazonDiscountBrandSettingsRepository;
use Affilun\Application\AmazonDiscount\AmazonDiscountCategory;
use Affilun\Application\AmazonDiscount\AmazonDiscountRepository;
use Affilun\Presentation\Admin\AdminCapability;
use Affilun\Presentation\Shortcode\AmazonDiscountShortcodeRenderer;
use DateTimeZone;

/**
 * Renders shortcode controls and a server-side canonical-card preview.
 */
final class AmazonDiscountShortcodeCardRenderer {

	/**
	 * Create the admin configurator.
	 *
	 * @param AmazonDiscountRepository                   $repository        Snapshot repository.
	 * @param AmazonDiscountShortcodeRenderer            $shortcodeRenderer Shortcode renderer.
	 * @param bool                                       $manualSyncAvailable Whether Creators API sync is configured.
	 * @param AmazonDiscountBrandSettingsRepository|null $brandSettings Priority-brand settings.
	 */
	public function __construct(
		private AmazonDiscountRepository $repository,
		private AmazonDiscountShortcodeRenderer $shortcodeRenderer,
		private bool $manualSyncAvailable = true,
		private ?AmazonDiscountBrandSettingsRepository $brandSettings = null
	) {
	}

	/**
	 * @param array<string,mixed> $requestData GET-like request data.
	 */
	public function render( array $requestData ): void {
		$brands         = $this->brands( $requestData );
		$categories     = $this->categories( $requestData );
		$sort           = AmazonDiscountShortcodeRenderer::sortFromAttributes(
			array( 'sort' => $requestData['affilun_discount_sort'] ?? null )
		);
		$perCategory    = AmazonDiscountShortcodeRenderer::perCategoryFromAttributes(
			array( 'per_category' => $requestData['affilun_discount_per_category'] ?? null )
		);
		$brandAttribute = $brands === array()
			? ''
			: sprintf( ' brands="%s"', implode( ',', $brands ) );
		$shortcode      = sprintf(
			'[affilun_amazon_discounts%s categories="%s" sort="%s" per_category="%d"]',
			$brandAttribute,
			implode( ',', $categories ),
			$sort,
			$perCategory
		);

		echo '<div class="affilun-products-list-card affilun-amazon-discount-shortcode-card">';
		echo '<h2>' . esc_html( 'Amazon割引商品ショートコード' ) . '</h2>';
		echo '<p>' . esc_html( 'Creators APIから日次取得した割引商品を、カテゴリ見出しとaffilun商品カードで表示します。' ) . '</p>';
		$this->renderSyncNotice( $requestData );
		$this->renderStatus();
		$this->renderManualSyncForm();
		$canConfigureBrands = ! function_exists( 'current_user_can' )
			|| current_user_can( AdminCapability::CAP_RUN_SYNC );
		$path               = $canConfigureBrands ? 'admin-post.php' : 'admin.php';
		$url                = function_exists( 'admin_url' ) ? admin_url( $path ) : $path;
		echo '<form method="' . ( $canConfigureBrands ? 'post' : 'get' ) . '" action="'
			. esc_url( $url )
			. '" class="affilun-amazon-discount-config">';
		if ( $canConfigureBrands ) {
			echo '<input type="hidden" name="action" value="'
				. esc_attr( AmazonDiscountConfigFormToken::FORM_ACTION )
				. '">';
		} else {
			echo '<input type="hidden" name="page" value="affilun-products">';
		}
		if ( $canConfigureBrands && function_exists( 'wp_nonce_field' ) ) {
			wp_nonce_field(
				AmazonDiscountConfigFormToken::NONCE_ACTION,
				AmazonDiscountConfigFormToken::NONCE_FIELD
			);
		}
		echo '<p><label for="affilun-discount-brands"><strong>'
			. esc_html( '優先ブランド' )
			. '</strong></label> ';
		echo '<input id="affilun-discount-brands" class="regular-text" type="text" '
			. 'name="affilun_discount_brands" value="'
			. esc_attr( implode( ',', $brands ) )
			. '" placeholder="Anker,Dyson"'
			. ( $canConfigureBrands ? '' : ' readonly' )
			. '>';
		echo '<span class="description"> '
			. esc_html(
				$canConfigureBrands
					? 'カンマ区切り、最大10ブランド。保存後の取得からブランド別商品が追加されます。'
					: '優先ブランドの変更には同期実行権限が必要です。'
			)
			. '</span></p>';
		echo '<fieldset><legend>' . esc_html( '表示カテゴリ' ) . '</legend>';
		foreach ( AmazonDiscountCategory::LABELS as $value => $label ) {
			$checked = in_array( $value, $categories, true ) ? ' checked="checked"' : '';
			echo '<label class="affilun-amazon-discount-config__category">';
			// phpcs:disable WordPress.Security.EscapeOutput.OutputNotEscaped -- Closed static HTML attribute.
			echo '<input type="checkbox" name="affilun_discount_categories[]" value="'
				. esc_attr( $value ) . '"' . $checked . '> ';
			// phpcs:enable WordPress.Security.EscapeOutput.OutputNotEscaped
			echo esc_html( $label ) . '</label>';
		}
		echo '</fieldset>';
		echo '<label for="affilun-discount-sort">' . esc_html( '表示順' ) . '</label> ';
		echo '<select id="affilun-discount-sort" name="affilun_discount_sort">';
		foreach ( AmazonDiscountShortcodeRenderer::SORT_LABELS as $value => $label ) {
			echo '<option value="' . esc_attr( $value ) . '"'
				. ( $value === $sort ? ' selected' : '' ) . '>'
				. esc_html( $label ) . '</option>';
		}
		echo '</select> ';
		echo '<label for="affilun-discount-count">' . esc_html( 'ブランド・カテゴリごとの商品数' ) . '</label> ';
		echo '<input id="affilun-discount-count" type="number" min="1" max="20" name="affilun_discount_per_category" value="'
			. esc_attr( (string) $perCategory ) . '"> ';
		echo '<button type="submit" class="button button-secondary">' . esc_html( 'プレビューを更新' ) . '</button>';
		echo '</form>';
		echo '<p><label for="affilun-amazon-discount-shortcode"><strong>'
			. esc_html( 'ショートコード' ) . '</strong></label></p>';
		echo '<input id="affilun-amazon-discount-shortcode" class="large-text code" type="text" readonly value="'
			. esc_attr( $shortcode ) . '" onclick="this.select();">';
		echo '<div class="affilun-amazon-discount-preview">';
		echo '<h3>' . esc_html( 'プレビュー' ) . '</h3>';
		$previewAttributes = array(
			'brands'       => implode( ',', $brands ),
			'categories'   => implode( ',', $categories ),
			'sort'         => $sort,
			'per_category' => $perCategory,
		);
		echo $this->shortcodeRenderer->render( // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Canonical renderer returns escaped plugin HTML.
			$previewAttributes,
			true
		);
		echo '</div></div>';
	}

	/**
	 * Render the local snapshot status.
	 */
	private function renderStatus(): void {
		$count  = $this->repository->count();
		$latest = $this->repository->latestFetchedAt();
		$text   = $latest === null
			? sprintf( '保存済み商品: %d件（初回の日次取得待ち）', $count )
			: sprintf(
				'保存済み商品: %d件 / 最終取得: %s JST',
				$count,
				$latest->setTimezone( new DateTimeZone( 'Asia/Tokyo' ) )->format( 'Y-m-d H:i' )
			);
		echo '<p class="description">' . esc_html( $text ) . '</p>';
	}

	/**
	 * Render the nonce-protected background synchronization control.
	 */
	private function renderManualSyncForm(): void {
		$path       = 'admin-post.php';
		$url        = function_exists( 'admin_url' ) ? admin_url( $path ) : $path;
		$canRunSync = ! function_exists( 'current_user_can' )
			|| current_user_can( AdminCapability::CAP_RUN_SYNC );
		$enabled    = $this->manualSyncAvailable && $canRunSync;

		echo '<form method="post" action="' . esc_url( $url ) . '" class="affilun-amazon-discount-sync">';
		echo '<input type="hidden" name="action" value="'
			. esc_attr( AmazonDiscountSyncFormToken::FORM_ACTION )
			. '">';
		if ( function_exists( 'wp_nonce_field' ) ) {
			wp_nonce_field(
				AmazonDiscountSyncFormToken::NONCE_ACTION,
				AmazonDiscountSyncFormToken::NONCE_FIELD
			);
		}
		echo '<button type="submit" class="button button-secondary"'
			. ( $enabled ? '' : ' disabled' )
			. '>'
			. esc_html( 'Amazon割引商品を今すぐ取得' )
			. '</button>';
		echo '<span class="description"> '
			. esc_html(
				match ( true ) {
					! $canRunSync => '手動取得には同期実行権限が必要です。',
					! $this->manualSyncAvailable => 'Amazon Creators APIの資格情報を設定すると利用できます。',
					default => 'バックグラウンドで取得を開始します。完了後にこの画面を再読み込みしてください。',
				}
			)
			. '</span>';
		echo '</form>';
	}

	/**
	 * Render the result of a manual synchronization request.
	 *
	 * @param array<string,mixed> $requestData GET-like request data.
	 */
	private function renderSyncNotice( array $requestData ): void {
		$status = is_string( $requestData[ AmazonDiscountSyncFormToken::STATUS_QUERY ] ?? null )
			? $requestData[ AmazonDiscountSyncFormToken::STATUS_QUERY ]
			: '';
		$notice = match ( $status ) {
			'queued' => array( 'notice notice-success inline', 'Amazon割引商品の手動取得を開始しました。' ),
			'forbidden' => array( 'notice notice-error inline', '手動取得を実行する権限がありません。' ),
			'invalid_nonce' => array( 'notice notice-error inline', '手動取得リクエストを確認できませんでした。' ),
			'unavailable' => array( 'notice notice-error inline', 'Amazon Creators APIが設定されていません。' ),
			'error' => array( 'notice notice-error inline', '手動取得を開始できませんでした。' ),
			default => null,
		};
		if ( $notice === null ) {
			return;
		}

		echo '<div class="'
			. self::escapeAttribute( $notice[0] ) // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Escaped by the local attribute helper.
			. '"><p>'
			. self::escapeHtml( $notice[1] ) // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Escaped by the local HTML helper.
			. '</p></div>';
	}

	/**
	 * Escape visible notice text.
	 *
	 * @param string $value Raw text.
	 */
	private static function escapeHtml( string $value ): string {
		return htmlspecialchars( $value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8' );
	}

	/**
	 * Escape a notice class attribute.
	 *
	 * @param string $value Raw attribute value.
	 */
	private static function escapeAttribute( string $value ): string {
		return htmlspecialchars( $value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8' );
	}

	/**
	 * @param array<string,mixed> $requestData Request data.
	 *
	 * @return list<string>
	 */
	private function categories( array $requestData ): array {
		$raw = $requestData['affilun_discount_categories'] ?? null;
		if ( is_array( $raw ) ) {
			$categories = AmazonDiscountCategory::normalize( array_values( $raw ) );
			if ( $categories !== array() ) {
				return $categories;
			}
		}
		return AmazonDiscountCategory::DEFAULT_SELECTION;
	}

	/**
	 * @param array<string,mixed> $requestData Request data.
	 *
	 * @return list<string>
	 */
	private function brands( array $requestData ): array {
		if ( array_key_exists( 'affilun_discount_brands', $requestData ) ) {
			return AmazonDiscountBrand::parse( $requestData['affilun_discount_brands'] );
		}
		return $this->brandSettings !== null ? $this->brandSettings->load() : array();
	}
}
