<?php
/**
 * Theme Builder — custom Header / Footer documents with display conditions.
 * Based on header_footer.txt (MVP: layout, sticky, transparent, WP menus, scope).
 *
 * @package AuraBuilder
 */

declare(strict_types=1);

namespace AuraBuilder\Theme;

use AuraBuilder\Document\Document;
use AuraBuilder\Document\DocumentSanitizer;
use AuraBuilder\Renderer\DocumentRenderer;

final class ThemeBuilder {

	public const OPTION        = 'aptb_theme_parts';
	public const OPTION_LEGACY = 'aura_builder_theme_parts';

	/**
	 * @return array{header:array<string,mixed>,footer:array<string,mixed>,single:array<string,mixed>}
	 */
	public static function defaults(): array {
		return array(
			'header' => self::default_part( 'header' ),
			'footer' => self::default_part( 'footer' ),
			// Dynamic single post layout (templates.txt §18).
			'single' => self::default_part( 'single' ),
		);
	}

	/**
	 * @return list<string>
	 */
	public static function part_types(): array {
		return array( 'header', 'footer', 'single' );
	}

	public static function normalize_type( string $type ): string {
		$type = sanitize_key( $type );
		return in_array( $type, self::part_types(), true ) ? $type : 'header';
	}

	/**
	 * @return array<string, mixed>
	 */
	public static function default_part( string $type ): array {
		return array(
			// Single ships with a starter layout; still off until user saves (or auto-enable on save).
			'enabled'     => false,
			// all | home | singular | none
			'display'     => $type === 'single' ? 'singular' : 'all',
			// include / exclude page IDs (comma list)
			'include_ids' => '',
			'exclude_ids' => '',
			// replace theme part | use_theme (disabled effectively)
			'source'      => 'builder',
			// sticky / transparent (header)
			'sticky'      => '0',
			'sticky_bg'   => '#ffffff',
			'sticky_on'   => array( 'desktop', 'tablet', 'mobile' ),
			'transparent' => '0',
			'height'      => '',
			// document tree
			'document'    => self::empty_document( $type ),
			// Single chrome: which header/footer to use on singular pages/posts
			// global = Theme Builder default (or theme if TB off) | builder | theme | none
			'header_source' => 'global',
			'footer_source' => 'global',
		);
	}

	/**
	 * @return array<string, mixed>
	 */
	public static function empty_document( string $type ): array {
		// Single post gets a ready-to-edit dynamic layout (title, meta, image, content).
		if ( $type === 'single' ) {
			return self::starter_single_document();
		}

		$root = 'root_' . $type;
		$sec  = 'sec_' . $type;
		$doc  = array(
			'id'       => 'theme_' . $type,
			'rootId'   => $root,
			'version'  => 1,
			'settings' => array(
				'showHeader' => false,
				'showFooter' => false,
			),
			'metadata' => array(
				'kind' => 'theme_' . $type,
			),
			'nodes'    => array(
				$root => array(
					'id'       => $root,
					'type'     => 'root',
					'parentId' => null,
					'children' => array( $sec ),
					'settings' => array(
						'content_width' => 'full',
						'max_width'     => '1200px',
					),
					'styles'   => array(
						'desktop' => array(),
						'tablet'  => array(),
						'mobile'  => array(),
					),
					'attributes' => array(),
					'classes'    => array(),
					'locked'     => false,
					'hidden'     => false,
				),
				$sec  => array(
					'id'       => $sec,
					'type'     => 'section',
					'parentId' => $root,
					'children' => array(),
					// Header: flex row (logo | menu). Footer: grid (colunas).
					'settings' => array(
						'layout'    => 'full',
						'direction' => $type === 'header' ? 'row' : 'grid',
						'columns'   => $type === 'header' ? 2 : 1,
						'gap'       => '16px',
					),
					'styles'   => array(
						'desktop' => array(
							'padding'         => '12px 24px',
							'background'      => '#ffffff',
							'align_items'     => 'center',
							'justify_content' => $type === 'header' ? 'space-between' : 'flex-start',
						),
						'tablet'  => array(),
						'mobile'  => array(),
					),
					'attributes' => array(),
					'classes'    => array( 'ab-theme-' . $type ),
					'locked'     => false,
					'hidden'     => false,
				),
			),
		);
		return $doc;
	}

