<?php

declare( strict_types=1 );

namespace Simplyin\Simplyin_W_Plugin\Controller\Ajax\Widget;

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

use Simplyin\Simplyin_W_Plugin\Controller\Ajax\Abstract_Response;
use Simplyin\Simplyin_W_Plugin\Helper\Logger_Helper;
use Simplyin\Simplyin_W_Plugin\Lib\Session\Simplyin_Session;
use Simplyin\Simplyin_W_Plugin\Payments\Gateway\Payment_Fields\Payment_Fields;
use Simplyin\Simplyin_W_Plugin\Service\Geolocation\Closest_Parcel_Lockers_Request_Builder;
use Simplyin\Simplyin_W_Plugin\Shipping_Mapping\Mapping;


class Widget_Controller {

	private const ADD_PLUGIN_INFO = true;

	public $warnings = [];

	private $allowed_types = [
		'autocomplete',
		'confirmation',
		'delivery',
		'sms',
	];

	private Simplyin_Session $simplyin_session;

	private function print_r( $value, bool $return = true ): string {
		return Logger_Helper::print_r_truncate( $value );
	}

	public function __construct() {
		$this->simplyin_session = simplyin()->get_simplyin_session();
	}

	public function register_ajax_hooks() {

		add_action(
			'wp_ajax_simplyin_session_status',
			[
				$this,
				'session_status_ajax_action',
			]
		);
		add_action(
			'wp_ajax_nopriv_simplyin_session_status',
			[
				$this,
				'session_status_ajax_action',
			]
		);

		add_action(
			'wp_ajax_simplyin_submit_email',
			[
				$this,
				'submit_email_ajax_action',
			]
		);
		add_action(
			'wp_ajax_nopriv_simplyin_submit_email',
			[
				$this,
				'submit_email_ajax_action',
			]
		);

		add_action(
			'wp_ajax_simplyin_get_new_account_form',
			[
				$this,
				'get_new_account_form_ajax_action',
			]
		);
		add_action(
			'wp_ajax_nopriv_simplyin_get_new_account_form',
			[
				$this,
				'get_new_account_form_ajax_action',
			]
		);

		add_action(
			'wp_ajax_simplyin_check_push_notification',
			[
				$this,
				'check_push_notification_ajax_action',
			]
		);
		add_action(
			'wp_ajax_nopriv_simplyin_check_push_notification',
			[
				$this,
				'check_push_notification_ajax_action',
			]
		);

		add_action(
			'wp_ajax_simplyin_send_email_code',
			[
				$this,
				'send_email_code_ajax_action',
			]
		);
		add_action(
			'wp_ajax_nopriv_simplyin_send_email_code',
			[
				$this,
				'send_email_code_ajax_action',
			]
		);

		add_action(
			'wp_ajax_simplyin_check_code',
			[
				$this,
				'check_code_ajax_action',
			]
		);
		add_action(
			'wp_ajax_nopriv_simplyin_check_code',
			[
				$this,
				'check_code_ajax_action',
			]
		);

		add_action(
			'wp_ajax_simplyin_send_sms_code',
			[
				$this,
				'send_sms_code_ajax_action',
			]
		);
		add_action(
			'wp_ajax_nopriv_simplyin_send_sms_code',
			[
				$this,
				'send_sms_code_ajax_action',
			]
		);

		add_action(
			'wp_ajax_simplyin_select_address',
			[
				$this,
				'select_address_ajax_action',
			]
		);
		add_action(
			'wp_ajax_nopriv_simplyin_select_address',
			[
				$this,
				'select_address_ajax_action',
			]
		);

		add_action(
			'wp_ajax_simplyin_edit_address',
			[
				$this,
				'edit_address_ajax_action',
			]
		);
		add_action(
			'wp_ajax_nopriv_simplyin_edit_address',
			[
				$this,
				'edit_address_ajax_action',
			]
		);

		add_action(
			'wp_ajax_simplyin_delete_address',
			[
				$this,
				'delete_address_ajax_action',
			]
		);
		add_action(
			'wp_ajax_nopriv_simplyin_delete_address',
			[
				$this,
				'delete_address_ajax_action',
			]
		);

		add_action(
			'wp_ajax_simplyin_get_form',
			[ $this, 'get_form_ajax_action' ]
		);
		add_action(
			'wp_ajax_nopriv_simplyin_get_form',
			[ $this, 'get_form_ajax_action' ]
		);

		add_action(
			'wp_ajax_simplyin_user_data_commerce',
			[
				$this,
				'user_data_commerce_ajax_action',
			]
		);
		add_action(
			'wp_ajax_nopriv_simplyin_user_data_commerce',
			[
				$this,
				'user_data_commerce_ajax_action',
			]
		);

		add_action(
			'wp_ajax_simplyin_search_address',
			[
				$this,
				'search_address_ajax_action',
			]
		);
		add_action(
			'wp_ajax_nopriv_simplyin_search_address',
			[
				$this,
				'search_address_ajax_action',
			]
		);

		add_action(
			'wp_ajax_simplyin_get_closest_parcel_lockers',
			[
				$this,
				'get_closest_parcel_lockers_ajax_action',
			]
		);
		add_action(
			'wp_ajax_nopriv_simplyin_get_closest_parcel_lockers',
			[
				$this,
				'get_closest_parcel_lockers_ajax_action',
			]
		);

		add_action(
			'wp_ajax_simplyin_add_address',
			[
				$this,
				'add_address_ajax_action',
			]
		);
		add_action(
			'wp_ajax_nopriv_simplyin_add_address',
			[
				$this,
				'add_address_ajax_action',
			]
		);

		add_action(
			'wp_ajax_simplyin_submit_address',
			[
				$this,
				'submit_address_ajax_action',
			]
		);
		add_action(
			'wp_ajax_nopriv_simplyin_submit_address',
			[
				$this,
				'submit_address_ajax_action',
			]
		);

		// todo
		add_action(
			'wp_ajax_simplyin_place_order',
			[
				$this,
				'place_order_ajax_action',
			]
		);
		add_action(
			'wp_ajax_nopriv_simplyin_place_order',
			[
				$this,
				'place_order_ajax_action',
			]
		);

		add_action(
			'wp_ajax_simplyin_store_new_account_details',
			[
				$this,
				'store_new_account_details_ajax_action',
			]
		);
		add_action(
			'wp_ajax_nopriv_simplyin_store_new_account_details',
			[
				$this,
				'store_new_account_details_ajax_action',
			]
		);

		add_action(
			'wp_ajax_simplyin_logout',
			[
				$this,
				'logout_ajax_action',
			]
		);

		add_action(
			'wp_ajax_nopriv_simplyin_logout',
			[
				$this,
				'logout_ajax_action',
			]
		);
	}


	public function get_form_ajax_action(): void {
		$type = '';
		if ( isset( $_POST['type'] ) ) {
			if ( check_ajax_referer(
				'simplyin_ajax',
				'ajax_token',
				false
			) ) {

				if ( isset( $_POST['type'] ) ) {
					$type = sanitize_text_field( wp_unslash( $_POST['type'] ) );
				} else {
					$type = '';
				}
			} else {
				$this->output_response_unauthorized();
			}
		}

		$resp = new Get_Form_Response();

		if ( ! in_array( $type, $this->allowed_types, true ) ) {
			$resp->set_is_success( false );
			$resp->set_error( 'INVALID_FORM_TYPE' );
			$this->output_response( $resp );

			return;
		}

		$html = $this->render_ajax_form( $type );

		$resp->set_is_success( true );
		$resp->set_html( $html );
		$this->output_response( $resp );
	}

	private function setNotificationId(
		string $email,
		$api_response = null
	): void {
		$notification_token_id = '';
		if ( is_object( $api_response ) && isset( $api_response->notificationTokenId ) ) {
			$notification_token_id = (string) $api_response->notificationTokenId;
		} elseif ( is_string( $api_response ) && '' !== $api_response ) {
			$notification_token_id = $api_response;
		}

		if ( '' !== $notification_token_id ) {
			$this->simplyin_session->set(
				'simplyin_notification_token_id',
				$notification_token_id
			);
		}

		$this->simplyin_session->set( 'simplyin_pending_email', $email );
		$this->simplyin_session->set( 'simplyin_push_confirmed', false );
	}

	private function output_response( Abstract_Response $response ) {
		$response_arr = $response->to_array();

		simplyin()->get_simplyin_logger()->log_if_wpdebug_on(
			sprintf(
				'[Widget_Controller] [output_response] %s',
				$this->print_r(
					[
						'response_arr' => $response_arr,
					],
					true
				),
			),
			'widget_ajax'
		);

		wp_send_json( $response_arr );
	}

	private function output_response_bad_request() {
		wp_send_json_error( [ 'code' => 'BAD_REQUEST' ], 400 );
	}

	private function output_response_unauthorized() {
		wp_send_json_error( [ 'code' => 'UNAUTHORIZED' ], 401 );
	}

	public function session_status_ajax_action(): void {
		$logger = simplyin()->get_simplyin_logger();

		$session_email         = (string) $this->simplyin_session->get(
			'simplyin_email',
			''
		);
		$auth_token            = (string) $this->simplyin_session->get(
			'simplyin_auth_token',
			''
		);
		$notification_token_id = (string) $this->simplyin_session->get(
			'simplyin_notification_token_id',
			''
		);
		$is_push_confirmed     = (bool) $this->simplyin_session->get(
			'simplyin_push_confirmed',
			false
		);

		$logger->log_if_wpdebug_on(
			'[session_status] SESSION ' . $this->print_r(
				compact(
					'session_email',
					'auth_token',
					'notification_token_id',
					'is_push_confirmed'
				),
				true
			),
			'widget_ajax'
		);

		$is_local_session_valid = ( '' !== $auth_token && ( '' === $notification_token_id || $is_push_confirmed ) );
		if ( ! $is_local_session_valid ) {
			$response = new Check_Push_Notification_Response();
			$response->set_is_success( false );
			$response->set_error( '' !== $notification_token_id && ! $is_push_confirmed ? 'PUSH_NOT_CONFIRMED' : 'NO_ACTIVE_SESSION' );

			if ( 'NO_ACTIVE_SESSION' === $response->get_error() ) {
				$this->reset_simplyin_session();
			}

			$this->output_response( $response );

			return;
		}

		try {
			$user_data_payload = $this->user_data_commerce_ajax_action();
		} catch ( \Throwable $e ) {
			$response = new Check_Push_Notification_Response();
			$response->set_is_success( false );
			$response->set_error( 'API_ERROR' );
			$this->output_response( $response );

			return;
		}

		if ( ! empty( $user_data_payload['code'] ) ) {
			$response = new Check_Push_Notification_Response();
			$response->set_is_success( false );
			$response->set_error( $user_data_payload['code'] );
			$this->output_response( $response );

			return;
		}

		if ( ! isset( $user_data_payload['data'] ) && isset( $user_data_payload['userData'] ) ) {
			$user_data_payload['data'] = $user_data_payload['userData'];
		}

		$user_data = $user_data_payload['data'] ?? [];
		$api_email = isset( $user_data['email'] ) ? (string) $user_data['email'] : '';

		if ( '' !== $api_email && '' !== $session_email && 0 !== strcasecmp(
			$api_email,
			$session_email
		) ) {
			$response = new Check_Push_Notification_Response();
			$response->set_is_success( false );
			$response->set_error( 'EMAIL_MISMATCH' );
			$this->output_response( $response );

			return;
		}

		$login_summary = [
			'is_guest'  => true,
			'firstname' => $user_data['name'] ?? '',
			'lastname'  => $user_data['surname'] ?? '',
			'email'     => $session_email,
		];

		$checkout_sync = $this->sync_checkout_delivery_from_commerce_payload(
			$user_data_payload
		);

		$delivery_form_html = $this->render_delivery_form( [ 'data' => $user_data ] );

		$response = new Check_Push_Notification_Response();
		$response->set_is_success( true );
		$response->set_login_result( $login_summary );
		$response->set_html( $delivery_form_html );
		$this->apply_invoice_and_phone_to_login_response( $response, $user_data, $checkout_sync );

		$logger->log_if_wpdebug_on(
			'[session_status] OUTPUT ' . $this->print_r(
				$response->to_array(),
				true
			),
			'widget_ajax'
		);

		$this->output_response( $response );
	}

