<?php
declare( strict_types=1 );

namespace Simplyin\Simplyin_W_Plugin\Controller\Ajax\Widget;

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\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 ( $response->get_error() === 'NO_ACTIVE_SESSION' ) {
				$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 !== '' && strcasecmp(
			                                                   $api_email,
			                                                   $session_email
		                                                   ) !== 0 ) {
			$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,
		];

		$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 );
		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] );
		}

		$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 && strcasecmp(
			                                   $stored_email,
			                                   $current_email
		                                   ) !== 0 ) || ( $pending_email && strcasecmp(
			                                                                    $pending_email,
			                                                                    $current_email
		                                                                    ) !== 0 ) ) {
			$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 ( $simplyin_api_response->code === 'NOT_FOUND' ) {
				$ajax_response->set_is_success( false );
				$ajax_response->set_error( $simplyin_api_response->code );
				$this->output_response( $ajax_response );

				return;
			}

			if ( $simplyin_api_response->code === 'UNAUTHORIZED' ) {
				$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 ) || ( $api_response->code ?? '' ) === 'NOT_FOUND' ) {
			$response->set_is_success( false );
			$response->set_error( $api_response->code ?? 'NOT_FOUND' );
			$this->output_response( $response );

			return;
		}

		if ( ( $api_response->code ?? '' ) === 'UNAUTHORIZED' ) {
			$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 ) || ( $api_response->code ?? '' ) === 'NOT_FOUND' ) {
			$response->set_is_success( false );
			$response->set_error( $api_response->code ?? 'NOT_FOUND' );
			$this->output_response( $response );

			return;
		}

		if ( ( $api_response->code ?? '' ) === 'UNAUTHORIZED' ) {
			$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 ( ( $api_response->code ?? '' ) === 'UNAUTHORIZED' ) {
			$ajax_response->set_is_success( false );
			$ajax_response->set_error( 'UNAUTHORIZED' );
			simplyin()->handle_api_unauthorized();
			$this->output_response( $ajax_response );

			return;
		}

		$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_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', '' );

		$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
		);
		$html    = $this->render_delivery_form( [ 'data' => $payload ] );

		if ( ! empty( $payload['billingAddresses'] ) ) {
			$invoice_address = $payload['billingAddresses'][0];
			$ajax_response->set_invoice_address( $invoice_address );
		}

		$ajax_response->set_is_success( true );
		$ajax_response->set_login_result( $login_result );
		$ajax_response->set_html( $html );
		if ( isset( $payload['phoneNumber'] ) ) {
			$ajax_response->set_phone_number( (string) $payload['phoneNumber'] );
		}

		$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
		);
		$html          = $this->render_delivery_form( [ 'data' => $user_data_arr ] );

		if ( ! empty( $user_data_arr['billingAddresses'] ) ) {
			$invoice_address = $user_data_arr['billingAddresses'][0];
			$ajax_response->set_invoice_address( $invoice_address );
		}

		$ajax_response->set_is_success( true );
		$ajax_response->set_html( $html );
		$ajax_response->set_login_result( $login_result );
		if ( isset( $user_data_arr['phoneNumber'] ) ) {
			$ajax_response->set_phone_number( (string) $user_data_arr['phoneNumber'] );
		}

		$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 ( $type === 'delivery' ) {
			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'] ) && $api_payload['code'] === 'INTERNAL_SERVER_ERROR' ) {
			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'];
			}
			if ( $selected === $parcel['_id'] || $selected === false && $parcel['service_type'] == 'parcel_machine' ) {
				$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 )
			     || $selected === false ) {
				$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 ( $item['_id'] == $id ) {
				$address      = $item;
				$address_type = 'billingAddresses';
			}
		}

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

		foreach ( $data['parcelLockers'] as $item ) {
			if ( $item['_id'] == $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 ( $address_type !== 'parcel' ) {
			$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 {
			$html = $this->get_html(
				'autocomplete_form.php',
				[
					'module_path' => simplyin()->get_plugin_url(),
				]
			);
		}

		$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 ( ( $address_in['address_type'] ?? '' ) === 'parcelLockers' ) {
			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 ( $is_valid !== true ) {
			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 ( $item['_id'] === $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 ( $is_valid !== true ) {
			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 ) {
					return [
						'add_address'  => $existing,
						'address_type' => $address_type,
						'html'         => $this->render_delivery_form(),
						'is_valid'     => true,
						'success'      => true,
					];
				}
			}
		}

		$is_parcel_locker = ( $address_type === 'parcelLockers' );

		$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,
		];
	}

	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;
		} 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,
				],
				true
			),
			'widget_ajax'
		);

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

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

		$body = [
			'numberOfItemsToFind'           => $limit,
			'searchRadiusInMeters'          => $radius,
			'coordinates'                   => [
				'lat' => $lat,
				'lng' => $lng,
			],
			'acceptedParcelLockerProviders' => $providers,
		];

		$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 = $service_type === 'address';
		$need_selected_point   = $service_type === 'parcelLocker';

		$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;
		}

		$this->simplyin_session->set( 'simplyin_billing', $id_invoice_address );
		$this->simplyin_session->set(
			'simplyin_shipping',
			$id_delivery_address
		);
		$this->simplyin_session->set( 'simplyin_parcel', $id_selected_point );
		$this->simplyin_session->set( 'service_type', $service_type );
		$api_data         = $this->user_data_commerce_ajax_action();
		$invoice_address  = $this->api_find_address(
			$api_data,
			$id_invoice_address
		);
		$delivery_address = $this->api_find_address(
			$api_data,
			$id_delivery_address
		);
		$parcel_address   = $this->api_find_address(
			$api_data,
			$id_selected_point
		);

		if ( is_array( $parcel_address ) ) {
			$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
				);
			}
			// Persist full locker data to session so DTO factory can build shippingData from it
			$this->simplyin_session->set(
				'simplyin_parcel_locker_full',
				$parcel_address
			);
		}
		$phone_number = $this->api_find_phone_number( $api_data );

		if ( $invoice_address ) {
			$this->wc_apply_address( $invoice_address, 'billing' );
		}
		if ( $delivery_address ) {
			$this->wc_apply_address( $delivery_address, 'shipping' );
		}

		$payload = [
			'invoice_address'  => $invoice_address ?: null,
			'delivery_address' => $delivery_address ?: null,
			'parcel_address'   => $parcel_address ?: ( $this->simplyin_session->get(
				'simplyin_parcel_locker_full',
				null
			) ?: null ),
			'service_type'     => $service_type,
			'phone_number'     => $phone_number,
		];

		$response->set_is_success( true );
		$response->set_warnings( $this->warnings );
		$response->set_redirect( false );
		$response->set_data( $payload );

		$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 ( $cached_data !== null && $cache_timestamp !== null ) {
			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;
	}

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

		$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();
	}

	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() {
		if ( check_ajax_referer(
			'simplyin_ajax',
			'ajax_token',
			false
		) ) {
			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,
						],
						true
					)
				),
				'widget_ajax'
			);
		} else {
			$this->output_response_unauthorized();
		}

		$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         = str_starts_with( $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 {
		$this->simplyin_session->clear();

		$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;
	}

}
