<?php
/**
 * Admin Module
 *
 * @author    Wpayme <hi@wpayme.com>
 * @copyright 2024-2025 Wpayme
 * @license   GPL-3.0-or-later
 * @package   Wpayme\WordPress\Pay\Admin
 */

namespace Wpayme\WordPress\Pay\Admin;

use Wpayme\WordPress\DateTime\DateTimeImmutable;
use Wpayme\WordPress\Money\TaxedMoney;
use Wpayme\WordPress\Number\Number;
use Wpayme\WordPress\Money\Currency;
use Wpayme\WordPress\Money\Money;
use Wpayme\WordPress\Pay\Address;
use Wpayme\WordPress\Pay\AddressHelper;
use Wpayme\WordPress\Pay\ContactName;
use Wpayme\WordPress\Pay\ContactNameHelper;
use Wpayme\WordPress\Pay\Core\Util;
use Wpayme\WordPress\Pay\CreditCard;
use Wpayme\WordPress\Pay\Customer;
use Wpayme\WordPress\Pay\CustomerHelper;
use Wpayme\WordPress\Pay\Payments\Payment;
use Wpayme\WordPress\Pay\Payments\PaymentLines;
use Wpayme\WordPress\Pay\Plugin;
use Wpayme\WordPress\Pay\Subscriptions\Subscription;
use Wpayme\WordPress\Pay\Subscriptions\SubscriptionInterval;
use Wpayme\WordPress\Pay\Subscriptions\SubscriptionPhase;

/**
 * WordPress Pay admin
 *
 * @author  Remco Tolsma
 * @version 2.5.0
 * @since   1.0.0
 */
class AdminModule {
	/**
	 * Plugin.
	 *
	 * @var Plugin
	 */
	private $plugin;

	/**
	 * Admin settings page.
	 *
	 * @var AdminSettings
	 */
	public $settings;

	/**
	 * Admin about page.
	 *
	 * @var AdminAboutPage|null
	 */
	public $about_page;

	/**
	 * Admin dashboard page.
	 *
	 * @var AdminDashboard
	 */
	public $dashboard;

	/**
	 * Admin site health.
	 *
	 * @var AdminHealth
	 */
	public $health;

	/**
	 * Admin tour page.
	 *
	 * @var AdminTour
	 */
	public $tour;

	/**
	 * Singleton instance.
	 *
	 * @var self|null
	 */
	private static $instance = null;

	/**
	 * Construct and initialize an admin object.
	 *
	 * @param Plugin $plugin Plugin.
	 */
	public function __construct( Plugin $plugin ) {
		$this->plugin = $plugin;

		// Actions.
		add_action( 'admin_init', [ $this, 'admin_init' ] );
		add_action( 'admin_menu', [ $this, 'admin_menu' ] );

		add_action( 'load-post.php', [ $this, 'maybe_test_payment' ] );

		add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_scripts' ] );

		add_filter( 'parent_file', [ $this, 'admin_menu_parent_file' ] );

		// Modules.
		$this->settings  = new AdminSettings( $plugin );
		$this->dashboard = new AdminDashboard();
		$this->health    = new AdminHealth( $plugin );
		$this->tour      = new AdminTour( $plugin );

		// About page.
		$about_page_file = $this->plugin->get_option( 'about_page_file' );

		if ( null !== $about_page_file ) {
			$this->about_page = new AdminAboutPage( $plugin, $about_page_file );
		}
	}

	/**
	 * Admin initialize.
	 *
	 * @return void
	 */
	public function admin_init() {
		// Maybe.
		$this->maybe_redirect();

		// Post types.
		new AdminGatewayPostType( $this->plugin );

		$admin_payment_post_type = new AdminPaymentPostType( $this->plugin );

		$admin_payment_post_type->admin_init();

		$admin_subscription_post_type = new AdminSubscriptionPostType( $this->plugin );

		$admin_subscription_post_type->admin_init();
	}

	/**
	 * Maybe redirect.
	 *
	 * @link https://github.com/woothemes/woocommerce/blob/2.4.4/includes/admin/class-wc-admin.php#L29
	 * @link https://github.com/woothemes/woocommerce/blob/2.4.4/includes/admin/class-wc-admin.php#L96-L122
	 *
	 * @return void
	 */
	public function maybe_redirect() {
		$redirect = get_transient( 'wpayme_pay_admin_redirect' );

		// Check.
		if (
			empty( $redirect )
				||
			\wp_doing_ajax()
				||
			\wp_doing_cron()
				||
			is_network_admin()
				||
			filter_has_var( INPUT_GET, 'activate-multi' )
				||
			! current_user_can( 'manage_options' )
		) {
			return;
		}

		/**
		 * Delete the `wpayme_pay_admin_redirect` transient.
		 *
		 * If we don't get the `true` confirmation we will bail out
		 * so users will not get stuck in a redirect loop.
		 *
		 * We have had issues with this with caching plugins like
		 * W3 Total Cache and on Savvii hosting environments.
		 *
		 * @link https://developer.wordpress.org/reference/functions/delete_transient/
		 */
		$result = delete_transient( 'wpayme_pay_admin_redirect' );

		if ( true !== $result ) {
			return;
		}

		/**
		 * Redirect.
		 */
		wp_safe_redirect( $redirect );

		exit;
	}

