<?php
/**
 * General helper functions.
 *
 * @package northmill-online-shared
 */

declare(strict_types=1);

namespace Northmill_Online_Shared\Helpers;

/**
 * General helper functions.
 */
class General_Helpers {
	/**
	 * Ensures that a string has a trailing slash.
	 *
	 * @param string $current_string The string.
	 *
	 * @return string
	 */
	public static function ensure_trailing_slash( string $current_string ): string {
		return self::ensure_trailing_symbol( $current_string, '/' );
	}

	/**
	 * Ensures that the given string has the specified symbol at the end.
	 *
	 * @param string $current_string The string to check and modify.
	 * @param string $symbol         The symbol to ensure at the end of the string.
	 *
	 * @return string The modified string with the trailing symbol.
	 */
	public static function ensure_trailing_symbol( string $current_string, string $symbol ): string {
		return rtrim( $current_string, $symbol ) . $symbol;
	}

	/**
	 * Format a number.
	 *
	 * @param float|string $number The number to format.
	 * @param mixed        $decimal_precision Int of decimal places to round to, blank to use woocommerce_price_num_decimals, or false to avoid all rounding.
	 * @param bool         $trim_zeroes Whether to trim trailing zeroes.
	 *
	 * @return string The formatted number.
	 */
	public static function round_and_format_number(
		float|string $number,
		mixed $decimal_precision = 2,
		bool $trim_zeroes = false
	): string {
		return wc_format_decimal( $number, $decimal_precision, $trim_zeroes );
	}

	/**
	 * Truncate string to a certain length.
	 * Based on https://github.com/delight-im/PHP-Str/blob/8fd0c608d5496d43adaa899642c1cce047e076dc/src/Str.php#L599
	 *
	 * @param string $input_string String to truncate.
	 * @param int    $max_length   Maximum length of the string.
	 * @param string $ellipsis     Ellipsis to append to the string.
	 * @param bool   $safe         Whether to truncate at a word boundary.
	 *
	 * @return string
	 * @noinspection PhpUnused Used by plugins loading this composer package.
	 */
	public static function truncate_string(
		string $input_string,
		int $max_length = 30,
		string $ellipsis = '...',
		bool $safe = true
	): string {
		// If the string doesn't need truncating.
		if ( mb_strlen( $input_string ) <= $max_length ) {
			return $input_string;

		} else { // If the string needs truncating.
			// Calculate the actual maximum length without the ellipsis.
			$max_length -= mb_strlen( $ellipsis );

			// Truncate the string to the desired length.
			$raw_string = mb_substr( $input_string, 0, $max_length );

			// If we don't want to break words.
			if ( $safe ) {
				// If the truncated string *does* end _within_ a word.
				if ( ! preg_match( '/\\W/u', mb_substr( $input_string, $max_length - 1, 2 ) ) ) {
					// If there is some word boundary before.
					if ( preg_match( '/.*\\W/u', $raw_string, $matches ) ) {
						// Truncate there instead.
						$raw_string = $matches[0];
					}
				}
			}

			// Return the correctly truncated string together with the ellipsis.
			return $raw_string . $ellipsis;
		}
	}

	/**
	 * Sanitize a phone number.
	 *
	 * @param string $phone_number The phone number to sanitize.
	 *
	 * @return string
	 */
	public static function sanitize_phone_number( string $phone_number ): string {
		return preg_replace( '/[^a-z+0-9-]/i', '', $phone_number );
	}

	/**
	 * Maybe define a constant.
	 *
	 * @param string $name The name of the constant.
	 * @param mixed  $value The value of the constant.
	 *
	 * @return bool True if the constant was defined, false if it was defined before.
	 */
	public static function maybe_define_constant( string $name, mixed $value ): bool {
		if ( ! defined( $name ) ) {
			define( $name, $value );
			return true;
		}

		return false;
	}

	/**
	 * Attempts to get the constant with the constant() function.
	 * If that also hasn't been set, attempt to get a value from filters.
	 *
	 * @param string $name The name of the constant.
	 * @param mixed  $default_value The default value to return if the constant doesn't exist.
	 *
	 * @return mixed null if the constant doesn't exist or the value of the constant.
	 */
	public static function get_constant( string $name, mixed $default_value = null ): mixed {

		if ( defined( $name ) ) {
			return constant( $name );
		}

		/**
		 * Filters the value of the constant.
		 *
		 * @param mixed $default_value The constant value to be filtered. The default is null.
		 * @param string $name The constant name.
		 */
		$value = apply_filters( 'northmill_online_shared_constant_default_value', $default_value, $name );
		return apply_filters( 'northmill_online_shared_constant_default_value_' . $name, $value, $name );
	}

	/**
	 * Retrieves the constant value for the given name and ensures it has a trailing slash.
	 *
	 * @param string $name          The name of the constant to retrieve.
	 * @param string $default_value The default value to return if the constant is not defined.
	 *
	 * @return string The constant value with a trailing slash.
	 */
	public static function get_constant_path( string $name, string $default_value = '' ): string {
		$value = self::get_constant( $name, $default_value );
		if ( ! is_string( $value ) ) {
			return $default_value;
		}

		return self::ensure_trailing_slash( $value );
	}

	/**
	 * Generates new guid
	 *
	 * @param bool $site_id_prefix Whether to prefix the guid with the site id.
	 *
	 * @return string
	 *
	 * @used Northmill Checkout
	 */
	public static function create_new_guid( bool $site_id_prefix = true ): string {
		$prefix = $site_id_prefix ? get_current_blog_id() . '-' : '';

		return $prefix . wp_generate_uuid4();
	}

	/**
	 * Map a semantic version to a numeric priority.
	 *
	 * @param string $version Semantic version (for example `1.2.3`).
	 *
	 * @return int Numeric priority for the hook.
	 */
	public static function version_to_priority( string $version ): int {
		// Split the version into components (major, minor, patch).
		$parts = explode( '.', $version );
		$major = isset( $parts[0] ) ? (int) $parts[0] : 0;
		$minor = isset( $parts[1] ) ? (int) $parts[1] : 0;
		$patch = isset( $parts[2] ) ? (int) $parts[2] : 0;

		// Convert to a single integer: major * 10,000 + minor * 100 + patch.
		return $major * 10000 + $minor * 100 + $patch;
	}
}
