<?php
/**
 * Debugging functions.
 *
 * @package northmill-online-shared
 *
 * @noinspection PhpDocMissingThrowsInspection The exception is thrown in the set_error_handler.
 */

declare(strict_types=1);

namespace Northmill_Online_Shared\Modules;

use ErrorException;
use Northmill_Online_Shared\Abstract_Shared_Base;
use Northmill_Online_Shared\Helpers\General_Helpers;

defined( 'ABSPATH' ) || exit;

/**
 * Debug class.
 */
class Debug_Module extends Abstract_Module {

	public const CONSTANT_NAME  = 'NM_DEBUG';
	public const TRANSIENT_NAME = 'northmill_online_shared_debug';
	public const QUERY_VAR_NAME = 'nm-debug';


	/**
	 * Debug_Module constructor.
	 *
	 * @param Abstract_Shared_Base $shared The shared instance.
	 *
	 * @noinspection PhpUnhandledExceptionInspection The exception is thrown in the set_error_handler.
	 */
	public function __construct( Abstract_Shared_Base $shared ) {
		parent::__construct( $shared );

		// Exception to the rule of not doing anything in a module constructor.
		$this->maybe_throw_exception_on_warning();
	}

	/**
	 * Setup method.
	 *
	 * @return void
	 */
	public function setup(): void {
		add_action( 'query_vars', array( $this, 'add_query_var' ) );
	}

	/**
	 * Maybe crash on warnings, if the constant NM_SHARED_THROW_EXCEPTION_ON_WARNING is defined and true.
	 * This is useful for debugging,
	 * as it will throw an exception on warnings helping to identify the source with stack trace.
	 *
	 * @return void
	 * @noinspection PhpUnhandledExceptionInspection
	 */
	private function maybe_throw_exception_on_warning(): void {
		if (
			defined( 'NM_SHARED_THROW_EXCEPTION_ON_WARNING' ) &&
			constant( 'NM_SHARED_THROW_EXCEPTION_ON_WARNING' )
		) {
			// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_set_error_handler
			set_error_handler(
				function ( int $err_no, string $err_str, string $err_file, int $err_line ) {
					// phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
					throw new ErrorException( $err_str, 0, $err_no, $err_file, $err_line );
				}
			);
		}
	}

	/**
	 * Check if debug mode is enabled.
	 *
	 * @return bool
	 * @noinspection PhpUnused Used by packages loading this composer package.
	 */
	public function is_on(): bool {

		if (
			wp_get_environment_type() === 'local' ||
			true === General_Helpers::get_constant( self::CONSTANT_NAME )
		) {
			return true;
		} elseif ( self::check_cached_debug_status() ) {
			return true;
		} else {
			return false;
		}
	}

	/**
	 * Add nm-debug as allowed query var.
	 *
	 * @param array $vars Query vars.
	 */
	public function add_query_var( array $vars ): array {
		$vars[] = self::QUERY_VAR_NAME;
		return $vars;
	}

	/**
	 * Check if debug mode is cached to be enabled.
	 *
	 * @return bool
	 *
	 * @phpcs:disable WordPress.Security.NonceVerification.Recommended
	 */
	private function check_cached_debug_status(): bool {
		if ( current_user_can( 'manage_options' ) ) {
			if (
				get_transient( self::TRANSIENT_NAME ) ||
				(
					isset( $_GET[ self::QUERY_VAR_NAME ] ) &&
					'on' === $_GET[ self::QUERY_VAR_NAME ]
				)
			) {
				set_transient( self::TRANSIENT_NAME, 'on', MINUTE_IN_SECONDS * 5 ); // Cache for 5 minutes.
				return true;
			}
		}

		return false;
	}
	// phpcs:enable WordPress.Security.NonceVerification.Recommended


	/**
	 * Display the hooks registered for a given hook name.
	 *
	 * This method iterates over the global `$wp_filter` variable
	 * to find the hooks registered for the specified hook name
	 * and displays them with their priorities and associated functions/methods.
	 *
	 * @param string $hook_name The name of the hook.
	 *
	 * @return void
	 * @noinspection PhpUnused Used by packages loading this composer package.
	 */
	public function display_hooks( string $hook_name ): void {
		global $wp_filter;
		if ( isset( $wp_filter[ $hook_name ] ) ) {
			$hooks = $wp_filter[ $hook_name ]->callbacks;
		} else {
			$hooks = null;
		}

		$this->shared->template_loader->get_admin_template_part(
			'status/debug-wp-hooks',
			array(
				'hook_name' => $hook_name,
				'hooks'     => $hooks,
			),
			false
		);
	}
}
