<?php

if ( ! defined( 'ABSPATH' ) ) {
	exit; // Exit if accessed directly....
}

require_once 'utils.php';
require_once 'api.php';

$ADDED_SHIPPING_METHODS = array();
$SHIPPING_METHODS_AND_CLASSES = array();
$UNDEFINED_SHIPPING_PRICE = -1.00;


add_action('admin_menu', function () {
    add_action("admin_enqueue_scripts", function ($hook){
        if($hook !== "woocommerce_page_wc-settings"){
            return;
        }
		if (! empty(nele_get_db_errors())) {
			foreach (nele_get_db_errors() as $error) {
				WC_Admin_Notices::add_notice($error);
			}
		}
//        if (nele_get_shipping_method()->is_disabled()) {
//            return;
//        }
//        global $SHIPPING_METHODS_AND_CLASSES;
        if ( ! wp_script_is( 'jquery', 'done' ) ) {
            wp_enqueue_script( 'jquery' );
        }
        if ( ! wp_script_is( 'jquery-ui-core', 'done' ) ) {
            wp_enqueue_script( 'jquery-ui-core' );
        }
        if ( ! wp_script_is( 'jquery-ui-tabs', 'done' ) ) {
            wp_enqueue_script( 'jquery-ui-tabs' );
        }
	    $inline_js = "const IS_WP_DEBUG = `" . WP_DEBUG . "`;";
	    wp_add_inline_script('jquery', $inline_js);
        wp_enqueue_style('nele-spinner-css', NELE_PLUGIN_URL . 'css/spinner.css', array(), '1.0.7');
	    wp_enqueue_style('nele-admin-css', NELE_PLUGIN_URL . 'css/shipping_method.css', array(), '1.0.4');
        wp_enqueue_script('nele-spinner-js', NELE_PLUGIN_URL . 'js/spinner.js', array('jquery'), '1.0.9', true);
        wp_enqueue_script('nele-admin-js', NELE_PLUGIN_URL . 'js/shipping_method.js', array('jquery-ui-tabs'), '1.0.26', true);
	    nele_shipping_method_init();
		$inline_js = "const neleShippingMethods = " . json_encode(get_option("shipping_methods_and_classes", array())) . ";";
        wp_add_inline_script('nele-admin-js', $inline_js);
    });
});

function nele_shipping_method_init() {
	try {
		nele_shipping_method_init_unsafe();
	} catch (Exception $e) {
		write_log("nele_shipping_method_init_unsafe error: ".$e->getMessage());
	}
}