	public function submit_email_ajax_action(): void {

		if ( check_ajax_referer(
			'simplyin_ajax',
			'ajax_token',
			false
		) ) {

			if ( isset( $_POST['email'] ) ) {
				$email = sanitize_email( wp_unslash( $_POST['email'] ) );
			} else {
				$this->output_response_bad_request();
			}

			if ( isset( $_POST['logout'] ) ) {
				$logout = sanitize_text_field( wp_unslash( $_POST['logout'] ) );
				$logout = in_array(
					$logout,
					[ '1', 'true', 'yes', 'on' ],
					true
				);
			} else {
				$this->output_response_bad_request();
			}
		} else {
			$this->output_response_unauthorized();
		}

		$session        = $this->simplyin_session;
		$pending_token  = $session->get( 'simplyin_notification_token_id', '' );
		$push_confirmed = $session->get( 'simplyin_push_confirmed', false );
		$stored_email   = $session->get( 'simplyin_email', '' );
		$pending_email  = (string) $session->get(
			'simplyin_pending_email',
			''
		);
		$current_email  = $email;

		if ( $pending_token && ! $push_confirmed && $pending_email === $current_email ) {
			$response = new Submit_Email_Response();
			$response->set_is_success( true );
			$response->set_show_popup( true );
			$response->set_user_used_push_notifications( true );
			$response->set_html(
				$this->get_popup_confirmation_html(
					'',
					'',
					'',
					true
				)
			);
			$this->output_response( $response );

			return;
		}

		$api   = simplyin()->get_api();
		$login = false;

		/*
		 * If the widget explicitly requests a logout OR the user typed a different e-mail address
		 * than the one stored in the session, we must invalidate any previously saved tokens.
		 * Otherwise the next order would still be created for the previous account.
		 */
		if ( $logout || ( $stored_email && 0 !== strcasecmp(
			$stored_email,
			$current_email
		) ) || ( $pending_email && 0 !== strcasecmp(
			$pending_email,
			$current_email
		) ) ) {
			$previous_token      = (string) $session->get( 'simplyin_auth_token', '' );
			$previous_session_id = (string) $session->get( 'id', '' );
			Payment_Fields::clear_methods_cache( $previous_token, $previous_session_id );
			$session->set( 'simplyin_auth_token', '' );
			$session->set( 'simplyin_refresh_token', '' );
			$session->set( 'simplyin_notification_token_id', '' );
			$session->set( 'simplyin_push_confirmed', false );
			$session->set( 'simplyin_pending_email', '' );
		}

		$merchantToken         = get_option( 'simplyin_api_key' );
		$simplyin_api_response = $api->sendRequest(
			'checkout/submitEmail',
			'POST',
			$this->update_array_with_plugin_info(
				[
					'email'  => $email,
					'apiKey' => $merchantToken,
				]
			)
		);

		simplyin()->get_simplyin_logger()->log_if_wpdebug_on(
			sprintf(
				'[Widget_Controller] [submit_email_ajax_action] %s',
				$this->print_r(
					[
						'api response' => $simplyin_api_response,
					],
					true
				),
			),
			'widget_ajax'
		);

		$ajax_response = new Submit_Email_Response();

		if ( is_object( $simplyin_api_response ) && isset( $simplyin_api_response->code ) ) {
			if ( 'NOT_FOUND' === $simplyin_api_response->code ) {
				$ajax_response->set_is_success( false );
				$ajax_response->set_error( $simplyin_api_response->code );
				$this->output_response( $ajax_response );

				return;
			}

			if ( 'UNAUTHORIZED' === $simplyin_api_response->code ) {
				$ajax_response->set_is_success( false );
				$ajax_response->set_error( 'UNAUTHORIZED' );
				$ajax_response->set_show_popup( false );
				simplyin()->handle_api_unauthorized();
				$this->output_response( $ajax_response );

				return;
			}
		} else {
			$ajax_response->set_is_success( false );
			$ajax_response->set_error( 'API_ERROR' );
			$this->output_response( $ajax_response );

			return;
		}

		$ajax_response->set_is_success( true );
		$ajax_response->set_show_popup( true );

		if ( ! empty( $simplyin_api_response->notificationTokenId ) ) {
			$this->simplyin_session->set(
				'simplyin_notification_token_id',
				$simplyin_api_response->notificationTokenId
			);
		}

		$session->set( 'simplyin_pending_email', $current_email );

		simplyin()->get_simplyin_logger()->log_if_wpdebug_on(
			sprintf(
				'[Widget_Controller] [submit_email_ajax_action] %s',
				$this->print_r(
					[
						'simplyin_session_email' => $this->simplyin_session->get( 'simplyin_email' ),
					],
					true
				),
			),
			'widget_ajax'
		);
		$user_used_push_notifications = isset( $simplyin_api_response->userUsedPushNotifications ) && $simplyin_api_response->userUsedPushNotifications;
		$ajax_response->set_user_used_push_notifications( $user_used_push_notifications );

		$code    = $simplyin_api_response->code;
		$data    = $simplyin_api_response->data;
		$message = $simplyin_api_response->message;
		$html    = $this->get_popup_confirmation_html(
			$code,
			$data ?: '',
			$message,
			$user_used_push_notifications
		);

		$ajax_response->set_html( $html );

		$this->output_response( $ajax_response );
	}

	public function send_sms_code_ajax_action(): void {

		if ( check_ajax_referer(
			'simplyin_ajax',
			'ajax_token',
			false
		) ) {

			if ( isset( $_POST['email'] ) ) {
				$email = sanitize_email( wp_unslash( $_POST['email'] ) );
			} else {
				$this->output_response_bad_request();
			}
		} else {
			$this->output_response_unauthorized();
		}

		simplyin()->get_simplyin_logger()->log_if_wpdebug_on(
			'[send_sms_code] INPUT: ' . $this->print_r(
				[ 'email' => $email ],
				true
			),
			'widget_ajax'
		);

		$api           = simplyin()->get_api();
		$merchantToken = get_option( 'simplyin_api_key' );
		$api_response  = $api->sendRequest(
			'checkout/submitEmail',
			'POST',
			$this->update_array_with_plugin_info(
				[
					'email'    => $email,
					'apiKey'   => $merchantToken,
					'forceSms' => true,
				]
			)
		);

		simplyin()->get_simplyin_logger()->log_if_wpdebug_on(
			'[send_sms_code] API RESPONSE: ' . $this->print_r(
				$api_response,
				true
			),
			'widget_ajax'
		);

		$response = new Send_SMS_Code_Response();

		if ( ! is_object( $api_response ) || 'NOT_FOUND' === ( $api_response->code ?? '' ) ) {
			$response->set_is_success( false );
			$response->set_error( $api_response->code ?? 'NOT_FOUND' );
			$this->output_response( $response );

			return;
		}

		if ( 'UNAUTHORIZED' === ( $api_response->code ?? '' ) ) {
			$response->set_is_success( false );
			$response->set_error( 'UNAUTHORIZED' );
			$response->set_show_popup( false );
			simplyin()->handle_api_unauthorized();
			$this->output_response( $response );

			return;
		}

		$this->simplyin_session->set(
			'simplyin_notification_token_id',
			$api_response->notificationTokenId ?? ''
		);
		$this->simplyin_session->set( 'simplyin_pending_email', $email );

		$response->set_is_success( true );
		$response->set_show_popup( true );
		$response->set_notification_token_id( $api_response->notificationTokenId ?? '' );
		$response->set_user_used_push_notifications( $api_response->userUsedPushNotifications ?? false );
		$response->set_html(
			$this->get_popup_confirmation_html(
				$api_response->code,
				$api_response->data ?? '',
				$api_response->message ?? '',
				$api_response->userUsedPushNotifications ?? false
			)
		);

		$this->output_response( $response );
	}

	public function send_email_code_ajax_action(): void {

		if ( check_ajax_referer(
			'simplyin_ajax',
			'ajax_token',
			false
		) ) {

			if ( isset( $_POST['email'] ) ) {
				$email = sanitize_email( wp_unslash( $_POST['email'] ) );
			} else {
				$this->output_response_bad_request();
			}
		} else {
			$this->output_response_unauthorized();
		}

		simplyin()->get_simplyin_logger()->log_if_wpdebug_on(
			'[Widget_Controller] [send_email_code_ajax_action] INPUT: ' . $this->print_r(
				[ 'email' => $email ],
				true
			),
			'widget_ajax'
		);

		$api           = simplyin()->get_api();
		$merchantToken = get_option( 'simplyin_api_key' );
		$api_response  = $api->sendRequest(
			'checkout/resend-checkout-code-via-email',
			'POST',
			[
				'email'  => $email,
				'apiKey' => $merchantToken,
			]
		);

		simplyin()->get_simplyin_logger()->log_if_wpdebug_on(
			'[Widget_Controller] [send_email_code_ajax_action] API RESPONSE: ' . $this->print_r(
				$api_response,
				true
			),
			'widget_ajax'
		);

		$response = new Send_Email_Code_Response();

		if ( ! is_object( $api_response ) || 'NOT_FOUND' === ( $api_response->code ?? '' ) ) {
			$response->set_is_success( false );
			$response->set_error( $api_response->code ?? 'NOT_FOUND' );
			$this->output_response( $response );

			return;
		}

		if ( 'UNAUTHORIZED' === ( $api_response->code ?? '' ) ) {
			$response->set_is_success( false );
			$response->set_error( 'UNAUTHORIZED' );
			$response->set_show_popup( false );
			simplyin()->handle_api_unauthorized();
			$this->output_response( $response );

			return;
		}

		$session_token   = (string) $this->simplyin_session->get(
			'simplyin_notification_token_id',
			''
		);
		$effective_token = $api_response->notificationTokenId ?? $session_token;
		$this->setNotificationId( $email, $effective_token );

		$response->set_is_success( true );
		$response->set_show_popup( true );
		$response->set_notification_token_id( $effective_token );
		$response->set_user_used_push_notifications( $api_response->userUsedPushNotifications ?? false );
		$response->set_html(
			$this->get_popup_confirmation_html(
				$api_response->code ?? '',
				$api_response->data ?? '',
				$api_response->message ?? '',
				$api_response->userUsedPushNotifications ?? false
			)
		);

		$this->output_response( $response );
	}

