
­­­­­­­­­­­­­­­­­­
<!DOCTYPE html>
<html>
<?php

namespace ElementPack\Includes\Debug;

if (!defined('ABSPATH')) {
	exit;
}

/**
 * Element Pack Debug Manager
 *
 * Tracks per-widget render time, memory usage, and DB queries.
 * Renders an admin-bar summary and a frontend debug panel.
 *
 * Activation (any one):
 *   - define('BDTEP_DEBUG', true) in wp-config.php
 *   - ?ep_debug=1 URL parameter (admin users only)
 */
class EP_Debug_Manager {

	private static $instance = null;

	private $widgets_data        = [];
	private $current_widget_stack = [];
	private $query_log           = [];

	private $page_start_time;
	private $page_start_memory;
	private $page_start_queries;

	/**
	 * Persist ?ep_debug=1/0 as a per-user transient so the flag carries
	 * from the Elementor editor page into the preview iframe (separate request).
	 * Call on admin_init to handle admin-context URLs.
	 */
	public static function persist_debug_flag() {
		if (!isset($_GET['ep_debug'])) {
			return;
		}
		if (!function_exists('is_user_logged_in') || !is_user_logged_in()) {
			return;
		}
		if (!current_user_can('manage_options')) {
			return;
		}

		$user_id = get_current_user_id();
		if ($_GET['ep_debug'] == '1') {
			set_transient('ep_debug_active_' . $user_id, '1', HOUR_IN_SECONDS);
		} elseif ($_GET['ep_debug'] == '0') {
			delete_transient('ep_debug_active_' . $user_id);
		}
	}

	/**
	 * Check whether the debug system should be active for the current
	 * page load.  Works on frontend pages and Elementor preview iframes.
	 */
	public static function should_enable() {
		if (!function_exists('is_user_logged_in') || !is_user_logged_in()) {
			return false;
		}
		if (!current_user_can('manage_options')) {
			return false;
		}

		// Persist the flag first (frontend context)
		self::persist_debug_flag();

		// Never activate inside regular wp-admin screens
		if (is_admin() && !wp_doing_ajax()) {
			return false;
		}

		if (defined('BDTEP_DEBUG') && BDTEP_DEBUG) {
			return true;
		}
		if (isset($_GET['ep_debug']) && $_GET['ep_debug'] == '1') {
			return true;
		}

		// Check transient set from the editor page or a previous request
		$user_id = get_current_user_id();
		if (get_transient('ep_debug_active_' . $user_id)) {
			return true;
		}

		return false;
	}