function nele_shipping_method_init_unsafe() {
	if (! nele_db_is_empty()) {  # this is needed for the upgrade from older clients
		if (empty(get_option("nele_setup_completed", "") && ! get_transient("nele_upgrade_running"))) {
			update_option("nele_setup_completed", true);
			delete_transient("nele_upgrade_running");
		}
	}
    // this is actual shipping method shown at the checkout page
	if ( ! class_exists( 'WC_NextLevel_PerCountry_Shipping_Method' ) ) {
	    global $ADDED_SHIPPING_METHODS;
		if (! isset($ADDED_SHIPPING_METHODS)) {
			$ADDED_SHIPPING_METHODS   = array();
		}
		class WC_NextLevel_PerCountry_Shipping_Method extends WC_Shipping_Method {
			public function __construct( $id, $title ) {
				parent::__construct();
				$this->id                 = $id;
				$this->method_title       = $title;
				$this->method_description = __( 'Originated from the NextLevel Delivery shipping method', 'nextlevel_delivery' );
				$this->title              = $title;
				$this->enabled            = $this->get_option( 'enabled', 'yes' );
				$this->init();
			}

			function init() {
				$this->init_form_fields();
				$this->init_settings();
				add_action( 'woocommerce_update_options_shipping_' . $this->id, array(
					$this,
					'process_admin_options'
				) );
			}

			public function init_form_fields() {
				$this->form_fields = array(
					'enabled' => array(
						'title'   => __( 'Enable/Disable', 'nextlevel_delivery' ),
						'type'    => 'checkbox',
						'label'   => __( 'Enable NextLevel Delivery', 'nextlevel_delivery' ),
						'default' => 'yes'
					)
				);
			}

			public function calculate_shipping( $package = array() ) {
				$rate = array(
					'id'    => $this->id,
					'label' => $this->title,
					'cost'  => 0
				);
				$this->add_rate( $rate );
			}
		}
	}
    // this is a generic shipping method - used to store all settings
	if ( ! class_exists( 'WC_NextLevel_Shipping_Method' ) ) {
		class WC_NextLevel_Shipping_Method extends WC_Shipping_Method {
			public string $product_weight;
			public string $email_text;
			public string $send_email;
			public string $auto_gen_waybill;
			public string $geo_zone;
			public ApiCredentials $creds;
			public string $sender_id;
			public string $app_secret;
			public string $app_id;
            public string $is_include_vat;

			public function __construct() {
				parent::__construct();
				$this->id                 = 'nele_shipping_method';
				$this->method_title       = __( 'NextLevel Delivery', 'nextlevel_delivery' );
				$this->method_description = __( 'NextLevel Delivery shipping method', 'nextlevel_delivery' );
				$this->title              = __( 'NextLevel Delivery', 'nextlevel_delivery' );
				$this->app_id             = $this->get_option( 'app_id' );
				$this->app_secret         = $this->get_option( 'app_secret' );
				$this->sender_id          = $this->get_option( 'sender_id' );
				$this->creds              = new ApiCredentials( $this->app_id, $this->app_secret, $this->sender_id );
				$this->enabled            = $this->get_option( 'enabled', 'yes' );
				$this->geo_zone           = $this->get_option( 'geo_zone', '0' );
				$this->send_email         = $this->get_option( 'send_email', 'yes' );
				$this->auto_gen_waybill   = $this->get_option( 'auto_gen_waybill', 'yes' );
                $this->is_include_vat     = $this->get_option( 'is_include_vat' , 'no');
				$this->email_text         = $this->get_option( 'email_text', 'Text for the email about the way-bill generation for {shipment_number}' );
				$this->product_weight     = $this->get_option( 'product_weight', 'Default weight to be used if not explicitly set' );

				$this->init();

				if ( get_option( 'nele_plugin_activated' ) !== '1' ) {  #  && $this->get_option( 'enabled') === 'yes'
					update_option( 'nele_plugin_activated', '1' );
					$countries = array();
					foreach ( $this->enabled_for_countries() as $country_obj ) {
						$countries[] = $country_obj->code;
					}
					nele_api_init_install( $this->creds, $countries );
				}
			}

			public function init() {
				$this->init_form_fields();
				$this->init_settings();

				add_action( 'woocommerce_update_options_shipping_' . $this->id, array(
					$this,
					'process_admin_options'
				) );
			}

			public function init_form_fields() {
				$fields = array(
					'enabled'        => array(
						'title'   => __( 'Enable/Disable', 'nextlevel_delivery' ),
						'type'    => 'checkbox',
						'label'   => __( 'Enable NextLevel Delivery', 'nextlevel_delivery' ),
						'default' => 'yes'
					),
					'app_id'         => array(
						'title'       => __( 'App ID', 'nextlevel_delivery' ),
						'type'        => 'text',
						'description' => __( 'API connection details - App ID', 'nextlevel_delivery' )
					),
					'app_secret'     => array(
						'title'       => __( 'App Secret', 'nextlevel_delivery' ),
						'type'        => 'text',
						'description' => __( 'API connection details - App Secret', 'nextlevel_delivery' )
					),
					'sender_id'      => array(
						'title'       => __( 'Sender ID', 'nextlevel_delivery' ),
						'type'        => 'text',
						'description' => __( 'API connection details - Sender ID', 'nextlevel_delivery' )
					),
					'refresh_button' => array(
						'title' => __( 'Refresh Table Data', 'nextlevel_delivery' ),
						'type'  => 'button',
						'class' => 'button-secondary'
					),
//				'log_info' => array(
//					'title'       => __( 'Log Info', 'nextlevel_delivery' ),
//					'type'        => 'textarea', // Use 'textarea' type for a larger text field
//					'description' => __( 'Real time plugin logs', 'nextlevel_delivery' ),
//				)
				);

			if (/*$this->is_disabled()*/ ! get_option("nele_setup_completed", false)) {
				# no sense proceeding further till the DB is not populated with the plugin's data
				$this->form_fields = $fields;
				return;
			}

				$geo_zones[0] = 'All regions';
				foreach ( nele_db_countries() as $country ) {
					$geo_zones[ $country->code ] = $country->name;
				}

				$fields2      = array(
					'section_general'  => array(
						'title'       => __( 'General Settings', 'nextlevel_delivery' ),
						'type'        => 'title',
						'description' => '',
					),
					'show_state_field' => array(
						'title'       => __( 'Show the State field', 'nextlevel_delivery' ),
						'type'        => 'checkbox',
						'description' => __( 'Shows the State select field on the checkout', 'nextlevel_delivery' ),
						'default'     => 'no'
					),
					'product_weight'   => array(
						'title'       => __( 'Default product weight', 'nextlevel_delivery' ),
						'type'        => 'text',
						'description' => __( 'To be used when value is not explicitly set on product', 'nextlevel_delivery' ),
						'default'     => '1'
					),
					'auto_gen_waybill' => array(
						'title'       => __( 'Automatic waybill generation', 'nextlevel_delivery' ),
						'type'        => 'checkbox',
						'description' => __( 'Generate the waybill on the order creation', 'nextlevel_delivery' ),
						'default'     => 'no'
					),
                    'is_include_vat' => array(
                        'title'   => __( 'Force include VAT', 'nextlevel_delivery' ),
                        'type'    => 'checkbox',
                        'label'   => __( 'Always show API shipping price including VAT', 'nextlevel_delivery' ),
                        'default' => 'no'
                    ),
					'geo_zone'         => array(
						'title'       => __( 'Geo Zone', 'nextlevel_delivery' ),
						'type'        => 'select',
						'class'       => 'wc-enhanced-select',
						'options'     => $geo_zones,
						'description' => __( 'Zones to serve', 'nextlevel_delivery' ),
					),
//				'initial_order_status' => array(
//					'title' => __( 'Initial order status', 'nextlevel_delivery' ),
//					'type' => 'select',
//					'options' => wc_get_order_statuses(),
//					'description' => __('Status after the way-bill is generated', 'nextlevel_delivery'),
//				),
					'send_email'       => array(
						'title'       => __( 'Send email?', 'nextlevel_delivery' ),
						'type'        => 'checkbox',
						'description' => __( 'Send email on order status change', 'nextlevel_delivery' ),
						'default'     => 'yes'
					),
					'email_text'       => array(
						'title'       => __( 'Email text', 'nextlevel_delivery' ),
						'type'        => 'textarea',
						'description' => __( 'Email text template', 'nextlevel_delivery' ),
						'default'     => 'Text for the email about the way-bill generation for {shipment_number}'
					)
				);
				$payer_opts   = array(
					'sender'   => __( 'sender', 'nextlevel_delivery' ),
					'receiver' => __( 'receiver', 'nextlevel_delivery' )
				);
				$service_opts = array(
					''     => __( '-', 'nextlevel_delivery' ),
					'OPEN' => __( 'Open', 'nextlevel_delivery' ),
					'TEST' => __( 'Test', 'nextlevel_delivery' )
				);

				global $DELIVERY_OPTS, $ADDED_SHIPPING_METHODS, $SHIPPING_METHODS_AND_CLASSES;
				$shipping_methods_fields = array();
				foreach ( $ADDED_SHIPPING_METHODS as $id => $title ) {
					$class                                        = $id . ' nele_general_opt';
					$SHIPPING_METHODS_AND_CLASSES[ $id ]['class'] = explode( " ", $class );
					$fields3                                      = array(
						nele_shipping_option_key( "section", $id )        => array(
							'title'       => $title . " " . __( "settings", 'nextlevel_delivery' ),
							'type'        => 'title',
							'description' => '',
							'class'       => $class
						),
						nele_shipping_option_key( "order", $id )          => array(
							'title'       => __( 'Order', 'nextlevel_delivery' ),
							'type'        => 'text',
							'default'     => '0',
							'description' => __( 'Display order', 'nextlevel_delivery' ),
							'class'       => $class
						),
						nele_shipping_option_key( "max_weight", $id )     => array(
							'title'       => __( 'Max weight', 'nextlevel_delivery' ),
							'type'        => 'text',
							'default'     => '0',
							'description' => __( 'Max weight allowed for this delivery method', 'nextlevel_delivery' ),
							'class'       => $class
						),
						nele_shipping_option_key( "payer", $id )          => array(
							'title'       => __( 'Payer', 'nextlevel_delivery' ),
							'type'        => 'select',
							'options'     => $payer_opts,
							'description' => __( 'Who pays for the delivery', 'nextlevel_delivery' ),
							'class'       => $class
						),
						nele_shipping_option_key( "include_price", $id )  => array(
							'title'   => __( 'Include delivery price in payment', 'nextlevel_delivery' ),
							'type'    => 'checkbox',
							'default' => 'no',
							'class'   => $class
						),
						nele_shipping_option_key( "declared_value", $id ) => array(
							'title'       => __( 'Declared value', 'nextlevel_delivery' ),
							'type'        => 'checkbox',
							'default'     => 'no',
							'description' => __( 'Declared value', 'nextlevel_delivery' ),
							'class'       => $class
						),
						nele_shipping_option_key( "service", $id )        => array(
							'title'       => __( 'Service before delivery', 'nextlevel_delivery' ),
							'type'        => 'select',
							'options'     => $service_opts,
							'default'     => '',
							'description' => __( 'Service before delivery', 'nextlevel_delivery' ),
							'class'       => $class
						),
						nele_shipping_option_key( "fragile", $id )        => array(
							'title'   => __( 'Is fragile?', 'nextlevel_delivery' ),
							'type'    => 'checkbox',
							'default' => 'no',
							'class'   => $class
						)
					);
					$delivery_fields                              = array();
					foreach ( $DELIVERY_OPTS as $delivery_opt_key => $delivery_opt_val ) {
						if ( ! isset( $SHIPPING_METHODS_AND_CLASSES[ $id ]['opts'] ) ) {
							$SHIPPING_METHODS_AND_CLASSES[ $id ]['opts'] = array();
						}
						$class = $id . ' nele_' . $delivery_opt_key;
						if ( ! isset( $SHIPPING_METHODS_AND_CLASSES[ $id ]['opts'][ $delivery_opt_key ] ) ) {
							$SHIPPING_METHODS_AND_CLASSES[ $id ]['opts'][ $delivery_opt_key ]          = array();
							$SHIPPING_METHODS_AND_CLASSES[ $id ]['opts'][ $delivery_opt_key ]['title'] = $delivery_opt_val;
							$SHIPPING_METHODS_AND_CLASSES[ $id ]['opts'][ $delivery_opt_key ]['class'] = explode( " ", $class );
						}
						$delivery_fields_add = array(
							nele_shipping_option_key( "section", $id, $delivery_opt_key )                   => array(
								'title'       => $delivery_opt_val,
								'type'        => 'title',
								'description' => '',
								'class'       => $class
							),
							nele_shipping_option_key( "auto_price_calc", $id, $delivery_opt_key )           => array(
								'title'       => __( 'API price calculation?', 'nextlevel_delivery' ),
								'type'        => 'checkbox',
								'default'     => 'yes',
								'description' => __( 'Ask NextLevel for the delivery price', 'nextlevel_delivery' ),
								'class'       => $class
							),
							nele_shipping_option_key( "fixed_price_delivery", $id, $delivery_opt_key )      => array(
								'title'       => __( 'Fixed price delivery', 'nextlevel_delivery' ),
								'type'        => 'text',
								'description' => __( 'Fixed delivery price (if no API price calculation is enabled)', 'nextlevel_delivery' ),
								'class'       => $class
							),
							nele_shipping_option_key( "free_delivery_from", $id, $delivery_opt_key )        => array(
								'title'       => __( 'Free delivery from', 'nextlevel_delivery' ),
								'type'        => 'text',
								'description' => __( 'Minimum order amount to have free delivery', 'nextlevel_delivery' ),
								'class'       => $class
							),
							nele_shipping_option_key( "free_delivery_till_weight", $id, $delivery_opt_key ) => array(
								'title'       => __( 'Free delivery till weight', 'nextlevel_delivery' ),
								'type'        => 'text',
								'description' => __( 'Order weight should be <= to be applicable for the free delivery', 'nextlevel_delivery' ),
								'class'       => $class
							),
							nele_shipping_option_key( "subject_bg", $id, $delivery_opt_key )                => array(
								'title'       => __( 'Subject', 'nextlevel_delivery' ),
								'type'        => 'text',
								'default'     => $delivery_opt_val,
								'description' => __( 'Subject', 'nextlevel_delivery' ),
								'class'       => $class
							),
							nele_shipping_option_key( "description_bg", $id, $delivery_opt_key )            => array(
								'title'       => __( 'Description', 'nextlevel_delivery' ),
								'type'        => 'textarea',
								'default'     => $delivery_opt_val,
								'description' => __( 'Description', 'nextlevel_delivery' ),
								'class'       => $class
							)
						);
						$delivery_fields     = array_merge( $delivery_fields, $delivery_fields_add );
					}
					$shipping_methods_fields = array_merge( $shipping_methods_fields, $fields3, $delivery_fields );
				}
				$this->form_fields = array_merge( $fields, $fields2, $shipping_methods_fields );
			}

			public function calculate_shipping( $package = array() ) {
				// This is intentionally left blank to prevent this method from appearing at the checkout page
			}

			function enabled_for_countries(): array {
				return nele_db_countries( empty( $this->geo_zone ) || $this->geo_zone == "0" ? null : $this->geo_zone );
			}

			function is_disabled(): bool {
				return empty( $this->app_id ) || empty( $this->app_secret ) || empty( $this->sender_id ) || $this->enabled === 'no';
			}
		}
	}

	global $SHIPPING_METHODS_AND_CLASSES;
	if (nele_need_shipping_opt_refresh() || empty(get_option("shipping_methods_and_classes", array()))) {
		$parent_obj = new WC_NextLevel_Shipping_Method();
		$parent_obj->init_form_fields();
		nele_add_shipping_method(array(), $parent_obj);  # update only when needed -> avoid dead loop
		update_option("shipping_methods_and_classes", $SHIPPING_METHODS_AND_CLASSES);
	}
}
add_action( 'woocommerce_shipping_init', 'nele_shipping_method_init' );