	/**
	 * Starter layout for Single Post (dynamic widgets already placed).
	 *
	 * @return array<string, mixed>
	 */
	public static function starter_single_document(): array {
		$root = 'root_single';
		$sec  = 'sec_single';
		$col  = 'col_single';
		$title = 'dyn_title';
		$meta  = 'dyn_meta';
		$img   = 'dyn_image';
		$body  = 'dyn_content';

		$mk = static function ( string $id, string $type, ?string $parent, array $children, array $settings, array $desk = array() ): array {
			return array(
				'id'         => $id,
				'type'       => $type,
				'parentId'   => $parent,
				'children'   => $children,
				'settings'   => $settings,
				'styles'     => array(
					'desktop' => $desk,
					'tablet'  => array(),
					'mobile'  => array(),
				),
				'attributes' => array(),
				'classes'    => array(),
				'locked'     => false,
				'hidden'     => false,
			);
		};

		return array(
			'id'       => 'theme_single',
			'rootId'   => $root,
			'version'  => 1,
			// Keep Theme Builder / theme chrome visible (content template only).
			'settings' => array(
				'showHeader'    => true,
				'showFooter'    => true,
				'header_source' => 'global',
				'footer_source' => 'global',
			),
			'metadata' => array(
				'kind'    => 'theme_single',
				'starter' => true,
			),
			'nodes'    => array(
				$root  => $mk(
					$root,
					'root',
					null,
					array( $sec ),
					array(
						'content_width' => 'full',
						'max_width'     => '1200px',
						'gap'           => '0',
					)
				),
				$sec   => $mk(
					$sec,
					'section',
					$root,
					array( $col ),
					array(
						'layout'    => 'boxed',
						'direction' => 'column',
						'columns'   => 1,
						'gap'       => '24px',
					),
					array(
						'padding'    => '48px 24px',
						'background' => '#ffffff',
					)
				),
				$col   => $mk(
					$col,
					'container',
					$sec,
					array( $title, $meta, $img, $body ),
					array(
						'direction' => 'column',
						'gap'       => '16px',
						'width'     => '100%',
					)
				),
				$title => $mk(
					$title,
					'dynamic',
					$col,
					array(),
					array(
						'source' => 'post_title',
						'align'  => 'left',
						'link'   => '0',
					),
					array(
						'font_size'   => '36px',
						'font_weight' => '700',
						'color'       => '#0f172a',
					)
				),
				$meta  => $mk(
					$meta,
					'dynamic',
					$col,
					array(),
					array(
						'source' => 'post_date',
						'align'  => 'left',
						'link'   => '0',
					),
					array(
						'font_size' => '14px',
						'color'     => '#64748b',
					)
				),
				$img   => $mk(
					$img,
					'dynamic',
					$col,
					array(),
					array(
						'source' => 'featured_image',
						'align'  => 'center',
						'link'   => '0',
					),
					array(
						'radius' => '12px',
					)
				),
				$body  => $mk(
					$body,
					'dynamic',
					$col,
					array(),
					array(
						'source' => 'post_content',
						'align'  => 'left',
						'link'   => '0',
					),
					array(
						'font_size'   => '17px',
						'line_height' => '1.7',
						'color'       => '#1e293b',
					)
				),
			),
		);
	}

	/**
	 * @return array{header:array<string,mixed>,footer:array<string,mixed>,single:array<string,mixed>}
	 */
	public static function get_all(): array {
		$raw = get_option( self::OPTION, null );
		if ( ! is_array( $raw ) ) {
			$legacy = get_option( self::OPTION_LEGACY, array() );
			$raw    = is_array( $legacy ) ? $legacy : array();
		}
		$out = array();
		foreach ( self::part_types() as $type ) {
			$out[ $type ] = self::normalize_part(
				is_array( $raw[ $type ] ?? null ) ? $raw[ $type ] : array(),
				$type
			);
		}
		return $out;
	}

	/**
	 * @return array<string, mixed>
	 */
	public static function get_part( string $type ): array {
		$type = self::normalize_type( $type );
		$all  = self::get_all();
		return $all[ $type ];
	}

