<?php
/**
 * Product edit admin service provider.
 *
 * @package Affilun\Presentation\Admin\ProductEdit
 */

declare(strict_types=1);

// phpcs:disable Squiz.Commenting.VariableComment.Missing,Squiz.Commenting.FunctionComment.MissingParamTag

namespace Affilun\Presentation\Admin\ProductEdit;

use Affilun\Application\Provider\ProviderRegistry;
use Affilun\Application\Product\UseCase\UpdateProductManualFieldsUseCase;
use Affilun\Application\Cache\CacheServiceProvider;
use Affilun\Application\Cache\RenderCache;
use Affilun\Bootstrap\HookRegistrar;
use Affilun\Bootstrap\ServiceProvider;
use Affilun\Bootstrap\ServiceProviderRegistry;
use Affilun\Infrastructure\Persistence\PersistenceServiceProvider;
use Affilun\Infrastructure\Persistence\ProductListQuery;
use Affilun\Infrastructure\Persistence\ProductMetaRepository;
use Affilun\Infrastructure\Persistence\ProductRecord;
use Affilun\Infrastructure\Persistence\ProductRepository;
use Affilun\Infrastructure\Persistence\ProductUrlRecord;
use Affilun\Infrastructure\Persistence\ProductUrlRepository;
use Affilun\Presentation\Rest\ProviderServiceProvider;
use LogicException;
use Throwable;

/**
 * Adds a saved-product edit page and update handler.
 */
final class ProductEditAdminServiceProvider implements ServiceProvider {

	private const MENU_SLUG = 'affilun-edit-product';

	private const FORM_ACTION = 'affilun_update_product';

	private const NONCE_FIELD = '_affilun_update_product_nonce';

	private const VIEW_NONCE_FIELD = '_affilun_edit_product_nonce';

	/**
	 * Provider labels shown in the URL edit section.
	 *
	 * @var array<string,string>
	 */
	private const PROVIDERS = array(
		'amazon'         => 'Amazon',
		'rakuten'        => '楽天市場',
		'yahoo_shopping' => 'Yahoo!ショッピング',
		'mercari'        => 'メルカリ',
	);

	private ?ProductRepository $repository = null;

	private ?ProductMetaRepository $metaRepository = null;

	private ?ProductUrlRepository $urlRepository = null;

	private ?ProviderRegistry $providerRegistry = null;

	private ?RenderCache $renderCache = null;

	/**
	 * Register repository.
	 *
	 * @throws LogicException When repository binding is missing/invalid.
	 */
	public function register( ServiceProviderRegistry $registry ): void {
		$key = PersistenceServiceProvider::BINDING_PRODUCT_REPOSITORY;
		if ( ! $registry->has( $key ) ) {
			throw new LogicException( 'ProductEditAdminServiceProvider requires ProductRepository.' );
		}
		$repository = $registry->get( $key );
		if ( ! $repository instanceof ProductRepository ) {
			throw new LogicException( 'ProductEditAdminServiceProvider expected ProductRepository.' );
		}

		$metaKey = PersistenceServiceProvider::BINDING_PRODUCT_META_REPOSITORY;
		if ( ! $registry->has( $metaKey ) ) {
			throw new LogicException( 'ProductEditAdminServiceProvider requires ProductMetaRepository.' );
		}
		$metaRepository = $registry->get( $metaKey );
		if ( ! $metaRepository instanceof ProductMetaRepository ) {
			throw new LogicException( 'ProductEditAdminServiceProvider expected ProductMetaRepository.' );
		}

		$urlKey = PersistenceServiceProvider::BINDING_PRODUCT_URL_REPOSITORY;
		if ( $registry->has( $urlKey ) ) {
			$urlRepository = $registry->get( $urlKey );
			if ( ! $urlRepository instanceof ProductUrlRepository ) {
				throw new LogicException( 'ProductEditAdminServiceProvider expected ProductUrlRepository.' );
			}
			$this->urlRepository = $urlRepository;
		}

		$providerRegistryKey = ProviderServiceProvider::BINDING_PROVIDER_REGISTRY;
		if ( $registry->has( $providerRegistryKey ) ) {
			$providerRegistry = $registry->get( $providerRegistryKey );
			if ( $providerRegistry instanceof ProviderRegistry ) {
				$this->providerRegistry = $providerRegistry;
			}
		}

		$this->repository     = $repository;
		$this->metaRepository = $metaRepository;

		$cacheKey = CacheServiceProvider::BINDING_RENDER_CACHE;
		if ( $registry->has( $cacheKey ) ) {
			$cache             = $registry->get( $cacheKey );
			$this->renderCache = $cache instanceof RenderCache ? $cache : null;
		}
	}