	/**
	 * Inject a small "DEBUG" badge and status into the Elementor editor
	 * toolbar. Called via elementor/editor/after_enqueue_scripts.
	 */
	public static function inject_editor_ui() {
		if (!function_exists('is_user_logged_in') || !is_user_logged_in()) {
			return;
		}
		if (!current_user_can('manage_options')) {
			return;
		}

		$user_id = get_current_user_id();
		$is_active = (defined('BDTEP_DEBUG') && BDTEP_DEBUG)
			|| (isset($_GET['ep_debug']) && $_GET['ep_debug'] == '1')
			|| get_transient('ep_debug_active_' . $user_id);

		if (!$is_active) {
			return;
		}

		$off_url = add_query_arg('ep_debug', '0');
		?>
		<style>
			.ep-debug-editor-badge{position:fixed;bottom:12px;left:12px;z-index:99999;background:#1e1e2e;color:#89b4fa;font:600 11px/1 'SF Mono',Consolas,monospace;padding:6px 12px;border-radius:6px;border:1px solid #45475a;box-shadow:0 2px 12px rgba(0,0,0,.3);display:flex;align-items:center;gap:8px;}
			.ep-debug-editor-badge span.dot{width:6px;height:6px;border-radius:50%;background:#a6e3a1;display:inline-block;}
			.ep-debug-editor-badge a{color:#f38ba8;text-decoration:none;font-size:10px;margin-left:4px;}
			.ep-debug-editor-badge a:hover{text-decoration:underline;}
		</style>
		<script>
		(function(){
			function addBadge(){
				var badge=document.createElement('div');
				badge.className='ep-debug-editor-badge';
				badge.innerHTML='<span class="dot"></span> EP Debug Active <a href="<?php echo esc_js(esc_url($off_url)); ?>" title="Disable debug mode">&times; off</a>';
				document.body.appendChild(badge);
			}
			if(document.readyState==='loading'){document.addEventListener('DOMContentLoaded',addBadge);}
			else{addBadge();}
		})();
		</script>
		<?php
	}

	public static function instance() {
		if (is_null(self::$instance)) {
			self::$instance = new self();
		}
		return self::$instance;
	}

	private function __construct() {
		$this->page_start_time   = defined('BDTEP_DEBUG_PAGE_START') ? BDTEP_DEBUG_PAGE_START : microtime(true);
		$this->page_start_memory = defined('BDTEP_DEBUG_PAGE_START_MEMORY') ? BDTEP_DEBUG_PAGE_START_MEMORY : memory_get_usage(true);

		global $wpdb;
		$this->page_start_queries = $wpdb->num_queries;

		$this->setup_hooks();
	}

	private function setup_hooks() {
		add_action('elementor/frontend/widget/before_render', [$this, 'on_before_render'], 1);
		add_action('elementor/frontend/widget/after_render', [$this, 'on_after_render'], 999);
		add_action('admin_bar_menu', [$this, 'add_admin_bar_menu'], 999);
		add_action('wp_footer', [$this, 'output_debug_panel'], 8);
		add_action('wp_head', [$this, 'output_early_js'], 5);
		add_action('wp_enqueue_scripts', [$this, 'enqueue_assets'], 999999);
		add_action('admin_bar_init', [$this, 'admin_bar_styles']);
	}

	/* ─── Widget Profiling ─────────────────────────────────── */

	public function on_before_render($widget) {
		$widget_id = $widget->get_id();

		global $wpdb;

		$this->current_widget_stack[$widget_id] = [
			'name'          => $widget->get_name(),
			'title'         => wp_strip_all_tags($widget->get_title()),
			'start_time'    => microtime(true),
			'start_memory'  => memory_get_usage(false),
			'start_peak'    => memory_get_peak_usage(false),
			'start_queries' => $wpdb->num_queries,
			'start_q_index' => defined('SAVEQUERIES') && SAVEQUERIES ? count($wpdb->queries) : 0,
		];
	}

	public function on_after_render($widget) {
		$widget_id = $widget->get_id();

		if (!isset($this->current_widget_stack[$widget_id])) {
			return;
		}

		global $wpdb;

		$start       = $this->current_widget_stack[$widget_id];
		$end_time    = microtime(true);
		$end_memory  = memory_get_usage(false);
		$end_peak    = memory_get_peak_usage(false);
		$end_queries = $wpdb->num_queries;

		$queries = [];
		if (defined('SAVEQUERIES') && SAVEQUERIES) {
			$end_q_index = count($wpdb->queries);
			for ($i = $start['start_q_index']; $i < $end_q_index; $i++) {
				if (isset($wpdb->queries[$i])) {
					$queries[] = [
						'sql'  => $wpdb->queries[$i][0],
						'time' => round($wpdb->queries[$i][1] * 1000, 3),
					];
				}
			}
		}

		$memory_delta = $end_memory - $start['start_memory'];
		$peak_during  = $end_peak - $start['start_peak'];

		$this->widgets_data[] = [
			'id'            => $widget_id,
			'name'          => $start['name'],
			'title'         => $start['title'],
			'render_time'   => round(($end_time - $start['start_time']) * 1000, 2),
			'memory_delta'  => $memory_delta,
			'memory_peak'   => max($memory_delta, $peak_during),
			'memory_after'  => $end_memory,
			'db_queries'    => $end_queries - $start['start_queries'],
			'queries'       => $queries,
		];

		unset($this->current_widget_stack[$widget_id]);
	}

	/* ─── Data Aggregation ─────────────────────────────────── */

	private function get_debug_data() {
		global $wpdb;

		$page_end_time   = microtime(true);
		$page_end_memory = memory_get_usage(true);

		$total_render_time    = 0;
		$total_widget_queries = 0;
		$total_memory_delta   = 0;

		foreach ($this->widgets_data as $w) {
			$total_render_time    += $w['render_time'];
			$total_widget_queries += $w['db_queries'];
			$total_memory_delta   += $w['memory_delta'];
		}

		$sorted = $this->widgets_data;
		usort($sorted, function ($a, $b) {
			return $b['render_time'] <=> $a['render_time'];
		});

		$scripts = wp_scripts();
		$styles  = wp_styles();

		$all_scripts_detail = [];
		$all_styles_detail  = [];
		$ep_scripts = [];
		$ep_styles  = [];

		foreach ($scripts->done as $handle) {
			$reg = isset($scripts->registered[$handle]) ? $scripts->registered[$handle] : null;
			$src = $reg ? $reg->src : '';
			$is_ep = strpos($handle, 'ep-') === 0 || strpos($handle, 'bdt-') === 0 || strpos($handle, 'element-pack') === 0;
			$is_elementor = strpos($handle, 'elementor') !== false;

			$entry = [
				'handle'    => $handle,
				'src'       => $src,
				'ver'       => $reg ? ($reg->ver ?: '') : '',
				'deps'      => $reg ? count($reg->deps) : 0,
				'in_footer' => $reg && isset($reg->extra['group']) && $reg->extra['group'] === 1,
				'source'    => $is_ep ? 'ep' : ($is_elementor ? 'elementor' : 'other'),
			];

			$all_scripts_detail[] = $entry;
			if ($is_ep) {
				$ep_scripts[] = ['handle' => $handle, 'src' => $src];
			}
		}

		foreach ($styles->done as $handle) {
			$reg = isset($styles->registered[$handle]) ? $styles->registered[$handle] : null;
			$src = $reg ? $reg->src : '';
			$is_ep = strpos($handle, 'ep-') === 0 || strpos($handle, 'bdt-') === 0 || strpos($handle, 'element-pack') === 0;
			$is_elementor = strpos($handle, 'elementor') !== false;

			$entry = [
				'handle'    => $handle,
				'src'       => $src,
				'ver'       => $reg ? ($reg->ver ?: '') : '',
				'deps'      => $reg ? count($reg->deps) : 0,
				'in_footer' => false,
				'source'    => $is_ep ? 'ep' : ($is_elementor ? 'elementor' : 'other'),
			];

			$all_styles_detail[] = $entry;
			if ($is_ep) {
				$ep_styles[] = ['handle' => $handle, 'src' => $src];
			}
		}

		return [
			'page' => [
				'total_time'        => round(($page_end_time - $this->page_start_time) * 1000, 2),
				'memory_usage'      => $page_end_memory,
				'memory_peak'       => memory_get_peak_usage(true),
				'memory_limit'      => $this->get_memory_limit_bytes(),
				'memory_limit_raw'  => ini_get('memory_limit'),
				'db_queries_total'  => $wpdb->num_queries,
				'db_queries_ep'     => $total_widget_queries,
				'php_version'       => PHP_VERSION,
				'wp_version'        => get_bloginfo('version'),
				'elementor_version' => defined('ELEMENTOR_VERSION') ? ELEMENTOR_VERSION : 'N/A',
				'ep_version'        => BDTEP_VER,
				'savequeries'       => defined('SAVEQUERIES') && SAVEQUERIES,
				'asset_optimization'=> function_exists('element_pack_is_asset_optimization_enabled') ? element_pack_is_asset_optimization_enabled() : false,
			],
			'widgets' => [
				'count'             => count($this->widgets_data),
				'total_render_time' => round($total_render_time, 2),
				'total_queries'     => $total_widget_queries,
				'total_memory'      => $total_memory_delta,
				'items'             => $sorted,
			],
			'assets' => [
				'scripts_total'   => count($scripts->done),
				'styles_total'    => count($styles->done),
				'ep_scripts'      => $ep_scripts,
				'ep_styles'       => $ep_styles,
				'scripts_detail'  => $all_scripts_detail,
				'styles_detail'   => $all_styles_detail,
			],
			'server' => [
				'max_execution_time' => ini_get('max_execution_time'),
				'php_sapi'           => PHP_SAPI,
				'opcache_enabled'    => function_exists('opcache_get_status') && @opcache_get_status() !== false,
				'object_cache'       => wp_using_ext_object_cache(),
			],
		];
	}

	private function get_memory_limit_bytes() {
		$limit = ini_get('memory_limit');
		if ($limit === '-1') {
			return -1;
		}
		$value = (int) $limit;
		$unit  = strtolower(substr(trim($limit), -1));
		switch ($unit) {
			case 'g': $value *= 1024 * 1024 * 1024; break;
			case 'm': $value *= 1024 * 1024; break;
			case 'k': $value *= 1024; break;
		}
		return $value;
	}

	/* ─── Admin Bar ────────────────────────────────────────── */

	public function admin_bar_styles() {
		if (!is_admin_bar_showing()) {
			return;
		}
		add_action('wp_head', function () {
			echo '<style>
				#wpadminbar .ep-debug-bar-icon { font-family: dashicons; font-size: 16px; vertical-align: middle; margin-right: 4px; }
				#wpadminbar #wp-admin-bar-ep-debug > .ab-item { background: #1e1e2e !important; color: #89b4fa !important; }
				#wpadminbar #wp-admin-bar-ep-debug:hover > .ab-item { background: #313244 !important; }
				#wpadminbar #wp-admin-bar-ep-debug .ab-submenu { background: #1e1e2e !important; }
				#wpadminbar #wp-admin-bar-ep-debug .ab-submenu .ab-item { color: #cdd6f4 !important; }
				#wpadminbar #wp-admin-bar-ep-debug .ab-submenu .ab-item:hover { background: #313244 !important; color: #89b4fa !important; }
			</style>';
		}, 999);
	}

	public function add_admin_bar_menu($wp_admin_bar) {
		if (is_admin()) {
			return;
		}

		$data = $this->get_debug_data();

		$time_color    = $data['page']['total_time'] > 3000 ? '#f38ba8' : ($data['page']['total_time'] > 1000 ? '#fab387' : '#a6e3a1');
		$queries_color = $data['page']['db_queries_total'] > 100 ? '#f38ba8' : ($data['page']['db_queries_total'] > 50 ? '#fab387' : '#a6e3a1');

		$wp_admin_bar->add_node([
			'id'    => 'ep-debug',
			'title' => sprintf(
				'<span class="ep-debug-bar-icon">&#xf227;</span> EP Debug: <span style="color:%s">%sms</span> | %s | <span style="color:%s">%dq</span>',
				$time_color,
				number_format($data['page']['total_time'], 0),
				size_format($data['page']['memory_usage']),
				$queries_color,
				$data['page']['db_queries_total']
			),
			'href'  => '#',
			'meta'  => [
				'class'   => 'ep-debug-admin-bar',
				'onclick' => 'if(window.epDebugPanel){window.epDebugPanel.toggle();}return false;',
			],
		]);

		$wp_admin_bar->add_node([
			'id'     => 'ep-debug-widgets',
			'parent' => 'ep-debug',
			'title'  => sprintf(
				'%d EP widgets &mdash; %sms render | %d queries',
				$data['widgets']['count'],
				number_format($data['widgets']['total_render_time'], 1),
				$data['widgets']['total_queries']
			),
		]);

		$top5 = array_slice($data['widgets']['items'], 0, 5);
		foreach ($top5 as $i => $widget) {
			$color = $widget['render_time'] > 100 ? '#f38ba8' : ($widget['render_time'] > 30 ? '#fab387' : '#a6e3a1');
			$wp_admin_bar->add_node([
				'id'     => 'ep-debug-widget-' . $i,
				'parent' => 'ep-debug-widgets',
				'title'  => sprintf(
					'<span style="color:%s">%sms</span> %s (%dq)',
					$color,
					number_format($widget['render_time'], 1),
					esc_html($widget['name']),
					$widget['db_queries']
				),
			]);
		}

		$mem_pct   = $data['page']['memory_limit'] > 0 ? round(($data['page']['memory_usage'] / $data['page']['memory_limit']) * 100) : 0;
		$mem_color = $mem_pct > 80 ? '#f38ba8' : ($mem_pct > 60 ? '#fab387' : '#a6e3a1');

		$wp_admin_bar->add_node([
			'id'     => 'ep-debug-memory',
			'parent' => 'ep-debug',
			'title'  => sprintf(
				'Memory: %s / %s (<span style="color:%s">%d%%</span>) &mdash; Peak: %s',
				size_format($data['page']['memory_usage']),
				esc_html($data['page']['memory_limit_raw']),
				$mem_color,
				$mem_pct,
				size_format($data['page']['memory_peak'])
			),
		]);

		$wp_admin_bar->add_node([
			'id'     => 'ep-debug-assets',
			'parent' => 'ep-debug',
			'title'  => sprintf(
				'Assets: %d scripts | %d styles (EP: %d js, %d css)',
				$data['assets']['scripts_total'],
				$data['assets']['styles_total'],
				count($data['assets']['ep_scripts']),
				count($data['assets']['ep_styles'])
			),
		]);

		$wp_admin_bar->add_node([
			'id'     => 'ep-debug-env',
			'parent' => 'ep-debug',
			'title'  => sprintf(
				'PHP %s | WP %s | Elementor %s | EP %s',
				esc_html($data['page']['php_version']),
				esc_html($data['page']['wp_version']),
				esc_html($data['page']['elementor_version']),
				esc_html($data['page']['ep_version'])
			),
		]);

		$flags = [];
		if ($data['page']['asset_optimization']) {
			$flags[] = 'Asset Opt: ON';
		}
		if ($data['server']['opcache_enabled']) {
			$flags[] = 'OPcache: ON';
		}
		if ($data['server']['object_cache']) {
			$flags[] = 'Object Cache: ON';
		}
		if ($data['page']['savequeries']) {
			$flags[] = 'SAVEQUERIES: ON';
		}
		if (!empty($flags)) {
			$wp_admin_bar->add_node([
				'id'     => 'ep-debug-flags',
				'parent' => 'ep-debug',
				'title'  => implode(' | ', $flags),
			]);
		}
	}

	/* ─── Asset Enqueue ────────────────────────────────────── */

	public function enqueue_assets() {
		if (is_admin()) {
			return;
		}
		wp_enqueue_style('ep-debug-panel', BDTEP_URL . 'includes/debug/ep-debug-panel.css', [], BDTEP_VER);
		wp_enqueue_script('ep-debug-panel', BDTEP_URL . 'includes/debug/ep-debug-panel.js', [], BDTEP_VER, true);
	}

	/**
	 * Inline bootstrap that runs before any widget JS, capturing
	 * elementorFrontend hook execution times by intercepting doAction
	 * (execution) rather than addAction (registration).
	 */
	public function output_early_js() {
		if (is_admin()) {
			return;
		}
		?>
		<script>
		(function(){
			window.epDebugJS={widgets:[],pageStart:performance.now(),
				track:function(tag,el,ms,wt,eid,nodes){this.widgets.push({tag:tag,ms:ms,widgetType:wt||'',elId:eid||'',nodes:nodes||0});}
			};
			var _patched=false;
			function patchHooks(){
				if(!window.elementorFrontend||!elementorFrontend.hooks)return false;
				if(_patched)return true;
				_patched=true;
				var _origDo=elementorFrontend.hooks.doAction;
				elementorFrontend.hooks.doAction=function(tag){
					if(tag.indexOf('frontend/element_ready/')===0&&tag.indexOf('/global')===-1){
						var s=performance.now();
						_origDo.apply(this,arguments);
						var d=performance.now()-s;
						var $el=arguments[1],wt='',eid='',nodes=0;
						if($el){
							if($el[0]){wt=($el[0].getAttribute('data-widget_type')||'');eid=($el[0].getAttribute('data-id')||'');nodes=$el[0].querySelectorAll('*').length;}
							else if($el.data){wt=$el.data('widget_type')||'';eid=$el.data('id')||'';nodes=$el.find('*').length;}
							else if($el.querySelectorAll){wt=$el.getAttribute('data-widget_type')||'';eid=$el.getAttribute('data-id')||'';nodes=$el.querySelectorAll('*').length;}
						}
						window.epDebugJS.track(tag,$el,d,wt,eid,nodes);
						return;
					}
					return _origDo.apply(this,arguments);
				};
				return true;
			}
			if(!patchHooks()){
				var iv=setInterval(function(){if(patchHooks())clearInterval(iv);},5);
				setTimeout(function(){clearInterval(iv);},5000);
			}
		})();
		</script>
		<?php
	}

	/* ─── Debug Panel Output ───────────────────────────────── */

	public function output_debug_panel() {
		if (is_admin()) {
			return;
		}

		$data = $this->get_debug_data();
		?>
		<script>var epDebugData=<?php echo wp_json_encode($data); ?>;</script>
		<?php
	}
}