	/**
	 * @param array<string, mixed> $part
	 * @return array<string, mixed>
	 */
	public static function save_part( string $type, array $part ): array {
		$type         = self::normalize_type( $type );
		$all          = self::get_all();
		$all[ $type ] = self::normalize_part( $part, $type );
		update_option( self::OPTION, $all, false );
		return $all[ $type ];
	}

	/**
	 * @param array<string, mixed> $part
	 * @return array<string, mixed>
	 */
	public static function normalize_part( array $part, string $type ): array {
		$d = self::default_part( $type );
		$flag = static function ( $v, string $def = '0' ): string {
			if ( $v === null || $v === '' ) {
				return $def;
			}
			return ( $v === true || $v === 1 || $v === '1' || $v === 'true' || $v === 'yes' || $v === 'on' ) ? '1' : '0';
		};
		$display = sanitize_key( (string) ( $part['display'] ?? $d['display'] ) );
		if ( $type === 'single' && ( $display === '' || $display === 'all' ) ) {
			// Single template defaults to all posts (singular).
			$display = 'singular';
		}
		if ( ! in_array( $display, array( 'all', 'home', 'singular', 'none' ), true ) ) {
			$display = $type === 'single' ? 'singular' : 'all';
		}
		$source = sanitize_key( (string) ( $part['source'] ?? 'builder' ) );
		if ( ! in_array( $source, array( 'builder', 'theme' ), true ) ) {
			$source = 'builder';
		}
		$doc_in = $part['document'] ?? null;
		if ( is_array( $doc_in ) && ! empty( $doc_in['nodes'] ) && ! empty( $doc_in['rootId'] ) ) {
			$doc = DocumentSanitizer::sanitize( $doc_in )->to_array();
			// Upgrade legacy empty single (only root+empty section) to starter layout.
			if ( $type === 'single' && self::is_effectively_empty_doc( $doc ) ) {
				$doc = self::starter_single_document();
			}
		} else {
			$doc = $d['document'];
		}
		// Header: logo+menu as leaves in 12-col grid → menu stacks. Force row.
		if ( $type === 'header' && is_array( $doc ) ) {
			$doc = self::fix_header_row_layout( $doc );
		}
		$sticky_on = $part['sticky_on'] ?? $d['sticky_on'];
		if ( ! is_array( $sticky_on ) ) {
			$sticky_on = $d['sticky_on'];
		}
		$sticky_on = array_values(
			array_filter(
				array_map( 'sanitize_key', $sticky_on ),
				static function ( $v ) {
					return in_array( $v, array( 'desktop', 'tablet', 'mobile' ), true );
				}
			)
		);
		if ( ! $sticky_on ) {
			$sticky_on = array( 'desktop', 'tablet', 'mobile' );
		}

		$chrome = static function ( $v ): string {
			$v = sanitize_key( (string) $v );
			return in_array( $v, array( 'global', 'builder', 'theme', 'none' ), true ) ? $v : 'global';
		};

		return array(
			'enabled'       => $flag( $part['enabled'] ?? null, '0' ) === '1',
			'display'       => $display,
			'include_ids'   => self::sanitize_id_list( (string) ( $part['include_ids'] ?? '' ) ),
			'exclude_ids'   => self::sanitize_id_list( (string) ( $part['exclude_ids'] ?? '' ) ),
			'source'        => $source,
			'sticky'        => $flag( $part['sticky'] ?? null, '0' ),
			'sticky_bg'     => self::sanitize_color( (string) ( $part['sticky_bg'] ?? '#ffffff' ), '#ffffff' ),
			'sticky_on'     => $sticky_on,
			'transparent'   => $flag( $part['transparent'] ?? null, '0' ),
			'height'        => sanitize_text_field( (string) ( $part['height'] ?? '' ) ),
			'document'      => $doc,
			'header_source' => $chrome( $part['header_source'] ?? 'global' ),
			'footer_source' => $chrome( $part['footer_source'] ?? 'global' ),
		);
	}