function nele_need_shipping_opt_refresh(): bool {
	global $SHIPPING_METHODS_AND_CLASSES;
	$result = false;
	$result |= ! $SHIPPING_METHODS_AND_CLASSES;
	foreach ($SHIPPING_METHODS_AND_CLASSES as $value) {
		$result |= ! $value;
		if ($value) {
			$result |= ! array_key_exists("class", $value);
		}
	}
	return $result;
}

function nele_plugin_deactivation_function() {
	delete_option('nele_plugin_activated');
}
register_deactivation_hook(__FILE__, 'nele_plugin_deactivation_function');

add_filter( 'woocommerce_shipping_methods', 'nele_add_shipping_method');
function nele_add_shipping_method($methods, $given_parent=null) {
//	$methods['nele_shipping_method'] = 'WC_NextLevel_Shipping_Method';
	$parent_obj = $given_parent?? nele_parent_obj();
//	if ($parent_obj->is_disabled()) {
//		return $methods;
//	}
    global $ADDED_SHIPPING_METHODS, $SHIPPING_METHODS_AND_CLASSES;
	# following sanitization rules are needed since we use eval() down the code
	foreach ($parent_obj->enabled_for_countries() as $country) {
		$country_code = sanitize_text_field($country->code);
		foreach (nele_db_couriers($country->id) as $courier) {  // couriers are taken from offices
			$courier_str = sanitize_text_field($courier->subcontractor);
			$suffix = strtolower($country_code."_".$courier_str);
			$id = "nele_shipping_method_".$suffix;
            $title = "$courier_str ($country_code)";
            if (! isset($ADDED_SHIPPING_METHODS[$id])) {
                $ADDED_SHIPPING_METHODS[$id] = $title;
            }
            if (! isset($SHIPPING_METHODS_AND_CLASSES[$id])) {
                $SHIPPING_METHODS_AND_CLASSES[$id] = array("title" => $title);
            }
			# next is needed since we need shipping methods with constructors accepting 0 args
			$class_name = "WC_NextLevel_".$suffix;
	        $class_code = "if (!class_exists( '$class_name' )) {class $class_name extends WC_NextLevel_PerCountry_Shipping_Method {
    public function __construct() {parent::__construct('$id', '$title');}}}";
	        eval($class_code);
			#  display custom shipping method only when setup is done
			if (get_option("nele_setup_completed", false)) {
//				$methods[$id] = $class_name;
				$methods[] = $class_name;
			}
            $SHIPPING_METHODS_AND_CLASSES[$id]["php_class"] = $class_name;
		}
	}
	if (is_array($SHIPPING_METHODS_AND_CLASSES)) {
		update_option("shipping_methods_defined", array_keys($SHIPPING_METHODS_AND_CLASSES));
	}