	/**
	 * Register hooks.
	 */
	public function boot( HookRegistrar $hooks ): void {
		$hooks->addAction( 'admin_menu', array( $this, 'onAdminMenu' ) );
		$hooks->addAction( 'admin_post_' . self::FORM_ACTION, array( $this, 'onSavePost' ) );
	}

	/**
	 * Add submenu page.
	 */
	public function onAdminMenu(): void {
		if ( ! function_exists( 'add_submenu_page' ) ) {
			return;
		}
		add_submenu_page(
			'',
			'affilun 商品編集',
			'affilun 商品編集',
			'edit_posts',
			self::MENU_SLUG,
			array( $this, 'renderPage' )
		);
	}

	/**
	 * Render edit page.
	 */
	public function renderPage(): void {
		if ( $this->repository === null ) {
			return;
		}

		$getData   = self::currentGetData();
		$productId = self::requestInt( $getData, 'product_id' );
		$record    = $productId === null ? null : $this->repository->findById( $productId );

		echo '<div class="wrap affilun-admin-page affilun-product-edit-page">';
		$this->renderHero();
		if ( $record === null ) {
			echo '<div class="affilun-product-edit-card"><p>' . esc_html( '編集する商品を選択してください。' ) . '</p></div></div>';
			return;
		}

		echo '<div class="affilun-product-edit-card">';
		$this->renderForm( $record );
		echo '</div>';
		echo '</div>';
	}

	/**
	 * Render affilun branded page hero.
	 */
	private function renderHero(): void {
		$logoUrl = defined( 'AFFILUN_PLUGIN_URL' )
			? (string) constant( 'AFFILUN_PLUGIN_URL' ) . 'assets/img/brand/logo-landscape-transparent.png'
			: '';

		echo '<div class="affilun-admin-hero">';
		echo '<div class="affilun-admin-hero__content">';
		if ( $logoUrl !== '' ) {
			echo '<img class="affilun-admin-logo" src="' . esc_url( $logoUrl ) . '" alt="affilun / あふぃるん">';
		}
		echo '<p class="affilun-admin-kicker">' . esc_html( 'つなぐ・分析する・成長する' ) . '</p>';
		echo '<h1 class="affilun-admin-title">' . esc_html( 'アフィリエイト管理' ) . '</h1>';
		echo '</div>';
		echo '<div class="affilun-admin-hero__aside" aria-hidden="true"><div class="affilun-admin-palette">'
			. '<span></span><span></span><span></span><span></span><span></span></div></div>';
		echo '</div>';
	}

	/**
	 * Render form.
	 */
	private function renderForm( ProductRecord $record ): void {
		$id = $record->id();
		if ( $id === null ) {
			return;
		}

		echo '<form method="post" action="' . esc_url( self::adminPostUrl() ) . '">';
		echo '<input type="hidden" name="action" value="' . esc_attr( self::FORM_ACTION ) . '">';
		echo '<input type="hidden" name="product_id" value="' . esc_attr( (string) $id ) . '">';
		if ( function_exists( 'wp_nonce_field' ) ) {
			wp_nonce_field( self::nonceAction( $id ), self::NONCE_FIELD );
		}

		$this->renderTextInput( 'title', '商品名', $record->title(), true );
		$this->renderUrlInput( 'image_primary_url', '画像URL', $record->imagePrimaryUrl() ?? '', false );
		$this->renderUrlInput( 'detail_url', '主要詳細URL', $record->detailUrl(), true );
		$this->renderUrlInput( 'affiliate_url', '主要アフィリエイトURL', $record->affiliateUrl() ?? '', false );
		$this->renderProviderUrlInputs( $record );
		$this->renderTextInput( 'price_label', '価格ラベル', $record->priceLabel() ?? '', false );
		$this->renderLifecycleSelect( $record->lifecycleState() );

		echo '<p><button type="submit" class="button button-primary">' . esc_html( '保存' ) . '</button></p>';
		echo '</form>';
	}

