<?php
/**
 * BaseCloud Boost — Elementor Speed
 *
 * Elementor-specific optimizations, applied at the output buffer (never at
 * enqueue time — Elementor re-adds or lazy-loads assets that are dequeued,
 * see elementor/elementor#14712 / #16678). Three passes, all opt-in behind
 * the `elementor_speed` master toggle:
 *
 *  1. Inline per-page CSS (`elementor_inline_post_css`): replaces each
 *     render-blocking uploads/elementor/css/post-*.css / loop-*.css <link>
 *     with an inline <style> holding the same (minified) rules at the same
 *     document position. Elementor sites ship 4–27 of these tiny blocking
 *     requests per page; inlining removes every one of those round-trips
 *     while keeping the rules render-applied before first paint — strictly
 *     safer than async delivery, which broke Elementor per-page styling
 *     (monitornet Careers, 2026-06-18). Over-cap or unreadable files simply
 *     keep their <link> (fail open).
 *
 *  2. Unused icon CSS removal (`elementor_drop_unused_icons`): drops the
 *     always-enqueued eicons stylesheet and Elementor's Font Awesome packs
 *     ONLY when the full page HTML provably contains no icon usage and no
 *     feature that injects icon markup at runtime (lightbox, popups, video
 *     overlays, carousels). Uncertain = keep. Scripts are NEVER touched.
 *
 *  3. DOM-size advisory (`elementor_dom_warnings`): measures the final HTML
 *     and records a note for the settings screen when a page exceeds the
 *     threshold. Boost never restructures the DOM — that breaks layouts —
 *     so this surfaces the problem for a human instead.
 *
 * The Settings Advisor (`elementor_settings_advisor`) is read-only: it
 * inspects Elementor's own options/experiments and renders recommendations
 * inside the Elementor Speed card (see admin/views/file-optimization.php).
 * Nothing is ever auto-applied to Elementor.
 *
 * Freshness: inlined CSS is baked into cached HTML, so this module purges
 * the page cache when Elementor regenerates its CSS files
 * (elementor/core/files/clear_cache) and when an Elementor Library template
 * (header/footer/popup/loop — CSS embedded across many pages) is saved.
 * Regular page saves are already covered by Plugin_Compat's purge_post on
 * elementor/editor/after_save.
 *
 * @package BaseCloud\Boost\Optimizer
 */

declare( strict_types=1 );

namespace BaseCloud\Boost\Optimizer;

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

use BaseCloud\Boost\Core\Cache_Purge;

/**
 * Elementor-specific buffer optimizations + read-only settings advisor.
 */
class Elementor_Optimizer {

	/** @var int Default per-file cap for inlined post-CSS (bytes, minified). */
	private const INLINE_MAX_DEFAULT = 15360;

	/** @var int Default per-page total budget for inlined post-CSS (bytes, minified). */
	private const INLINE_TOTAL_MAX_DEFAULT = 122880;

	/** @var int HTML size (bytes) above which the DOM advisory is recorded. */
	private const DOM_WARN_BYTES = 409600;

	/** @var string Transient key holding the DOM-size advisory note. */
	private const DOM_NOTE_KEY = 'BCBOOST_elementor_dom_note';

	/**
	 * Boot the module. Safe in every context — it self-selects what to
	 * register (the same pattern as Critical_CSS): cache-freshness listeners
	 * always (Elementor CSS regeneration and template saves fire in
	 * admin/AJAX), buffer passes only on optimizable front-end requests.
	 * The advisor needs no hooks — the settings view calls advisor_items().
	 *
	 * @return void
	 */
	public function boot(): void {
		if ( ! defined( 'ELEMENTOR_VERSION' ) ) {
			return;
		}
		if ( ! BCBOOST_get_option( 'elementor_speed', false ) ) {
			return;
		}

		if ( BCBOOST_get_option( 'elementor_inline_post_css', true ) ) {
			// "Regenerate CSS" / clear cache rewrites every post-*.css file —
			// every cached page holding an inlined copy is now stale.
			add_action( 'elementor/core/files/clear_cache', [ $this, 'purge_after_css_regen' ] );
			// A saved Library template's CSS (header/footer/popup/loop) is
			// embedded across many pages — purge them all. Regular posts are
			// covered by Plugin_Compat's purge_post on the same hook.
			add_action( 'elementor/editor/after_save', [ $this, 'purge_after_template_save' ] );
		}

		if ( is_admin() ) {
			return;
		}
		if ( ! BCBOOST_is_optimizable_request() ) {
			return;
		}

		// Priorities matter: both run BEFORE the CSS combiner (priority 9,
		// CSS_Minifier::combine_links_in_buffer) so a dropped icon sheet can
		// never ride into a bundle and an inlined post-CSS link never gets
		// double-processed.
		if ( BCBOOST_get_option( 'elementor_drop_unused_icons', true ) ) {
			add_filter( 'BCBOOST_buffer', [ $this, 'drop_unused_icon_css' ], 5 );
		}
		if ( BCBOOST_get_option( 'elementor_inline_post_css', true ) ) {
			add_filter( 'BCBOOST_buffer', [ $this, 'inline_post_css' ], 6 );
		}
		if ( BCBOOST_get_option( 'elementor_dom_warnings', true ) ) {
			// Late (50): measures the final document; never modifies it.
			add_filter( 'BCBOOST_buffer', [ $this, 'measure_dom' ], 50 );
		}
	}