//	write_log("nele_add_shipping_method: shipping methods:");
//	write_log($methods);
	$methods[] = 'WC_NextLevel_Shipping_Method';
	return $methods;
}

add_filter( 'woocommerce_checkout_fields', 'nele_checkout_fields_trigger_refresh', 9999 );
function nele_checkout_fields_trigger_refresh( $fields ) {
    $fields['billing']['billing_country']['class'][] = 'update_totals_on_change';
    return $fields;
}

add_filter('woocommerce_package_rates', 'nele_filter_shipping_methods_by_country', 100);
function nele_filter_shipping_methods_by_country($rates): array {
	$shipping_country = WC()->customer->get_shipping_country();
	$cart_weight = nele_get_cart_items_weight();
	$shipping_methods_with_order = array();
	foreach ($rates as $rate_key => $rate) {
		// filter out only shipping methods added by the plugin
		$is_nele_shipping = str_contains($rate_key, "nele_shipping_method_");
		if ($is_nele_shipping && ! str_contains($rate->method_id, strtolower($shipping_country))) {
			unset($rates[$rate_key]);
			write_log("nele_filter_shipping_methods_by_country: unsetting rate $rate_key since not matching country ($shipping_country): " . $rate->method_id);
			continue;
		}
		$max_weight_allowed = floatval(nele_shipping_option_value("max_weight", $rate->method_id));
		write_log("nele_filter_shipping_methods_by_country: max_weight_allowed=$max_weight_allowed");
		if ($is_nele_shipping && $max_weight_allowed > 0 && $cart_weight > $max_weight_allowed) {
			unset($rates[$rate_key]);
			write_log("nele_filter_shipping_methods_by_country: unsetting rate $rate_key since weight is bigger that allowed $max_weight_allowed");
			continue;
		}

		$order_number = floatval(nele_shipping_option_value("order", $rate->method_id));
		write_log("nele_filter_shipping_methods_by_country: order_number=$order_number");
		$shipping_methods_with_order[$rate_key] = array(
			'order_number' => $order_number,
			'rate' => $rate
		);
	}
	usort($shipping_methods_with_order, function($a, $b) {
		return $a['order_number'] - $b['order_number'];
	});

	$new_rates = array();
	foreach ($shipping_methods_with_order as $rate_key => $shipping_method) {
		$new_rates[$rate_key] = $shipping_method['rate'];
	}
	return $new_rates;
}

