<?php
/**
 * Embed PDF shortcode + block renderer.
 *
 * @package PDFDraft
 */

namespace PDFDraft;

use PDFDraft\Blocks\BlockRegistrar;
use PDFDraft\Services\StorageService;
use PDFDraft\Services\DesignService;
use WP_Error;
use WP_REST_Request;
use WP_REST_Response;

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

/**
 * Handles PDF embedding via shortcode and dynamic block.
 */
class EmbedPdf {
	/**
	 * Shortcode tag.
	 *
	 * @var string
	 */
	private const SHORTCODE = 'pdfdraft_embed_pdf';

	/**
	 * Block name.
	 *
	 * @var string
	 */
	private const BLOCK_NAME = 'pdfdraft/embed-pdf';

	/**
	 * Initialize hooks.
	 *
	 * @return void
	 */
	public function init(): void {
		add_action( 'init', array( $this, 'registerBlocks' ) );
		add_action( 'rest_api_init', array( $this, 'registerRoutes' ) );
		add_action( 'enqueue_block_editor_assets', array( $this, 'enqueueBlockEditorAssets' ) );
		add_action( 'wp_ajax_pdfdraft_embed_pdf', array( $this, 'serveTemplatePdfAjax' ) );
		add_action( 'wp_ajax_nopriv_pdfdraft_embed_pdf', array( $this, 'serveTemplatePdfAjax' ) );
		add_action( 'wp_trash_post', array( $this, 'deleteStoredPdfForDesign' ) );
		add_action( 'before_delete_post', array( $this, 'deleteStoredPdfForDesign' ) );
		add_shortcode( self::SHORTCODE, array( $this, 'renderShortcode' ) );
	}

	/**
	 * Register REST routes for embed PDF storage/serving.
	 *
	 * @return void
	 */
	public function registerRoutes(): void {
		register_rest_route(
			'pdfdraft/v1',
			'/embed-pdf/templates/(?P<slug>[\w-]+)/pdf',
			array(
				array(
					'methods'             => \WP_REST_Server::READABLE,
					'callback'            => array( $this, 'serveTemplatePdf' ),
					'permission_callback' => '__return_true',
				),
				array(
					'methods'             => \WP_REST_Server::CREATABLE,
					'callback'            => array( $this, 'saveTemplatePdf' ),
					'permission_callback' => function (): bool {
						return current_user_can( 'edit_posts' );
					},
					'args'                => array(
						'pdf' => array(
							'type'              => 'string',
							'required'          => true,
							'sanitize_callback' => static function ( $value ) {
								return is_string( $value ) ? trim( $value ) : '';
							},
						),
					),
				),
			)
		);
	}

	/**
	 * Register dynamic block.
	 *
	 * @return void
	 */
	public function registerBlocks(): void {
		( new BlockRegistrar() )->registerBlocks( $this->getServerBlockDefinitions() );
		if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
			error_log( '[PDFDraft Embed] registered block definitions.' ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
		}
	}

	/**
	 * Enqueue block editor assets.
	 *
	 * @return void
	 */
	public function enqueueBlockEditorAssets(): void {
		( new BlockRegistrar() )->registerAndEnqueueEditorScripts( $this->getEditorBlockScriptDefinitions() );
	}

	/**
	 * Server block registry definitions.
	 *
	 * @return array<int, array{name:string,args:array}>
	 */
	private function getServerBlockDefinitions(): array {
		return array(
			array(
				'name' => self::BLOCK_NAME,
				'args' => array(
					'api_version'     => 3,
					'editor_script'   => 'pdfdraft-embed-pdf-block',
					'render_callback' => array( $this, 'renderBlock' ),
					'attributes'      => array(
						'templateId' => array( 'type' => 'string' ),
						'orderId'    => array( 'type' => 'string' ),
						'width'      => array( 'type' => 'string' ),
						'height'     => array( 'type' => 'string' ),
					),
					'supports'        => array( 'html' => false ),
				),
			),
		);
	}