	// ─── Cache freshness ──────────────────────────────────────────────────────

	/**
	 * Elementor regenerated its CSS files — purge everything so no cached page
	 * keeps an inlined copy of the old rules.
	 *
	 * @return void
	 */
	public function purge_after_css_regen(): void {
		( new Cache_Purge() )->purge_all();
	}

	/**
	 * An Elementor Library template (header/footer/popup/loop) was saved. Its
	 * post-CSS is embedded (inlined) on every page that renders the template,
	 * so a targeted purge_post cannot reach them — purge all pages.
	 *
	 * @param int $post_id Saved post ID (passed by elementor/editor/after_save).
	 * @return void
	 */
	public function purge_after_template_save( int $post_id ): void {
		if ( 'elementor_library' === get_post_type( $post_id ) ) {
			( new Cache_Purge() )->purge_all();
		}
	}

	// ─── Pass 1: unused icon CSS removal (priority 5) ─────────────────────────

	/**
	 * Remove Elementor's always-enqueued icon stylesheets (eicons + Font
	 * Awesome packs) when the page provably uses no icons.
	 *
	 * Veto model (uncertain = keep, fail closed): runtime-injected UI —
	 * lightbox chrome, popups, video play overlays, carousel arrows — uses
	 * eicon- and fa- classes that never appear in the static HTML, so ANY
	 * hint of those features keeps the stylesheets. Only <link> tags are
	 * ever touched; scripts are never removed or delayed here.
	 *
	 * @param string $html Full page HTML.
	 * @return string
	 */
	public function drop_unused_icon_css( string $html ): string {
		try {
			if ( ! BCBOOST_is_optimizable_request() ) {
				return $html;
			}
			// Cheap bail: neither icon library referenced at all.
			if ( false === stripos( $html, '/elementor/assets/lib/' ) ) {
				return $html;
			}

			// Collect the candidate icon <link> tags FIRST. The veto scan must
			// run on the page WITHOUT them: the Font Awesome links' own id
			// attributes (elementor-icons-fa-solid-css, …) contain "fa-" and
			// would otherwise self-veto every removal.
			if ( ! preg_match_all( '#<link\b[^>]*>#i', $html, $links, PREG_OFFSET_CAPTURE ) ) {
				return $html;
			}

			$candidates = [];
			foreach ( $links[0] as $lk ) {
				$tag    = $lk[0];
				$offset = (int) $lk[1];

				if ( ! preg_match( '#\srel\s*=\s*["\']?stylesheet["\']?#i', $tag ) ) {
					continue;
				}
				if ( ! preg_match( '#\shref\s*=\s*["\']([^"\']+)["\']#i', $tag, $hm ) ) {
					continue;
				}
				$href = html_entity_decode( $hm[1], ENT_QUOTES );

				$is_eicons = ( false !== stripos( $href, '/elementor/assets/lib/eicons/' ) )
					|| ( false !== stripos( $tag, "id='elementor-icons-css'" ) )
					|| ( false !== stripos( $tag, 'id="elementor-icons-css"' ) );
				$is_fa     = ( false !== stripos( $href, '/elementor/assets/lib/font-awesome/' ) );

				if ( $is_eicons || $is_fa ) {
					$candidates[] = [
						'offset' => $offset,
						'len'    => strlen( $tag ),
						'href'   => $href,
						'eicons' => $is_eicons,
					];
				}
			}

			if ( empty( $candidates ) ) {
				return $html;
			}

			// Veto scan copy: the full buffer minus the candidate tags
			// (back-to-front so offsets stay valid).
			$scan = $html;
			for ( $i = count( $candidates ) - 1; $i >= 0; $i-- ) {
				$scan = substr_replace( $scan, '', $candidates[ $i ]['offset'], $candidates[ $i ]['len'] );
			}

			// Runtime icon injectors — veto BOTH removals.
			$runtime_veto = ( false !== stripos( $scan, 'data-elementor-open-lightbox' ) )
				|| ( false !== stripos( $scan, 'elementor-lightbox' ) )
				|| ( false !== stripos( $scan, 'e-popup' ) )
				|| ( false !== stripos( $scan, 'elementor-popup-modal' ) );

			// eicons: any eicon-* class (server-rendered icons, widget chrome),
			// video widgets (play overlay) or carousels (Swiper navigation
			// arrows are injected as eicon-chevron-* at runtime) keep the sheet.
			$drop_eicons = ! $runtime_veto
				&& ( false === stripos( $scan, 'eicon-' ) )
				&& ( false === stripos( $scan, 'elementor-widget-video' ) )
				&& ( false === stripos( $scan, 'swiper' ) );

			// Font Awesome (Elementor's bundled packs only): any fa/fab/fas/
			// far/fal class token anywhere in the buffer — including inside
			// inline script templates — keeps the sheets.
			$drop_fa = ! $runtime_veto
				&& ! preg_match( '/\bfa[bsrl]?[- ]/i', $scan );

			if ( ! $drop_eicons && ! $drop_fa ) {
				return $html;
			}

			$removals = [];
			foreach ( $candidates as $c ) {
				if ( ( $c['eicons'] && $drop_eicons ) || ( ! $c['eicons'] && $drop_fa ) ) {
					$removals[] = $c;
				}
			}

			if ( empty( $removals ) ) {
				return $html;
			}

			// Splice back-to-front so earlier offsets stay valid.
			for ( $i = count( $removals ) - 1; $i >= 0; $i-- ) {
				$html = substr_replace( $html, '', $removals[ $i ]['offset'], $removals[ $i ]['len'] );
			}

			if ( defined( 'WP_DEBUG_LOG' ) && WP_DEBUG_LOG ) {
				foreach ( $removals as $r ) {
					error_log( 'BCBOOST Elementor Speed: dropped unused icon CSS ' . $r['href'] );
				}
			}

			return $html;
		} catch ( \Throwable $e ) {
			if ( defined( 'WP_DEBUG_LOG' ) && WP_DEBUG_LOG ) {
				error_log( 'BCBOOST Elementor Speed icon drop failed: ' . $e->getMessage() );
			}
			return $html;
		}
	}