function nele_parent_obj(): WC_NextLevel_Shipping_Method
{
    nele_shipping_method_init();
    return new WC_NextLevel_Shipping_Method();
}

function nele_cart_shipping_price_title($fee = 1): string {
	$label = floatval($fee) < PHP_FLOAT_EPSILON ? __('Free shipping', 'nextlevel_delivery') : __('Shipping', 'nextlevel_delivery');
	write_log("nele_cart_shipping_price_title: fee=$fee, label=$label");
    return $label;
}

function nele_add_cart_shipping_price($fee, $tax): void
{
    global $UNDEFINED_SHIPPING_PRICE;
    if (abs($fee - $UNDEFINED_SHIPPING_PRICE) <= PHP_FLOAT_EPSILON) {
        return;
    }
    WC()->cart->add_fee( nele_cart_shipping_price_title($fee), $fee );
	if (! nele_is_include_vat()) {
		WC()->cart->set_shipping_tax( $tax );
	}
	WC()->cart->set_shipping_total( $fee );
//	WC()->cart->calculate_fees();  # TODO calc
//	WC()->cart->calculate_totals();
    WC()->session->set('shipping_price', $fee);
}

function nele_remove_cart_shipping_price() {
    WC()->session->set('shipping_price', 0);
    $fees = WC()->cart->get_fees();
    foreach ($fees as $key => $fee) {
        if($fee->name === nele_cart_shipping_price_title() || $fee->name === nele_cart_shipping_price_title(0)) {
            unset($fees[$key]);
        }
    }
    WC()->cart->fees_api()->set_fees($fees);
}