	/**
	 * Convert header sections that host leaf widgets (logo, nav) from grid → row
	 * so the menu is not squeezed into a single 1/12 track.
	 *
	 * @param array<string, mixed> $doc
	 * @return array<string, mixed>
	 */
	public static function fix_header_row_layout( array $doc ): array {
		$nodes = $doc['nodes'] ?? null;
		if ( ! is_array( $nodes ) ) {
			return $doc;
		}
		foreach ( $nodes as $id => $node ) {
			if ( ! is_array( $node ) || ( $node['type'] ?? '' ) !== 'section' ) {
				continue;
			}
			$children = $node['children'] ?? array();
			if ( ! is_array( $children ) || ! $children ) {
				continue;
			}
			$col_ids  = array();
			$has_leaf = false;
			foreach ( $children as $cid ) {
				$c = $nodes[ $cid ] ?? null;
				if ( ! is_array( $c ) ) {
					continue;
				}
				$t = (string) ( $c['type'] ?? '' );
				if ( $t === 'container' ) {
					$col_ids[] = $cid;
				} elseif ( $t !== 'section' ) {
					$has_leaf = true;
				}
			}

			$settings = is_array( $node['settings'] ?? null ) ? $node['settings'] : array();
			$styles   = is_array( $node['styles'] ?? null ) ? $node['styles'] : array();
			$desk     = is_array( $styles['desktop'] ?? null ) ? $styles['desktop'] : array();

			// Multi-column header (logo | menu): force grid + 25/75 when both full.
			if ( count( $col_ids ) >= 2 ) {
				$settings['direction'] = 'grid';
				$settings['columns']   = count( $col_ids );
				$all_full              = true;
				foreach ( $col_ids as $cid ) {
					$c  = $nodes[ $cid ];
					$cs = is_array( $c['settings'] ?? null ) ? $c['settings'] : array();
					$sp = (int) ( $cs['col_span'] ?? 0 );
					if ( $sp < 1 && ! empty( $cs['width'] ) && preg_match( '/([\d.]+)/', (string) $cs['width'], $wm ) ) {
						$sp = (int) max( 1, min( 12, (int) round( ( (float) $wm[1] / 100 ) * 12 ) ) );
					}
					if ( $sp > 0 && $sp < 12 ) {
						$all_full = false;
					}
				}
				if ( $all_full && count( $col_ids ) === 2 ) {
					foreach ( array( 3, 9 ) as $i => $span ) {
						$cid = $col_ids[ $i ];
						$c   = $nodes[ $cid ];
						$cs  = is_array( $c['settings'] ?? null ) ? $c['settings'] : array();
						$cs['col_span'] = $span;
						$cs['width']    = $span === 3 ? '25%' : '75%';
						$c['settings']  = $cs;
						$nodes[ $cid ]  = $c;
					}
				}
				if ( empty( $desk['align_items'] ) ) {
					$desk['align_items'] = 'center';
				}
				unset( $desk['display'] );
				$styles['desktop'] = $desk;
				$node['settings']  = $settings;
				$node['styles']    = $styles;
				$nodes[ $id ]      = $node;
				continue;
			}

			if ( ! $has_leaf ) {
				continue;
			}
			$dir = (string) ( $settings['direction'] ?? 'grid' );
			if ( $dir === 'grid' || $dir === '' ) {
				$settings['direction'] = 'row';
				$settings['columns']   = max( 2, count( $children ) );
			}
			if ( empty( $desk['align_items'] ) ) {
				$desk['align_items'] = 'center';
			}
			if ( empty( $desk['justify_content'] ) ) {
				$desk['justify_content'] = 'space-between';
			}
			unset( $desk['display'] ); // layout from direction only
			$styles['desktop'] = $desk;
			$node['settings']  = $settings;
			$node['styles']    = $styles;
			$nodes[ $id ]      = $node;

			foreach ( $children as $cid ) {
				$c = $nodes[ $cid ] ?? null;
				if ( ! is_array( $c ) ) {
					continue;
				}
				if ( ( $c['type'] ?? '' ) === 'nav_menu' ) {
					$cs            = is_array( $c['settings'] ?? null ) ? $c['settings'] : array();
					$cs['layout']  = 'horizontal';
					$c['settings'] = $cs;
					$cst           = is_array( $c['styles'] ?? null ) ? $c['styles'] : array();
					$cd            = is_array( $cst['desktop'] ?? null ) ? $cst['desktop'] : array();
					// Fixed resize widths break flex row.
					unset( $cd['width'] );
					$cst['desktop'] = $cd;
					$c['styles']    = $cst;
					$nodes[ $cid ]  = $c;
				}
			}
		}
		$doc['nodes'] = $nodes;
		return $doc;
	}