	// ─── Pass 2: inline per-page post-CSS (priority 6) ────────────────────────

	/**
	 * Replace each Elementor per-page/per-loop stylesheet <link> with an
	 * inline <style> at the same document position. Same rules, same cascade
	 * order, applied before first paint — minus one blocking round-trip per
	 * file. Anything that cannot be resolved, read, or fits neither the
	 * per-file cap nor the page budget keeps its <link> untouched (fail open).
	 *
	 * Filters:
	 * - BCBOOST_elementor_inline_css_max        per-file cap (bytes, minified)
	 * - BCBOOST_elementor_inline_css_total_max  per-page budget (bytes, minified)
	 *
	 * @param string $html Full page HTML.
	 * @return string
	 */
	public function inline_post_css( string $html ): string {
		try {
			if ( ! BCBOOST_is_optimizable_request() ) {
				return $html;
			}
			// Cheap bail: no per-page CSS in either shape — the raw uploads file
			// or the copy CSS_Minifier's enqueue-time pass may have rewritten it
			// to (wp-content/cache/basecloud-boost-min/css/post-6-<hash>.min.css).
			if ( false === stripos( $html, '/elementor/css/' )
				&& false === stripos( $html, 'basecloud-boost-min/css/post-' )
				&& false === stripos( $html, 'basecloud-boost-min/css/loop-' ) ) {
				return $html;
			}

			$max    = (int) apply_filters( 'BCBOOST_elementor_inline_css_max', self::INLINE_MAX_DEFAULT );
			$budget = (int) apply_filters( 'BCBOOST_elementor_inline_css_total_max', self::INLINE_TOTAL_MAX_DEFAULT );
			if ( $max <= 0 || $budget <= 0 ) {
				return $html;
			}

			if ( ! preg_match_all( '#<link\b[^>]*>#i', $html, $links, PREG_OFFSET_CAPTURE ) ) {
				return $html;
			}

			$replacements = [];
			$seen         = [];
			foreach ( $links[0] as $lk ) {
				$tag    = $lk[0];
				$offset = (int) $lk[1];

				if ( ! preg_match( '#\srel\s*=\s*["\']?stylesheet["\']?#i', $tag ) ) {
					continue;
				}
				if ( preg_match( '#\srel\s*=\s*["\']?preload#i', $tag ) ) {
					continue;
				}
				// Only all-media (or unspecified) sheets — a media-query sheet
				// must keep its own media attribute.
				if ( preg_match( '#\smedia\s*=\s*["\']([^"\']+)["\']#i', $tag, $mm ) ) {
					$media = strtolower( trim( $mm[1] ) );
					if ( '' !== $media && 'all' !== $media ) {
						continue;
					}
				}
				if ( ! preg_match( '#\shref\s*=\s*["\']([^"\']+)["\']#i', $tag, $hm ) ) {
					continue;
				}
				$href = html_entity_decode( $hm[1], ENT_QUOTES );

				// Elementor per-page / per-loop CSS only — in either shape: the
				// raw uploads file, or the minified copy CSS_Minifier's
				// enqueue-time pass may have rewritten the src to. Matching on
				// the URL PATH makes this work whether the href is on the origin
				// or already CDN-rewritten (the BunnyCDN rewriter's buffer runs
				// before BCBOOST_buffer — pull zones mirror origin paths).
				$url_path = (string) wp_parse_url( $href, PHP_URL_PATH );
				if ( ! preg_match( '#/uploads/(?:sites/\d+/)?elementor/css/((?:post|loop)-\d+)\.css$#i', $url_path, $pm )
					&& ! preg_match( '#/cache/basecloud-boost-min/css/((?:post|loop)-\d+)-[0-9a-f]{12}\.min\.css$#i', $url_path, $pm ) ) {
					continue;
				}
				$basename = strtolower( $pm[1] );
				if ( isset( $seen[ $basename ] ) ) {
					continue; // Duplicate link for the same file — leave it.
				}

				$path = $this->href_to_path( $url_path );
				if ( null === $path || ! is_file( $path ) || ! is_readable( $path ) ) {
					continue; // Unresolvable (custom content dir, remote) — fail open.
				}

				$css = (string) @file_get_contents( $path );
				if ( '' === trim( $css ) ) {
					continue;
				}

				// Relative url()/@import must be made absolute — the rules move
				// from uploads/elementor/css/ into the document. Base on the href
				// as seen (origin or CDN) so assets resolve exactly as the file
				// itself would have.
				$css = CSS_Minifier::rewrite_relative_urls( $css, $this->dir_url( $href ) );
				$css = preg_replace( '/@charset\s+["\'][^"\']*["\']\s*;/i', '', $css ) ?? $css;
				$css = CSS_Minifier::minify( $css );

				$len = strlen( $css );
				if ( 0 === $len || $len > $max || $len > $budget ) {
					continue; // Over cap/budget — the link stays render-blocking (safe).
				}
				// Never inline content that could terminate the style element.
				if ( false !== stripos( $css, '</style' ) || false !== stripos( $css, '<script' ) ) {
					continue;
				}

				$budget           -= $len;
				$seen[ $basename ] = true;
				$replacements[]    = [
					'offset' => $offset,
					'len'    => strlen( $tag ),
					'style'  => '<style id="bcboost-elementor-inline-' . $basename . '">' . $css . '</style>',
				];
			}

			if ( empty( $replacements ) ) {
				return $html;
			}

			// Splice back-to-front so earlier offsets stay valid.
			for ( $i = count( $replacements ) - 1; $i >= 0; $i-- ) {
				$r    = $replacements[ $i ];
				$html = substr_replace( $html, $r['style'], $r['offset'], $r['len'] );
			}

			return $html;
		} catch ( \Throwable $e ) {
			if ( defined( 'WP_DEBUG_LOG' ) && WP_DEBUG_LOG ) {
				error_log( 'BCBOOST Elementor Speed inline CSS failed: ' . $e->getMessage() );
			}
			return $html;
		}
	}