function nele_calculate_shipping_price($shipping_method, $ship_to, $country_code, $postcode, $city, $city_name, $receiver_office_id, $weight, $subtotal, $is_machine, $include_vat): array {
    global $UNDEFINED_SHIPPING_PRICE, $nele_to_office_key;
    write_log("nele_calculate_shipping_price #1: shipping_method=$shipping_method, ship_to=$ship_to, country_code=$country_code, postcode=$postcode, city.id=$city, city_name=$city_name, receiver_office_id=$receiver_office_id, weight=$weight, subtotal=$subtotal, is_machine=$is_machine, include_vat=$include_vat");
    if (empty($shipping_method)) {
		write_log("nele_calculate_shipping_price: shipping method was not found");
        return [$UNDEFINED_SHIPPING_PRICE, 0];
    }
	if (empty($ship_to)) {
		write_log("nele_calculate_shipping_price: ship_to was not found");
		return [$UNDEFINED_SHIPPING_PRICE, 0];
	}
    $hardcoded_price = nele_get_default_shipping_price($shipping_method, $ship_to);
    write_log("nele_calculate_shipping_price: hardcoded_price=$hardcoded_price");
    if ($hardcoded_price != $UNDEFINED_SHIPPING_PRICE) {
        write_log("nele_calculate_shipping_price: setting the proposed price to $hardcoded_price");
        return [$hardcoded_price, 0];
    }
    if (empty($country_code)) {
        write_log("nele_calculate_shipping_price: country code was not found");
        return [$UNDEFINED_SHIPPING_PRICE, 0];
    }
    $country_obj = nele_db_countries($country_code);
    $fixed_price = floatval(nele_shipping_option_value("fixed_price_delivery", $shipping_method, $ship_to));
    $ship_to_office = ($ship_to === $nele_to_office_key);
	if (empty($country_obj) || empty($subtotal) || empty($weight) || ($ship_to_office && empty($receiver_office_id))) {
		write_log("nele_calculate_shipping_price: returning undefined price since country=$country_code, subtotal=$subtotal, weight=$weight or office=$receiver_office_id was not set");
		return [$UNDEFINED_SHIPPING_PRICE, 0];
	}
	if ($fixed_price > 0) {
		write_log("nele_calculate_shipping_price: returning fixed price $fixed_price");
		return [$fixed_price, 0];
	}
	$city_name = preg_replace('/\s*\([^)]*\)/', '', $city_name);
	if (! empty($city_name)) {
		$place = $city_name;
	} else if (! empty($city)) {
		$places = nele_db_cities_by_id($city);  # since there is no separate table for cities
		if (empty($places)) {
			write_log("nele_calculate_shipping_price: place by id was not found");
			return [$UNDEFINED_SHIPPING_PRICE, 0];
		}
		$place = $places[0]->name;
	} else {
		return [$fixed_price, 0];
	}
    $currency = $country_obj[0]->currency;
    $courier_name = nele_courier_by_shipping_method($shipping_method);
    write_log("nele_calculate_shipping_price #2: shipping_method=$shipping_method, country_code=$country_code, place=$place, postcode=$postcode, weight=$weight, subtotal=$subtotal, currency=$currency, courier=$courier_name, office_id=$receiver_office_id");
    if (empty($place)) {
        write_log("nele_calculate_shipping_price: Shipping price calculation is not possible: city is empty");
        return [$UNDEFINED_SHIPPING_PRICE, 0];
    }
	if (empty($postcode)) {
		write_log("nele_calculate_shipping_price: Shipping price calculation is not possible: postcode is empty");
		return [$UNDEFINED_SHIPPING_PRICE, 0];
	}
	if (empty($courier_name)) {
		write_log("nele_calculate_shipping_price: Shipping price calculation is not possible: courier is empty");
		return [$UNDEFINED_SHIPPING_PRICE, 0];
	}
    [$shipping_price, $tax] = nele_get_api_shipping_price($shipping_method, $country_code, $place, $postcode, $weight, $subtotal, $currency, preg_replace('/\s*\([^)]*\)/', '', $courier_name), $receiver_office_id, $is_machine, $include_vat);
    if (is_numeric($shipping_price)) {
		return [$shipping_price, $tax];
    }
	return [$UNDEFINED_SHIPPING_PRICE, 0];
}