	private function get_html( string $name, array $args = [] ): string {
		ob_start();

		simplyin()->locate_template(
			$name,
			[ 'args' => $args ],
			'src/Views/Widget'
		);

		$output = ob_get_contents();
		ob_end_clean();

		return (string) $output;
	}

	public function get_popup_confirmation_html(
		string $code,
		string $data,
		string $message,
		bool $user_used_push_notifications
	) {

		if ( $user_used_push_notifications ) {
			return $this->get_html( 'confirmation_form.php' );
		} else {
			return $this->get_html(
				'code_form.php',
				[
					'code'    => $code,
					'data'    => $data,
					'message' => $message,
				]
			);
		}
	}

	public function check_code_ajax_action(): void {

		$logger = simplyin()->get_simplyin_logger();

		if ( check_ajax_referer(
			'simplyin_ajax',
			'ajax_token',
			false
		) ) {

			if ( isset( $_POST['email'] ) ) {
				$email = sanitize_email( wp_unslash( $_POST['email'] ) );
			} else {
				$this->output_response_bad_request();
			}

			if ( isset( $_POST['code'] ) ) {
				$code = sanitize_text_field( wp_unslash( $_POST['code'] ) );
			} else {
				$this->output_response_bad_request();
			}
		} else {
			$this->output_response_unauthorized();
		}

		$notification_token_id = (string) $this->simplyin_session->get(
			'simplyin_notification_token_id',
			''
		);

		$logger->log_if_wpdebug_on(
			'[check_code] INPUT: ' . $this->print_r(
				compact( 'code', 'email' ),
				true
			),
			'widget_ajax'
		);

		$api           = simplyin()->get_api();
		$merchantToken = get_option( 'simplyin_api_key' );
		$api_response  = $api->sendRequest(
			'checkout/submitCheckoutCodeCommerce',
			'POST',
			[
				'email'               => $email,
				'code'                => $code,
				'notificationTokenId' => $notification_token_id,
				'apiKey'              => $merchantToken,
			]
		);

		$logger->log_if_wpdebug_on(
			'[check_code] API RESPONSE: ' . $this->print_r(
				$api_response,
				true
			),
			'widget_ajax'
		);

		$ajax_response = new Check_Code_Response();

		if ( ! is_object( $api_response ) || ( empty( $api_response->isCodeValid ) && empty( $api_response->authToken ) ) ) {
			$ajax_response->set_is_success( false );
			$ajax_response->set_error( $api_response->errorCode ?? 'INVALID_CODE' );
			$this->output_response( $ajax_response );

			return;
		}

		if ( 'UNAUTHORIZED' === ( $api_response->code ?? '' ) ) {
			$ajax_response->set_is_success( false );
			$ajax_response->set_error( 'UNAUTHORIZED' );
			simplyin()->handle_api_unauthorized();
			$this->output_response( $ajax_response );

			return;
		}

		$previous_token = (string) $this->simplyin_session->get( 'simplyin_auth_token', '' );
		$session_id     = (string) $this->simplyin_session->get( 'id', '' );
		Payment_Fields::clear_methods_cache( $previous_token, $session_id );

		$this->simplyin_session->set(
			'simplyin_auth_token',
			$api_response->authToken ?? ''
		);
		$this->simplyin_session->set(
			'simplyin_refresh_token',
			$api_response->refreshToken ?? ''
		);
		Payment_Fields::clear_methods_cache( (string) ( $api_response->authToken ?? '' ), $session_id );
		$this->simplyin_session->set( 'simplyin_email', $email );
		$this->simplyin_session->set( 'simplyin_pending_email', '' );
		$this->simplyin_session->set( 'simplyin_push_confirmed', true );
		$this->simplyin_session->set( 'simplyin_notification_token_id', '' );

		Payment_Fields::clear_methods_cache( (string) ( $api_response->authToken ?? '' ), $session_id );

		$this->simplyin_session->set( 'simplyin_user_data_commerce', null );
		$this->simplyin_session->set(
			'simplyin_user_data_commerce_timestamp',
			null
		);

		$login_result = $this->login_customer_by_email( $email, $api_response );

		$payload = json_decode(
			json_encode( $api_response->data ?? [] ),
			true
		);
		if ( ! is_array( $payload ) ) {
			$payload = [];
		}

		$commerce_for_sync = [ 'data' => $payload ];
		$checkout_sync = $this->sync_checkout_delivery_from_commerce_payload(
			$commerce_for_sync
		);

		$html = $this->render_delivery_form( [ 'data' => $payload ] );

		$ajax_response->set_is_success( true );
		$ajax_response->set_login_result( $login_result );
		$ajax_response->set_html( $html );
		$this->apply_invoice_and_phone_to_login_response( $ajax_response, $payload, $checkout_sync );

		$this->output_response( $ajax_response );
	}

	public function check_push_notification_ajax_action(): void {

		$logger = simplyin()->get_simplyin_logger();

		$session_email_confirmed = (string) $this->simplyin_session->get(
			'simplyin_email',
			''
		);
		$session_email_pending   = (string) $this->simplyin_session->get(
			'simplyin_pending_email',
			''
		);
		$email_from_session      = $session_email_pending ?: $session_email_confirmed;

		$notification_token_id = (string) $this->simplyin_session->get(
			'simplyin_notification_token_id',
			''
		);

		$api            = simplyin()->get_api();
		$merchant_token = get_option( 'simplyin_api_key' );

		if ( check_ajax_referer(
			'simplyin_ajax',
			'ajax_token',
			false
		) ) {

		} else {
			$this->output_response_unauthorized();
		}

		$logger->log_if_wpdebug_on(
			sprintf(
				'[Widget_Controller] [check_push_notification_ajax_action] session data %s',
				$this->print_r(
					[
						'notification_token_id' => $notification_token_id,
						'email_confirmed'       => $session_email_confirmed,
						'email_pending'         => $session_email_pending,
					],
					true
				)
			),
			'widget_ajax'
		);

		if ( check_ajax_referer(
			'simplyin_ajax',
			'ajax_token',
			false
		) ) {

			if ( isset( $_POST['email'] ) ) {
				$email = sanitize_email( wp_unslash( $_POST['email'] ) );
			} else {
				$email = $email_from_session;
			}
		} else {
			$this->output_response_unauthorized();
		}

		$ajax_response = new Check_Push_Notification_Response();

		if ( empty( $email ) || empty( $notification_token_id ) ) {
			$ajax_response->set_is_success( false );
			$ajax_response->set_error( 'MISSING_DATA' );
			$this->output_response( $ajax_response );

			return;
		}

		if ( check_ajax_referer(
			'simplyin_ajax',
			'ajax_token',
			false
		) ) {
			$logger->log_if_wpdebug_on(
				sprintf(
					'[Widget_Controller] [check_push_notification_ajax_action] INPUT %s',
					$this->print_r(
						[
							'email'               => $email,
							'notificationTokenId' => $notification_token_id,
							'user_logged'         => is_user_logged_in() ? get_current_user_id() : 0,
						],
						true
					)
				),
				'widget_ajax'
			);
		} else {
			$this->output_response_unauthorized();
		}

		$api_response = $api->sendRequest(
			'checkout/checkIfSubmitEmailPushNotificationWasConfirmedCommerce',
			'POST',
			[
				'email'               => $email,
				'notificationTokenId' => $notification_token_id,
				'apiKey'              => $merchant_token,
			]
		);

		$is_ok = is_object( $api_response ) && ! empty( $api_response->ok );
		if ( ! $is_ok ) {

			$ajax_response->set_is_success( false );
			$ajax_response->set_error( $api_response->code ?? 'BAD_REQUEST' );

			$this->output_response( $ajax_response );

			return;
		}

		$this->simplyin_session->set( 'simplyin_push_confirmed', true );
		$this->simplyin_session->set( 'simplyin_email', $email );
		$this->simplyin_session->set( 'simplyin_pending_email', '' );
		$this->simplyin_session->set(
			'simplyin_auth_token',
			$api_response->authToken ?? ''
		);

		$this->simplyin_session->set(
			'simplyin_refresh_token',
			$api_response->refreshToken ?? ''
		);

		$this->simplyin_session->set( 'simplyin_user_data_commerce', null );
		$this->simplyin_session->set(
			'simplyin_user_data_commerce_timestamp',
			null
		);

		$login_result = [
			'is_guest'  => true,
			'firstname' => $api_response->userData->name ?? '',
			'lastname'  => $api_response->userData->surname ?? '',
		];

		$user_data_arr = json_decode(
			json_encode( $api_response->userData ),
			true
		);
		if ( ! is_array( $user_data_arr ) ) {
			$user_data_arr = [];
		}

		$commerce_for_sync = [ 'data' => $user_data_arr ];
		$checkout_sync = $this->sync_checkout_delivery_from_commerce_payload(
			$commerce_for_sync
		);

		$html = $this->render_delivery_form( [ 'data' => $user_data_arr ] );

		$ajax_response->set_is_success( true );
		$ajax_response->set_html( $html );
		$ajax_response->set_login_result( $login_result );
		$this->apply_invoice_and_phone_to_login_response( $ajax_response, $user_data_arr, $checkout_sync );

		$this->output_response( $ajax_response );
	}

	private function render_ajax_form( string $type ): string {
		$common = [
			'module_path' => simplyin()->get_plugin_url(),
			'form_type'   => $type,
			'form_action' => admin_url( 'admin-ajax.php' ),
		];

		if ( 'delivery' === $type ) {
			return $this->render_delivery_form();
		}

		return $this->get_html(
			"{$type}_form.php",
			$common
		);
	}

	protected function render_delivery_form( ?array $api_payload = null ): string {
		if ( empty( $api_payload ) ) {
			$api_payload = $this->user_data_commerce_ajax_action();
		}
		if ( isset( $api_payload['code'] ) && 'INTERNAL_SERVER_ERROR' === $api_payload['code'] ) {
			wp_send_json_error( [ 'error' => 'Session expired' ] );
		}

		if ( ! isset( $api_payload['data'] ) && isset( $api_payload['userData'] ) ) {
			$api_payload['data'] = $api_payload['userData'];
		}
		$user = $api_payload['data'] ?? [];

		$context = [
			'module_path'        => simplyin()->get_plugin_url(),
			'carriers_cost'      => WC()->cart->get_cart_contents_total(),
			'_id'                => $user['_id'] ?? '',
			'name'               => $user['name'] ?? '',
			'surname'            => $user['surname'] ?? '',
			'billing_addresses'  => $this->parseAddress(
				$user['billingAddresses'] ?? [],
				$this->getSelectedBillingAddress()
			),
			'shipping_addresses' => $this->parseAddress(
				$user['shippingAddresses'] ?? [],
				$this->getSelectedShippingAddress()
			),
			'parcel_lockers'     => $this->getParcelLockers(
				$api_payload,
				$this->getSelectedParcel()
			),
			'service_type'       => $this->getServiceType(),
			'delivery_address'   => $this->getSelectedShippingAddress(),
			'billing_address'    => $this->getSelectedBillingAddress(),
			'selected_parcel'    => $this->getSelectedParcel(),
		];

		ob_start();
		simplyin()->locate_template(
			'delivery_form.php',
			[ 'args' => $context ],
			'src/Views/Widget'
		);

		return ob_get_clean();
	}