	/**
	 * Block editor script registry definitions.
	 *
	 * @return array<int, array<string,mixed>>
	 */
	private function getEditorBlockScriptDefinitions(): array {
		$deps = array(
			'wp-blocks',
			'wp-element',
			'wp-i18n',
			'wp-components',
			'wp-block-editor',
			'wp-api-fetch',
		);
		if ( file_exists( PDFDRAFT_DIST_DIR . '/runtime.js' ) ) {
			$deps[] = 'pdfdraft-embed-runtime';
		}

		return array(
			array(
				'handle'          => 'pdfdraft-embed-pdf-block',
				'src'             => trailingslashit( PDFDRAFT_DIST_URI ) . 'embed-pdf-block.js',
				'asset_file'      => PDFDRAFT_DIST_DIR . '/embed-pdf-block.asset.php',
				'deps'            => $deps,
				'localize_object' => '__PDFDRAFT_EMBED_PDF__',
				'localize_data'   => array(
					'defaults' => $this->getEmbedDefaults(),
				),
			),
		);
	}

	/**
	 * Render shortcode.
	 *
	 * @param array<string, mixed> $atts Shortcode attributes.
	 * @return string
	 */
	public function renderShortcode( array $atts = array() ): string {
		$atts = shortcode_atts(
			array(
				'template_id' => '',
				'order_id'    => '',
				'width'       => '',
				'height'      => '',
			),
			$atts,
			self::SHORTCODE
		);
		return $this->renderEmbedHtml(
			array(
				'template_id' => (string) $atts['template_id'],
				'order_id'    => (string) $atts['order_id'],
				'width'       => (string) $atts['width'],
				'height'      => (string) $atts['height'],
				'use_template_defaults' => true,
				'use_template_width_default' => false,
			)
		);
	}

	/**
	 * Render dynamic block.
	 *
	 * @param array<string, mixed> $attributes Block attributes.
	 * @return string
	 */
	public function renderBlock( array $attributes = array() ): string {
		return $this->renderEmbedHtml(
			array(
				'template_id' => (string) ( $attributes['templateId'] ?? '' ),
				'order_id'    => (string) ( $attributes['orderId'] ?? '' ),
				'width'       => (string) ( $attributes['width'] ?? '' ),
				'height'      => (string) ( $attributes['height'] ?? '' ),
				'use_template_defaults' => false,
			)
		);
	}