	/**
	 * Map a URL path to a local file, tolerating CDN-rewritten hrefs (only
	 * the path is used, and pull zones mirror origin paths). Restricted to
	 * files under wp-content/…/elementor/css/ or Boost's own minify cache
	 * (cache/basecloud-boost-min/css/) — anything else returns null.
	 *
	 * @param string $url_path URL path component (no host, no query).
	 * @return string|null Absolute local path, or null when unresolvable.
	 */
	private function href_to_path( string $url_path ): ?string {
		$pos = strpos( $url_path, '/wp-content/' );
		if ( false === $pos ) {
			return null;
		}
		$rel = substr( $url_path, $pos + strlen( '/wp-content' ) );
		if ( false !== strpos( $rel, '..' ) ) {
			return null;
		}
		$file = wp_normalize_path( WP_CONTENT_DIR . $rel );
		if ( false === strpos( $file, '/elementor/css/' )
			&& false === strpos( $file, '/cache/basecloud-boost-min/css/' ) ) {
			return null;
		}
		return $file;
	}

	/**
	 * Return the directory URL (with trailing slash) of a stylesheet URL.
	 *
	 * @param string $url Stylesheet URL (may include a query string).
	 * @return string
	 */
	private function dir_url( string $url ): string {
		$url = (string) strtok( $url, '?#' );
		return preg_replace( '#/[^/]*$#', '/', $url ) ?? $url;
	}