	/**
	 * Sanitize the specified value to a boolean.
	 *
	 * @param mixed $value Value.
	 * @return boolean
	 */
	public static function sanitize_boolean( $value ) {
		return filter_var( $value, FILTER_VALIDATE_BOOLEAN );
	}

	/**
	 * Configurations dropdown.
	 *
	 * @param array $args Arguments.
	 * @return string|null
	 */
	public static function dropdown_configs( $args ) {
		$defaults = [
			'name'           => 'wpayme_pay_config_id',
			'echo'           => true,
			'selected'       => false,
			'payment_method' => null,
		];

		$args = wp_parse_args( $args, $defaults );

		// Output.
		$output = '';

		// Dropdown.
		$id       = $args['name'];
		$name     = $args['name'];
		$selected = $args['selected'];

		if ( false === $selected ) {
			$selected = get_option( $id );
		}

		$output .= sprintf(
			'<select id="%s" name="%s">',
			esc_attr( $id ),
			esc_attr( $name )
		);

		$options = Plugin::get_config_select_options( $args['payment_method'] );

		foreach ( $options as $value => $name ) {
			$output .= sprintf(
				'<option value="%s" %s>%s</option>',
				esc_attr( $value ),
				selected( $value, $selected, false ),
				esc_html( $name )
			);
		}

		$output .= sprintf( '</select>' );

		// Output.
		if ( $args['echo'] ) {
			echo wp_kses(
				$output,
				[
					'select' => [
						'id'   => [],
						'name' => [],
					],
					'option' => [
						'value'    => [],
						'selected' => [],
					],
				]
			);

			return null;
		}

		return $output;
	}

	/**
	 * Check if scripts should be enqueued based on the hook and current screen.
	 *
	 * @link https://developer.wordpress.org/reference/functions/get_current_screen/
	 * @link https://developer.wordpress.org/reference/classes/wp_screen/
	 *
	 * @param string $hook Hook.
	 * @return bool True if scripts should be enqueued, false otherwise.
	 */
	private function should_enqueue_scripts( $hook ) {
		// Check if the hook contains the value 'wpayme_pay'.
		if ( false !== strpos( $hook, 'wpayme_pay' ) ) {
			return true;
		}

		// Check if the hook contains the value 'wpayme'.
		if ( false !== strpos( $hook, 'wpayme' ) ) {
			return true;
		}

		// Check current screen for some values related to Wpayme Pay.
		$screen = get_current_screen();

		if ( null === $screen ) {
			return false;
		}

		// Current screen is dashboard.
		if ( 'dashboard' === $screen->id ) {
			return true;
		}

		// Gravity Forms.
		if ( 'toplevel_page_gf_edit_forms' === $screen->id ) {
			return true;
		}

		// CHeck if current screen post type is related to Wpayme Pay.
		if ( in_array(
			$screen->post_type,
			[
				'wpayme_gateway',
				'wpayme_payment',
				'wpayme_pay_form',
				'wpayme_pay_gf',
				'wpayme_pay_subscr',
			],
			true
		) ) {
			return true;
		}

		// Other.
		return false;
	}

	/**
	 * Enqueue admin scripts.
	 *
	 * @param string $hook Hook.
	 * @return void
	 */
	public function enqueue_scripts( $hook ) {
		if ( ! $this->should_enqueue_scripts( $hook ) ) {
			return;
		}

		$min = SCRIPT_DEBUG ? '' : '.min';

		// Tippy.js - https://atomiks.github.io/tippyjs/.
		// Updated to v6.3.7 (latest stable version as of 2024).
		wp_register_script(
			'tippy.js',
			plugins_url( '../../assets/tippy.js/tippy.all' . $min . '.js', __FILE__ ),
			[],
			'6.3.7',
			true
		);

		// Wpayme.
		wp_register_style(
			'wpayme-pay-icons',
			plugins_url( '../../fonts/dist/wpayme-pay-icons.css', __FILE__ ),
			[],
			$this->plugin->get_version()
		);

		wp_register_style(
			'wpayme-pay-admin',
			plugins_url( '../../css/admin' . $min . '.css', __FILE__ ),
			[ 'wpayme-pay-icons' ],
			$this->plugin->get_version()
		);

		wp_register_script(
			'wpayme-pay-admin',
			plugins_url( '../../js/dist/admin' . $min . '.js', __FILE__ ),
			[ 'jquery', 'tippy.js' ],
			$this->plugin->get_version(),
			true
		);

		// Enqueue.
		wp_enqueue_style( 'wpayme-pay-admin' );
		wp_enqueue_script( 'wpayme-pay-admin' );

		// Add inline styles and scripts for settings page.
		if ( 'wpayme_pay_settings' === $hook ) {
			$this->enqueue_settings_page_assets();
		}

		// Add inline styles and scripts for subscription meta box.
		$screen = get_current_screen();
		if ( null !== $screen && 'wpayme_pay_subscr' === $screen->post_type ) {
			$this->enqueue_subscription_meta_box_assets();
		}

		// Add inline styles for dashboard widget.
		if (
			null !== $screen
			&& in_array(
				$screen->id,
				[
					'dashboard',           // Default WordPress dashboard.
					'toplevel_page_wpayme' // WPayme main dashboard page.
				],
				true
			)
		) {
			$this->enqueue_dashboard_widget_assets();
		}
	}