	/**
	 * Render frontend embed HTML.
	 *
	 * @param array<string, string> $args Embed args.
	 * @return string
	 */
	private function renderEmbedHtml( array $args ): string {
		$template = sanitize_title( (string) ( $args['template_id'] ?? '' ) );
		$useTemplateDefaults = (bool) ( $args['use_template_defaults'] ?? true );
		$defaults            = $useTemplateDefaults
			? $this->getEmbedDefaults( $template )
			: array(
				'width'  => '100%',
				'height' => '840px',
			);
		if ( false === (bool) ( $args['use_template_width_default'] ?? true ) ) {
			$defaults['width'] = '100%';
		}
		$width    = $this->sanitizeDimension( (string) ( $args['width'] ?? '' ), $defaults['width'] );
		$height   = $this->sanitizeDimension( (string) ( $args['height'] ?? '' ), $defaults['height'] );
		$height   = $this->normalizeViewportHeight( $height );

		if ( '' === $template ) {
			return '';
		}
		if ( ! $this->templateDesignExists( $template ) ) {
			return '';
		}

		$pdf            = $this->resolveTemplatePdf( $template );
		$dynamic_config = $this->buildDynamicPdfConfig( $template );
		if ( null === $pdf && null === $dynamic_config ) {
			return '';
		}

		$this->enqueueViewerAssets();
		$config_id = 'pdfdraft-embed-' . wp_unique_id();
		if ( null !== $dynamic_config ) {
			$this->enqueueDynamicPdfConfig( $config_id, $dynamic_config );
		}
		$id = 'pdfdraft-embed-pdf-' . wp_unique_id();

		ob_start();
		?>
		<div
			id="<?php echo esc_attr( $id ); ?>"
			class="pdfdraft-embed-pdf is-loading"
			data-pdf-url="<?php echo esc_url( $pdf['url'] ?? '' ); ?>"
			data-pdf-filename="<?php echo esc_attr( $template . '.pdf' ); ?>"
			data-pdfdraft-config-id="<?php echo esc_attr( $config_id ); ?>"
			style="width:<?php echo esc_attr( $width ); ?>;max-width:100%;box-sizing:border-box;"
		>
			<div class="pdfdraft-embed-pdf__toolbar">
				<button type="button" class="pdfdraft-embed-pdf__btn" data-action="prev"><?php echo esc_html__( 'Previous', 'pdfdraft' ); ?></button>
				<span class="pdfdraft-embed-pdf__status" data-role="status">1 / 1</span>
				<button type="button" class="pdfdraft-embed-pdf__btn" data-action="next"><?php echo esc_html__( 'Next', 'pdfdraft' ); ?></button>
				<span class="pdfdraft-embed-pdf__spacer"></span>
				<button type="button" class="pdfdraft-embed-pdf__btn" data-action="zoom-out"><?php echo esc_html__( 'Zoom -', 'pdfdraft' ); ?></button>
				<span class="pdfdraft-embed-pdf__status" data-role="zoom">100%</span>
				<button type="button" class="pdfdraft-embed-pdf__btn" data-action="zoom-in"><?php echo esc_html__( 'Zoom +', 'pdfdraft' ); ?></button>
				<button type="button" class="pdfdraft-embed-pdf__btn" data-action="download"><?php echo esc_html__( 'Download', 'pdfdraft' ); ?></button>
				<button type="button" class="pdfdraft-embed-pdf__btn" data-action="print" aria-label="<?php echo esc_attr__( 'Print', 'pdfdraft' ); ?>">
					<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="6 9 6 2 18 2 18 9"/><path d="M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2"/><rect x="6" y="14" width="12" height="8"/></svg>
					<span><?php echo esc_html__( 'Print', 'pdfdraft' ); ?></span>
				</button>
				<button type="button" class="pdfdraft-embed-pdf__btn pdfdraft-embed-pdf__btn--fullscreen" data-action="fullscreen" aria-label="<?php echo esc_attr__( 'Fullscreen', 'pdfdraft' ); ?>" aria-pressed="false">
					<svg class="pdfdraft-embed-pdf__icon-expand" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="15 3 21 3 21 9"/><polyline points="9 21 3 21 3 15"/><line x1="21" y1="3" x2="14" y2="10"/><line x1="3" y1="21" x2="10" y2="14"/></svg>
					<svg class="pdfdraft-embed-pdf__icon-compress" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="4 14 10 14 10 20"/><polyline points="20 10 14 10 14 4"/><line x1="10" y1="14" x2="3" y2="21"/><line x1="21" y1="3" x2="14" y2="10"/></svg>
					<span class="pdfdraft-embed-pdf__fs-label"><?php echo esc_html__( 'Fullscreen', 'pdfdraft' ); ?></span>
				</button>
			</div>
			<div class="pdfdraft-embed-pdf__viewport" style="height:<?php echo esc_attr( $height ); ?>;">
				<canvas class="pdfdraft-embed-pdf__canvas"></canvas>
				<div class="pdfdraft-embed-pdf__loader">
					<div class="pdfdraft-embed-pdf__loader-spinner" aria-hidden="true"></div>
					<div class="pdfdraft-embed-pdf__loader-text"><?php echo esc_html__( 'Loading PDF...', 'pdfdraft' ); ?></div>
				</div>
			</div>
		</div>
		<?php
		return (string) ob_get_clean();
	}

	/**
	 * Build frontend generation config for dynamic WP data fields.
	 *
	 * @param string $template Template slug.
	 * @return array<string,mixed>|null
	 */
	private function buildDynamicPdfConfig( string $template ): ?array {
		$design = get_page_by_path( $template, OBJECT, 'pdfdraft-design' );
		if ( ! $this->isUsableDesignPost( $design ) ) {
			return null;
		}

		$decoded = json_decode( $design->post_content, true );
		if ( ! is_array( $decoded ) ) {
			return null;
		}

		return array(
			'postId'          => (int) get_the_ID(),
			'templateData'    => $decoded,
			'filenamePattern' => '{post_title}.pdf',
			'includeImages'   => true,
			'fontFamily'      => 'Inter',
			'restUrl'         => rest_url(),
			'nonce'           => wp_create_nonce( 'wp_rest' ),
		);
	}