	// ─── Pass 3: DOM-size advisory (priority 50, never modifies) ──────────────

	/**
	 * Record an advisory note when a page's final HTML exceeds the threshold.
	 * Purely observational — the buffer is returned byte-identical. One note
	 * is held at a time (a week, or until the settings screen shows it and
	 * the situation is addressed).
	 *
	 * @param string $html Full page HTML.
	 * @return string Unchanged HTML.
	 */
	public function measure_dom( string $html ): string {
		try {
			$threshold = (int) apply_filters( 'BCBOOST_elementor_dom_warn_bytes', self::DOM_WARN_BYTES );
			if ( $threshold <= 0 || strlen( $html ) < $threshold ) {
				return $html;
			}
			if ( false !== get_transient( self::DOM_NOTE_KEY ) ) {
				return $html; // A note is already pending — no repeat writes.
			}
			$uri = isset( $_SERVER['REQUEST_URI'] ) ? esc_url_raw( wp_unslash( (string) $_SERVER['REQUEST_URI'] ) ) : '/';
			set_transient(
				self::DOM_NOTE_KEY,
				[ 'url' => $uri, 'bytes' => strlen( $html ), 'time' => time() ],
				WEEK_IN_SECONDS
			);
			return $html;
		} catch ( \Throwable $e ) {
			return $html;
		}
	}

	// ─── Settings Advisor (read-only; rendered by the settings view) ──────────