	/**
	 * Render text input.
	 */
	private function renderTextInput( string $name, string $label, string $value, bool $required ): void {
		$this->renderInputRow( $name, $label, $value, $required, 'text', null );
	}

	/**
	 * Render URL input with an adjacent open link.
	 */
	private function renderUrlInput( string $name, string $label, string $value, bool $required ): void {
		$this->renderInputRow( $name, $label, $value, $required, 'url', $value );
	}

	/**
	 * Render one label/control row.
	 */
	private function renderInputRow(
		string $name,
		string $label,
		string $value,
		bool $required,
		string $type,
		?string $linkUrl
	): void {
		$id = 'affilun-product-' . $name;
		echo '<div class="affilun-product-edit-field">';
		echo '<label for="' . esc_attr( $id ) . '">' . esc_html( $label ) . '</label>';
		echo '<div class="affilun-product-edit-control">';
		echo '<input class="large-text" type="' . esc_attr( $type ) . '" id="' . esc_attr( $id ) . '"';
		echo ' name="' . esc_attr( $name ) . '" value="' . esc_attr( $value ) . '"';
		echo esc_attr( $required ? ' required' : '' );
		echo '>';
		$this->renderOpenLink( $linkUrl );
		echo '</div>';
		echo '</div>';
	}

	/**
	 * Render provider URL rows.
	 */
	private function renderProviderUrlInputs( ProductRecord $record ): void {
		$id = $record->id();
		if ( $id === null ) {
			return;
		}

		$urls = $this->loadProviderUrlInputs( $record );
		echo '<fieldset class="affilun-product-edit-provider-urls" style="border:1px solid #dcdcde;padding:12px;margin:16px 0;">';
		echo '<legend style="font-weight:600;">' . esc_html( 'プロバイダー別リンク' ) . '</legend>';
		echo '<p class="description">' . esc_html( '商品カードの各プロバイダーボタンに使う商品ページURLです。空欄にするとそのプロバイダーの紐付けを削除します。' ) . '</p>';
		foreach ( $this->editableProviders() as $provider => $label ) {
			$row            = $urls[ $provider ] ?? array(
				'url'              => '',
				'provider_item_id' => '',
			);
			$url            = is_string( $row['url'] ?? null ) ? $row['url'] : '';
			$providerItemId = is_string( $row['provider_item_id'] ?? null ) ? $row['provider_item_id'] : '';
			echo '<div class="affilun-product-edit-provider-url" style="margin:12px 0;padding-top:10px;border-top:1px solid #f0f0f1;">';
			echo '<h3 style="margin:0 0 8px;">' . esc_html( $label ) . '</h3>';
			$this->renderProviderInputRow(
				$provider,
				'provider_item_id',
				'商品ID',
				$providerItemId,
				'text',
				null
			);
			$this->renderProviderInputRow(
				$provider,
				'url',
				'URL',
				$url,
				'url',
				$url
			);
			echo '</div>';
		}
		echo '</fieldset>';
	}

	/**
	 * Providers currently editable on this site.
	 *
	 * @return array<string,string>
	 */
	private function editableProviders(): array {
		if ( $this->providerRegistry === null ) {
			return self::PROVIDERS;
		}

		$providers = array();
		foreach ( $this->providerRegistry->keys() as $provider ) {
			if ( array_key_exists( $provider, self::PROVIDERS ) ) {
				$providers[ $provider ] = self::PROVIDERS[ $provider ];
			}
		}

		return $providers;
	}

	/**
	 * Render one provider-link input row.
	 */
	private function renderProviderInputRow(
		string $provider,
		string $field,
		string $label,
		string $value,
		string $type,
		?string $linkUrl
	): void {
		$id = 'affilun-product-provider-' . $provider . '-' . $field;
		echo '<div class="affilun-product-edit-field affilun-product-edit-field--provider">';
		echo '<label for="' . esc_attr( $id ) . '">' . esc_html( $label ) . '</label>';
		echo '<div class="affilun-product-edit-control">';
		echo '<input class="large-text" type="' . esc_attr( $type ) . '" id="' . esc_attr( $id ) . '"';
		echo ' name="provider_urls[' . esc_attr( $provider ) . '][' . esc_attr( $field ) . ']" value="' . esc_attr( $value ) . '"';
		if ( $type === 'url' ) {
			echo ' placeholder="https://"';
		}
		echo '>';
		$this->renderOpenLink( $linkUrl );
		echo '</div>';
		echo '</div>';
	}