	/**
	 * True when the document has no leaf widgets (only root / section / empty containers).
	 *
	 * @param array<string, mixed> $doc
	 */
	public static function is_effectively_empty_doc( array $doc ): bool {
		$nodes = $doc['nodes'] ?? null;
		if ( ! is_array( $nodes ) || ! $nodes ) {
			return true;
		}
		foreach ( $nodes as $node ) {
			if ( ! is_array( $node ) ) {
				continue;
			}
			$t = (string) ( $node['type'] ?? '' );
			if ( $t !== '' && $t !== 'root' && $t !== 'section' && $t !== 'container' ) {
				return false;
			}
		}
		return true;
	}

	public static function sanitize_id_list( string $raw ): string {
		$ids = array();
		foreach ( preg_split( '/[\s,;]+/', $raw ) ?: array() as $p ) {
			$n = absint( $p );
			if ( $n > 0 ) {
				$ids[] = $n;
			}
		}
		return implode( ',', array_unique( $ids ) );
	}

	private static function sanitize_color( string $raw, string $fallback ): string {
		$raw = trim( $raw );
		if ( $raw === '' ) {
			return $fallback;
		}
		if ( preg_match( '/^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/', $raw ) ) {
			return strtolower( $raw );
		}
		return $fallback;
	}

	/**
	 * Should this part render on the current request?
	 *
	 * @param array<string, mixed> $part
	 */
	public static function should_display( array $part, ?int $post_id = null ): bool {
		if ( empty( $part['enabled'] ) || ( $part['source'] ?? 'builder' ) !== 'builder' ) {
			return false;
		}
		$display = (string) ( $part['display'] ?? 'all' );
		if ( $display === 'none' ) {
			return false;
		}

		$post_id = $post_id ?? ( is_singular() ? (int) get_queried_object_id() : 0 );

		$exclude = array_filter( array_map( 'absint', explode( ',', (string) ( $part['exclude_ids'] ?? '' ) ) ) );
		if ( $post_id && in_array( $post_id, $exclude, true ) ) {
			return false;
		}

		$include = array_filter( array_map( 'absint', explode( ',', (string) ( $part['include_ids'] ?? '' ) ) ) );
		if ( $include ) {
			return $post_id > 0 && in_array( $post_id, $include, true );
		}

		if ( $display === 'all' ) {
			return true;
		}
		if ( $display === 'home' ) {
			return is_front_page() || is_home();
		}
		if ( $display === 'singular' ) {
			return is_singular();
		}
		return true;
	}

	/**
	 * Page-level override from document settings.
	 * header_source / footer_source: theme | builder | none | global (default)
	 */
	/**
	 * Resolve header/footer source for the current singular view.
	 * When the page uses the Single template (no own layout), honor Single chrome settings.
	 */
	public static function resolve_source_for_current_view( string $type ): string {
		$type = $type === 'footer' ? 'footer' : 'header';
		$doc  = self::current_builder_doc();
		if ( $doc ) {
			return self::resolve_source_for_page( $type, $doc );
		}
		// Singular without own builder layout → Single template chrome.
		if ( is_singular( array( 'post', 'page' ) ) ) {
			$id = (int) get_queried_object_id();
			if ( $id > 0 && ! ( new \AuraBuilder\Storage\DocumentRepository() )->is_enabled( $id ) ) {
				$single = self::get_part( 'single' );
				if ( ! empty( $single['enabled'] ) && self::should_display( $single, $id ) ) {
					$key = $type === 'footer' ? 'footer_source' : 'header_source';
					$v   = sanitize_key( (string) ( $single[ $key ] ?? 'global' ) );
					if ( in_array( $v, array( 'global', 'builder', 'theme', 'none' ), true ) ) {
						return $v;
					}
				}
			}
		}
		return 'global';
	}