function nele_has_free_delivery($shipping_method, $ship_to): bool {
	$price = WC()->cart->get_cart_contents_total() + WC()->cart->get_taxes_total();
	$free_delivery_from = nele_shipping_option_value("free_delivery_from", $shipping_method, $ship_to);
	$free_delivery_from_float = floatval($free_delivery_from);
	$weight = nele_get_cart_items_weight();
	$free_delivery_till_weight = nele_shipping_option_value("free_delivery_till_weight", $shipping_method, $ship_to);
	$free_delivery_till_weight_float = floatval($free_delivery_till_weight);
	write_log("nele_get_default_shipping_price: $shipping_method price=$price, weight=$weight, free_delivery_from=$free_delivery_from, free_delivery_from_float=$free_delivery_from_float, free_delivery_till_weight=$free_delivery_till_weight, free_delivery_till_weight_float=$free_delivery_till_weight_float");
	$hit_free_delivery = $free_delivery_from !== "" && $free_delivery_from_float > PHP_FLOAT_EPSILON && ($price - $free_delivery_from_float) > PHP_FLOAT_EPSILON;
	$has_weight_limit = $free_delivery_till_weight !== "" && $free_delivery_till_weight_float > PHP_FLOAT_EPSILON;
	$exceeding_weight_limit = ($weight - $free_delivery_till_weight_float) > PHP_FLOAT_EPSILON;
	write_log("nele_set_default_shipping_price: $shipping_method hit_free_delivery=$hit_free_delivery ($price > $free_delivery_from_float), has_weight_limit=$has_weight_limit, exceeding_weight_limit=$exceeding_weight_limit ($weight > $free_delivery_till_weight_float)");
	if ($hit_free_delivery && (! $has_weight_limit || ! $exceeding_weight_limit)) {
		write_log("nele_set_default_shipping_price: $shipping_method free delivery is reached -> shipping price will be set to 0");
		return True;
	}
	return False;
}

function nele_get_default_shipping_price($shipping_method = null, $ship_to = null): float {
    write_log("nele_get_default_shipping_price: ".$shipping_method.", ".$ship_to);
    global $UNDEFINED_SHIPPING_PRICE;
    if (! isset($shipping_method) || ! isset($ship_to)) {
        write_log("nele_set_default_shipping_price: shipping method or shipping to are not set -> shipping price will not be set");
        return $UNDEFINED_SHIPPING_PRICE;
    }
	if (nele_has_free_delivery($shipping_method, $ship_to)) {
		return 0;
	}
    $auto_price_calc = wc_string_to_bool(nele_shipping_option_value("auto_price_calc", $shipping_method, $ship_to));
    $fixed_price_delivery = floatval(nele_shipping_option_value("fixed_price_delivery", $shipping_method, $ship_to));
    if (! $auto_price_calc && $fixed_price_delivery > 0) {
        write_log("nele_set_default_shipping_price: setting fixed shipping price to $fixed_price_delivery");
        return $fixed_price_delivery;
    }
    return $UNDEFINED_SHIPPING_PRICE;
}

function nele_is_include_vat(): bool {
    return WC()->cart->display_prices_including_tax() || nele_parent_obj()->is_include_vat == 'yes';
}