	/**
	 * Render an adjacent URL link when a valid HTTP(S) URL exists.
	 */
	private function renderOpenLink( ?string $url ): void {
		$url = is_string( $url ) ? trim( $url ) : '';
		if ( $url === '' || preg_match( '#\Ahttps?://#i', $url ) !== 1 ) {
			return;
		}
		echo '<a class="button button-small affilun-product-edit-open-link" href="' . esc_url( $url ) . '" target="_blank" rel="noopener noreferrer">' . esc_html( '開く' ) . '</a>';
	}

	/**
	 * Load provider URLs for the edit form.
	 *
	 * @return array<string,array{url:string,provider_item_id:string}>
	 */
	private function loadProviderUrlInputs( ProductRecord $record ): array {
		$result = array();
		$id     = $record->id();
		if ( $id !== null && $this->urlRepository !== null ) {
			try {
				foreach ( $this->urlRepository->findByProductId( $id ) as $urlRecord ) {
					/** @var ProductUrlRecord $urlRecord */
					$provider = $urlRecord->providerType();
					if ( ! array_key_exists( $provider, self::PROVIDERS ) || array_key_exists( $provider, $result ) ) {
						continue;
					}
					$result[ $provider ] = array(
						'url'              => $urlRecord->url(),
						'provider_item_id' => $urlRecord->providerItemId() ?? '',
					);
				}
			} catch ( Throwable $unused ) {
				unset( $unused );
			}
		}

		$primary = $record->provider();
		if ( array_key_exists( $primary, self::PROVIDERS ) && ! array_key_exists( $primary, $result ) ) {
			$result[ $primary ] = array(
				'url'              => $record->detailUrl(),
				'provider_item_id' => $record->providerItemId(),
			);
		}

		return $result;
	}

	/**
	 * Render lifecycle select.
	 */
	private function renderLifecycleSelect( string $current ): void {
		$states = array(
			ProductListQuery::LIFECYCLE_STATE_ACTIVE   => '公開済み',
			ProductListQuery::LIFECYCLE_STATE_ARCHIVED => '未使用',
			ProductListQuery::LIFECYCLE_STATE_DELETED_PENDING => 'ゴミ箱',
		);
		echo '<div class="affilun-product-edit-field">';
		echo '<label for="affilun-product-lifecycle">' . esc_html( '状態' ) . '</label>';
		echo '<div class="affilun-product-edit-control">';
		echo '<select id="affilun-product-lifecycle" name="lifecycle_state">';
		foreach ( $states as $value => $label ) {
			$selected = $current === $value ? ' selected="selected"' : '';
			echo '<option value="' . esc_attr( $value ) . '"'
				// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- $selected is hard-coded.
				. $selected . '>' . esc_html( $label ) . '</option>';
		}
		echo '</select>';
		echo '</div>';
		echo '</div>';
	}

	/**
	 * Save post handler.
	 */
	public function onSavePost(): void {
		if ( $this->repository === null || ! self::canEdit() ) {
			self::redirectList( 'forbidden' );
			return;
		}

		$postData  = self::currentPostData();
		$productId = self::requestInt( $postData, 'product_id' );
		if ( $productId === null || ! self::verifyNonce( $productId, $postData ) ) {
			self::redirectList( 'invalid_nonce' );
			return;
		}

		$fields = self::fieldsFromPost( $postData );
		try {
			$useCase = new UpdateProductManualFieldsUseCase( $this->repository, $this->metaRepository, $this->urlRepository );
			$useCase->execute( $productId, $fields );
			$this->flushRenderCache();
			self::redirectList( 'saved' );
		} catch ( Throwable $unused ) {
			unset( $unused );
			self::redirectEdit( $productId, 'error' );
		}
	}

	/**
	 * Flush rendered card HTML after a product display-field update.
	 */
	private function flushRenderCache(): void {
		if ( $this->renderCache === null ) {
			return;
		}

		try {
			$this->renderCache->flush();
		} catch ( Throwable $unused ) {
			unset( $unused );
		}
	}