	public static function resolve_source_for_page( string $type, ?Document $doc ): string {
		if ( ! $doc ) {
			return 'global';
		}
		$key = $type === 'footer' ? 'footer_source' : 'header_source';
		$v   = sanitize_key( (string) ( $doc->settings[ $key ] ?? 'global' ) );
		if ( ! in_array( $v, array( 'global', 'theme', 'builder', 'none' ), true ) ) {
			return 'global';
		}
		// Legacy showHeader/showFooter false → none.
		if ( $type === 'header' && empty( $doc->settings['showHeader'] ) && $v === 'global' ) {
			// keep global but ContentFilter may hide theme — leave as global
		}
		return $v;
	}

	/**
	 * Render HTML for a theme part.
	 *
	 * @param array<string, mixed> $part
	 */
	public static function render_part_html( array $part, bool $editor = false ): string {
		$doc_arr = $part['document'] ?? null;
		if ( ! is_array( $doc_arr ) ) {
			return '';
		}
		$doc = DocumentSanitizer::sanitize( $doc_arr );
		$out = ( new DocumentRenderer() )->render(
			$doc,
			array(
				'editor' => $editor,
				'postId' => is_singular() ? (int) get_queried_object_id() : 0,
			)
		);
		$type  = ! empty( $part['document']['metadata']['kind'] ) && str_contains( (string) $part['document']['metadata']['kind'], 'footer' )
			? 'footer'
			: 'header';
		// Prefer explicit type from caller via classes on wrap.
		$cls = array( 'ab-theme-builder-part', 'ab-theme-builder-' . ( isset( $part['_type'] ) ? (string) $part['_type'] : 'header' ) );
		if ( ! empty( $part['sticky'] ) && $part['sticky'] === '1' ) {
			$cls[] = 'ab-theme-builder--sticky';
		}
		if ( ! empty( $part['transparent'] ) && $part['transparent'] === '1' ) {
			$cls[] = 'ab-theme-builder--transparent';
		}
		$html  = '<' . ( ( $part['_type'] ?? '' ) === 'footer' ? 'footer' : 'header' );
		$html .= ' class="' . esc_attr( implode( ' ', $cls ) ) . '" data-ab-theme-part="' . esc_attr( (string) ( $part['_type'] ?? 'header' ) ) . '"';
		if ( ! empty( $part['sticky_bg'] ) ) {
			$html .= ' style="--ab-tb-sticky-bg:' . esc_attr( (string) $part['sticky_bg'] ) . '"';
		}
		$html .= '>';
		// Part CSS is enqueued via print_part_css → Assets::inline_style (no inline <style> in HTML).
		$html .= \AuraBuilder\Support\Assets::kses_builder( (string) ( $out['html'] ?? '' ) );
		$html .= ( ( $part['_type'] ?? '' ) === 'footer' ) ? '</footer>' : '</header>';
		return $html;
	}

	public static function hooks(): void {
		// Inject early in body / late before footer close — use block filters for FSE.
		add_filter( 'render_block', array( self::class, 'filter_template_part' ), 5, 2 );
		add_action( 'wp_body_open', array( self::class, 'maybe_print_header' ), 1 );
		add_action( 'wp_footer', array( self::class, 'maybe_print_footer' ), 1 );
		add_action( 'wp_enqueue_scripts', array( self::class, 'enqueue_front' ), 25 );
		add_action( 'wp_enqueue_scripts', array( self::class, 'print_part_css' ), 26 );
	}

	/**
	 * Hide theme header/footer template parts when builder owns them.
	 *
	 * @param array<string, mixed> $block
	 */
	public static function filter_template_part( string $content, array $block ): string {
		$name = (string) ( $block['blockName'] ?? '' );
		if ( $name !== 'core/template-part' ) {
			return $content;
		}
		$slug = (string) ( $block['attrs']['slug'] ?? '' );
		if ( $slug === 'header' && self::should_replace_on_front( 'header' ) ) {
			do_action( 'aptb_theme_header_printed' );
			return self::front_html( 'header' );
		}
		if ( $slug === 'footer' && self::should_replace_on_front( 'footer' ) ) {
			return self::front_html( 'footer' );
		}
		return $content;
	}