	public function getParcelLockers( $data, $selected = false ) {
		$mapping            = new Mapping();
		$available_carriers = $mapping->get_available_providers( true );

		if ( empty( $available_carriers ) ) {
			return [];
		}

		$parcel_lockers = $data['data']['parcelLockers'];
		if ( ! is_array( $parcel_lockers ) ) {
			return [];
		}

		foreach ( $parcel_lockers as $key => $parcel ) {
			if ( ! isset( $parcel['providerName'] ) || ! in_array(
				$parcel['providerName'],
				$available_carriers
			) ) {
				unset( $parcel_lockers[ $key ] );
				continue;
			}
			if ( empty( $parcel['addressName'] ) ) {
				$parcel_lockers[ $key ]['addressName'] = $parcel['lockerId'];
			}
			$parcel_lockers[ $key ]['address_desc'] = $parcel['desc'] ?? '';
			if ( $parcel['_id'] === $selected || ( false === $selected && 'parcel_machine' === ( $parcel['service_type'] ?? '' ) ) ) {
				$parcel_lockers[ $key ]['selected'] = true;
				$selected                           = $parcel['_id'];
			} else {
				$parcel_lockers[ $key ]['selected'] = false;
			}
		}
		$return = array_values( $parcel_lockers );

		$order_by_data = get_option( 'SimplyInLogisticsPartnersOrder' );

		if ( ! empty( $return ) && is_array( $order_by_data ) && ! empty( $order_by_data ) ) {
			$return = $this->sortItemsByProvider( $return, $order_by_data );
		}

		return $return;
	}


	/**
	 * Sorts $items according to the priority defined in $orderBy.
	 *
	 * @param array $items The unsorted list of items (each item must contain a 'providerName' key).
	 * @param array $orderBy An indexed array that contains provider names in the desired order.
	 *
	 * @return array Sorted array of items.
	 */
	private function sortItemsByProvider(
		array $items,
		array $orderBy
	): array {
		// 1. Build a lookup table: providerName => priority index (0‑based)
		$priority = [];
		foreach ( $orderBy as $idx => $provider ) {
			// Normalise the provider name – e.g. case‑insensitive match.
			// If you need exact matching, remove strtolower().
			$priority[ strtolower( $provider ) ] = $idx;
		}

		// 2. Separate items into two buckets:
		// - $ordered   : items that have a defined priority
		// - $unmatched : items whose provider is not in the order list
		$ordered   = [];
		$unmatched = [];

		foreach ( $items as $item ) {
			if ( ! isset( $item['providerName'] ) ) {
				// If an item has no providerName, treat it as unmatched.
				$unmatched[] = $item;
				continue;
			}

			$key = strtolower( $item['providerName'] );

			if ( array_key_exists( $key, $priority ) ) {
				// Store the priority so we can sort later
				$ordered[ $priority[ $key ] ][] = $item;
			} else {
				$unmatched[] = $item;
			}
		}

		// 3. Merge buckets in order of priority.
		// Items that share the same priority keep their original relative order.
		ksort( $ordered );          // Ensure priorities are processed from lowest to highest
		$sorted = [];
		foreach ( $ordered as $bucket ) {
			foreach ( $bucket as $itm ) {
				$sorted[] = $itm;
			}
		}

		// 4. Append the unmatched items at the end.
		return array_merge( $sorted, $unmatched );
	}


	public function parseAddress( $addresses, $selected ) {
		if ( empty( $addresses ) || ! is_array( $addresses ) ) {
			return [];
		}

		foreach ( $addresses as $key => $address ) {
			$addresses[ $key ]['selected'] = false;
		}

		foreach ( $addresses as $key => $address ) {
			if ( ( isset( $address['_id'] ) && $address['_id'] === $selected )
				|| false === $selected ) {
				$addresses[ $key ]['selected'] = true;
				break;
			}
		}

		return $addresses;
	}

	public function getSelectedShippingAddress() {
		$shipping = $this->simplyin_session->get( 'simplyin_shipping', '' );
		if ( ! empty( $shipping ) ) {
			return $shipping;
		}

		return false;
	}

	public function getSelectedParcel() {
		$parcel = $this->simplyin_session->get( 'simplyin_parcel', '' );
		if ( ! empty( $parcel ) ) {
			return $parcel;
		}

		return false;
	}

	public function getSelectedBillingAddress() {
		$billing = $this->simplyin_session->get( 'simplyin_billing', '' );
		if ( ! empty( $billing ) ) {
			return $billing;
		}

		return false;
	}

	public function getServiceType() {
		$service_type = $this->simplyin_session->get( 'service_type', '' );
		if ( ! empty( $service_type ) ) {
			return $service_type;
		}

		return false;
	}

	public function delete_address_ajax_action(): void {

		if ( check_ajax_referer(
			'simplyin_ajax',
			'ajax_token',
			false
		) ) {

			if ( isset( $_POST['id'] ) ) {
				$address_id = sanitize_text_field( wp_unslash( $_POST['id'] ) );
			} else {
				$this->output_response_bad_request();
			}
		} else {
			$this->output_response_unauthorized();
		}

		$api_payload = $this->user_data_commerce_ajax_action();
		$user_data   = $api_payload['data'] ?? $api_payload['userData'] ?? [];

		$address_type = '';
		foreach (
			[
				'billingAddresses',
				'shippingAddresses',
				'parcelLockers',
			] as $section
		) {
			foreach ( $user_data[ $section ] ?? [] as $id => $row ) {
				if ( ( $row['_id'] ?? '' ) === $address_id ) {
					unset( $user_data[ $section ][ $id ] );
					$user_data[ $section ] = array_values( $user_data[ $section ] );
					$address_type          = $section;
					break 2;
				}
			}
		}

		$response = new Delete_Address_Response();

		if ( '' === $address_type ) {
			$response->set_is_success( false );
			$response->set_error( 'ADDRESS_NOT_FOUND' );
			$this->output_response( $response );

			return;
		}

		$api          = simplyin()->get_api();
		$auth_token   = $this->getAuthToken();
		$api_response = $api->sendRequest(
			'userData',
			'PATCH',
			[ 'userData' => $user_data ],
			[ 'api_token' => $auth_token ]
		);

		$this->simplyin_session->set( 'simplyin_user_data_commerce', null );
		$this->simplyin_session->set(
			'simplyin_user_data_commerce_timestamp',
			null
		);

		$updated = json_decode(
			json_encode( $api_response->data->$address_type ?? [] ),
			true
		);

		$response->set_is_success( true );
		$response->set_address_type( $address_type );
		$response->set_addresses( $updated );

		$logger = simplyin()->get_simplyin_logger();
		$logger->log_if_wpdebug_on(
			'[delete_address] OUTPUT ' . $this->print_r(
				$response->to_array(),
				true
			),
			'widget_ajax'
		);
		$this->output_response( $response );
	}


	public function findAddress( $data, $id ) {
		$data         = isset( $data['data'] ) ? $data['data'] : $data['userData'];
		$address_type = '';
		$address      = false;
		foreach ( $data['billingAddresses'] as $item ) {
			if ( $id === $item['_id'] ) {
				$address      = $item;
				$address_type = 'billingAddresses';
			}
		}

		foreach ( $data['shippingAddresses'] as $item ) {
			if ( $id === $item['_id'] ) {
				$address      = $item;
				$address_type = 'shippingAddresses';
			}
		}

		foreach ( $data['parcelLockers'] as $item ) {
			if ( $id === $item['_id'] ) {
				$address      = $item;
				$address_type = 'parcelLockers';
			}
		}

		return [
			'address'      => $address,
			'address_type' => $address_type,
		];
	}

	public function getDefaultCountryIso(): string {
		return WC()->countries->get_base_country();
	}

	public function edit_address_ajax_action(): void {

		if ( check_ajax_referer(
			'simplyin_ajax',
			'ajax_token',
			false
		) ) {

			if ( isset( $_POST['id'] ) ) {
				$address_id = sanitize_text_field( wp_unslash( $_POST['id'] ) );
			} else {
				$this->output_response_bad_request();
			}
		} else {
			$this->output_response_unauthorized();
		}
		$logger = simplyin()->get_simplyin_logger();

		$api_payload   = $this->user_data_commerce_ajax_action();
		$address_entry = $this->api_find_address(
			$api_payload,
			$address_id,
			true
		);

		$response = new Edit_Address_Response();

		if ( ! $address_entry ) {
			$response->set_is_success( false );
			$response->set_error( 'ADDRESS_NOT_FOUND' );
			$this->output_response( $response );

			return;
		}

		$countries_list = WC()->countries->get_countries();
		$default_iso    = WC()->countries->get_base_country();

		$html = $this->get_html(
			'edit_address_form.php',
			[
				'address'             => $address_entry['address'],
				'address_type'        => $address_entry['address_type'],
				'countries'           => $countries_list,
				'default_country_iso' => $default_iso,
				'module_path'         => simplyin()->get_plugin_url(),
			]
		);

		$response->set_is_success( true );
		$response->set_html( $html );

		$logger->log_if_wpdebug_on(
			'[edit_address] OUTPUT ' . $this->print_r(
				$response->to_array(),
				true
			),
			'widget_ajax'
		);
		$this->output_response( $response );
	}

	public function create_empty_address( $address_type ) {
		return [
			'address_type' => $address_type,
			'address'      => [
				'_id'         => '',
				'name'        => '',
				'surname'     => '',
				'street'      => '',
				'postalCode'  => '',
				'city'        => '',
				'companyName' => '',
				'addressName' => '',
				'taxId'       => '',
				'country'     => $this->getDefaultCountryIso(),
			],
		];
	}

	public function add_address_ajax_action() {

		$logger = simplyin()->get_simplyin_logger();

		if ( check_ajax_referer(
			'simplyin_ajax',
			'ajax_token',
			false
		) ) {

			if ( isset( $_POST['address_type'] ) ) {
				$address_type = sanitize_text_field( wp_unslash( $_POST['address_type'] ) );
			} else {
				$this->output_response_bad_request();
			}
		} else {
			$this->output_response_unauthorized();
		}

		$ajax_response = new Add_Address_Response();

		if ( ! $address_type ) {
			$ajax_response->set_is_success( false );
			$ajax_response->set_error( 'ADDRESS_TYPE_NOT_FOUND' );
			$this->output_response( $ajax_response );
		}

		$countries_list = WC()->countries->get_countries();
		$default_iso    = WC()->countries->get_base_country();
		$address_data   = $this->create_empty_address( $address_type );

		if ( 'parcel' !== $address_type ) {
			$html = $this->get_html(
				'edit_address_form.php',
				[
					'address'             => $address_data['address'],
					'address_type'        => $address_data['address_type'],
					'countries'           => $countries_list,
					'default_country_iso' => $default_iso,
					'module_path'         => simplyin()->get_plugin_url(),
				]
			);
		} else {
			$pickup_point_kind = '';
			if ( isset( $_POST['pickup_point_kind'] ) ) {
				$pickup_point_kind = sanitize_text_field(
					wp_unslash( $_POST['pickup_point_kind'] )
				);
			}
			$pickup_point_kind = Closest_Parcel_Lockers_Request_Builder::normalize_service_type(
				'' !== $pickup_point_kind ? $pickup_point_kind : null
			);

			$html = $this->get_html(
				'autocomplete_form.php',
				[
					'module_path'         => simplyin()->get_plugin_url(),
					'pickup_point_kind'   => $pickup_point_kind ?? '',
				]
			);
		}

		$ajax_response->set_is_success( true );
		$ajax_response->set_html( $html );

		if ( check_ajax_referer(
			'simplyin_ajax',
			'ajax_token',
			false
		) ) {
			$logger->log_if_wpdebug_on(
				sprintf(
					'[Widget_Controller] [add_address_ajax_action] INPUT %s',
					$this->print_r(
						[
							'ajax_response' => $ajax_response,
						],
						true
					)
				),
				'widget_ajax'
			);
		} else {
			$this->output_response_unauthorized();
		}

		$this->output_response( $ajax_response );
	}