	/**
	 * Extract fields from POST.
	 *
	 * @param array<string,mixed> $postData POST data.
	 *
	 * @return array<string,mixed>
	 */
	private static function fieldsFromPost( array $postData ): array {
		$keys   = array(
			'title',
			'image_primary_url',
			'detail_url',
			'affiliate_url',
			'price_label',
			'lifecycle_state',
		);
		$fields = array();
		foreach ( $keys as $key ) {
			$value          = $postData[ $key ] ?? '';
			$fields[ $key ] = is_scalar( $value ) ? (string) $value : '';
		}
		$fields['provider_urls'] = self::providerUrlsFromPost( $postData['provider_urls'] ?? array() );
		return $fields;
	}

	/**
	 * Extract provider URL fields from POST.
	 *
	 * @param mixed $raw Raw provider URL data.
	 *
	 * @return array<string,array{url:string,provider_item_id:string}>
	 */
	private static function providerUrlsFromPost( mixed $raw ): array {
		if ( ! is_array( $raw ) ) {
			return array();
		}

		$result = array();
		foreach ( self::PROVIDERS as $provider => $unusedLabel ) {
			if ( ! array_key_exists( $provider, $raw ) ) {
				continue;
			}
			$entry = $raw[ $provider ];
			if ( ! is_array( $entry ) ) {
				continue;
			}
			$urlRaw              = $entry['url'] ?? '';
			$itemRaw             = $entry['provider_item_id'] ?? '';
			$result[ $provider ] = array(
				'url'              => is_scalar( $urlRaw ) ? (string) $urlRaw : '',
				'provider_item_id' => is_scalar( $itemRaw ) ? (string) $itemRaw : '',
			);
		}

		return $result;
	}

	/**
	 * Verify nonce.
	 *
	 * @param int                 $productId Product id.
	 * @param array<string,mixed> $postData POST data.
	 */
	private static function verifyNonce( int $productId, array $postData ): bool {
		$value = $postData[ self::NONCE_FIELD ] ?? '';
		$nonce = is_scalar( $value ) ? self::sanitizeTextField( (string) $value ) : '';
		return $nonce !== '' && function_exists( 'wp_verify_nonce' )
			&& (bool) wp_verify_nonce( $nonce, self::nonceAction( $productId ) );
	}

	/**
	 * Verify the edit-page URL nonce when a product id is supplied.
	 *
	 * @param int                 $productId Product id.
	 * @param array<string,mixed> $getData Sanitized GET data.
	 */
	private static function verifyViewNonce( int $productId, array $getData ): bool {
		$value = $getData[ self::VIEW_NONCE_FIELD ] ?? '';
		$nonce = is_scalar( $value ) ? self::sanitizeTextField( (string) $value ) : '';
		return $nonce !== '' && function_exists( 'wp_verify_nonce' )
			&& (bool) wp_verify_nonce( $nonce, self::viewNonceAction( $productId ) );
	}

	/**
	 * Request int helper.
	 *
	 * @param array<mixed> $source Source.
	 */
	private static function requestInt( array $source, string $key ): ?int {
		$value = $source[ $key ] ?? null;
		$value = is_scalar( $value ) ? trim( (string) $value ) : '';
		return preg_match( '/\A\d+\z/', $value ) === 1 && (int) $value > 0 ? (int) $value : null;
	}

	/**
	 * Nonce action.
	 */
	private static function nonceAction( int $productId ): string {
		return self::FORM_ACTION . '_' . $productId;
	}

	/**
	 * Edit-page URL nonce action.
	 */
	private static function viewNonceAction( int $productId ): string {
		return 'affilun_edit_product_' . $productId;
	}

	/**
	 * Capability check.
	 */
	private static function canEdit(): bool {
		return function_exists( 'current_user_can' ) && current_user_can( 'edit_posts' );
	}

	/**
	 * Return sanitized POST data for the product-edit form.
	 *
	 * @return array<string,mixed>
	 */
	private static function currentPostData(): array {
		// phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce is verified before callers mutate state.
		$raw = $_POST;
		return self::sanitizeRequestData( $raw );
	}

	/**
	 * Return sanitized GET data for the read-only product-edit page.
	 *
	 * @return array<string,mixed>
	 */
	private static function currentGetData(): array {
		// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Product id is rejected below unless its product-specific view nonce verifies.
		$raw  = $_GET;
		$data = self::sanitizeRequestData( $raw );

		$productId = self::requestInt( $data, 'product_id' );
		if ( $productId !== null && ! self::verifyViewNonce( $productId, $data ) ) {
			return array();
		}

		return $data;
	}