	/**
	 * Enqueue inline styles and scripts for settings page.
	 *
	 * @return void
	 */
	private function enqueue_settings_page_assets() {
		// Get current tab.
		// phpcs:ignore WordPress.Security.NonceVerification.Recommended
		$current_tab = isset( $_GET['tab'] ) ? sanitize_text_field( wp_unslash( $_GET['tab'] ) ) : 'general';

		// Toggle switch styles.
		$toggle_switch_css = '
		/* Toggle Switch Styles */
		.wpayme-toggle-container {
			display: flex;
			align-items: center;
			margin-bottom: 10px;
		}
		
		.wpayme-toggle-switch {
			position: relative;
			display: inline-block;
			width: 50px;
			height: 24px;
			margin-right: 10px;
			vertical-align: middle;
			overflow: visible;
		}
		
		.wpayme-toggle-switch input {
			opacity: 0;
			width: 0;
			height: 0;
			margin: 0;
			padding: 0;
			position: absolute;
		}
		
		.wpayme-toggle-slider {
			position: absolute;
			cursor: pointer;
			top: 0;
			left: 0;
			right: 0;
			bottom: 0;
			background-color: #ccc;
			transition: background-color 0.4s;
			border-radius: 24px;
		}
		
		.wpayme-toggle-slider:before {
			position: absolute;
			content: "";
			height: 18px;
			width: 18px;
			left: 3px;
			bottom: 3px;
			background-color: white;
			transition: transform 0.4s;
			border-radius: 50%;
			box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
		}
		
		.wpayme-toggle-switch input:checked + .wpayme-toggle-slider {
			background-color: #0073aa;
		}
		
		.wpayme-toggle-switch input:focus + .wpayme-toggle-slider {
			box-shadow: 0 0 1px #0073aa;
		}
		
		.wpayme-toggle-switch input:checked + .wpayme-toggle-slider:before {
			transform: translateX(26px);
		}
		
		.wpayme-toggle-slider.checked {
			background-color: #0073aa;
		}
		
		.wpayme-toggle-slider.checked:before {
			transform: translateX(26px);
		}
		
		.wpayme-toggle-label {
			font-weight: 400;
		}

		/* Section Heading Styles */
		.wpayme-pay-settings h2 {
			background: #f8f9fa;
			padding: 12px 15px;
			margin: 25px 0 15px;
			border-left: 4px solid #0073aa;
			font-size: 16px;
			position: relative;
			border-radius: 0 3px 3px 0;
			box-shadow: 0 1px 2px rgba(0,0,0,0.05);
		}
		
		.wpayme-pay-settings h2::before {
			font-family: dashicons;
			display: inline-block;
			vertical-align: middle;
			margin-right: 10px;
			font-size: 18px;
			color: #0073aa;
			line-height: 1;
			width: 20px;
			height: 20px;
		}
		
		/* General section icons */
		.wpayme-pay-settings #wpayme_pay_general::before {
			content: "\f111"; /* Dashicons-admin-generic */
		}
		