	private function validate_address( array $address_in ) {
		if ( 'parcelLockers' === ( $address_in['address_type'] ?? '' ) ) {
			return true;
		}

		$errors = [];

		$first_name = $address_in['name'] ?? '';
		$last_name  = $address_in['surname'] ?? '';

		$person_regex = '/^[\p{L}\p{M}\s\'\-]{1,255}$/u';

		if ( ! preg_match( $person_regex, $first_name ) ) {
			$errors['name'] = __( 'Invalid first name', 'simplyin' );
		}
		if ( ! preg_match( $person_regex, $last_name ) ) {
			$errors['surname'] = __( 'Invalid last name', 'simplyin' );
		}

		$company_name = $address_in['companyName'] ?? '';
		$label_name   = $address_in['addressName'] ?? '';
		$tax_id       = $address_in['taxId'] ?? '';

		$generic_rx = '/^[\p{L}\p{M}\d\.\,\-\'\s]{0,255}$/u';

		if ( '' !== $company_name && ! preg_match(
			$generic_rx,
			$company_name
		) ) {
			$errors['companyName'] = __(
				'Invalid company name',
				'simplyin'
			);
		}
		if ( '' !== $label_name && ! preg_match( $generic_rx, $label_name ) ) {
			$errors['addressName'] = __(
				'Invalid address label',
				'simplyin'
			);
		}
		if ( '' !== $tax_id && ! preg_match(
			'/^[\p{Nd}A-Za-z\-\s]{3,32}$/u',
			$tax_id
		) ) {
			$errors['taxId'] = __( 'Invalid tax ID', 'simplyin' );
		}

		$street = $address_in['street'] ?? '';
		$city   = $address_in['city'] ?? '';

		if ( '' === $street || mb_strlen( $street ) > 255 ) {
			$errors['street'] = __( 'Invalid street', 'simplyin' );
		}
		if ( ! preg_match( '/^[\p{L}\p{M}\s\'\-]{1,255}$/u', $city ) ) {
			$errors['city'] = __( 'Invalid city', 'simplyin' );
		}

		$country_iso = strtoupper( $address_in['country'] ?? '' );
		$postcode    = $address_in['postalCode'] ?? '';

		if ( ! class_exists( '\WC_Validation' ) ) {
			require_once WC_ABSPATH . 'includes/class-wc-validation.php';
		}

		if ( ! \WC_Validation::is_postcode( $postcode, $country_iso ) ) {
			$errors['postalCode'] = __(
				'Invalid postal code for selected country',
				'simplyin'
			);
		}

		return empty( $errors ) ? true : $errors;
	}

	private function save_existing_address(
		array $address,
		string $id
	): array {

		$is_valid = $this->validate_address( $address );
		if ( true !== $is_valid ) {
			return [
				'is_valid' => $is_valid,
				'success'  => false,
			];
		}

		$api_payload = $this->user_data_commerce_ajax_action();
		$user_data   = [ 'userData' => $api_payload['data'] ];

		foreach ( [ 'billingAddresses', 'shippingAddresses' ] as $section ) {
			foreach ( $user_data['userData'][ $section ] as $k => $item ) {
				if ( $id === $item['_id'] ) {
					foreach ( $address as $key => $val ) {
						if ( array_key_exists( $key, $item ) ) {
							$user_data['userData'][ $section ][ $k ][ $key ] = $val;
						}
					}
					break 2;
				}
			}
		}

		$api   = simplyin()->get_api();
		$token = $this->getAuthToken();

		$patched = $api->sendRequest(
			'userData',
			'PATCH',
			$user_data,
			[ 'api_token' => $token ]
		);

		$this->simplyin_session->set( 'simplyin_user_data_commerce', null );
		$this->simplyin_session->set(
			'simplyin_user_data_commerce_timestamp',
			null
		);

		$address_data = $this->api_find_address(
			json_decode( json_encode( $patched ), true ),
			$id,
			true
		);

		return [
			'address'  => $address_data['address'] ?? $address,
			'is_valid' => true,
			'success'  => true,
		];
	}

	private function create_new_address( array $address ): array {

		$is_valid = $this->validate_address( $address );
		if ( true !== $is_valid ) {
			return [
				'is_valid' => $is_valid,
				'success'  => false,
			];
		}

		$api_payload  = $this->user_data_commerce_ajax_action();
		$user_data    = [ 'userData' => $api_payload['data'] ];
		$address_type = $address['address_type'];

		// ID-based idempotency: if payload contains _id that already exists in target list, reuse and do not append
		$incoming_id = isset( $address['_id'] ) ? (string) $address['_id'] : '';
		if ( '' !== $incoming_id ) {
			foreach ( (array) ( $user_data['userData'][ $address_type ] ?? [] ) as $existing ) {
				if ( isset( $existing['_id'] ) && (string) $existing['_id'] === $incoming_id ) {
					if ( 'parcelLockers' === $address_type ) {
						$this->remember_parcel_locker_selection( $existing );
					}
					return $this->create_new_address_duplicate_response( $existing, $address_type );
				}
			}
		}

		// Content-based idempotency for billing/shipping: detect duplicate when _id is absent (new address form)
		if ( in_array( $address_type, [ 'billingAddresses', 'shippingAddresses' ], true ) ) {
			$fields = [ 'name', 'surname', 'street', 'city', 'postalCode' ];
			foreach ( (array) ( $user_data['userData'][ $address_type ] ?? [] ) as $existing ) {
				$match = true;
				foreach ( $fields as $field ) {
					if ( strtolower( trim( (string) ( $address[ $field ] ?? '' ) ) )
						!== strtolower( trim( (string) ( $existing[ $field ] ?? '' ) ) ) ) {
						$match = false;
						break;
					}
				}
				if ( $match ) {
					return $this->create_new_address_duplicate_response( $existing, $address_type );
				}
			}
		}

		// For parcel lockers: also check by lockerId + provider (in case _id differs between API contexts)
		$is_parcel_locker = ( 'parcelLockers' === $address_type );
		if ( $is_parcel_locker ) {
			$incoming_locker_id = isset( $address['lockerId'] ) ? trim( (string) $address['lockerId'] ) : '';
			$incoming_provider  = strtolower(
				trim( (string) ( $address['label'] ?? $address['providerName'] ?? '' ) )
			);
			if ( '' !== $incoming_locker_id && '' !== $incoming_provider ) {
				foreach ( (array) ( $user_data['userData']['parcelLockers'] ?? [] ) as $existing ) {
					$existing_locker_id = isset( $existing['lockerId'] ) ? trim( (string) $existing['lockerId'] ) : '';
					$existing_provider  = strtolower(
						trim( (string) ( $existing['providerName'] ?? $existing['label'] ?? '' ) )
					);
					if ( '' !== $existing_locker_id && '' !== $existing_provider
						&& $incoming_locker_id === $existing_locker_id
						&& $incoming_provider === $existing_provider ) {
						$this->remember_parcel_locker_selection( $existing );
						return $this->create_new_address_duplicate_response( $existing, $address_type );
					}
				}
			}
		}

		$address_for_api = $address;
		if ( $is_parcel_locker ) {
			$address_for_api = $this->normalize_parcel_locker_payload( $address_for_api );
		}

		if ( ! isset( $user_data['userData'][ $address_type ] ) || ! is_array( $user_data['userData'][ $address_type ] ) ) {
			$user_data['userData'][ $address_type ] = [];
		}

		$user_data['userData'][ $address_type ][] = $address_for_api;

		$api   = simplyin()->get_api();
		$token = $this->getAuthToken();

		$patched = $api->sendRequest(
			'userData',
			'PATCH',
			$user_data,
			[ 'api_token' => $token ]
		);

		$this->simplyin_session->set( 'simplyin_user_data_commerce', null );
		$this->simplyin_session->set(
			'simplyin_user_data_commerce_timestamp',
			null
		);

		$list    = json_decode(
			json_encode( $patched->data->$address_type ?? [] ),
			true
		);
		$created = end( $list );

		if ( is_array( $created ) && ! isset( $created['address_type'] ) ) {
			$created['address_type'] = $address_type;
		}

		if ( $is_parcel_locker && is_array( $created ) ) {
			$this->remember_parcel_locker_selection( $created );
		}

		return [
			'add_address'  => $created,
			'address_type' => $address_type,
			'html'         => $this->render_delivery_form(),
			'is_valid'     => true,
			'success'      => true,
		];
	}

	/**
	 * Returns the response for a duplicate address (already in list).
	 * Used when _id or lockerId+provider match is found for parcel lockers.
	 *
	 * @param array  $existing     The existing address/parcel from userData.
	 * @param string $address_type 'parcelLockers', 'billingAddresses', or 'shippingAddresses'.
	 * @return array Response array with html and success.
	 */
	private function create_new_address_duplicate_response( array $existing, string $address_type ): array {
		return [
			'add_address'  => $existing,
			'address_type' => $address_type,
			'html'         => $this->render_delivery_form(),
			'is_duplicate' => true,
			'is_valid'     => true,
			'success'      => true,
		];
	}

	private function normalize_parcel_locker_payload( array $parcel ): array {
		if ( isset( $parcel['address_type'] ) ) {
			unset( $parcel['address_type'] );
		}

		if ( ! isset( $parcel['providerName'] ) && isset( $parcel['label'] ) ) {
			$parcel['providerName'] = (string) $parcel['label'];
		}

		if ( empty( $parcel['addressName'] ) && isset( $parcel['lockerId'] ) ) {
			$parcel['addressName'] = (string) $parcel['lockerId'];
		}

		return $parcel;
	}

	private function remember_parcel_locker_selection( array $parcel ): void {
		$this->simplyin_session->set( 'simplyin_parcel_locker_full', $parcel );

		if ( isset( $parcel['_id'] ) && '' !== $parcel['_id'] ) {
			$this->simplyin_session->set(
				'simplyin_parcel',
				(string) $parcel['_id']
			);
		}

		if ( isset( $parcel['lockerId'] ) && '' !== $parcel['lockerId'] ) {
			$this->simplyin_session->set(
				'simplyin_parcel_locker_id',
				(string) $parcel['lockerId']
			);
		}

		if ( isset( $parcel['providerName'] ) && '' !== $parcel['providerName'] ) {
			$this->simplyin_session->set(
				'simplyin_parcel_provider',
				(string) $parcel['providerName']
			);
		}
	}

