<?php
/**
 * Editor page handler.
 *
 * Manages the full-screen page builder editor.
 * This page hides all WordPress admin chrome and renders
 * an Inertia.js-powered React application for the builder.
 *
 * @package Antimanual_Builder
 * @since   1.0.0
 */

// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Class AMBLD_Admin_Editor_Page
 *
 * Handles the standalone editor page that replaces the standard
 * WordPress admin layout with a full-screen builder experience.
 *
 * @since 1.0.0
 */
class AMBLD_Admin_Editor_Page {

	/**
	 * Constructor. Sets up editor-specific hooks.
	 *
	 * @since 1.0.0
	 */
	public function __construct() {
		add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_editor_assets' ) );
		add_action( 'admin_init', array( $this, 'maybe_hide_admin_chrome' ) );
		add_action( 'admin_init', array( $this, 'handle_inertia_request' ), 1 );
	}

	/**
	 * Determine whether the current request is for the builder editor page.
	 *
	 * @since  1.0.0
	 * @return bool True when loading the editor page.
	 */
	private function is_editor_page_request() {
		// phpcs:ignore WordPress.Security.NonceVerification.Recommended
		return isset( $_GET['page'] ) && 'ambld-editor' === sanitize_text_field( wp_unslash( $_GET['page'] ) );
	}

	/**
	 * Handle Inertia XHR requests for the editor page.
	 *
	 * @since  1.0.0
	 * @return void
	 */
	public function handle_inertia_request() {
		if ( ! AMBLD_Admin_Inertia::is_inertia_request() ) {
			return;
		}

		// phpcs:ignore WordPress.Security.NonceVerification.Recommended
		$page_slug = isset( $_GET['page'] ) ? sanitize_text_field( wp_unslash( $_GET['page'] ) ) : '';

		if ( 'ambld-editor' !== $page_slug ) {
			return;
		}

		$props = $this->get_editor_props();
		AMBLD_Admin_Inertia::render( 'Editor', $props );
	}

	/**
	 * Get props for the editor Inertia page.
	 *
	 * @since  1.0.0
	 * @return array
	 */
	private function get_editor_props() {
		$post_data = $this->get_current_post_data();
		$design_defaults = get_option( 'ambld_design_defaults', array() );

		return array(
			'restUrl'       => esc_url_raw( rest_url( 'ambld/v1/' ) ),
			'nonce'         => wp_create_nonce( 'wp_rest' ),
			'adminUrl'      => esc_url( admin_url() ),
			'dashboardUrl'  => esc_url( admin_url( 'admin.php?page=ambld-dashboard' ) ),
			// esc_url_raw, not esc_url: these are JSON props consumed by React,
			// and esc_url's &#038; entity turns the query string into a fragment.
			'editorUrl'     => esc_url_raw( admin_url( 'admin.php?page=ambld-editor' ) ),
			'componentEditorUrl' => esc_url_raw( admin_url( 'admin.php?page=ambld-editor&entity_type=' . rawurlencode( AMBLD_Post_Type_Builder_Component::POST_TYPE ) ) ),
			'templateEditorUrl'  => esc_url_raw( admin_url( 'admin.php?page=ambld-editor&entity_type=' . rawurlencode( AMBLD_Post_Type_Builder_Template::POST_TYPE ) ) ),
			'pluginUrl'     => esc_url( AMBLD_PLUGIN_URL ),
			'uploadsUrl'    => esc_url( wp_upload_dir()['baseurl'] . '/' . AMBLD_UPLOAD_DIR . '/' ),
			'version'       => AMBLD_VERSION,
			// Pro gate for editor-side premium features (e.g. Dynamic Content).
			// esc_url_raw (not esc_url) so the JSON prop keeps a clean query string.
			'isPro'         => ambld_is_pro(),
			'upgradeUrl'    => esc_url_raw( ambld_pro_upgrade_url() ),
			'entityType'    => $this->get_requested_entity_type(),
			'templateKind'  => $this->get_requested_template_kind(),
			'editorAction'  => $this->get_requested_editor_action(),
			'migrationBehavior'  => get_option( 'ambld_migration_behavior', 'duplicate' ),
			'requestedBehavior'  => $this->get_requested_migration_behavior(),
			'requestedMethod'    => $this->get_requested_migration_method(),
			'postData'      => $post_data,
			'aiSettings'    => $this->get_safe_ai_settings(),
			'designDefaults' => $design_defaults,
			'headerTemplates' => $this->get_template_options( 'header' ),
			'footerTemplates' => $this->get_template_options( 'footer' ),
			'themePageTemplates' => $this->get_theme_page_templates( $post_data ),
			'antimanualKnowledgeBase' => AMBLD_Antimanual_Knowledge_Base::get_payload(),
			'blocks'        => $this->get_registered_blocks(),
			'user'          => array(
				'id'          => get_current_user_id(),
				'name'        => wp_get_current_user()->display_name,
				'canPublish'  => current_user_can( 'publish_pages' ),
				'canEditCSS'  => true,
				'canAccessCodeEditor' => true,
			),
			'siteInfo'      => array(
				'title' => get_bloginfo( 'name' ),
				'url'   => get_site_url(),
			),
		);
	}