	/**
	 * Inspect Elementor's own options/experiments and return recommendation
	 * items for the Elementor Speed card. Read-only — Boost never changes an
	 * Elementor setting. Unknown/absent options are skipped or reported as
	 * 'info', never guessed.
	 *
	 * @return array<int, array{level:string, text:string}> level: ok|info|warn.
	 */
	public static function advisor_items(): array {
		$items = [];
		if ( ! defined( 'ELEMENTOR_VERSION' ) ) {
			return $items;
		}

		// 1. Google Fonts display strategy (Elementor → Settings → Performance).
		$display = (string) get_option( 'elementor_font_display', 'auto' );
		if ( in_array( $display, [ 'swap', 'fallback', 'optional' ], true ) ) {
			$items[] = [
				'level' => 'ok',
				'text'  => sprintf(
					/* translators: %s: current font-display value */
					__( 'Elementor Google Fonts Load is "%s" — text stays visible while fonts load.', 'basecloud-boost' ),
					$display
				),
			];
		} else {
			$items[] = [
				'level' => 'warn',
				'text'  => __( 'Elementor loads Google Fonts without a swap-style font-display (text is invisible while fonts download). Set Elementor → Settings → Performance → Google Fonts Load to "Swap". Boost already patches Google Fonts links at the buffer, but fixing it at the source also covers previews and non-buffered contexts.', 'basecloud-boost' ),
			];
		}

		// 2. Inline Font Icons (renders icons as inline SVG — removes the Font
		// Awesome and eicons files entirely; bigger than any buffer-level drop).
		$svg = (string) get_option( 'elementor_experiment-e_font_icon_svg', 'default' );
		if ( 'active' === $svg ) {
			$items[] = [
				'level' => 'ok',
				'text'  => __( 'Inline Font Icons is active — icons render as inline SVG, no icon-font files load.', 'basecloud-boost' ),
			];
		} elseif ( 'inactive' === $svg ) {
			$items[] = [
				'level' => 'warn',
				'text'  => __( 'Inline Font Icons (Elementor → Settings → Features) is off. Enabling it renders icons as inline SVG and removes the Font Awesome + eicons font files — the single biggest icon-weight win. Check icons across the site after enabling.', 'basecloud-boost' ),
			];
		} else {
			$items[] = [
				'level' => 'info',
				'text'  => __( 'Inline Font Icons is not explicitly enabled (Elementor → Settings → Features). If this site still loads Font Awesome / eicons files, enabling it removes them in favour of inline SVG.', 'basecloud-boost' ),
			];
		}

		// 3. Element Caching alongside Boost's full-page cache.
		$element_cache = (string) get_option( 'elementor_experiment-e_element_cache', 'default' );
		if ( 'active' === $element_cache && BCBOOST_get_option( 'cache_enabled', true ) ) {
			$items[] = [
				'level' => 'warn',
				'text'  => __( 'Elementor Element Caching is active while Boost full-page caching is on. Element Caching is known to cause stale/incorrect widget content next to full-page caches (WP Rocket documents the same conflict) and adds nothing once the whole page is cached — recommend setting it to Inactive.', 'basecloud-boost' ),
			];
		}

		// 4. Lazy Load Background Images vs Boost's LCP handling.
		$lazy_bg = (string) get_option( 'elementor_experiment-e_lazyload', 'default' );
		if ( 'active' === $lazy_bg ) {
			$items[] = [
				'level' => 'warn',
				'text'  => __( 'Elementor "Lazy Load Background Images" is active. It can lazy-load the hero/LCP background that Boost\'s LCP Optimizer is trying to prioritise — recommend Inactive and let Boost handle lazy-loading and hero priority.', 'basecloud-boost' ),
			];
		}

		// 5. Optimized Image Loading (only reported when the option exists —
		// never guessed).
		$img_loading = get_option( 'elementor_optimized_image_loading', null );
		if ( '1' === $img_loading || 1 === $img_loading ) {
			$items[] = [
				'level' => 'info',
				'text'  => __( 'Elementor "Optimized Image Loading" is on. Boost\'s LCP Optimizer runs later (at the buffer) and takes precedence; if you ever see conflicting fetchpriority/lazy hints on the hero, turn the Elementor toggle off.', 'basecloud-boost' ),
			];
		}

		// 6. CSS print method — internal embedding makes the inline pass a no-op.
		$print_method = (string) get_option( 'elementor_css_print_method', 'external' );
		if ( 'internal' === $print_method ) {
			$items[] = [
				'level' => 'info',
				'text'  => __( 'Elementor CSS Print Method is "Internal Embedding" — Elementor already inlines its per-page CSS, so Boost\'s "Inline per-page CSS" pass will simply find nothing to do on most pages (harmless).', 'basecloud-boost' ),
			];
		}

		// 7. DOM-size advisory recorded by the buffer pass.
		if ( BCBOOST_get_option( 'elementor_dom_warnings', true ) ) {
			$note = get_transient( self::DOM_NOTE_KEY );
			if ( is_array( $note ) && ! empty( $note['bytes'] ) ) {
				$items[] = [
					'level' => 'warn',
					'text'  => sprintf(
						/* translators: 1: page URL, 2: HTML size in KB */
						__( 'A recent page (%1$s) rendered %2$s KB of HTML — a very large DOM slows every interaction (INP) and style pass. Boost never restructures your DOM: automated div/container rewriting is how optimizers break Elementor layouts. Reduce it in Elementor instead — audit deeply nested sections/containers, split extremely long pages, and consider rebuilding the heaviest sections with Elementor V4 atomic elements (single-wrapper markup).', 'basecloud-boost' ),
						(string) ( isset( $note['url'] ) ? $note['url'] : '/' ),
						number_format_i18n( (float) $note['bytes'] / 1024, 0 )
					),
				];
			}
		}

		return $items;
	}
}