	/**
	 * Attach dynamic PDF generation config to the viewer bundle.
	 *
	 * @param string              $config_id Config id.
	 * @param array<string,mixed> $config Config payload.
	 * @return void
	 */
	private function enqueueDynamicPdfConfig( string $config_id, array $config ): void {
		wp_add_inline_script(
			'pdfdraft-embed-pdf-viewer',
			"window.__PDFDRAFT__ = window.__PDFDRAFT__ || { defaultFont: { family: 'Inter' } };",
			'before'
		);
		wp_add_inline_script(
			'pdfdraft-embed-pdf-viewer',
			'window.__PDFDRAFT_DYNAMIC_PDFS__ = window.__PDFDRAFT_DYNAMIC_PDFS__ || {}; window.__PDFDRAFT_DYNAMIC_PDFS__[' . wp_json_encode( $config_id ) . '] = ' . wp_json_encode( $config ) . ';',
			'before'
		);
	}

	/**
	 * Resolve stored template PDF file.
	 *
	 * @param string $slug Template slug.
	 * @return array{path:string,url:string}|null
	 */
	private function resolveTemplatePdf( string $slug ): ?array {
		if ( ! $this->templateDesignExists( $slug ) ) {
			return null;
		}
		$relative = 'projects/' . $slug . '.pdf';
		if ( ! pdfdraft( StorageService::class )->fileExists( $relative ) ) {
			return null;
		}
		return array(
			'path' => $relative,
			'url'  => add_query_arg(
				array(
					'action' => 'pdfdraft_embed_pdf',
					'slug'   => rawurlencode( $slug ),
				),
				admin_url( 'admin-ajax.php' )
			),
		);
	}

	/**
	 * Persist exported PDF for a template slug.
	 *
	 * @param WP_REST_Request $request REST request.
	 * @return WP_REST_Response|WP_Error
	 */
	public function saveTemplatePdf( WP_REST_Request $request ) {
		$slug = sanitize_title( (string) $request['slug'] );
		if ( '' === $slug ) {
			return new WP_Error( 'pdfdraft_embed_invalid_slug', __( 'Invalid template slug.', 'pdfdraft' ), array( 'status' => 400 ) );
		}
		if ( ! $this->templateDesignExists( $slug ) ) {
			return new WP_Error( 'pdfdraft_embed_not_found', __( 'Template design not found.', 'pdfdraft' ), array( 'status' => 404 ) );
		}
		$base64 = (string) $request->get_param( 'pdf' );
		if ( '' === $base64 ) {
			return new WP_Error( 'pdfdraft_embed_missing_pdf', __( 'PDF data is required.', 'pdfdraft' ), array( 'status' => 400 ) );
		}
		// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
		$pdf = base64_decode( $base64, true );
		if ( false === $pdf || '' === $pdf ) {
			return new WP_Error( 'pdfdraft_embed_invalid_pdf', __( 'Invalid PDF payload.', 'pdfdraft' ), array( 'status' => 400 ) );
		}
		$relative = 'projects/' . $slug . '.pdf';
		$saved    = $this->savePdfAtomically( $relative, $pdf );
		if ( ! $saved ) {
			return new WP_Error( 'pdfdraft_embed_save_failed', __( 'Could not save template PDF.', 'pdfdraft' ), array( 'status' => 500 ) );
		}
		update_post_meta( (int) $request->get_param( 'design_id' ), '_pdfdraft_embed_pdf_relative_path', $relative );

		return new WP_REST_Response(
			array(
				'success' => true,
				'slug'    => $slug,
				'path'    => $relative,
			),
			200
		);
	}