		.wpayme-pay-settings #wpayme_pay_payment_methods::before {
			content: "\f481"; /* Dashicons-money */
		}
		
		/* Email section icons */
		.wpayme-pay-settings #wpayme_pay_email_settings::before {
			content: "\f465"; /* Dashicons-email */
		}
		
		.wpayme-pay-settings #wpayme_pay_email_buyer_settings::before {
			content: "\f110"; /* Dashicons-admin-users */
		}
		
		.wpayme-pay-settings #wpayme_pay_email_seller_settings::before {
			content: "\f307"; /* Dashicons-cart */
		}
		
		.wpayme-pay-settings #wpayme_pay_email_error_settings::before {
			content: "\f534"; /* Dashicons-warning */
		}
		
		.wpayme-pay-settings #wpayme_pay_email_rate_limit_settings::before {
			content: "\f469"; /* Dashicons-chart-bar */
		}
		
		.wpayme-pay-settings #wpayme_pay_email_additional_settings::before {
			content: "\f111"; /* Dashicons-admin-generic */
		}
		
		/* Ensure field following h2 has proper spacing */
		.wpayme-pay-settings h2 + .form-table {
			margin-top: 15px;
		}
		
		/* Add subtle animation on hover */
		.wpayme-pay-settings h2:hover {
			background: #f0f0f1;
			transition: background 0.2s ease;
		}
		
		/* Improve description text readability */
		.wpayme-pay-settings .form-table td p.description {
			margin-top: 8px;
			color: #666;
			font-style: italic;
			line-height: 1.5;
			max-width: 95%;
		}
		
		/* Add a bit more spacing between sections */
		.wpayme-pay-settings .form-table {
			margin-bottom: 25px;
		}';

		// Email tab specific styles.
		if ( 'email' === $current_tab ) {
			$toggle_switch_css .= '
		/* Style the WYSIWYG editor to fit the admin form properly */
		.wpayme-wysiwyg-editor {
			width: 100%;
		}
		.wp-editor-area {
			width: 100%;
		}
		.wp-editor-container {
			clear: both;
			border: 1px solid #ddd;
		}
		/* Add spacing between editor and description */
		.wp-editor-container + p.description {
			margin-top: 10px;
		}
		/* Email tags styles */
		.wpayme-email-tags-wrapper {
			margin-top: 8px;
		}
		.wpayme-email-tags {
			margin-top: 10px;
			background: #f9f9f9;
			padding: 10px 15px;
			border-left: 4px solid #0073aa;
		}
		.wpayme-email-tags-table {
			border-collapse: collapse;
			width: 100%;
			margin-top: 10px;
		}
		.wpayme-email-tags-table td {
			padding: 5px 10px;
			border-bottom: 1px solid #eee;
		}
		.wpayme-email-tags-table tr:last-child td {
			border-bottom: none;
		}
		.wpayme-email-tags-table td:first-child {
			font-family: monospace;
			width: 180px;
		}
		summary {
			cursor: pointer;
			color: #0073aa;
			display: inline-block;
			padding: 3px 0;
		}
		summary:hover {
			color: #00a0d2;
		}';
		}

		wp_add_inline_style( 'wpayme-pay-admin', $toggle_switch_css );

		// Settings page JavaScript.
		$settings_js = '
		jQuery(document).ready(function($) {
			// Initialize toggle switches
			function initializeToggleSwitches() {
				$(".wpayme-toggle-switch input[type=\"checkbox\"]").each(function() {
					if ($(this).is(":checked")) {
						$(this).next(".wpayme-toggle-slider").addClass("checked");
					} else {
						$(this).next(".wpayme-toggle-slider").removeClass("checked");
					}
				});
				
				// Handle toggle switch changes
				$(".wpayme-toggle-switch input").on("change", function() {
					if ($(this).is(":checked")) {
						$(this).next(".wpayme-toggle-slider").addClass("checked");
					} else {
						$(this).next(".wpayme-toggle-slider").removeClass("checked");
					}
				});
			}
			
			// Initialize on page load
			initializeToggleSwitches();';

		if ( 'email' === $current_tab ) {
			$settings_js .= '
			
			// Tab click handler - to properly initialize editors when switching tabs
			$(".wpayme-nav-tab-wrapper .nav-tab").on("click", function() {
				// This event happens before page navigation
				// Not needed for actual navigation since page will reload
				// But useful for future dynamic tab switching
			});
			
			// Fix potential issues with editor display
			setTimeout(function() {
				// Try to fix any display issues with the WYSIWYG editors
				if (typeof tinymce !== "undefined") {
					tinymce.each(tinymce.editors, function(editor) {
						editor.on("init", function() {
							editor.setContent(editor.getContent());
						});
					});
				}
			}, 500);';
		}

		$settings_js .= '
		});';

		wp_add_inline_script( 'wpayme-pay-admin', $settings_js );
	}

	/**
	 * Enqueue inline styles and scripts for subscription meta box.
	 *
	 * @return void
	 */
	private function enqueue_subscription_meta_box_assets() {
		// Ensure clipboard script is enqueued.
		wp_enqueue_script( 'clipboard' );

		// Action link styles.
		$action_link_css = '
		.wpayme-pay-action-link {
			align-items: center;
			display: flex;
			flex-direction: row;
			gap: 5px;
		}

		.wpayme-pay-action-link-clipboard {
			align-items: center;
			display: flex;
			flex-direction: row;
			flex-wrap: no-wrap;
			gap: 5px;
		}

		.wpayme-pay-action-link-clipboard .success {
			color: #007017;
		}';

		wp_add_inline_style( 'wpayme-pay-admin', $action_link_css );

		// Action link JavaScript.
		$action_link_js = '
		function wpayme_pay_action_links() {
			const elements = document.querySelectorAll( ".wpayme-pay-copy-url" );

			elements.forEach( function ( element ) {
				var clipboard = new ClipboardJS( element ),
					successTimeout;

				clipboard.on( "success", function( event ) {
					var triggerElement = jQuery( event.trigger ),
						successElement = jQuery( ".success", triggerElement.closest( ".wpayme-pay-action-link-clipboard" ) );

					event.clearSelection();

					clearTimeout( successTimeout );

					successElement.removeClass( "hidden" );

					successTimeout = setTimeout( function() {
						successElement.addClass( "hidden" );
					}, 3000 );
				} );
			} );
		}

		addEventListener( "DOMContentLoaded", function ( event ) {
			wpayme_pay_action_links();
		} );';

		wp_add_inline_script( 'clipboard', $action_link_js );
	}

	/**
	 * Enqueue inline styles for dashboard widget.
	 *
	 * @return void
	 */
	private function enqueue_dashboard_widget_assets() {
		$widget_css = '
		.wpayme-pay-status-widget {
			background: #fff;
			border-radius: 8px;
			overflow: hidden;
			max-width: 100%;
			width: 100%;
		}

		.wpayme-pay-status-list {
			margin: 0;
			padding: 0;
			list-style: none;
			display: flex;
			flex-direction: column;
			width: 100%;
		}

		.wpayme-pay-status-list li {
			margin: 0;
			border-bottom: 1px solid #f0f0f0;
			transition: all 0.2s ease;
			width: 100%;
			box-sizing: border-box;
		}

		.wpayme-pay-status-list li:last-child {
			border-bottom: none;
		}

		.wpayme-pay-status-list li:hover {
			background-color: #f9f9f9;
		}

		.wpayme-pay-status-list a {
			display: flex;
			align-items: center;
			padding: 10px 15px;
			text-decoration: none;
			color: #333;
			width: 100%;
			box-sizing: border-box;
			flex-direction: row;
			justify-content: flex-start;
		}

		.wpayme-status-icon {
			display: flex;
			align-items: center;
			justify-content: center;
			width: 24px;
			height: 24px;
			border-radius: 50%;
			margin-right: 10px;
			flex-shrink: 0;
		}

		.wpayme-status-icon .dashicons {
			color: white;
			font-size: 14px;
			width: 14px;
			height: 14px;
		}

		.wpayme-status-text {
			font-size: 13px;
			line-height: 1.3;
			flex-grow: 1;
			display: flex;
			align-items: center;
			gap: 10px;
		}

		.wpayme-status-tag {
			display: inline-block;
			padding: 3px 8px;
			border-radius: 12px;
			color: white;
			font-size: 11px;
			font-weight: 500;
			text-transform: capitalize;
			letter-spacing: 0.3px;
			min-width: 60px;
			text-align: center;
		}

		.wpayme-status-text strong {
			display: inline;
			font-size: 14px;
			color: #333;
		}

		@media screen and (max-width: 782px) {
			.wpayme-pay-status-list a {
				padding: 8px 12px;
			}
			
			.wpayme-status-icon {
				width: 22px;
				height: 22px;
				margin-right: 8px;
			}
			
			.wpayme-status-icon .dashicons {
				font-size: 12px;
				width: 12px;
				height: 12px;
			}
			
			.wpayme-status-text {
				font-size: 12px;
			}
			
			.wpayme-status-tag {
				font-size: 10px;
				padding: 2px 6px;
				min-width: 50px;
			}
		}';

		wp_add_inline_style( 'wpayme-pay-admin', $widget_css );
	}

	/**
	 * Maybe test payment.
	 *
	 * @return void
	 */
	public function maybe_test_payment() {
		if ( ! \filter_has_var( \INPUT_POST, 'test_pay_gateway' ) ) {
			return;
		}

		if ( ! \check_admin_referer( 'test_pay_gateway', 'wpayme_pay_test_nonce' ) ) {
			return;
		}

		// Amount.
		$currency_code = 'EUR';

		if ( \array_key_exists( 'test_currency_code', $_POST ) ) {
			$currency_code = \sanitize_text_field( \wp_unslash( $_POST['test_currency_code'] ) );
		}

		$value = array_key_exists( 'test_amount', $_POST ) ? \sanitize_text_field( \wp_unslash( $_POST['test_amount'] ) ) : '';

		try {
			$amount = Number::from_string( $value );
		} catch ( \Exception $e ) {
			\wp_die( \esc_html( $e->getMessage() ) );
		}

		$price = new TaxedMoney( $amount, $currency_code, 0, 0 );

		/*
		 * Payment.
		 */
		$payment = new Payment();

		$order_id = (string) \time();

		$payment->order_id = $order_id;

		$config_id = \filter_input( \INPUT_POST, 'post_ID', \FILTER_SANITIZE_NUMBER_INT );

		if ( false !== $config_id ) {
			$payment->set_config_id( (int) $config_id );
		}

		if ( \array_key_exists( 'wpayme_pay_test_payment_method', $_POST ) ) {
			$payment_method = \sanitize_text_field( \wp_unslash( $_POST['wpayme_pay_test_payment_method'] ) );

			$payment->set_payment_method( $payment_method );
		}

		// Description.
		$description = \sprintf(
			/* translators: %s: order ID */
			__( 'Test %s', 'wpayme' ),
			$order_id
		);

		$payment->set_description( $description );

		// Source.
		$payment->set_source( 'test' );
		$payment->set_source_id( $order_id );

		/*
		 * Credit Card.
		 * Test card to simulate a 3-D Secure registered card.
		 *
		 * @link http://www.paypalobjects.com/en_US/vhelp/paypalmanager_help/credit_card_numbers.htm
		 */
		$credit_card = new CreditCard();

		$expiration_date = new \DateTime( '+5 years' );

		$credit_card->set_expiration_month( (int) $expiration_date->format( 'n' ) );
		$credit_card->set_expiration_year( (int) $expiration_date->format( 'Y' ) );
		$credit_card->set_name( 'Wpayme' );
		$credit_card->set_number( '5300000000000006' );
		$credit_card->set_security_code( '123' );

		$payment->set_credit_card( $credit_card );

		// Data.
		$user = \wp_get_current_user();

		// Name.
		$name = ContactNameHelper::from_array(
			[
				'first_name' => $user->first_name,
				'last_name'  => $user->last_name,
			]
		);

		// Customer.
		$customer = CustomerHelper::from_array(
			[
				'name'    => $name,
				'email'   => $user->user_email,
				'phone'   => \array_key_exists( 'test_phone', $_POST ) ? \sanitize_text_field( \wp_unslash( $_POST['test_phone'] ) ) : '',
				'user_id' => $user->ID,
			]
		);

		$payment->set_customer( $customer );

		// Billing address.
		$address = AddressHelper::from_array(
			[
				'name'         => $name,
				'email'        => $user->user_email,
				'phone'        => null === $customer ? null : $customer->get_phone(),
				'line_1'       => 'Billing Line 1',
				'postal_code'  => '1234 AB',
				'city'         => 'Billing City',
				'country_code' => 'NL',
			]
		);

		$payment->set_billing_address( $address );

		$address = AddressHelper::from_array(
			[
				'name'         => $name,
				'email'        => $user->user_email,
				'phone'        => null === $customer ? null : $customer->get_phone(),
				'line_1'       => 'Shipping Line 1',
				'postal_code'  => '5678 XY',
				'city'         => 'Shipping City',
				'country_code' => 'NL',
			]
		);

		$payment->set_shipping_address( $address );

		// Lines.
		$payment->lines = new PaymentLines();

		$line = $payment->lines->new_line();

		$line->set_name( __( 'Test', 'wpayme' ) );
		$line->set_unit_price( $price );
		$line->set_quantity( 1 );
		$line->set_total_amount( $price );

		$payment->set_total_amount( $payment->lines->get_amount() );

		// Subscription.
		$test_subscription = \filter_input( \INPUT_POST, 'wpayme_pay_test_subscription', \FILTER_VALIDATE_BOOLEAN );
		$interval          = \filter_input( \INPUT_POST, 'wpayme_pay_test_repeat_interval', \FILTER_VALIDATE_INT );
		$interval_period   = \array_key_exists( 'wpayme_pay_test_repeat_frequency', $_POST ) ? \sanitize_text_field( \wp_unslash( $_POST['wpayme_pay_test_repeat_frequency'] ) ) : '';

		if ( ! empty( $test_subscription ) && ! empty( $interval ) && ! empty( $interval_period ) ) {
			$subscription = new Subscription();

			$subscription->set_description( $description );
			$subscription->set_lines( $payment->get_lines() );

			// Phase.
			$phase = new SubscriptionPhase(
				$subscription,
				new DateTimeImmutable(),
				new SubscriptionInterval( 'P' . $interval . Util::to_period( $interval_period ) ),
				$price
			);

			// Ends on.
			$total_periods = null;

			if ( \array_key_exists( 'wpayme_pay_ends_on', $_POST ) ) {
				$total_periods = null;

				switch ( $_POST['wpayme_pay_ends_on'] ) {
					case 'count':
						$total_periods = (int) \filter_input( \INPUT_POST, 'wpayme_pay_ends_on_count', \FILTER_VALIDATE_INT );

						break;
					case 'date':
						$end_date = \array_key_exists( 'wpayme_pay_ends_on_date', $_POST ) ? \sanitize_text_field( \wp_unslash( $_POST['wpayme_pay_ends_on_date'] ) ) : '';

						if ( ! empty( $end_date ) ) {
							$period = new \DatePeriod(
								$phase->get_start_date(),
								$phase->get_interval(),
								new \DateTime( $end_date )
							);

							$total_periods = iterator_count( $period );
						}

						break;
				}

				if ( null !== $total_periods ) {
					$end_date = $phase->get_start_date()->add( $phase->get_interval()->multiply( $total_periods ) );

					$phase->set_end_date( $end_date );
				}
			}

			$subscription->add_phase( $phase );

			$period = $subscription->new_period();

			if ( null !== $period ) {
				$payment->add_period( $period );
			}
		}

		$gateway = $payment->get_gateway();

		if ( null === $gateway ) {
			return;
		}

		// Start.
		try {
			$payment = Plugin::start_payment( $payment );

			$gateway->redirect( $payment );
		} catch ( \Exception $e ) {
			Plugin::render_exception( $e );

			exit;
		}
	}

	/**
	 * Admin menu parent file.
	 *
	 * @param string $parent_file Parent file for admin menu.
	 * @return string
	 */
	public function admin_menu_parent_file( $parent_file ) {
		$screen = get_current_screen();

		if ( null === $screen ) {
			return $parent_file;
		}

		switch ( $screen->id ) {
			case AdminGatewayPostType::POST_TYPE:
			case AdminPaymentPostType::POST_TYPE:
			case AdminSubscriptionPostType::POST_TYPE:
				return 'wpayme';
		}

		return $parent_file;
	}

	/**
	 * Get menu icon URL.
	 *
	 * @link https://developer.wordpress.org/reference/functions/add_menu_page/
	 * @return string
	 * @throws \Exception Throws exception when retrieving menu icon fails.
	 */
	private function get_menu_icon_url() {
		/**
		 * Icon URL.
		 *
		 * Pass a base64-encoded SVG using a data URI, which will be colored to match the color scheme.
		 * This should begin with 'data:image/svg+xml;base64,'.
		 *
		 * We use a SVG image with default fill color #A0A5AA from the default admin color scheme:
		 * https://github.com/WordPress/WordPress/blob/5.2/wp-includes/general-template.php#L4135-L4145
		 *
		 * The advantage of this is that users with the default admin color scheme do not see the repaint:
		 * https://github.com/WordPress/WordPress/blob/5.2/wp-admin/js/svg-painter.js
		 *
		 * @link https://developer.wordpress.org/reference/functions/add_menu_page/
		 */
		$file = __DIR__ . '/../../images/dist/wpayme.svg';

		if ( ! \is_readable( $file ) ) {
			throw new \Exception(
				\sprintf(
					'Could not read WordPress admin menu icon from file: %s.',
					\esc_html( $file )
				)
			);
		}

		$svg = \file_get_contents( $file, true );

		if ( false === $svg ) {
			throw new \Exception(
				\sprintf(
					'Could not read WordPress admin menu icon from file: %s.',
					\esc_html( $file )
				)
			);
		}

		$icon_url = \sprintf(
			'data:image/svg+xml;base64,%s',
			// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
			\base64_encode( $svg )
		);

		return $icon_url;
	}

	/**
	 * Create the admin menu.
	 *
	 * @return void
	 */
	public function admin_menu() {
		/**
		 * Badges.
		 *
		 * @link https://github.com/woothemes/woocommerce/blob/2.3.13/includes/admin/class-wc-admin-menus.php#L145
		 */
		$counts = wp_count_posts( 'wpayme_payment' );

		$payments_pending_count = \property_exists( $counts, 'payment_pending' ) ? $counts->payment_pending : 0;

		$counts = wp_count_posts( 'wpayme_pay_subscr' );

		$subscriptions_on_hold_count = \property_exists( $counts, 'subscr_on_hold' ) ? $counts->subscr_on_hold : 0;

		$badges = [
			'pay'           => [
				'title' => [],
				'count' => 0,
				'html'  => '',
			],
			'payments'      => [
				'title' => \sprintf(
					/* translators: %d: payments pending count */
					\_n( '%d payment pending', '%d payments pending', $payments_pending_count, 'wpayme' ),
					$payments_pending_count
				),
				'count' => $payments_pending_count,
				'html'  => '',
			],
			'subscriptions' => [
				'title' => \sprintf(
					/* translators: %d: subscriptions on hold count */
					\_n( '%d subscription on hold', '%d subscriptions on hold', $subscriptions_on_hold_count, 'wpayme' ),
					$subscriptions_on_hold_count
				),
				'count' => $subscriptions_on_hold_count,
				'html'  => '',
			],
		];

		foreach ( $badges as &$badge ) {
			$count = $badge['count'];

			if ( 0 === $count ) {
				continue;
			}

			$title = \array_key_exists( 'title', $badge ) && \is_string( $badge['title'] ) ? $badge['title'] : '';

			$badge['html'] = \sprintf(
				' <span class="awaiting-mod update-plugins count-%1$d" title="%2$s"><span class="processing-count">%1$d</span></span>',
				$count,
				$title
			);

			// Pay badge.
			$badges['pay']['count'] += $count;

			if ( ! empty( $title ) ) {
				$badges['pay']['title'][] = $title;
			}
		}

		$modules = \apply_filters( 'wpayme_pay_modules', [] );

		/**
		 * Submenu pages.
		 */
		$submenu_pages = [];

		$submenu_pages[] = [
			'page_title' => __( 'Payments', 'wpayme' ),
			'menu_title' => __( 'Payments', 'wpayme' ) . $badges['payments']['html'],
			'capability' => 'edit_payments',
			'menu_slug'  => 'edit.php?post_type=wpayme_payment',
		];

		if ( \in_array( 'subscriptions', $modules, true ) ) {
			$submenu_pages[] = [
				'page_title' => __( 'Subscriptions', 'wpayme' ),
				'menu_title' => __( 'Subscriptions', 'wpayme' ) . $badges['subscriptions']['html'],
				'capability' => 'edit_payments',
				'menu_slug'  => 'edit.php?post_type=wpayme_pay_subscr',
			];
		}

		// Add Coupons submenu directly in the core if forms module is active
		if ( \in_array( 'forms', $modules, true ) && \post_type_exists( 'wpayme_coupon' ) ) {
			$submenu_pages[] = [
				'page_title' => __( 'Coupons', 'wpayme' ),
				'menu_title' => __( 'Coupons', 'wpayme' ),
				'capability' => 'manage_options',
				'menu_slug'  => 'edit.php?post_type=wpayme_coupon',
			];
		}

		$submenu_pages[] = [
			'page_title' => __( 'Configurations', 'wpayme' ),
			'menu_title' => __( 'Configurations', 'wpayme' ),
			'capability' => 'manage_options',
			'menu_slug'  => 'edit.php?post_type=wpayme_gateway',
		];

		$submenu_pages[] = [
			'page_title' => __( 'Settings', 'wpayme' ),
			'menu_title' => __( 'Settings', 'wpayme' ),
			'capability' => 'manage_options',
			'menu_slug'  => 'wpayme_pay_settings',
			'function'   => function () {
				$this->render_page( 'settings' );
			},
		];

		$minimum_capability = $this->get_minimum_capability( $submenu_pages );

		try {
			$menu_icon_url = $this->get_menu_icon_url();
		} catch ( \Exception $e ) {
			// @todo Log.

			/**
			 * If retrieving the menu icon URL fails we will
			 * fallback to the WordPress money dashicon.
			 *
			 * @link https://developer.wordpress.org/resource/dashicons/#money
			 */
			$menu_icon_url = 'dashicons-money';
		}

		$pay_badge = '';

		if ( 0 !== $badges['pay']['count'] ) {
			$pay_badge = \sprintf(
				' <span class="awaiting-mod update-plugins count-%1$d" title="%2$s"><span class="processing-count">%1$d</span></span>',
				$badges['pay']['count'],
				\implode( ', ', $badges['pay']['title'] )
			);
		}

		add_menu_page(
			__( 'WPayme', 'wpayme' ),
			__( 'WPayme', 'wpayme' ) . $pay_badge,
			$minimum_capability,
			'wpayme',
			function () {
				$this->render_page( 'dashboard' );
			},
			$menu_icon_url
		);

		// Add submenu pages.
		foreach ( $submenu_pages as $page ) {
			/**
			 * To keep PHPStan happy we use an if/else statement for
			 * the 6th $function parameter which should be a callable
			 * function. Unfortunately this is not documented
			 * correctly in WordPress.
			 *
			 * @link https://github.com/WordPress/WordPress/blob/5.2/wp-admin/includes/plugin.php#L1296-L1377
			 */
			if ( array_key_exists( 'function', $page ) ) {
				add_submenu_page(
					'wpayme',
					$page['page_title'],
					$page['menu_title'],
					$page['capability'],
					$page['menu_slug'],
					$page['function']
				);
			} else {
				add_submenu_page(
					'wpayme',
					$page['page_title'],
					$page['menu_title'],
					$page['capability'],
					$page['menu_slug']
				);
			}
		}

		// Change title of plugin submenu page to 'Dashboard'.
		global $submenu;

		if ( isset( $submenu['wpayme'] ) ) {
			/* phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited */
			$submenu['wpayme'][0][0] = __( 'Dashboard', 'wpayme' );
		}
	}

	/**
	 * Get minimum capability from submenu pages.
	 *
	 * @param array $pages Submenu pages.
	 *
	 * @return string
	 */
	public function get_minimum_capability( array $pages ) {
		foreach ( $pages as $page ) {
			if ( \current_user_can( $page['capability'] ) ) {
				return $page['capability'];
			}
		}

		return 'edit_payments';
	}

	/**
	 * Render the specified page.
	 *
	 * @param string $name Page identifier.
	 * @return void
	 */
	public function render_page( $name ) {
		include __DIR__ . '/../../views/page-' . $name . '.php';
	}

	/**
	 * Get a CSS class for the specified post status.
	 *
	 * @param string $post_status Post status.
	 * @return string
	 */
	public static function get_post_status_icon_class( $post_status ) {
		switch ( $post_status ) {
			case 'payment_pending':
			case 'subscr_pending':
				return 'wpayme-pay-icon-pending';

			case 'payment_cancelled':
			case 'subscr_cancelled':
				return 'wpayme-pay-icon-cancelled';

			case 'payment_completed':
			case 'subscr_completed':
				return 'wpayme-pay-icon-completed';

			case 'payment_refunded':
				return 'wpayme-pay-icon-refunded';

			case 'payment_failed':
			case 'subscr_failed':
				return 'wpayme-pay-icon-failed';

			case 'payment_on_hold':
			case 'payment_expired':
			case 'subscr_expired':
			case 'subscr_on_hold':
				return 'wpayme-pay-icon-on-hold';

			case 'payment_authorized':
			case 'payment_reserved':
			case 'subscr_active':
			default:
				return 'wpayme-pay-icon-processing';
		}
	}

	/**
	 * Register a custom submenu directly under the main Wpayme menu
	 * This method can be called from anywhere to register a submenu item
	 * 
	 * @param string $title Page title
	 * @param string $menu_title Menu title
	 * @param string $capability Required capability
	 * @param string $menu_slug Menu slug
	 * @param callable|null $function Optional callback function
	 * @return string|false The resulting page's hook_suffix, or false if the user does not have the capability required.
	 */
	public function register_submenu($title, $menu_title, $capability, $menu_slug, $function = null) {
		if (null !== $function) {
			return \add_submenu_page(
				'wpayme',
				$title,
				$menu_title,
				$capability,
				$menu_slug,
				$function
			);
		} else {
			return \add_submenu_page(
				'wpayme',
				$title,
				$menu_title,
				$capability,
				$menu_slug
			);
		}
	}

	/**
	 * Get the singleton instance.
	 *
	 * @return self The singleton instance.
	 */
	public static function instance() {
		if (null === self::$instance) {
			self::$instance = new self(\Wpayme\WordPress\Pay\Plugin::instance());
		}

		return self::$instance;
	}
}