	public function submit_address_ajax_action(): void {

		$logger = simplyin()->get_simplyin_logger();

		if ( check_ajax_referer(
			'simplyin_ajax',
			'ajax_token',
			false
		) ) {

			if ( isset( $_POST['address'] ) && is_array( $_POST['address'] ) ) {
				$raw_address = array_map(
					'sanitize_text_field',
					wp_unslash( $_POST['address'] )
				);
			} else {
				$raw_address = [];
			}

			if ( isset( $_POST['parcel'] ) && is_array( $_POST['parcel'] ) ) {
				$raw_parcel = array_map(
					'sanitize_text_field',
					wp_unslash( $_POST['parcel'] )
				);
			} else {
				$raw_parcel = [];
			}
		} else {
			$this->output_response_unauthorized();
		}

		$payload   = $raw_address ?: $raw_parcel;
		$is_parcel = ! empty( $raw_parcel );

		$logger->log_if_wpdebug_on(
			'[submit_address] INPUT ' . $this->print_r(
				[
					'is_parcel' => $is_parcel,
					'payload'   => $payload,
				],
				true
			),
			'widget_ajax'
		);

		$response = new Submit_Address_Response();

		if ( empty( $payload ) ) {
			$response->set_is_success( false );
			$response->set_error( 'INVALID_ADDRESS' );
			$this->output_response( $response );
		}

		$address = is_string( $payload ) ? json_decode(
			$payload,
			true
		) : $payload;
		if ( ! is_array( $address ) ) {
			$response->set_is_success( false );
			$response->set_error( 'BAD_FORMAT' );
			$this->output_response( $response );
		}

		$id     = $address['_id'] ?? '';
		$result = ( $is_parcel || '' === $id )
			? $this->create_new_address( $address )
			: $this->save_existing_address( $address, $id );

		$response->set_is_success( true );
		$response->set_result( $result );

		$logger->log_if_wpdebug_on(
			'[submit_address] OUTPUT ' . $this->print_r(
				$response->to_array(),
				true
			),
			'widget_ajax'
		);
		$this->output_response( $response );
	}


	public function search_address_ajax_action(): void {

		if ( check_ajax_referer(
			'simplyin_ajax',
			'ajax_token',
			false
		) ) {

			if ( isset( $_POST['address'] ) ) {
				$address = sanitize_text_field( wp_unslash( $_POST['address'] ) );
			} else {
				$this->output_response_bad_request();
			}
		} else {
			$this->output_response_unauthorized();
		}
		$query = $address;

		$api          = simplyin()->get_api();
		$auth_token   = $this->getAuthToken();
		$api_response = $api->sendRequest(
			'addresses/find',
			'POST',
			[ 'searchAddressBy' => $query ],
			[ 'api_token' => $auth_token ]
		);

		$response = new Search_Address_Response();

		if ( ! is_object( $api_response ) || ! empty( $api_response->code ) ) {

			$response->set_is_success( false );
			$response->set_error( $api_response->code ?? 'API_ERROR' );
			$this->output_response( $response );

			return;
		}

		$addresses = json_decode(
			json_encode( $api_response->data ?? [] ),
			true
		);

		$response->set_is_success( true );
		$response->set_addresses( $addresses );

		$this->output_response( $response );
	}


	public function getAuthToken() {
		return $this->simplyin_session->get( 'simplyin_auth_token', false );
	}

	public function get_closest_parcel_lockers_ajax_action(): void {

		$logger = simplyin()->get_simplyin_logger();

		if ( check_ajax_referer(
			'simplyin_ajax',
			'ajax_token',
			false
		) ) {

			if ( isset( $_POST['coordinates'] ) ) {
				if ( is_array( $_POST['coordinates'] ) ) {
					$coordinates = array_map(
						'sanitize_text_field',
						wp_unslash( $_POST['coordinates'] )
					);
				} else {
					$coordinates = sanitize_text_field( wp_unslash( $_POST['coordinates'] ?? [] ) );
					if ( is_string( $coordinates ) ) {
						$coordinates = json_decode( $coordinates, true ) ?: [];
					}
				}
			} else {
				$coordinates = [];
			}

			$lat    = isset( $coordinates['lat'] ) ? (float) $coordinates['lat'] : 0.0;
			$lng    = isset( $coordinates['lng'] ) ? (float) $coordinates['lng'] : 0.0;
			$radius = isset( $_POST['radius'] ) ? (int) sanitize_text_field( wp_unslash( $_POST['radius'] ) ) : 5000;
			$limit  = isset( $_POST['limit'] ) ? (int) sanitize_text_field( wp_unslash( $_POST['limit'] ) ) : 20;

			$pickup_point_kind_raw = '';
			if ( isset( $_POST['pickup_point_kind'] ) ) {
				$pickup_point_kind_raw = sanitize_text_field(
					wp_unslash( $_POST['pickup_point_kind'] )
				);
			}
			$closest_service_type = Closest_Parcel_Lockers_Request_Builder::normalize_service_type(
				'' !== $pickup_point_kind_raw ? $pickup_point_kind_raw : null
			);
		} else {
			$this->output_response_unauthorized();
		}

		$logger->log_if_wpdebug_on(
			'[get_closest_parcel_lockers] INPUT ' . $this->print_r(
				[
					'lat'                 => $lat,
					'lng'                 => $lng,
					'radius'              => $radius,
					'limit'               => $limit,
					'pickup_point_kind'   => $closest_service_type,
				],
				true
			),
			'widget_ajax'
		);

		$api       = simplyin()->get_api();
		$authToken = $this->getAuthToken();

		$mapping   = new Mapping();
		$providers = $mapping->get_available_providers( true );

		$body = Closest_Parcel_Lockers_Request_Builder::build_body(
			$lat,
			$lng,
			$radius,
			$limit,
			$providers,
			$closest_service_type
		);

		$api_response = $api->sendRequest(
			'parcelLockers/getClosest',
			'POST',
			$body,
			[ 'api_token' => $authToken ]
		);

		$response = new Closest_Parcel_Lockers_Response();

		if ( ! is_object( $api_response ) || ! empty( $api_response->code ) ) {
			$response->set_is_success( false );
			$response->set_error( $api_response->code ?? 'API_ERROR' );
			$this->output_response( $response );

			return;
		}

		$points = json_decode( json_encode( $api_response->data ?? [] ), true );

		$response->set_is_success( true );
		$response->set_points( $points );

		$logger->log_if_wpdebug_on(
			'[get_closest_parcel_lockers] OUTPUT ' . $this->print_r(
				$response->to_array(),
				true
			),
			'widget_ajax'
		);
		$this->output_response( $response );
	}


	private function login_customer_by_email(
		string $email,
		object $api_response
	): array {
		return [
			'is_guest'  => true,
			'firstname' => $api_response->userData->name ?? '',
			'lastname'  => $api_response->userData->surname ?? '',
		];
	}

	public function select_address_ajax_action(): void {

		$logger = simplyin()->get_simplyin_logger();

		if ( check_ajax_referer(
			'simplyin_ajax',
			'ajax_token',
			false
		) ) {

			if ( isset( $_POST['invoice_address'] ) ) {
				$id_invoice_address = sanitize_text_field( wp_unslash( $_POST['invoice_address'] ) );
			} else {
				$this->output_response_bad_request();
			}

			if ( isset( $_POST['delivery_address'] ) ) {
				$id_delivery_address = sanitize_text_field( wp_unslash( $_POST['delivery_address'] ) );
			} else {
				$this->output_response_bad_request();
			}

			if ( isset( $_POST['selected_point'] ) ) {
				$id_selected_point = sanitize_text_field( wp_unslash( $_POST['selected_point'] ) );
			} else {
				$this->output_response_bad_request();
			}

			if ( isset( $_POST['service_type'] ) ) {
				$service_type = sanitize_text_field( wp_unslash( $_POST['service_type'] ) );
			} else {
				$this->output_response_bad_request();
			}
		} else {
			$this->output_response_unauthorized();
		}

		$need_delivery_address = 'address' === $service_type;
		$need_selected_point   = 'parcelLocker' === $service_type;

		$response = new Select_Address_Response();

		if ( $need_delivery_address && '' === $id_delivery_address ) {
			$response->set_is_success( false );
			$response->set_error( 'DELIVERY_ADDRESS_REQUIRED' );
			$this->output_response( $response );
		}

		if ( $need_selected_point && '' === $id_selected_point ) {
			$response->set_is_success( false );
			$response->set_error( 'PARCEL_LOCKER_REQUIRED' );
			$this->output_response( $response );
		}

		if ( '' === $id_delivery_address ) {
			$id_delivery_address = $id_invoice_address;
		}

		$commerce_payload = $this->user_data_commerce_ajax_action();
		$payload          = $this->apply_delivery_selection_to_checkout(
			$commerce_payload,
			$id_invoice_address,
			$id_delivery_address,
			$id_selected_point,
			$service_type
		);

		if ( [] === $payload ) {
			$response->set_is_success( false );
			$response->set_error( 'INVALID_ADDRESS_SELECTION' );
			$this->output_response( $response );

			return;
		}

		$response->set_is_success( true );
		$response->set_warnings( $this->warnings );
		$response->set_redirect( false );
		$response->set_data(
			[
				'invoice_address'  => $payload['invoice_address'] ?? null,
				'delivery_address' => $payload['delivery_address'] ?? null,
				'parcel_address'   => $payload['parcel_address'] ?? null,
				'service_type'     => $payload['service_type'] ?? '',
				'phone_number'     => $payload['phone_number'] ?? null,
			]
		);

		$logger->log_if_wpdebug_on(
			'[select_address] OUTPUT ' . $this->print_r(
				$response->to_array(),
				true
			),
			'widget_ajax'
		);
		$this->output_response( $response );
	}

	private function user_data_commerce_ajax_action(): array {

		$auth_token     = $this->getAuthToken();
		$merchant_token = get_option( 'simplyin_api_key' );

		$cached_data     = $this->simplyin_session->get( 'simplyin_user_data_commerce' );
		$cache_timestamp = $this->simplyin_session->get( 'simplyin_user_data_commerce_timestamp' );

		if ( null !== $cached_data && null !== $cache_timestamp ) {
			if ( ( time() - $cache_timestamp ) < 120 ) {
				return $cached_data;
			}
		}

		$api  = simplyin()->get_api();
		$raw  = $api->sendRequest(
			'userDataCommerce',
			'GET',
			[],
			[
				'apiKey'    => $merchant_token,
				'api_token' => $auth_token,
			]
		);
		$data = json_decode( json_encode( $raw ), true );

		$this->simplyin_session->set( 'simplyin_user_data_commerce', $data );
		$this->simplyin_session->set(
			'simplyin_user_data_commerce_timestamp',
			time()
		);

		return $data;
	}