	/**
	 * Serve stored template PDF inline for frontend viewer.
	 *
	 * @param WP_REST_Request $request REST request.
	 * @return WP_REST_Response|WP_Error
	 */
	public function serveTemplatePdf( WP_REST_Request $request ) {
		$slug = sanitize_title( (string) $request['slug'] );
		if ( '' === $slug ) {
			return new WP_Error( 'pdfdraft_embed_invalid_slug', __( 'Invalid template slug.', 'pdfdraft' ), array( 'status' => 400 ) );
		}
		if ( ! $this->templateDesignExists( $slug ) ) {
			return new WP_Error( 'pdfdraft_embed_not_found', __( 'Template PDF not found.', 'pdfdraft' ), array( 'status' => 404 ) );
		}
		$relative = 'projects/' . $slug . '.pdf';
		$base     = pdfdraft( StorageService::class )->getBasePath();
		$fullPath = $base . $relative;
		if ( ! is_file( $fullPath ) || ! is_readable( $fullPath ) ) {
			return new WP_Error( 'pdfdraft_embed_not_found', __( 'Template PDF not found.', 'pdfdraft' ), array( 'status' => 404 ) );
		}
		// phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
		$content = file_get_contents( $fullPath );
		if ( false === $content ) {
			return new WP_Error( 'pdfdraft_embed_read_failed', __( 'Could not read template PDF.', 'pdfdraft' ), array( 'status' => 500 ) );
		}
		$response = new WP_REST_Response( $content, 200 );
		$response->header( 'Content-Type', 'application/pdf' );
		$response->header( 'Content-Disposition', 'inline; filename="' . sanitize_file_name( $slug . '.pdf' ) . '"' );
		return $response;
	}

	/**
	 * Serve template PDF through admin-ajax for binary-safe output.
	 *
	 * @return void
	 */
	public function serveTemplatePdfAjax(): void {
		// Public endpoint for frontend embeds; no nonce required.
		// phpcs:ignore WordPress.Security.NonceVerification.Recommended
		$slug = sanitize_title( (string) wp_unslash( $_GET['slug'] ?? '' ) );
		// phpcs:ignore WordPress.Security.NonceVerification.Recommended
		$download = '1' === (string) wp_unslash( $_GET['download'] ?? '' );
		if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
			error_log( '[PDFDraft Embed] AJAX hit for slug: ' . $slug ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
		}
		if ( '' === $slug ) {
			if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
				error_log( '[PDFDraft Embed] AJAX missing slug.' ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
			}
			status_header( 400 );
			exit;
		}
		if ( ! $this->templateDesignExists( $slug ) ) {
			status_header( 404 );
			exit;
		}
		$relative = 'projects/' . $slug . '.pdf';
		$base     = pdfdraft( StorageService::class )->getBasePath();
		$fullPath = $base . $relative;
		if ( ! is_file( $fullPath ) || ! is_readable( $fullPath ) ) {
			if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
				error_log( '[PDFDraft Embed] AJAX PDF missing/unreadable at: ' . $fullPath ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
			}
			status_header( 404 );
			exit;
		}

		while ( ob_get_level() > 0 ) {
			ob_end_clean();
		}
		nocache_headers();
		header( 'Content-Type: application/pdf' );
		$disposition = $download ? 'attachment' : 'inline';
		header( 'Content-Disposition: ' . $disposition . '; filename="' . sanitize_file_name( $slug . '.pdf' ) . '"' );
		if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
			error_log( '[PDFDraft Embed] AJAX serving PDF: ' . $fullPath ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
		}
		// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_readfile
		readfile( $fullPath );
		exit;
	}

	/**
	 * Delete the stored frontend PDF when its design is trashed or deleted.
	 *
	 * @param int $post_id Post ID.
	 * @return void
	 */
	public function deleteStoredPdfForDesign( int $post_id ): void {
		$post = get_post( $post_id );
		if ( ! $post || 'pdfdraft-design' !== $post->post_type ) {
			return;
		}

		$slug = sanitize_title( $post->post_name );
		if ( '' === $slug ) {
			return;
		}

		$fullPath = pdfdraft( StorageService::class )->getBasePath() . 'projects/' . $slug . '.pdf';
		if ( is_file( $fullPath ) ) {
			wp_delete_file( $fullPath );
		}
	}