	/**
	 * Sanitize a GET/POST-like array recursively after unslashing.
	 *
	 * @param array<string,mixed> $data Request data.
	 *
	 * @return array<string,mixed>
	 */
	private static function sanitizeRequestData( array $data ): array {
		$unslashed = function_exists( 'wp_unslash' ) ? wp_unslash( $data ) : $data;
		if ( ! is_array( $unslashed ) ) {
			return array();
		}

		$sanitized = array();
		foreach ( $unslashed as $key => $value ) {
			if ( ! is_string( $key ) ) {
				continue;
			}
			$sanitized[ $key ] = self::sanitizeRequestValue( $key, $value );
		}

		return $sanitized;
	}

	/**
	 * Sanitize one request value using the field context.
	 *
	 * @param string $key   Request key.
	 * @param mixed  $value Request value.
	 *
	 * @return mixed
	 */
	private static function sanitizeRequestValue( string $key, $value ) {
		if ( is_array( $value ) ) {
			$result = array();
			foreach ( $value as $childKey => $childValue ) {
				if ( is_string( $childKey ) ) {
					$result[ $childKey ] = self::sanitizeRequestValue( $childKey, $childValue );
					continue;
				}
				if ( is_int( $childKey ) ) {
					$result[ $childKey ] = self::sanitizeRequestValue( $key, $childValue );
				}
			}
			return $result;
		}

		if ( ! is_scalar( $value ) ) {
			return '';
		}

		$string = (string) $value;
		if ( str_contains( $key, 'url' ) ) {
			return self::sanitizeUrlField( $string );
		}
		if ( $key === 'lifecycle_state' ) {
			return function_exists( 'sanitize_key' )
				? (string) sanitize_key( $string )
				: strtolower( preg_replace( '/[^a-z0-9_\-]/i', '', $string ) ?? '' );
		}

		return self::sanitizeTextField( $string );
	}

	/**
	 * Sanitize a URL field, preserving empty values for optional fields.
	 *
	 * @param string $value Raw URL.
	 */
	private static function sanitizeUrlField( string $value ): string {
		$value = trim( $value );
		if ( $value === '' ) {
			return '';
		}
		if ( function_exists( 'sanitize_url' ) ) {
			return (string) sanitize_url( $value );
		}
		if ( function_exists( 'esc_url_raw' ) ) {
			return (string) esc_url_raw( $value );
		}

		return filter_var( $value, FILTER_VALIDATE_URL ) === false ? '' : $value;
	}

	/**
	 * Sanitize a single-line text field.
	 *
	 * @param string $value Raw value.
	 */
	private static function sanitizeTextField( string $value ): string {
		if ( function_exists( 'sanitize_text_field' ) ) {
			return (string) sanitize_text_field( $value );
		}

		$value = preg_replace( '/<[^>]*>/', '', $value ) ?? $value;
		return trim( (string) preg_replace( '/\s+/', ' ', $value ) );
	}

	/**
	 * Redirect to edit page.
	 */
	private static function redirectEdit( int $productId, string $status ): void {
		$url = self::adminPageUrl()
			. '&product_id=' . rawurlencode( (string) $productId )
			. '&affilun_status=' . rawurlencode( $status );
		self::safeRedirect( $url );
	}

	/**
	 * Redirect to list page.
	 */
	private static function redirectList( string $status ): void {
		self::safeRedirect( self::listPageUrl() . '&affilun_status=' . rawurlencode( $status ) );
	}

	/**
	 * Safe redirect.
	 */
	private static function safeRedirect( string $url ): void {
		if ( function_exists( 'wp_safe_redirect' ) ) {
			wp_safe_redirect( $url );
			exit;
		}
	}

	/**
	 * Admin-post URL.
	 */
	private static function adminPostUrl(): string {
		return function_exists( 'admin_url' ) ? (string) admin_url( 'admin-post.php' ) : 'admin-post.php';
	}

	/**
	 * Admin edit URL.
	 */
	private static function adminPageUrl(): string {
		return function_exists( 'admin_url' )
			? (string) admin_url( 'admin.php?page=' . self::MENU_SLUG )
			: 'admin.php?page=' . self::MENU_SLUG;
	}

	/**
	 * Products list URL.
	 */
	private static function listPageUrl(): string {
		return function_exists( 'admin_url' )
			? (string) admin_url( 'admin.php?page=affilun-products' )
			: 'admin.php?page=affilun-products';
	}
}