function nele_get_api_shipping_price($id, $country_code, $place, $postcode, $weight, $price, $currency, $courier_name, $receiver_office_id, $is_machine, $include_vat): array {
    $creds = nele_parent_obj()->creds;
    $services = array('cod' => array('amount' => $price, 'currency' => $currency, 'processing_type' => 'CASH'));

    $include_shipping_price = wc_string_to_bool(nele_shipping_option_value("include_price", $id));
    $services['cod']['included_shipping_price'] = $include_shipping_price;

    $fragile = wc_string_to_bool(nele_shipping_option_value("fragile", $id));
    if ($fragile === true) {
        $services['fragile'] = true;
    }

    $declared_value = wc_string_to_bool(nele_shipping_option_value("declared_value", $id));
    if ($declared_value === true) {
        $services['dv']['amount'] = $price;
        $services['dv']['currency'] = $currency;
    }

    if ($is_machine === false) {
        $service = nele_shipping_option_value("service", $id);
        $services['obpd']['option'] = empty($service) ? 'OPEN' : strtoupper($service);

        $payer = nele_shipping_option_value("payer", $id);
        $services['obpd']['return_shipment_payer'] = empty($payer) ? 'SENDER' : strtoupper($payer);
    }

    $response = nele_api_calculate_price($creds, $country_code, $place, $postcode, $weight, $courier_name, $receiver_office_id, $services);
    if ($response->is_error) {
        write_log($response->msg);
	    WC_Admin_Notices::add_notice($response->msg);  // TODO does this work?
        global $UNDEFINED_SHIPPING_PRICE;
        return [$UNDEFINED_SHIPPING_PRICE, 0];
    }
	if ($include_vat) {
		write_log("nele_get_api_shipping_price: returning price with VAT");
		$delivery_price = $response->msg->total;
		$tax = 0;
	} else {
		write_log("nele_get_api_shipping_price: returning price without VAT");
		$delivery_price = $response->msg->subtotal;
		$tax = $response->msg->vat;
	}
    write_log("nele_get_api_shipping_price: price before conversion: $delivery_price");
    $delivery_price  = $delivery_price / nele_get_api_exchange_rate($currency);
    write_log("nele_get_api_shipping_price: price after conversion: $delivery_price");
	$tax = $tax / nele_get_api_exchange_rate($currency);
    return [$delivery_price, $tax];
}

function nele_get_api_exchange_rate($currency) {
    $woo_currency = get_woocommerce_currency();
    if ($woo_currency === 'BGN') {
        return 1;
    }
	$creds = nele_parent_obj()->creds;
    $currency_key = 'nele_currency_rate_'.$woo_currency;
    if (! get_transient($currency_key)) {
        set_transient($currency_key, nele_api_exchange_rate($creds, $currency), 60*60*24);
    }
    $rate = get_transient($currency_key);
    write_log("nele_get_api_exchange_rate: currency: $woo_currency, exchange rate (to BGN): $rate");
    return $rate;
}

function nele_get_cart_items_weight(): float {
    $cart_items = WC()->cart->get_cart();
    $total_weight = 0;
    $parent_obj = nele_parent_obj();
    $default_weight = floatval($parent_obj->get_option("product_weight", "1"));
    foreach ( $cart_items as $_ => $cart_item ) {
        $product = $cart_item['data'];
        $quantity = $cart_item['quantity'];
        $product_weight = floatval($product->get_weight());
        if ($product_weight < PHP_FLOAT_EPSILON) {
            $product_weight = $default_weight;
        }
        $total_weight += $product_weight * $quantity;
    }
    return $total_weight;
}

add_action('woocommerce_before_calculate_totals', 'nele_set_cart_shipping_price');
add_action('woocommerce_cart_calculate_fees', 'nele_set_cart_shipping_price', 10);
function nele_set_cart_shipping_price() {
    if (is_admin() && !defined('DOING_AJAX')) {
        return;
    }
	$shipping_method = WC()->session->get('_csm');
	if (! isset($shipping_method)) {
		return;
	}
	if (! str_contains($shipping_method, "nele_shipping_method")) {
		// do the price calculation only for the methods owned by the plugin
		return;
	}
    nele_remove_cart_shipping_price(); // in case we don't have/need it
    $ship_to = WC()->session->get('_ship_to');
    $country_code = WC()->session->get('_country');
    $postcode = WC()->session->get('_postcode');
    $city = WC()->session->get('_city');
    $city_name = WC()->session->get('_city_name');
    $office = WC()->session->get('_office');
    $is_machine = false;
	write_log("nele_set_cart_shipping_price: shipping_method=$shipping_method, ship_to=$ship_to, country_code=$country_code, postcode=$postcode, city.id=$city, city=$city_name, office=$office, is_machine=$is_machine");
	global $nele_to_office_key;
	if ($ship_to === $nele_to_office_key) {
        $offices = nele_db_offices_by_id($office);
        if ($offices) {
            $is_machine = $offices[0]->is_machine;
        }
    }
	$include_vat = nele_is_include_vat();
    $subtotal = WC()->cart->get_cart_contents_total() + WC()->cart->get_taxes_total() - WC()->cart->get_shipping_total(); // this way the VAT is included too
    $weight = nele_get_cart_items_weight();
    [$fee, $tax] = nele_calculate_shipping_price($shipping_method, $ship_to, $country_code, $postcode, $city, $city_name, $office, $weight, $subtotal, $is_machine, $include_vat);
    nele_add_cart_shipping_price( $fee, $tax );  // add only in case we have it
}