	public static function should_replace_on_front( string $type ): bool {
		$part = self::get_part( $type );
		if ( empty( $part['enabled'] ) || ( $part['source'] ?? '' ) !== 'builder' ) {
			return false;
		}
		// Page / Single chrome override.
		$src = self::resolve_source_for_current_view( $type );
		if ( $src === 'none' || $src === 'theme' ) {
			return false;
		}
		if ( $src === 'builder' ) {
			return true;
		}
		// global
		return self::should_display( $part );
	}

	private static function current_builder_doc(): ?Document {
		if ( ! is_singular() ) {
			return null;
		}
		$id = (int) get_queried_object_id();
		if ( $id <= 0 ) {
			return null;
		}
		$repo = new \AuraBuilder\Storage\DocumentRepository();
		if ( ! $repo->is_enabled( $id ) ) {
			return null;
		}
		return $repo->get( $id );
	}

	public static function front_html( string $type ): string {
		$part = self::get_part( $type );
		$part['_type'] = $type;
		if ( ! self::should_replace_on_front( $type ) ) {
			return '';
		}
		return self::render_part_html( $part, false );
	}

	/** Fallback inject if theme doesn't use template-part blocks. */
	public static function maybe_print_header(): void {
		// Only if FSE didn't already render (heuristic: not printed).
		if ( did_action( 'aptb_theme_header_printed' ) ) {
			return;
		}
		// Prefer block filter path; skip body_open for block themes that use template parts.
		if ( function_exists( 'wp_is_block_theme' ) && wp_is_block_theme() ) {
			return;
		}
		if ( ! self::should_replace_on_front( 'header' ) ) {
			return;
		}
		echo self::front_html( 'header' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
		do_action( 'aptb_theme_header_printed' );
	}

	public static function maybe_print_footer(): void {
		if ( function_exists( 'wp_is_block_theme' ) && wp_is_block_theme() ) {
			return;
		}
		if ( ! self::should_replace_on_front( 'footer' ) ) {
			return;
		}
		echo self::front_html( 'footer' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
	}

	public static function print_part_css(): void {
		$css = '';
		foreach ( array( 'header', 'footer' ) as $type ) {
			$part = self::get_part( $type );
			if ( empty( $part['enabled'] ) ) {
				continue;
			}
			// Document styles (grid spans, backgrounds, typography) — required for layout.
			// Previously discarded when rendering HTML → menu stacked in 1/12 columns.
			$doc_arr = is_array( $part['document'] ?? null ) ? $part['document'] : null;
			if ( is_array( $doc_arr ) && ! empty( $doc_arr['nodes'] ) ) {
				try {
					$doc = DocumentSanitizer::sanitize( $doc_arr );
					$out = ( new DocumentRenderer() )->render(
						$doc,
						array(
							'editor' => false,
							'postId' => is_singular() ? (int) get_queried_object_id() : 0,
						)
					);
					$part_css = trim( (string) ( $out['css'] ?? '' ) );
					if ( $part_css !== '' ) {
						// Scope under theme part so page rules don't bleed into wrong trees.
						$scope = '.ab-theme-builder-' . $type;
						// Keep media queries intact: prefix selectors outside @media only
						// by emitting as-is (selectors already use .ab-node-* unique ids).
						$css .= "\n/* theme part: {$type} */\n" . $part_css . "\n";
					}
				} catch ( \Throwable $e ) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch
					// Skip broken part CSS rather than fatal the front.
				}
			}
			if ( ( $part['sticky'] ?? '0' ) === '1' && $type === 'header' ) {
				$bg   = esc_attr( (string) ( $part['sticky_bg'] ?? '#fff' ) );
				$css .= '.ab-theme-builder-header.ab-theme-builder--sticky,'
					. 'header.ab-theme-builder--sticky{'
					. 'position:sticky;top:0;z-index:500;background:var(--ab-tb-sticky-bg,' . $bg . ');'
					. 'box-shadow:0 2px 12px rgba(15,23,42,.08);}'
					. 'body.admin-bar .ab-theme-builder-header.ab-theme-builder--sticky{top:32px;}';
			}
			if ( ( $part['transparent'] ?? '0' ) === '1' && $type === 'header' ) {
				$css .= '.ab-theme-builder-header.ab-theme-builder--transparent{'
					. 'position:absolute;left:0;right:0;top:0;z-index:500;background:transparent;}'
					. 'body.admin-bar .ab-theme-builder-header.ab-theme-builder--transparent{top:32px;}';
			}
			// Hide original theme header when builder replaces.
			if ( self::should_replace_on_front( $type ) ) {
				// Already replaced via filter; extra hide for classic dual output.
				if ( $type === 'header' ) {
					$css .= 'body.ab-tb-header-on .site-header:not(.ab-theme-builder-header),'
						. 'body.ab-tb-header-on #masthead:not(.ab-theme-builder-header){display:none!important;}';
				}
			}
		}
		// Always inject full-bleed rules for active builder header/footer so theme
		// has-global-padding / constrained layout cannot leave white side strips.
		foreach ( array( 'header', 'footer' ) as $t_chk ) {
			if ( self::should_replace_on_front( $t_chk ) ) {
				$css .= '.ab-theme-builder-' . $t_chk . ','
					. $t_chk . '.ab-theme-builder-' . $t_chk . '{'
					. 'width:100%!important;max-width:none!important;'
					. 'margin-left:0!important;margin-right:0!important;'
					. 'padding-left:0!important;padding-right:0!important;'
					. 'box-sizing:border-box!important;}'
					. 'body .wp-site-blocks>.ab-theme-builder-' . $t_chk . ','
					. 'body .has-global-padding>.ab-theme-builder-' . $t_chk . '{'
					. 'width:100vw!important;max-width:100vw!important;'
					. 'margin-left:calc(50% - 50vw)!important;'
					. 'margin-right:calc(50% - 50vw)!important;}'
					. '.ab-theme-builder-' . $t_chk . ' .ab-document,'
					. '.ab-theme-builder-' . $t_chk . ' .ab-type-root{'
					. 'width:100%!important;max-width:none!important;'
					. 'padding-left:0!important;padding-right:0!important;'
					. 'margin-left:0!important;margin-right:0!important;}'
					. '.ab-theme-builder-' . $t_chk . ' .ab-type-root.ab-root--wide{'
					. 'max-width:var(--ab-root-max,1200px)!important;'
					. 'margin-left:auto!important;margin-right:auto!important;}'
					. '.ab-theme-builder-' . $t_chk . ' .ab-type-section{'
					. 'width:100%!important;max-width:none!important;'
					. 'margin:0!important;margin-block:0!important;}'
					. '.ab-theme-builder-' . $t_chk . '{'
					. '--wp--style--block-gap:0px;}'
					. '.ab-theme-builder-' . $t_chk . ' .ab-type-root{'
					. 'gap:0!important;row-gap:0!important;'
					. 'display:flex!important;flex-direction:column!important;}'
					. '.ab-theme-builder-' . $t_chk . ' .ab-type-root>.ab-type-section{'
					. 'margin:0!important;margin-block-start:0!important;margin-block-end:0!important;}';
			}
		}
		if ( $css !== '' ) {
			\AuraBuilder\Support\Assets::inline_style( 'auranet-ptb-theme-builder', $css, array( 'aura-builder-front' ) );
		}
	}

	public static function enqueue_front(): void {
		if ( ! self::should_replace_on_front( 'header' ) && ! self::should_replace_on_front( 'footer' ) ) {
			return;
		}
		\AuraBuilder\Support\Assets::force_https_assets();
		$front_css = AURA_BUILDER_PATH . 'frontend/css/front.css';
		if ( is_file( $front_css ) ) {
			wp_register_style(
				'aura-builder-front',
				\AuraBuilder\Support\Assets::url( 'frontend/css/front.css' ),
				array(),
				(string) filemtime( $front_css )
			);
			wp_enqueue_style( 'aura-builder-front' );
		}
		$front_js = AURA_BUILDER_PATH . 'frontend/js/front-widgets.js';
		if ( is_file( $front_js ) ) {
			wp_register_script(
				'aura-builder-front-widgets',
				\AuraBuilder\Support\Assets::url( 'frontend/js/front-widgets.js' ),
				array(),
				(string) filemtime( $front_js ),
				true
			);
			wp_enqueue_script( 'aura-builder-front-widgets' );
		}
	}
}