	/**
	 * Conditionally hide WordPress admin chrome for the editor page.
	 *
	 * @since  1.0.0
	 * @return void
	 */
	public function maybe_hide_admin_chrome() {
		if ( ! $this->is_editor_page_request() ) {
			return;
		}

		global $title;

		if ( empty( $title ) ) {
			$title = __( 'Editor', 'antimanual-builder' );
		}

		// Remove admin bar.
		add_filter( 'show_admin_bar', '__return_false' );

		// Add full-screen class to body.
		add_filter( 'admin_body_class', array( $this, 'add_body_class' ) );

		// Hide admin menu and bar via enqueued inline CSS.
		add_action( 'admin_enqueue_scripts', array( $this, 'hide_chrome_css' ) );
	}

	/**
	 * Add custom body class for the editor.
	 *
	 * @since  1.0.0
	 * @param  string $classes Space-separated list of body classes.
	 * @return string Modified body classes.
	 */
	public function add_body_class( $classes ) {
		return ( $classes ?? '' ) . ' ambld-editor-page ambld-fullscreen';
	}

	/**
	 * Enqueue CSS to hide WordPress admin chrome on the editor page.
	 *
	 * Uses wp_add_inline_style() attached to the 'ambld-editor' stylesheet
	 * handle instead of outputting a raw <style> tag.
	 *
	 * @since  1.0.0
	 * @return void
	 */
	public function hide_chrome_css() {
		$css = '
			/* Hide all WordPress admin chrome for the builder */
			#wpadminbar,
			#adminmenumain,
			#adminmenuback,
			#adminmenuwrap,
			#wpfooter,
			.notice,
			.update-nag,
			.updated,
			.error:not(.ambld-error) {
				display: none !important;
			}

			html.wp-toolbar {
				padding-top: 0 !important;
				overflow:hidden;
			}

			#wpcontent,
			#wpbody,
			#wpbody-content {
				margin-left: 0 !important;
				padding: 0 !important;
				float: none !important;
			}

