<?php
declare( strict_types=1 );

namespace FiboFilters\Util;

/**
 * Class Helpers
 */
class Helpers {
	/**
	 * Checking the current code is run by the object of the given class
	 *
	 * @param string $class_name Class name.
	 * @param int    $backtrace_limit The number of stack frames that is tested backwards.
	 */
	public static function is_running_inside_class( string $class_name = '', int $backtrace_limit = 10 ): bool {
		if ( empty( $class_name ) ) {
			return false;
		}
		if ( intval( $backtrace_limit ) <= 0 ) {
			$backtrace_limit = 10;
		}

		$result    = false;
		$backtrace = self::debug_backtrace( 0, $backtrace_limit );
		if ( ! empty( $backtrace ) ) {
			foreach ( $backtrace as $item ) {
				if ( isset( $item['class'] ) && $item['class'] === $class_name ) {
					$result = true;
					break;
				}
			}
		}

		return $result;
	}

	private static function debug_backtrace( int $options, int $limit ): array {
		return debug_backtrace( $options, $limit ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_debug_backtrace
	}

	/**
	 * Check if language code has one of the following format:
	 * aa, AA, aaa, aa-aa, aa_AA, AA_AA
	 *
	 * @param string $lang Language code.
	 */
	public static function is_language_code( string $lang ): bool {
		return ! empty( $lang ) && is_string( $lang ) && (bool) preg_match( '/^([a-zA-Z]{2,10})$|^([a-zA-Z]{2}[-_][a-zA-Z]{2,4})$/', $lang );
	}

	/**
	 * Get link to the deep place in the WordPress customizer
	 *
	 * @param string $panel Panel name.
	 * @param string $section Section name.
	 */
	public static function get_customizer_link( string $panel = '', string $section = '' ): string {

		$slug = 'customize.php';

		if ( ! empty( $panel ) ) {
			$slug .= '?autofocus[panel]=' . $panel;
		} elseif ( ! empty( $section ) ) {
			$slug .= '?autofocus[section]=' . $section;
		}

		return admin_url( $slug );
	}

	public static function get_hours() {
		$hours   = [];
		$cycle12 = get_option( 'time_format' ) === 'H:i' ? false : true;

		for ( $i = 0; $i < 24; $i++ ) {
			$label = $cycle12 ? $i . ':00 am' : $i . ':00';

			if ( $cycle12 && $i === 0 ) {
				$label = 12 . ':00 am';
			}

			if ( $cycle12 && $i > 11 ) {
				if ( $i === 12 ) {
					$label = 12 . ':00 pm';
				} else {
					$label = $i - 12 . ':00 pm';
				}
			}
			$hours[ $i ] = $label;
		}

		return $hours;
	}

	/**
	 * Get Basic Auth reader
	 */
	public static function get_basic_auth_header(): string {
		$authorization = '';
		if ( defined( 'FIBOFILTERS_BA_USERNAME' ) && defined( 'FIBOFILTERS_BA_PASSWORD' ) ) {
			// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
			$authorization = 'Basic ' . base64_encode( wp_unslash( FIBOFILTERS_BA_USERNAME ) . ':' . wp_unslash( FIBOFILTERS_BA_PASSWORD ) );
		} elseif ( isset( $_SERVER['PHP_AUTH_USER'] ) && isset( $_SERVER['PHP_AUTH_PW'] ) ) {
			// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
			$authorization = 'Basic ' . base64_encode( wp_unslash( $_SERVER['PHP_AUTH_USER'] ) . ':' . wp_unslash( $_SERVER['PHP_AUTH_PW'] ) );
		}

		return $authorization;
	}

	/**
	 * Generates a short cache hash based on the MD5 of the input value.
	 *
	 * For arrays:
	 *  - Recursively sorts keys to ensure consistent ordering,
	 *  - Encodes to JSON (JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
	 *    falling back to serialize() on failure.
	 *
	 * For strings:
	 *  - Uses the string directly.
	 *
	 * Only the first $length characters of the MD5 hash are returned.
	 * Collisions are unlikely for small data sets.
	 *
	 * @param array|string $value  The input value to hash.
	 * @param int          $length The length of the returned hash. Default 20.
	 *
	 * @return string             The truncated MD5 hash of length $length.
	 */
	public static function generate_cache_hash( $value, int $length = 20 ): string {
		if ( is_array( $value ) ) {
			$value = wp_json_encode( self::normalize_array( $value ) );
		} else {
			$value = (string) $value;
		}

		return substr( md5( $value ), 0, $length );
	}

	/**
	 * Recursively sorts the keys of an array to produce a deterministic output.
	 *
	 * @param array $arr Array.
	 * @return array
	 */
	public static function normalize_array( array $arr ): array {
		ksort( $arr );
		foreach ( $arr as $key => $val ) {
			if ( is_array( $val ) ) {
				$arr[ $key ] = self::normalize_array( $val );
			}
		}
		return $arr;
	}
}