	/**
	 * Check whether a template slug still belongs to an active design post.
	 *
	 * @param string $slug Template/design slug.
	 * @return bool
	 */
	private function templateDesignExists( string $slug ): bool {
		return $this->isUsableDesignPost(
			get_page_by_path( sanitize_title( $slug ), OBJECT, 'pdfdraft-design' )
		);
	}

	/**
	 * Check whether a post object is a usable design.
	 *
	 * @param mixed $post Candidate post.
	 * @return bool
	 */
	private function isUsableDesignPost( $post ): bool {
		return $post instanceof \WP_Post
			&& 'pdfdraft-design' === $post->post_type
			&& ! in_array( $post->post_status, array( 'trash', 'auto-draft' ), true );
	}

	/**
	 * Enqueue viewer assets and localized strings.
	 *
	 * @return void
	 */
	private function enqueueViewerAssets(): void {
		$viewer_js_path  = PDFDRAFT_DIST_DIR . '/embed-pdf-viewer.js';
		$viewer_css_path = PDFDRAFT_DIST_DIR . '/embed-pdf-viewer.css';
		$viewer_version  = file_exists( $viewer_js_path ) ? (string) filemtime( $viewer_js_path ) : PDFDRAFT_VERSION;
		$viewer_css_ver  = file_exists( $viewer_css_path ) ? (string) filemtime( $viewer_css_path ) : PDFDRAFT_VERSION;
		$viewer_js_uri   = trailingslashit( PDFDRAFT_DIST_URI ) . 'embed-pdf-viewer.js';
		$viewer_css_uri  = trailingslashit( PDFDRAFT_DIST_URI ) . 'embed-pdf-viewer.css';

		if ( ! wp_style_is( 'pdfdraft-embed-pdf-viewer', 'registered' ) ) {
			wp_register_style(
				'pdfdraft-embed-pdf-viewer',
				$viewer_css_uri,
				array(),
				$viewer_css_ver
			);
		}

		if ( ! wp_script_is( 'pdfdraft-embed-pdf-viewer', 'registered' ) ) {
			$runtime_path = PDFDRAFT_DIST_DIR . '/runtime.js';
			if ( file_exists( $runtime_path ) && ! wp_script_is( 'pdfdraft-embed-runtime', 'registered' ) ) {
				wp_register_script(
					'pdfdraft-embed-runtime',
					trailingslashit( PDFDRAFT_DIST_URI ) . 'runtime.js',
					array(),
					(string) filemtime( $runtime_path ),
					true
				);
			}
			$asset = file_exists( PDFDRAFT_DIST_DIR . '/embed-pdf-viewer.asset.php' )
				? include PDFDRAFT_DIST_DIR . '/embed-pdf-viewer.asset.php'
				: array( 'dependencies' => array() );
			$deps  = $asset['dependencies'] ?? array();
			if ( wp_script_is( 'pdfdraft-embed-runtime', 'registered' ) ) {
				$deps[] = 'pdfdraft-embed-runtime';
			}
			$deps = array_values( array_unique( $deps ) );
			if ( ! wp_script_is( 'pdfdraft-embed-runtime', 'registered' ) ) {
				$deps = array_values( array_diff( $deps, array( 'pdfdraft-embed-runtime' ) ) );
			}
			wp_register_script(
				'pdfdraft-embed-pdf-viewer',
				$viewer_js_uri,
				array_values( array_unique( $deps ) ),
				$viewer_version,
				true
			);
		}
		wp_enqueue_style( 'pdfdraft-embed-pdf-viewer' );
		$this->enqueue_registered_script_handles_from_asset(
			PDFDRAFT_DIST_DIR . '/embed-pdf-viewer.asset.php'
		);
		wp_enqueue_script( 'pdfdraft-embed-pdf-viewer' );
	}