	private function api_find_address(
		array $payload,
		string $id,
		bool $withMeta = false
	): ?array {
		$data = $payload['data'] ?? $payload['userData'] ?? [];

		foreach (
			[
				'billingAddresses',
				'shippingAddresses',
				'parcelLockers',
			] as $section
		) {
			foreach ( $data[ $section ] ?? [] as $row ) {
				if ( ( $row['_id'] ?? '' ) === $id ) {
					return $withMeta
						? [
							'address'      => $row,
							'address_type' => $section,
						]
						: $row;
				}
			}
		}

		return null;
	}

	private function api_find_phone_number(
		array $payload
	): ?string {
		$data = $payload['data'] ?? $payload['userData'] ?? [];

		if ( ! empty( $data ) && isset( $data['phoneNumber'] ) ) {
			return $data['phoneNumber'];
		}

		return null;
	}

	public function addWarning( $msg ) {
		$this->warnings[] = $msg;
	}

	public function loadCountries() {
		$countries_json_path = simplyin()->get_plugin_dir() . 'public/widget/json/countries.json';
		if ( ! function_exists( 'WP_Filesystem' ) ) {
			require_once ABSPATH . 'wp-admin/includes/file.php';
		}
		$initialized = WP_Filesystem();
		global $wp_filesystem;
		if ( $initialized && $wp_filesystem ) {
			$contents = (string) $wp_filesystem->get_contents( $countries_json_path );
		} elseif ( is_readable( $countries_json_path ) ) {
			$contents = (string) file_get_contents( $countries_json_path );
		}

		$countries = json_decode( $contents, true );

		return is_array( $countries ) ? $countries : [];
	}