			.ambld-editor-page #wpcontent {
				height: 100vh;
				overflow: hidden;
			}

			.ambld-editor-page #wpbody-content {
				height: 100vh;
				overflow: hidden;
			}

			#ambld-editor-root {
				width: 100vw;
				height: 100vh;
				position: fixed;
				top: 0;
				left: 0;
				z-index: 99999;
			}
		';

		// Attach to ambld-editor if already registered, otherwise use a standalone handle.
		if ( wp_style_is( 'ambld-editor', 'registered' ) ) {
			wp_add_inline_style( 'ambld-editor', $css );
		} else {
			wp_register_style( 'ambld-editor-chrome', false, array(), AMBLD_VERSION );
			wp_enqueue_style( 'ambld-editor-chrome' );
			wp_add_inline_style( 'ambld-editor-chrome', $css );
		}
	}

	/**
	 * Enqueue editor-specific assets.
	 *
	 * @since  1.0.0
	 * @param  string $hook_suffix The current admin page hook suffix.
	 * @return void
	 */
	public function enqueue_editor_assets( $hook_suffix ) {
		// Only enqueue on the editor page.
		if ( 'admin_page_ambld-editor' !== $hook_suffix ) {
			return;
		}

		$asset_file = AMBLD_PLUGIN_DIR . 'build/editor.asset.php';
		$asset      = file_exists( $asset_file ) ? require $asset_file : array(
			'dependencies' => array( 'wp-element', 'wp-components', 'wp-i18n', 'wp-api-fetch' ),
			'version'      => AMBLD_VERSION,
		);

		$asset_dependencies = isset( $asset['dependencies'] ) && is_array( $asset['dependencies'] )
			? $asset['dependencies']
			: array();
		$script_dependencies = array();
		$style_dependencies  = array();

		foreach ( $asset_dependencies as $dependency ) {
			if ( ! is_string( $dependency ) || '' === $dependency ) {
				continue;
			}

			if ( preg_match( '#^(wp-[^/]+)/build-style/style(?:-rtl)?\\.css$#', $dependency, $matches ) ) {
				$style_dependencies[] = $matches[1];
				continue;
			}

			if ( str_ends_with( $dependency, '.css' ) ) {
				continue;
			}

			$script_dependencies[] = $dependency;
		}

		if ( in_array( 'wp-block-editor', $script_dependencies, true ) ) {
			$style_dependencies[] = 'wp-block-editor';
		}

		$style_dependencies = array_values( array_unique( $style_dependencies ) );
		$script_dependencies = array_values( array_unique( $script_dependencies ) );

		// Editor styles.
		wp_enqueue_style(
			'ambld-editor',
			AMBLD_PLUGIN_URL . 'build/editor.css',
			$style_dependencies,
			$asset['version']
		);

		// Editor script.
		wp_enqueue_script(
			'ambld-editor',
			AMBLD_PLUGIN_URL . 'build/editor.js',
			$script_dependencies,
			$asset['version'],
			true
		);

		// WordPress media uploader.
		wp_enqueue_media();
	}

	/**
	 * Get current post data for editing.
	 *
	 * @since  1.0.0
	 * @return array|null Post data array or null if creating new.
	 */
	private function get_current_post_data() {
		// phpcs:ignore WordPress.Security.NonceVerification.Recommended
		$post_id = isset( $_GET['post_id'] ) ? absint( $_GET['post_id'] ) : 0;

		if ( ! $post_id ) {
			return null;
		}

		$post = get_post( $post_id );
		if (
			! $post ||
			! in_array( $post->post_type, array( AMBLD_Page_Integration::POST_TYPE, AMBLD_Post_Type_Builder_Component::POST_TYPE, AMBLD_Post_Type_Builder_Template::POST_TYPE ), true ) ||
			! current_user_can( 'edit_post', $post_id )
		) {
			return null;
		}

		$blocks        = get_post_meta( $post_id, '_ambld_blocks', true );
		$rendered_html = get_post_meta( $post_id, '_ambld_rendered_html', true );

		if ( empty( $blocks ) && ! empty( $post->post_content ) ) {
			$blocks = wp_json_encode( array(
				array(
					'id'      => 'ambld-' . uniqid(),
					'type'    => 'html',
					'content' => array( 'html' => $post->post_content ),
					'styles'  => new stdClass(),
				)
			) );

			if ( empty( $rendered_html ) ) {
				$rendered_html = $post->post_content;
			}
		}

		$is_component = AMBLD_Post_Type_Builder_Component::POST_TYPE === $post->post_type;
		$is_template  = AMBLD_Post_Type_Builder_Template::POST_TYPE === $post->post_type;
		$template_kind = $is_template
			? sanitize_key( (string) get_post_meta( $post_id, '_ambld_template_kind', true ) )
			: '';
		if ( ! in_array( $template_kind, array( 'header', 'footer' ), true ) ) {
			$template_kind = '';
		}

		$import_document  = get_post_meta( $post_id, '_ambld_import_document', true );
		$imported_js_urls = array();
		$raw_js_urls      = get_post_meta( $post_id, '_ambld_imported_js_urls', true );
		if ( ! empty( $raw_js_urls ) ) {
			$decoded = json_decode( $raw_js_urls, true );
			if ( is_array( $decoded ) ) {
				$imported_js_urls = array_values( array_filter( $decoded, 'is_string' ) );
			}
		}

		// `customJs` is the canonical inline-JS field; fall back to the legacy
		// imported-inline-JS meta so older imported pages still load their JS.
		$custom_js = (string) get_post_meta( $post_id, '_ambld_custom_js', true );
		if ( '' === trim( $custom_js ) ) {
			$custom_js = (string) get_post_meta( $post_id, '_ambld_imported_inline_js', true );
		}

		// Per-script manifest (editable per-file JS for cloned imported pages).
		$imported_scripts = array();
		$raw_scripts      = get_post_meta( $post_id, '_ambld_imported_scripts', true );
		if ( ! empty( $raw_scripts ) ) {
			$decoded_scripts = json_decode( $raw_scripts, true );
			if ( is_array( $decoded_scripts ) ) {
				$imported_scripts = $decoded_scripts;
			}
		}

		// External CDN resources (CSS/JS attached via the CDN tab or AI).
		$cdn_resources = array();
		$raw_cdn       = get_post_meta( $post_id, '_ambld_cdn_resources', true );
		if ( ! empty( $raw_cdn ) ) {
			$decoded_cdn = json_decode( $raw_cdn, true );
			if ( is_array( $decoded_cdn ) ) {
				$cdn_resources = $decoded_cdn;
			}
		}

		return array(
			'id'               => $post->ID,
			'entityType'       => $is_template ? 'template' : ( $is_component ? 'component' : 'page' ),
			'templateKind'     => $template_kind ? $template_kind : $this->get_requested_template_kind(),
			'title'            => $post->post_title,
			'slug'             => $post->post_name,
			'status'           => $post->post_status,
			'excerpt'          => $post->post_excerpt,
			'permalink'        => ( $is_component || $is_template ) ? null : get_permalink( $post_id ),
			'date'             => $post->post_date,
			'modified'         => $post->post_modified,
			'blocks'           => $blocks,
			'renderedHtml'     => $rendered_html,
			'customCss'        => (string) get_post_meta( $post_id, '_ambld_custom_css', true ),
			'customJs'         => $custom_js,
			'styleMap'         => get_post_meta( $post_id, '_ambld_style_map', true ),
			'pageSettings'     => get_post_meta( $post_id, '_ambld_page_settings', true ),
			'designOverrides'  => get_post_meta( $post_id, '_ambld_design_overrides', true ),
			'isBuilderPage'    => ( $is_component || $is_template ) ? true : AMBLD_Page_Integration::is_builder_page( $post ),
			'canMigrate'       => ( $is_component || $is_template ) ? false : AMBLD_Page_Integration::has_migratable_content( $post ),
			'importDocument'   => $import_document ?: null,
			'importedJsUrls'   => $imported_js_urls,
			'importedInlineJs' => (string) get_post_meta( $post_id, '_ambld_imported_inline_js', true ),
			'importedScripts'  => $imported_scripts,
			'cdnResources'     => $cdn_resources,
		);
	}

	/**
	 * Get the requested editor entity type from the URL.
	 *
	 * @since 1.0.0
	 * @return string
	 */
	private function get_requested_entity_type() {
		// phpcs:ignore WordPress.Security.NonceVerification.Recommended
		$entity_type = isset( $_GET['entity_type'] ) ? sanitize_key( wp_unslash( $_GET['entity_type'] ) ) : '';

		if ( AMBLD_Post_Type_Builder_Component::POST_TYPE === $entity_type ) {
			return 'component';
		}

		if ( AMBLD_Post_Type_Builder_Template::POST_TYPE === $entity_type ) {
			return 'template';
		}

		return 'page';
	}

	/**
	 * Get requested template kind from URL.
	 *
	 * @since  1.0.0
	 * @return string
	 */
	private function get_requested_template_kind() {
		// phpcs:ignore WordPress.Security.NonceVerification.Recommended
		$template_kind = isset( $_GET['template_kind'] ) ? sanitize_key( wp_unslash( $_GET['template_kind'] ) ) : '';

		return in_array( $template_kind, array( 'header', 'footer' ), true ) ? $template_kind : '';
	}

	/**
	 * Get the requested editor action from the URL.
	 *
	 * @since  1.0.0
	 * @return string
	 */
	private function get_requested_editor_action() {
		// phpcs:ignore WordPress.Security.NonceVerification.Recommended
		$action = isset( $_GET['ambld_action'] ) ? sanitize_text_field( wp_unslash( $_GET['ambld_action'] ) ) : 'edit';

		return 'migrate' === $action ? 'migrate' : 'edit';
	}

	/**
	 * Get the pre-chosen migration behavior from the URL, when present.
	 *
	 * Set by the click-time launcher (`&ambld_behavior=replace|duplicate`) so the
	 * editor migrates immediately without re-asking. Empty when absent — the
	 * editor then falls back to showing its own choice modal.
	 *
	 * @since  1.0.0
	 * @return string `replace`, `duplicate`, or '' when not specified.
	 */
	private function get_requested_migration_behavior() {
		// phpcs:ignore WordPress.Security.NonceVerification.Recommended
		$behavior = isset( $_GET['ambld_behavior'] ) ? sanitize_key( wp_unslash( $_GET['ambld_behavior'] ) ) : '';

		return in_array( $behavior, array( 'replace', 'duplicate' ), true ) ? $behavior : '';
	}

	/**
	 * Get the pre-chosen migration method from the URL, when present.
	 *
	 * `snapshot` keeps the faithful copy; `clean` runs the AI-assisted
	 * deterministic rebuild. Set by the click-time launcher
	 * (`&ambld_method=snapshot|clean`). Empty when absent — the editor then
	 * falls back to its in-editor choice modal.
	 *
	 * @since  1.0.0
	 * @return string `snapshot`, `clean`, or '' when not specified.
	 */
	private function get_requested_migration_method() {
		// phpcs:ignore WordPress.Security.NonceVerification.Recommended
		$method = isset( $_GET['ambld_method'] ) ? sanitize_key( wp_unslash( $_GET['ambld_method'] ) ) : '';

		return in_array( $method, array( 'snapshot', 'clean' ), true ) ? $method : '';
	}

	/**
	 * Get AI settings with sensitive data removed.
	 *
	 * @since  1.0.0
	 * @return array Safe AI settings (no API keys).
	 */
	private function get_safe_ai_settings() {
		return AMBLD_Antimanual_Ai_Provider::get_settings_payload();
	}

	/**
	 * Get registered blocks for the editor.
	 *
	 * @since  1.0.0
	 * @return array List of available block types.
	 */
	private function get_registered_blocks() {
		/**
		 * Filter the list of registered blocks.
		 *
		 * @since 1.0.0
		 * @param array $blocks List of block definitions.
		 */
		return apply_filters( 'ambld_registered_blocks', $this->get_core_blocks() );
	}

	/**
	 * Get core block definitions.
	 *
	 * @since  1.0.0
	 * @return array Core block definitions.
	 */
	private function get_core_blocks() {
		return array(
			// Layout blocks.
			array(
				'type'     => 'section',
				'label'    => __( 'Section', 'antimanual-builder' ),
				'icon'     => 'layout',
				'category' => 'layout',
				'isLayout' => true,
			),
			array(
				'type'     => 'row',
				'label'    => __( 'Row', 'antimanual-builder' ),
				'icon'     => 'columns',
				'category' => 'layout',
				'isLayout' => true,
			),
			array(
				'type'     => 'column',
				'label'    => __( 'Column', 'antimanual-builder' ),
				'icon'     => 'align-left',
				'category' => 'layout',
				'isLayout' => true,
			),
			array(
				'type'     => 'container',
				'label'    => __( 'Container', 'antimanual-builder' ),
				'icon'     => 'align-center',
				'category' => 'layout',
				'isLayout' => true,
			),

			// Basic blocks.
			array(
				'type'     => 'heading',
				'label'    => __( 'Heading', 'antimanual-builder' ),
				'icon'     => 'heading',
				'category' => 'basic',
			),
			array(
				'type'     => 'paragraph',
				'label'    => __( 'Paragraph', 'antimanual-builder' ),
				'icon'     => 'text',
				'category' => 'basic',
			),
			array(
				'type'     => 'image',
				'label'    => __( 'Image', 'antimanual-builder' ),
				'icon'     => 'image',
				'category' => 'basic',
			),
			array(
				'type'     => 'button',
				'label'    => __( 'Button', 'antimanual-builder' ),
				'icon'     => 'button',
				'category' => 'basic',
			),
			array(
				'type'     => 'spacer',
				'label'    => __( 'Spacer', 'antimanual-builder' ),
				'icon'     => 'spacing',
				'category' => 'basic',
			),
			array(
				'type'     => 'divider',
				'label'    => __( 'Divider', 'antimanual-builder' ),
				'icon'     => 'minus',
				'category' => 'basic',
			),
			array(
				'type'     => 'video',
				'label'    => __( 'Video', 'antimanual-builder' ),
				'icon'     => 'video',
				'category' => 'basic',
			),
			array(
				'type'     => 'icon',
				'label'    => __( 'Icon', 'antimanual-builder' ),
				'icon'     => 'star',
				'category' => 'basic',
			),
			array(
				'type'     => 'html',
				'label'    => __( 'HTML', 'antimanual-builder' ),
				'icon'     => 'code',
				'category' => 'basic',
			),
			array(
				'type'     => 'list',
				'label'    => __( 'List', 'antimanual-builder' ),
				'icon'     => 'list',
				'category' => 'basic',
			),

			// Advanced blocks.
			array(
				'type'     => 'tabs',
				'label'    => __( 'Tabs', 'antimanual-builder' ),
				'icon'     => 'table',
				'category' => 'advanced',
			),
			array(
				'type'     => 'accordion',
				'label'    => __( 'Accordion', 'antimanual-builder' ),
				'icon'     => 'arrow-down',
				'category' => 'advanced',
			),
			array(
				'type'     => 'slider',
				'label'    => __( 'Slider', 'antimanual-builder' ),
				'icon'     => 'slides',
				'category' => 'advanced',
			),
			array(
				'type'     => 'form',
				'label'    => __( 'Form', 'antimanual-builder' ),
				'icon'     => 'feedback',
				'category' => 'advanced',
			),
			array(
				'type'     => 'testimonial',
				'label'    => __( 'Testimonial', 'antimanual-builder' ),
				'icon'     => 'format-quote',
				'category' => 'advanced',
			),
			array(
				'type'     => 'pricing-table',
				'label'    => __( 'Pricing Table', 'antimanual-builder' ),
				'icon'     => 'money-alt',
				'category' => 'advanced',
			),
			array(
				'type'     => 'counter',
				'label'    => __( 'Counter', 'antimanual-builder' ),
				'icon'     => 'plus-alt',
				'category' => 'advanced',
			),
			array(
				'type'     => 'progress-bar',
				'label'    => __( 'Progress Bar', 'antimanual-builder' ),
				'icon'     => 'minus',
				'category' => 'advanced',
			),
			array(
				'type'     => 'social-icons',
				'label'    => __( 'Social Icons', 'antimanual-builder' ),
				'icon'     => 'share',
				'category' => 'advanced',
			),
			array(
				'type'     => 'map',
				'label'    => __( 'Map', 'antimanual-builder' ),
				'icon'     => 'location',
				'category' => 'advanced',
			),
		);
	}

	/**
	 * Render the editor page using Inertia.
	 *
	 * @since  1.0.0
	 * @return void
	 */
	public function render() {
		if ( ! current_user_can( 'edit_pages' ) ) {
			wp_die( esc_html__( 'You do not have permission to access the page builder.', 'antimanual-builder' ) );
		}

		$props = $this->get_editor_props();
		// JSON_HEX_* flags encode `<`, `>`, `&`, `'`, `"` inside string values as
		// `\uXXXX`, which keeps pre-existing HTML entities (e.g. `&quot;`) intact
		// when the payload is round-tripped through `esc_attr()` and the browser
		// HTML parser. Without these flags, an `&` inside a string value would not
		// be re-encoded by `esc_attr()` (which sets `$double_encode = false`) and
		// the browser would decode `&quot;` back to `"`, corrupting the JSON.
		$page = array(
			'component' => 'Editor',
			'props'     => $props,
			'url'       => isset( $_SERVER['REQUEST_URI'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) ) : '/',
			'version'   => AMBLD_VERSION,
		);
		echo '<div id="ambld-editor-root" data-page="' . esc_attr( wp_json_encode( $page, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT ) ) . '"></div>';
	}

	/**
	 * Get published template options for a given kind.
	 *
	 * @since  1.0.0
	 * @param  string $kind Template kind (header|footer).
	 * @return array{ id: int, title: string }[]
	 */
	/**
	 * Theme-registered page templates available to the current page.
	 *
	 * Drives the "Page Template" selector in the Page Settings sidebar — lets
	 * the user pick a template the active theme registers via the
	 * `Template Name:` header (e.g. Twenty Twenty-Four's "Page No Title").
	 *
	 * @since  1.6.0
	 * @param  array $post_data Editor post payload (for matching the right post).
	 * @return array<int,array{file:string,name:string}>
	 */
	private function get_theme_page_templates( $post_data ) {
		$post_id = isset( $post_data['id'] ) ? absint( $post_data['id'] ) : 0;
		$post    = $post_id > 0 ? get_post( $post_id ) : null;

		// `get_page_templates()` is theme-scoped and accepts a post for context.
		// We pass `'page'` because all builder pages live on the `page` CPT.
		$templates = wp_get_theme()->get_page_templates( $post, 'page' );
		if ( ! is_array( $templates ) || empty( $templates ) ) {
			return array();
		}

		$out = array();
		foreach ( $templates as $file => $name ) {
			$out[] = array(
				'file' => sanitize_file_name( (string) $file ),
				'name' => sanitize_text_field( (string) $name ),
			);
		}
		return $out;
	}

	private function get_template_options( $kind ) {
		$kind = sanitize_key( (string) $kind );
		if ( ! in_array( $kind, array( 'header', 'footer' ), true ) ) {
			return array();
		}

		$query = new \WP_Query(
			array(
				'post_type'              => AMBLD_Post_Type_Builder_Template::POST_TYPE,
				'post_status'            => array( 'publish', 'private' ),
				'posts_per_page'          => 100,
				'meta_key'               => '_ambld_template_kind',
				'meta_value'             => $kind,
				'orderby'                => 'title',
				'order'                  => 'ASC',
				'no_found_rows'          => true,
				'update_post_meta_cache' => false,
				'update_post_term_cache'  => false,
			)
		);

		if ( ! $query->have_posts() ) {
			return array();
		}

		$templates = array();
		foreach ( $query->posts as $post ) {
			$templates[] = array(
				'id'    => $post->ID,
				'title' => $post->post_title,
			);
		}

		return $templates;
	}
}