	/**
	 * Enqueue every registered script handle declared in a *.asset.php file.
	 *
	 * The embed viewer chunk expects globals from lib bundles (e.g. window.pdfdraft.core).
	 * Depending on load order, those handles may not run before the viewer unless enqueued explicitly.
	 *
	 * @param string $asset_php Absolute path to the asset file.
	 * @return void
	 */
	private function enqueue_registered_script_handles_from_asset( string $asset_php ): void {
		if ( ! file_exists( $asset_php ) ) {
			return;
		}
		$asset = include $asset_php;
		$deps  = isset( $asset['dependencies'] ) && is_array( $asset['dependencies'] )
			? $asset['dependencies']
			: array();
		foreach ( $deps as $handle ) {
			if ( ! is_string( $handle ) || '' === $handle ) {
				continue;
			}
			if ( wp_script_is( $handle, 'registered' ) ) {
				wp_enqueue_script( $handle );
			}
		}
	}

	/**
	 * Get global embed defaults from settings.
	 *
	 * @param string $template Template slug.
	 * @return array{width:string,height:string}
	 */
	private function getEmbedDefaults( string $template = '' ): array {
		$fallback = array(
			'width'  => '100%',
			'height' => '840px',
		);
		if ( '' === $template ) {
			return $fallback;
		}
		$design = pdfdraft( DesignService::class )->getBySlug( $template );
		if ( ! is_array( $design ) ) {
			return $fallback;
		}
		$layout = $design['settings']['layout'] ?? null;
		if ( ! is_array( $layout ) ) {
			return $fallback;
		}
		$unit = isset( $layout['unit'] ) && is_string( $layout['unit'] ) ? strtolower( trim( $layout['unit'] ) ) : 'px';
		if ( ! in_array( $unit, array( 'px', 'in', 'cm' ), true ) ) {
			$unit = 'px';
		}
		$width  = isset( $layout['width'] ) && is_numeric( $layout['width'] ) ? (string) $layout['width'] . $unit : $fallback['width'];
		$height = isset( $layout['height'] ) && is_numeric( $layout['height'] ) ? (string) $layout['height'] . $unit : $fallback['height'];
		return array(
			'width'  => $this->sanitizeDimension( $width, $fallback['width'] ),
			'height' => $this->sanitizeDimension( $height, $fallback['height'] ),
		);
	}

	/**
	 * Sanitize a CSS dimension value.
	 *
	 * @param string $value Input value.
	 * @param string $fallback Fallback dimension.
	 * @return string
	 */
	private function sanitizeDimension( string $value, string $fallback ): string {
		$value = trim( $value );
		if ( '' === $value ) {
			return $fallback;
		}
		if ( preg_match( '/^\d+(?:\.\d+)?(px|%|vw|vh|rem|em|in|cm)$/', $value ) ) {
			return $value;
		}
		return $fallback;
	}

	/**
	 * Normalize viewport height so percentage inputs match editor preview behavior.
	 *
	 * @param string $height CSS height value.
	 * @return string
	 */
	private function normalizeViewportHeight( string $height ): string {
		if ( 1 === preg_match( '/^(\d+(?:\.\d+)?)%$/', $height, $matches ) ) {
			$percent = (float) ( $matches[1] ?? 0.0 );
			$percent = max( 0.0, min( 100.0, $percent ) );
			$px      = max( 120, (int) round( ( $percent / 100 ) * 640 ) );
			return $px . 'px';
		}
		return $height;
	}

	/**
	 * Atomically replace an existing template PDF to avoid partial reads.
	 *
	 * @param string $relative Relative PDF path under uploads/pdfdraft.
	 * @param string $content  Binary PDF content.
	 * @return bool
	 */
	private function savePdfAtomically( string $relative, string $content ): bool {
		$storage = pdfdraft( StorageService::class );
		$base    = $storage->getBasePath();
		$target  = $base . ltrim( $relative, '/\\' );
		$dir     = dirname( $target );
		if ( ! wp_mkdir_p( $dir ) ) {
			return false;
		}

		$temp = $target . '.tmp-' . wp_generate_password( 8, false, false );
		// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents
		$written = file_put_contents( $temp, $content );
		if ( false === $written ) {
			return false;
		}

		// Atomic replace on same filesystem.
		if ( ! @rename( $temp, $target ) ) { // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged,WordPress.WP.AlternativeFunctions.rename_rename
			// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_unlink
			@unlink( $temp ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged,WordPress.WP.AlternativeFunctions.unlink_unlink
			return false;
		}
		return true;
	}
}