	public function get_new_account_form_ajax_action() {
		$current_url = isset( $_POST['url'] ) ? esc_url_raw( wp_unslash( $_POST['url'] ) ) : '';
		$ajax_token  = isset( $_POST['ajax_token'] ) ? sanitize_text_field( wp_unslash( $_POST['ajax_token'] ) ) : '';

		$site_url = get_site_url();
		$home_url = home_url();

		$nonce_result = check_ajax_referer(
			'simplyin_ajax',
			'ajax_token',
			false
		);

		if ( ! $nonce_result ) {
			simplyin()->get_simplyin_logger()->force_log_to_woocommerce(
				sprintf(
					'[Widget_Controller] [get_new_account_form_ajax_action] UNAUTHORIZED nonce failed %s',
					$this->print_r(
						[
							'user'              => is_user_logged_in() ? get_current_user_id() : 0,
							'url'               => $current_url,
							'site_url'          => $site_url,
							'home_url'          => $home_url,
							'option_siteurl'    => get_option( 'siteurl' ),
							'option_home'       => get_option( 'home' ),
							'ajax_token_len'    => strlen( $ajax_token ),
							'ajax_token_hash'   => '' !== $ajax_token ? substr( sha1( $ajax_token ), 0, 12 ) : '',
							'wp_verify_nonce'   => '' !== $ajax_token ? wp_verify_nonce( $ajax_token, 'simplyin_ajax' ) : false,
							'http_host'         => isset( $_SERVER['HTTP_HOST'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_HOST'] ) ) : '',
							'http_referer'      => isset( $_SERVER['HTTP_REFERER'] ) ? esc_url_raw( wp_unslash( $_SERVER['HTTP_REFERER'] ) ) : '',
							'request_uri'       => isset( $_SERVER['REQUEST_URI'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) ) : '',
							'http_origin'       => isset( $_SERVER['HTTP_ORIGIN'] ) ? esc_url_raw( wp_unslash( $_SERVER['HTTP_ORIGIN'] ) ) : '',
							'current_url_empty' => '' === $current_url,
						],
						true
					)
				),
				'widget_ajax'
			);

			$this->output_response_unauthorized();
			return;
		}

		simplyin()->get_simplyin_logger()->log_if_wpdebug_on(
			sprintf(
				'[Widget_Controller] [get_new_account_form_ajax_action] INPUT %s',
				$this->print_r(
					[
						'user' => is_user_logged_in() ? get_current_user_id() : 0,
						'url'  => $current_url,
					],
					true
				)
			),
			'widget_ajax'
		);

		if ( ! empty( $current_url ) ) {
			$current_host = wp_parse_url( $current_url, PHP_URL_HOST );
			$home_host    = wp_parse_url( $home_url, PHP_URL_HOST );

			if ( ! $current_host || $current_host !== $home_host ) {
				simplyin()->get_simplyin_logger()->force_log_to_woocommerce(
					sprintf(
						'[Widget_Controller] [get_new_account_form_ajax_action] UNAUTHORIZED invalid URL %s',
						$this->print_r(
							[
								'site_url'         => $site_url,
								'home_url'         => $home_url,
								'current_url'      => $current_url,
								'current_host'     => $current_host,
								'home_host'        => $home_host,
								'option_siteurl'   => get_option( 'siteurl' ),
								'option_home'      => get_option( 'home' ),
								'request_uri'      => isset( $_SERVER['REQUEST_URI'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) ) : '',
								'http_origin'      => isset( $_SERVER['HTTP_ORIGIN'] ) ? esc_url_raw( wp_unslash( $_SERVER['HTTP_ORIGIN'] ) ) : '',
							],
							true
						)
					),
					'widget_ajax'
				);

				$this->output_response_unauthorized();
				return;
			}

			$parsed_url = wp_parse_url( $current_url );
			$path       = $parsed_url['path'] ?? '';

			// Check if this is a payment retry view by checking the pay_for_order query parameter
			$query        = isset( $parsed_url['query'] ) ? $parsed_url['query'] : '';
			$query_params = [];
			wp_parse_str( $query, $query_params );

			$is_payment_retry = isset( $query_params['pay_for_order'] ) && 'true' === $query_params['pay_for_order'];

			if ( $is_payment_retry ) {
				simplyin()->get_simplyin_logger()->log_if_wpdebug_on(
					sprintf(
						'[Widget_Controller] [get_new_account_form_ajax_action] User is on payment retry page (pay_for_order=true), blocking form display. URL: %s',
						$current_url
					),
					'widget_ajax'
				);

				$response = new New_Account_Form_Response( '', false );
				$this->output_response( $response );
				return;
			}

			$block_endpoints = [
				get_option( 'woocommerce_checkout_order_received_endpoint', 'order-received' ),
				get_option( 'woocommerce_checkout_order_pay_endpoint', 'order-pay' ),
			];

			if ( ! empty( $path ) ) {
				foreach ( $block_endpoints as $endpoint ) {
					if ( empty( $endpoint ) ) {
						continue;
					}

					$endpoint = sanitize_title( $endpoint );

					if ( strpos( $path, '/' . $endpoint . '/' ) !== false ) {
						simplyin()->get_simplyin_logger()->log_if_wpdebug_on(
							sprintf(
								'[Widget_Controller] [get_new_account_form_ajax_action] User is on blocked page (%s), blocking form display. URL: %s',
								$endpoint,
								$current_url
							),
							'widget_ajax'
						);

						$response = new New_Account_Form_Response( '', false );
						$this->output_response( $response );
						return;
					}
				}
			}
		}

		$countries = $this->loadCountries();

		$docs_raw = get_option( 'SIMPLYIN_DOCS_INFO', '' );
		$docs     = $docs_raw ? json_decode( $docs_raw, true ) : [];
		if ( empty( $docs ) ) {
			$docs = [
				'tc-b2b-pl' => 'https://www.simply.in/terms-b2b',
				'tc-b2c-pl' => 'https://www.simply.in/terms-b2c',
				'tc-b2c-en' => 'https://www.simply.in/en/terms-b2c',
				'tc-b2b-en' => 'https://www.simply.in/en/terms-b2b',
				'gdpr-en'   => 'https://www.simply.in/en/privacy-policy',
				'gdpr-pl'   => 'https://www.simply.in/privacy-policy',
			];
		}

		$lang         = get_locale();
		$lang         = 0 === strpos( $lang, 'pl' ) ? 'pl' : 'en';
		$terms_link   = esc_url( $docs[ 'tc-b2b-' . $lang ] ?? $docs[ 'tc-b2c-' . $lang ] );
		$privacy_link = esc_url( $docs[ 'gdpr-' . $lang ] ?? $docs['gdpr-en'] );

		$prefix  = '+48';
		$checked = false;
		$phone   = '';

		if ( is_user_logged_in() ) {
			$raw_phone = get_user_meta(
				get_current_user_id(),
				'billing_phone',
				true
			);
			if ( $raw_phone && preg_match(
					'/^\+(\d+)\s+(.+)$/',
					$raw_phone,
					$m
				) ) {
				$prefix  = '+' . $m[1];
				$phone   = $m[2];
				$checked = true;
			}
		}

		$module_path = plugin_dir_url(
			dirname(
				__DIR__,
				3
			) . '/simplyin.php'
		);

		$html = $this->get_html(
			'create_account.php',
			[
				'module_path'     => $module_path,
				'countries'       => $countries,
				'terms_link'      => $terms_link,
				'privacy_link'    => $privacy_link,
				'selected_prefix' => $prefix,
				'phone'           => $phone,
				'checked'         => $checked,
			]
		);

		simplyin()->get_simplyin_logger()->log_if_wpdebug_on(
			sprintf(
				'[Widget_Controller] [get_new_account_form_ajax_action] OUTPUT %s',
				$this->print_r(
					[
						'lang'     => $lang,
						'prefix'   => $prefix,
						'phone'    => $phone,
						'checked'  => $checked,
						'html_len' => strlen( $html ),
					],
					true
				)
			),
			'widget_ajax'
		);

		$response = new New_Account_Form_Response( $html );
		$this->output_response( $response );
	}

	/**
	 * AJAX handler that explicitly logs the user out from Simply.IN checkout – it clears
	 * all related tokens stored in the PHP session.
	 *
	 * JS can call it via:  action: 'simplyin_logout'.
	 */
	public function logout_ajax_action(): void {

		$this->reset_simplyin_session();
		wp_send_json_success();
	}

	private function reset_simplyin_session(): void {
		$previous_token      = (string) $this->simplyin_session->get( 'simplyin_auth_token', '' );
		$previous_session_id = (string) $this->simplyin_session->get( 'id', '' );
		$this->simplyin_session->clear();
		Payment_Fields::clear_methods_cache( $previous_token, $previous_session_id );

		$this->simplyin_session->set( 'simplyin_user_data_commerce', null );
		$this->simplyin_session->set(
			'simplyin_user_data_commerce_timestamp',
			null
		);
	}

	private function get_customer_message_on_unauthorized(): string {
		return '';
	}

	private function update_array_with_plugin_info( array $body_data ): array {
		$body_data['pluginVersion'] = SimplyInGetPluginVersion();
		$body_data['shopName']      = addslashes( sanitize_text_field( get_bloginfo( 'name' ) ) );
		$body_data['platform']      = 'WooCommerce';
		$body_data['shopVersion']   = defined( 'WC_VERSION' ) ? WC_VERSION : '';
		$body_data['lng']           = strtoupper(
			substr(
				get_locale(),
				0,
				2
			)
		);

		return $body_data;
	}

	/**
	 * @param Check_Code_Response|Check_Push_Notification_Response $response  Login or session response object.
	 * @param array<string, mixed>                                 $user_data Normalized commerce data root for fallbacks.
	 * @param array<string, mixed>                                 $sync      Payload from sync_checkout_delivery_from_commerce_payload().
	 */
	private function apply_invoice_and_phone_to_login_response(
		$response,
		array $user_data,
		array $sync
	): void {
		if ( [] !== $sync ) {
			$response->set_checkout_sync_payload( $sync );
			if ( ! empty( $sync['invoice_address'] ) && is_array( $sync['invoice_address'] ) ) {
				$response->set_invoice_address( $sync['invoice_address'] );
			}
			if ( isset( $sync['phone_number'] ) && is_string( $sync['phone_number'] ) ) {
				$response->set_phone_number( $sync['phone_number'] );
			}

			return;
		}

		if ( isset( $user_data['phoneNumber'] ) ) {
			$response->set_phone_number( (string) $user_data['phoneNumber'] );
		}

		if ( ! empty( $user_data['billingAddresses'] ) ) {
			$response->set_invoice_address( $user_data['billingAddresses'][0] );
		}
	}

	private function wc_checkout_customer_available(): bool {
		return function_exists( 'WC' ) && WC()->customer;
	}

	private function ensure_wc_session_loaded_for_ajax(): void {
		if ( ! defined( 'DOING_AJAX' ) || ! DOING_AJAX ) {
			return;
		}
		if ( ! function_exists( 'WC' ) ) {
			return;
		}
		if ( function_exists( 'wc_load_cart' ) ) {
			wc_load_cart();
		}
	}

	private function refresh_shipping_mapping_cookies_if_cart(): void {
		if ( ! function_exists( 'WC' ) || ! WC()->cart || WC()->cart->is_empty() ) {
			return;
		}
		( new Mapping() )->handle_checkout();
	}

	private function is_point_delivery_service_type( string $service_type ): bool {
		return 'parcel' === $service_type || 'delivery' === $service_type;
	}

	private function clear_simplyin_parcel_locker_extras(): void {
		$this->simplyin_session->set( 'simplyin_parcel_locker_id', '' );
		$this->simplyin_session->set( 'simplyin_parcel_provider', '' );
		$this->simplyin_session->set( 'simplyin_parcel_locker_full', null );
	}

	private function persist_parcel_locker_row_to_session( array $commerce_payload, string $point_id ): void {
		if ( '' === $point_id ) {
			return;
		}

		$parcel_address = $this->api_find_address( $commerce_payload, $point_id );
		if ( ! is_array( $parcel_address ) ) {
			return;
		}

		$locker_id = isset( $parcel_address['lockerId'] ) ? (string) $parcel_address['lockerId'] : '';
		$provider  = isset( $parcel_address['providerName'] ) ? (string) $parcel_address['providerName'] : '';

		if ( '' !== $locker_id ) {
			$this->simplyin_session->set( 'simplyin_parcel_locker_id', $locker_id );
		}
		if ( '' !== $provider ) {
			$this->simplyin_session->set( 'simplyin_parcel_provider', $provider );
		}

		$this->simplyin_session->set( 'simplyin_parcel_locker_full', $parcel_address );
	}

	private function wc_apply_address( array $addr, string $scope ): void {
		$customer = WC()->customer;
		$prefix   = $scope . '_';

		$customer->{"set_{$prefix}first_name"}( $addr['name'] ?? '' );
		$customer->{"set_{$prefix}last_name"}( $addr['surname'] ?? '' );
		$customer->{"set_{$prefix}company"}( $addr['companyName'] ?? '' );
		$customer->{"set_{$prefix}address_1"}( $addr['street'] ?? '' );
		$customer->{"set_{$prefix}address_2"}( $addr['appartmentNumber'] ?? '' );
		$customer->{"set_{$prefix}city"}( $addr['city'] ?? '' );
		$customer->{"set_{$prefix}postcode"}( $addr['postalCode'] ?? '' );
		$customer->{"set_{$prefix}country"}( $addr['country'] ?? '' );
		$customer->save();
	}

	/**
	 * Persist Simply.IN delivery choice to session + WC_Customer; build payload for insertFormData.
	 *
	 * @return array<string, mixed> Empty if WC customer missing or invoice id unknown in commerce payload.
	 */
	private function apply_delivery_selection_to_checkout(
		array $commerce_payload,
		string $invoice_id,
		string $delivery_id,
		string $point_id,
		string $service_type
	): array {
		$this->ensure_wc_session_loaded_for_ajax();

		if ( ! $this->wc_checkout_customer_available() ) {
			return [];
		}

		if ( '' === $invoice_id ) {
			return [];
		}

		$invoice_row = $this->api_find_address( $commerce_payload, $invoice_id );
		if ( ! is_array( $invoice_row ) ) {
			return [];
		}

		if ( '' === $delivery_id ) {
			$delivery_id = $invoice_id;
		}

		$this->simplyin_session->set( 'simplyin_billing', $invoice_id );
		$this->simplyin_session->set( 'simplyin_shipping', $delivery_id );
		$this->simplyin_session->set( 'simplyin_parcel', $point_id );
		$this->simplyin_session->set( 'service_type', $service_type );

		$is_point = $this->is_point_delivery_service_type( $service_type );
		if ( $is_point ) {
			if ( '' === $point_id ) {
				$this->clear_simplyin_parcel_locker_extras();
			} else {
				$this->persist_parcel_locker_row_to_session( $commerce_payload, $point_id );
			}
		} else {
			$this->clear_simplyin_parcel_locker_extras();
		}

		$delivery_row   = $this->api_find_address( $commerce_payload, $delivery_id );
		$parcel_row     = '' !== $point_id ? $this->api_find_address( $commerce_payload, $point_id ) : null;
		$phone_number   = $this->api_find_phone_number( $commerce_payload );

		$this->wc_apply_address( $invoice_row, 'billing' );
		if ( is_array( $delivery_row ) ) {
			$this->wc_apply_address( $delivery_row, 'shipping' );
		}

		$parcel_for_response = $parcel_row;
		if ( null === $parcel_for_response && $is_point ) {
			$cached              = $this->simplyin_session->get( 'simplyin_parcel_locker_full', null );
			$parcel_for_response = is_array( $cached ) ? $cached : null;
		}

		$response_delivery = $delivery_row;
		if ( $is_point ) {
			$response_delivery = null;
		}

		$result = [
			'invoice_address'  => $invoice_row,
			'delivery_address' => $response_delivery,
			'parcel_address'   => $parcel_for_response,
			'service_type'     => $service_type,
			'phone_number'     => $phone_number,
		];

		$this->refresh_shipping_mapping_cookies_if_cart();

		return $result;
	}

	private function pick_default_parcel_from_commerce_display( array $commerce_payload ): array {
		$list_payload = $commerce_payload;
		if ( ! isset( $list_payload['data'] ) && isset( $list_payload['userData'] ) ) {
			$list_payload = [ 'data' => $list_payload['userData'] ];
		}
		if ( ! isset( $list_payload['data'] ) || ! is_array( $list_payload['data'] ) ) {
			return [
				'point_id'         => '',
				'api_service_type' => '',
			];
		}

		$lockers = $this->getParcelLockers( $list_payload, false );
		foreach ( $lockers as $locker ) {
			if ( empty( $locker['selected'] ) ) {
				continue;
			}

			return [
				'point_id'         => (string) ( $locker['_id'] ?? '' ),
				'api_service_type' => (string) ( $locker['service_type'] ?? '' ),
			];
		}

		return [
			'point_id'         => '',
			'api_service_type' => '',
		];
	}

	/**
	 * @return array{invoice_id: string, delivery_id: string, point_id: string, service_type: string}|null
	 */
	private function resolve_stored_or_default_delivery_ids( array $commerce_payload ): ?array {
		$stored_billing = (string) $this->simplyin_session->get( 'simplyin_billing', '' );
		if ( '' !== $stored_billing ) {
			$shipping     = (string) $this->simplyin_session->get( 'simplyin_shipping', '' );
			$point_id     = (string) $this->simplyin_session->get( 'simplyin_parcel', '' );
			$service_type = (string) $this->simplyin_session->get( 'service_type', '' );

			if ( $this->is_point_delivery_service_type( $service_type ) && '' === $point_id ) {
				$hint = $this->pick_default_parcel_from_commerce_display( $commerce_payload );
				if ( '' !== $hint['point_id'] ) {
					$point_id = $hint['point_id'];
				}
			}

			return [
				'invoice_id'   => $stored_billing,
				'delivery_id'  => '' !== $shipping ? $shipping : $stored_billing,
				'point_id'     => $point_id,
				'service_type' => $service_type,
			];
		}

		$data    = $commerce_payload['data'] ?? $commerce_payload['userData'] ?? [];
		$billing = $data['billingAddresses'] ?? [];
		if ( ! is_array( $billing ) || [] === $billing ) {
			return null;
		}

		$first   = $billing[0];
		$invoice = is_array( $first ) ? (string) ( $first['_id'] ?? '' ) : '';
		if ( '' === $invoice ) {
			return null;
		}

		$base = [
			'invoice_id'   => $invoice,
			'delivery_id'  => $invoice,
			'point_id'     => '',
			'service_type' => '',
		];

		$hint = $this->pick_default_parcel_from_commerce_display( $commerce_payload );
		if ( '' !== $hint['point_id'] ) {
			$base['point_id']     = $hint['point_id'];
			$base['service_type'] = ( 'parcel_machine' === $hint['api_service_type'] ) ? 'parcel' : 'delivery';
		}

		return $base;
	}

	/**
	 * After login or on checkout load: restore saved selection or default courier + billing address.
	 *
	 * @return array<string, mixed> Empty when nothing applied.
	 */
	private function sync_checkout_delivery_from_commerce_payload( array $commerce_payload ): array {
		$ids = $this->resolve_stored_or_default_delivery_ids( $commerce_payload );
		if ( null === $ids ) {
			return [];
		}

		return $this->apply_delivery_selection_to_checkout(
			$commerce_payload,
			$ids['invoice_id'],
			$ids['delivery_id'],
			$ids['point_id'],
			$ids['service_type']
		);
	}

	public function maybe_resync_checkout_from_simplyin_session(): void {
		if ( ! function_exists( 'is_checkout' ) || ! is_checkout() ) {
			return;
		}
		if ( function_exists( 'is_wc_endpoint_url' ) && is_wc_endpoint_url( 'order-pay' ) ) {
			return;
		}
		if ( ( defined( 'DOING_AJAX' ) && DOING_AJAX ) || ( defined( 'REST_REQUEST' ) && REST_REQUEST ) ) {
			return;
		}
		if ( ! function_exists( 'WC' ) ) {
			return;
		}

		$auth = $this->getAuthToken();
		if ( ! is_string( $auth ) || '' === $auth ) {
			return;
		}

		if ( function_exists( 'wc_load_cart' ) ) {
			wc_load_cart();
		}

		if ( ! $this->wc_checkout_customer_available() ) {
			return;
		}

		try {
			$commerce_payload = $this->user_data_commerce_ajax_action();
		} catch ( \Throwable $e ) {
			return;
		}

		if ( ! is_array( $commerce_payload ) || ! empty( $commerce_payload['code'] ) ) {
			return;
		}

		$this->sync_checkout_delivery_from_commerce_payload( $commerce_payload );
	}
}
