<?php
/**
 * File Path: includes/metaboxes/class-mbs-metabox-format-repeater-render.php
 *
 * Format Repeater Metabox — Render Methods
 *
 * Contains all HTML-output methods for the Format & Pricing repeater metabox.
 * Extracted from MBS_Metabox_Format_Repeater (R-03, 2026-04-25).
 * render_row() decomposed into private field-group helpers (Wave-1, Session 113).
 * Row context data assembled by MBS_Format_Repeater_Data::build_row_context() (REF-007, Session 137).
 *
 * MBS_Metabox_Format_Repeater::render() delegates to:
 *   (new MBS_Metabox_Format_Repeater_Render())->render( $post )
 *
 * @package MyBookShowroom
 * @since   1.0.0
 */

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

class MBS_Metabox_Format_Repeater_Render { // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedClassFound -- mbs_/MBS_ prefix is correct; scanner not configured with plugin prefix

	// -------------------------------------------------------------------------
	// Public render entry point
	// -------------------------------------------------------------------------

	/**
	 * Render the full repeater (all saved rows + hidden JS-clone template row).
	 *
	 * @param WP_Post $post Current post object.
	 * @return void
	 * @since 1.0.0
	 */
	public function render( WP_Post $post ): void {
		wp_nonce_field( 'mbs_format_repeater_nonce', 'mbs_format_repeater_nonce' );

		$format_rows = MBS_Format_Row_DAO::get_by_post( $post->ID );
		$is_pro      = mbs_is_pro();
		$is_pub      = mbs_is_pub();

		// Group all buy links for this post by format_row_id for efficient lookup.
		// (One query here vs. N queries inside render_row.)
		$all_buy_links    = MBS_Buy_Link_DAO::get_all_for_post( $post->ID );
		$buy_links_by_row = array();
		foreach ( $all_buy_links as $bl ) {
			if ( null !== $bl->format_row_id && '' !== (string) $bl->format_row_id ) {
				$buy_links_by_row[(int) $bl->format_row_id][] = $bl;
			}
		}

		?>
		<div class="mbs-repeater" id="mbs-format-repeater" data-repeater-type="formats">

			<div class="mbs-format-repeater-toolbar">
				<button type="button" class="button-link mbs-expand-all">
					<?php esc_html_e( 'Expand all', 'my-book-showroom' ); ?>
				</button>
				<span class="mbs-toolbar-sep" aria-hidden="true">|</span>
				<button type="button" class="button-link mbs-collapse-all">
					<?php esc_html_e( 'Collapse all', 'my-book-showroom' ); ?>
				</button>
			</div><!-- /.mbs-format-repeater-toolbar -->

			<?php if ( count( $format_rows ) > 1 ) : ?>
			<div class="notice notice-info inline mbs-format-sort-hint">
				<p><?php esc_html_e( 'Sort formats to set the order they appear on this book\'s front-end page.', 'my-book-showroom' ); ?></p>
			</div>
			<?php endif; ?>

			<div class="mbs-repeater-rows" id="mbs-formats-rows">
				<?php foreach ( $format_rows as $i => $row ) : ?>
					<?php $this->render_row( $row, $is_pro, $i, $buy_links_by_row[(int) $row->id] ?? array(), $is_pub ); ?>
				<?php endforeach; ?>
			</div>

			<?php /* Hidden Template Row — JS-toggled: cloned by admin-repeater.js on "Add Format Row" click.
			         display:none must remain inline; do not move to CSS — the template must be hidden on load
			         before JS initializes and regardless of stylesheet load order. */ ?>
			<div id="formats-row-template" class="mbs-format-row-template" style="display:none;">
				<?php $this->render_row( null, $is_pro, 0, array(), $is_pub ); ?>
			</div>

			<button type="button" class="button mbs-add-format-row" style="margin-top:12px;">+ Add Format</button>
		</div>
		<?php
	}

	// -------------------------------------------------------------------------
	// Private: row orchestrator
	// -------------------------------------------------------------------------

	/**
	 * Render a single format row by delegating each field group to a private helper.
	 * $row is null when rendering the hidden JS-clone template.
	 *
	 * @param object|null $row              Saved format row, or null for the JS clone template.
	 * @param bool        $is_pro           Whether PRO features are enabled.
	 * @param int         $row_index        Position of this row in the rendered loop (0-based).
	 *                                      Used as the array key for buy link field names.
	 *                                      JS updates data-row-index on cloned rows.
	 * @param object[]    $format_buy_links Buy link rows associated with this format row.
	 * @param bool        $is_pub           Whether the PUB tier is active.
	 * @return void
	 * @since 1.0.0
	 */
	private function render_row( ?object $row, bool $is_pro, int $row_index = 0, array $format_buy_links = array(), bool $is_pub = false ): void {
		$ctx      = MBS_Format_Repeater_Data::build_row_context( $row );
		$row_attr = $ctx['is_template'] ? '' : ' data-row-id="' . esc_attr( $ctx['id'] ) . '"';
		?>
		<div class="mbs-format-row"<?php echo $row_attr;  // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- $row_attr contains only esc_attr() output ?>>

			<?php $this->render_row_header( $ctx, $is_pro ); ?>

			<div class="mbs-format-row-body">

			<input type="hidden" name="mbs_format_row_id[]" value="<?php echo esc_attr( $ctx['id'] ); ?>">
			<input type="hidden" name="mbs_format_sort_order[]" value="<?php echo esc_attr( $ctx['sort_order'] ); ?>">

			<div class="mbs-format-cols">
				<div class="mbs-format-col mbs-format-col--left">
					<?php $this->render_row_core_fields( $ctx ); ?>
					<?php $this->render_row_format_fields( $ctx ); ?>
				</div><!-- /.mbs-format-col--left -->
				<div class="mbs-format-col mbs-format-col--right">
					<?php $this->render_row_price_field( $ctx ); ?>
					<?php $this->render_row_buy_links( $row_index, $format_buy_links, $is_pro, $is_pub ); ?>
				</div><!-- /.mbs-format-col--right -->
			</div><!-- /.mbs-format-cols -->
			<div class="mbs-format-full-width">
				<?php $this->render_row_media_fields( $ctx, $is_pro ); ?>
				<?php $this->render_row_supply_chain( $ctx, $is_pub ); ?>
				<?php $this->render_row_pos_fields( $ctx, $row_index ); ?>
			</div><!-- /.mbs-format-full-width -->

			</div><!-- /.mbs-format-row-body -->

		</div>
		<?php
	}

	// -------------------------------------------------------------------------
	// Private: field-group helpers
	// -------------------------------------------------------------------------

	/**
	 * Render core identification fields: format type, subtitle, pub date,
	 * edition number, edition type, ISBN-13, ASIN, and price.
	 * All shown for every format.
	 *
	 * @param object|null $row         Saved row or null for template.
	 * @param bool        $is_template True when rendering the JS clone template.
	 * @param string      $format      Saved format key ('' for template).
	 * @return void
	 * @since 1.0.0
	 */
	private function render_row_core_fields( array $ctx ): void {
		$format_label   = $ctx['format_label'];
		$subtitle       = $ctx['subtitle'];
		$pub_date       = $ctx['pub_date'];
		$edition_number = $ctx['edition_number'];
		$edition_type   = $ctx['edition_type'];
		$isbn13         = $ctx['isbn13'];
		$asin           = $ctx['asin'];
		$format         = $ctx['format'];
		?>
		<!-- ── Format type ────────────────────────────────────────────────────── -->
		<div class="mbs-field mbs-field-format">
			<label><?php esc_html_e( 'Format', 'my-book-showroom' ); ?> <span class="required">*</span></label>
			<select name="mbs_format[]" class="mbs-format-select">
				<option value=""><?php esc_html_e( 'Select Format', 'my-book-showroom' ); ?></option>
				<?php foreach ( MBS_Format_Options::get_format_options() as $key => $label ) : ?>
					<option value="<?php echo esc_attr( $key ); ?>"
						<?php selected( $format, $key ); ?>>
						<?php echo esc_html( $label ); ?>
					</option>
				<?php endforeach; ?>
			</select>
			<?php /* JS-toggled: shown when no format is selected. Do not move to CSS. */ ?>
			<span class="mbs-format-required-hint" style="color:#d63638;font-size:12px;display:<?php echo esc_attr( '' === $format ? 'block' : 'none' ); ?>;margin-top:3px;">
				<?php esc_html_e( 'Please select a format type.', 'my-book-showroom' ); ?>
			</span>
			<?php /* JS-toggled: shown/hidden by admin-repeater.js on format select change. Do not move to CSS. */ ?>
			<input type="text" name="mbs_format_label[]"
			       value="<?php echo esc_attr( $format_label ); ?>"
			       class="mbs-custom-label"
			       autocomplete="off"
			       placeholder="<?php esc_attr_e( 'Custom label (if Other)', 'my-book-showroom' ); ?>"
			       style="display: <?php echo esc_attr( 'other' === $format ? 'inline-block' : 'none' ); ?>;">
		</div>

		<!-- ── Subtitle — all formats ────────────────────────────────────────── -->
		<div class="mbs-field mbs-field-all">
			<label><?php esc_html_e( 'Subtitle', 'my-book-showroom' ); ?></label>
			<input type="text" name="mbs_subtitle[]"
			       value="<?php echo esc_attr( $subtitle ); ?>"
			       class="large-text"
			       autocomplete="off"
			       placeholder="<?php esc_attr_e( 'Subtitle (this edition)', 'my-book-showroom' ); ?>">
		</div>

		<!-- ── Publication Date — all formats ────────────────────────────────── -->
		<div class="mbs-field mbs-field-all">
			<label><?php esc_html_e( 'Pub Date', 'my-book-showroom' ); ?></label>
			<input type="date" name="mbs_pub_date[]"
			       value="<?php echo esc_attr( $pub_date ); ?>">
		</div>

		<!-- ── Edition # / Edition Type — all formats ────────────────────────── -->
		<div class="mbs-field-pair mbs-field-all">
			<div class="mbs-field">
				<label><?php esc_html_e( 'Edition #', 'my-book-showroom' ); ?></label>
				<input type="number" name="mbs_edition_number_row[]"
				       value="<?php echo esc_attr( (string) $edition_number ); ?>"
				       class="small-text"
				       min="1" max="999" step="1"
				       placeholder="<?php esc_attr_e( 'e.g. 2', 'my-book-showroom' ); ?>">
			</div>
			<div class="mbs-field">
				<label><?php esc_html_e( 'Edition Type', 'my-book-showroom' ); ?></label>
				<select name="mbs_edition_type_row[]">
					<option value=""><?php esc_html_e( '-- None --', 'my-book-showroom' ); ?></option>
					<?php foreach ( MBS_ONIX_Lists::edition_types() as $et_code => $et_label ) : ?>
					<option value="<?php echo esc_attr( $et_code ); ?>" <?php selected( $edition_type, $et_code ); ?>>
						<?php echo esc_html( $et_code . ' — ' . $et_label ); ?>
					</option>
					<?php endforeach; ?>
				</select>
			</div>
		</div>

		<!-- ── ISBN-13 — all formats ─────────────────────────────────────────────── -->
		<div class="mbs-field mbs-field-all">
			<label><?php esc_html_e( 'ISBN-13', 'my-book-showroom' ); ?></label>
			<input type="text" name="mbs_isbn13[]"
			       value="<?php echo esc_attr( $isbn13 ); ?>"
			       placeholder="ISBN-13"
			       class="mbs-isbn-field regular-text"
			       autocomplete="off">
			<span class="mbs-field-error mbs-isbn-error" style="color:#d63638;font-size:12px;display:none;margin-top:2px;"></span>
		</div>

		<!-- ── ASIN — all formats ────────────────────────────────────────────────────── -->
		<div class="mbs-field mbs-field-all">
			<label><?php esc_html_e( 'ASIN', 'my-book-showroom' ); ?></label>
			<input type="text" name="mbs_asin[]"
			       value="<?php echo esc_attr( $asin ); ?>"
			       placeholder="ASIN"
			       class="regular-text mbs-asin-field"
			       autocomplete="off">
			<span class="mbs-field-error mbs-asin-error" style="color:#d63638;font-size:12px;display:none;margin-top:2px;"></span>
		</div>

		<?php
	}

	/**
	 * Render the Price field (right column, all formats).
	 *
	 * Extracted from render_row_core_fields during Sprint 179 two-column
	 * layout refactor so it can be placed in the right column independently.
	 *
	 * @param array $ctx  Row context array.
	 * @return void
	 * @since 1.0.0
	 */
	private function render_row_price_field( array $ctx ): void {
		$price = $ctx['price'];
		?>
		<!-- ── Price — all formats ──────────────────────────────────────── -->
		<div class="mbs-field mbs-field-all">
			<label><?php esc_html_e( 'Price', 'my-book-showroom' ); ?></label>
			<input type="text" name="mbs_price[]"
			       value="<?php echo esc_attr( $price ); ?>"
			       placeholder="<?php esc_attr_e( 'Price', 'my-book-showroom' ); ?>"
			       autocomplete="off"
			       class="small-text">
		</div>
		<?php
	}

	/**
	 * Render format-specific fields: page count (non-audiobook), file format
	 * (ebook only), narrator, duration, and audio sample (audiobook only).
	 *
	 * @param object|null $row            Saved row or null for template.
	 * @param bool        $is_template    True when rendering the JS clone template.
	 * @param string      $show_no_audio  CSS display value for non-audiobook fields.
	 * @param string      $show_ebook     CSS display value for ebook-only fields.
	 * @param string      $show_audiobook CSS display value for audiobook-only fields.
	 * @return void
	 * @since 1.0.0
	 */
	private function render_row_format_fields( array $ctx ): void {
		$page_length     = $ctx['page_length'];
		$file_format     = $ctx['file_format'];
		$narrator        = $ctx['narrator'];
		$duration_hh     = $ctx['duration_hh'];
		$duration_mm     = $ctx['duration_mm'];
		$audio_sample_id = $ctx['audio_sample_id'];
		$audio_title     = $ctx['audio_title'];
		$show_no_audio   = $ctx['show_no_audio'];
		$show_ebook      = $ctx['show_ebook'];
		$show_audiobook  = $ctx['show_audiobook'];
		?>
		<!-- ── Page Count — all except audiobook ───────────────────── -->
		<?php /* JS-toggled: hidden for audiobook. Do not move display to CSS. */ ?>
		<div class="mbs-field mbs-field-no-audiobook" style="display: <?php echo esc_attr( $show_no_audio ); ?>;">
			<label><?php esc_html_e( 'Page Count', 'my-book-showroom' ); ?></label>
			<input type="number" name="mbs_page_length[]"
			       value="<?php echo esc_attr( $page_length ); ?>"
			       class="small-text" min="0"
			       placeholder="<?php esc_attr_e( 'Pages', 'my-book-showroom' ); ?>">
		</div>

		<!-- ── File Format — ebook only ────────────────────────────── -->
		<?php /* JS-toggled: shown only for ebook. Do not move display to CSS. */ ?>
		<div class="mbs-field mbs-field-ebook-only" style="display: <?php echo esc_attr( $show_ebook ); ?>;">
			<label><?php esc_html_e( 'File Format', 'my-book-showroom' ); ?></label>
			<select name="mbs_file_format[]">
				<option value=""><?php esc_html_e( '— select —', 'my-book-showroom' ); ?></option>
				<?php
				foreach ( array( 'epub' => 'EPUB', 'mobi' => 'MOBI', 'pdf' => 'PDF', 'other' => 'Other' ) as $fkey => $flabel ) :
				?>
					<option value="<?php echo esc_attr( $fkey ); ?>"
						<?php selected( $file_format, $fkey ); ?>>
						<?php echo esc_html( $flabel ); ?>
					</option>
				<?php endforeach; ?>
			</select>
		</div>

		<!-- ── Narrator — audiobook only ───────────────────────────── -->
		<?php /* JS-toggled: shown only for audiobook. Do not move display to CSS. */ ?>
		<div class="mbs-field mbs-field-audiobook-only" style="display: <?php echo esc_attr( $show_audiobook ); ?>;">
			<input type="text" name="mbs_narrator[]"
			       value="<?php echo esc_attr( $narrator ); ?>"
			       class="regular-text"
			       autocomplete="off"
			       placeholder="<?php esc_attr_e( 'Narrator', 'my-book-showroom' ); ?>">
		</div>

		<!-- ── Duration HH:MM — audiobook only ─────────────────────── -->
		<div class="mbs-field mbs-field-audiobook-only" style="display: <?php echo esc_attr( $show_audiobook ); ?>;">
			<label><?php esc_html_e( 'Duration', 'my-book-showroom' ); ?></label>
			<input type="number" name="mbs_duration_hh[]"
			       value="<?php echo esc_attr( $duration_hh ); ?>"
			       class="tiny-text" min="0" max="999"
			       placeholder="HH">
			<span>:</span>
			<input type="number" name="mbs_duration_mm[]"
			       value="<?php echo esc_attr( $duration_mm ); ?>"
			       class="tiny-text" min="0" max="59"
			       placeholder="MM">
		</div>

		<!-- ── Audio Sample — audiobook only ───────────────────────── -->
		<div class="mbs-field mbs-field-audiobook-only" style="display: <?php echo esc_attr( $show_audiobook ); ?>;">
			<label><?php esc_html_e( 'Audio Sample', 'my-book-showroom' ); ?></label>
			<input type="hidden" name="mbs_audio_sample_id[]"
			       value="<?php echo esc_attr( $audio_sample_id ?: '' ); ?>">
			<button type="button" class="button mbs-upload-audio-sample">
				<?php echo $audio_sample_id
					? esc_html__( 'Change Audio File', 'my-book-showroom' )
					: esc_html__( 'Select Audio File', 'my-book-showroom' ); ?>
			</button>
			<span class="mbs-audio-sample-name"
			      style="margin-left:6px;<?php echo $audio_sample_id ? '' : 'display:none;'; ?>">
				<?php echo esc_html( $audio_title ); ?>
			</span>
			<button type="button" class="button mbs-remove-audio-sample"
			        style="<?php echo $audio_sample_id ? '' : 'display:none;'; ?>">
				<?php esc_html_e( 'Remove', 'my-book-showroom' ); ?>
			</button>
		</div>
		<?php
	}


	/**
	 * Render PRO/PUB media upload fields: format cover image and spinning book video.
	 * Outputs an HTML comment placeholder when PRO is not active.
	 *
	 * @param object|null $row         Saved row or null for template.
	 * @param bool        $is_template True when rendering the JS clone template.
	 * @param bool        $is_pro      Whether PRO features are enabled.
	 * @param string      $format      Saved format key (used to select cover hint copy).
	 * @return void
	 * @since 1.0.0
	 */
	private function render_row_media_fields( array $ctx, bool $is_pro ): void {
		$cover_image_id       = $ctx['cover_image_id'];
		$spinning_video_id    = $ctx['spinning_video_id'];
		$cover_thumb_url      = $ctx['cover_thumb_url'];
		$spinning_video_title = $ctx['spinning_video_title'];
		$format               = $ctx['format'];
		?>
		<!-- ── Cover Image — PRO/Publisher only, all formats ─────────────── -->
		<?php if ( $is_pro ) : ?>
			<div class="mbs-field mbs-field-cover">
				<label><?php esc_html_e( 'Format Cover', 'my-book-showroom' ); ?></label>
				<input type="hidden" name="mbs_cover_image_id[]"
				       value="<?php echo esc_attr( $cover_image_id ?: '' ); ?>">
				<?php if ( $cover_thumb_url ) : ?>
					<div class="mbs-cover-preview">
						<img src="<?php echo esc_url( $cover_thumb_url ); ?>"
						     alt="<?php esc_attr_e( 'Format cover', 'my-book-showroom' ); ?>"
						     style="max-width:80px;display:block;margin-bottom:4px;">
						<button type="button" class="button mbs-remove-cover">
							<?php esc_html_e( 'Remove', 'my-book-showroom' ); ?>
						</button>
					</div>
					<button type="button" class="button mbs-upload-cover" style="display:none;">
						<?php esc_html_e( 'Change Cover', 'my-book-showroom' ); ?>
					</button>
				<?php else : ?>
					<button type="button" class="button mbs-upload-cover">
						<?php esc_html_e( 'Set Format Cover', 'my-book-showroom' ); ?>
					</button>
				<?php endif; ?>
				<?php
				$cover_hint = ( false !== strpos( strtolower( $format ), 'audio' ) )
					? __( 'Recommended: 3000×3000 px JPEG (square). Audible requires 2400×2400 px minimum; Apple Audiobooks requires 3000×3000 px.', 'my-book-showroom' )
					: __( 'Recommended: 1600×2560 px JPEG (portrait, 1:1.6 ratio). Apple Books requires 1400 px on the shortest side.', 'my-book-showroom' );
				?>
				<p class="description mbs-cover-hint"
				   data-hint-portrait="<?php echo esc_attr( __( 'Recommended: 1600×2560 px JPEG (portrait, 1:1.6 ratio). Apple Books requires 1400 px on the shortest side.', 'my-book-showroom' ) ); ?>"
				   data-hint-audio="<?php echo esc_attr( __( 'Recommended: 3000×3000 px JPEG (square). Audible requires 2400×2400 px minimum; Apple Audiobooks requires 3000×3000 px.', 'my-book-showroom' ) ); ?>">
					<?php echo esc_html( $cover_hint ); ?>
				</p>
			</div>
		<?php else : ?>
			<?php MBS_Pro_Gate::comment( 'Format cover image' ); ?>
		<?php endif; ?>

		<!-- ── Spinning Book Video — PRO/Publisher only, all formats ──────── -->
		<?php if ( $is_pro ) : ?>
			<div class="mbs-field mbs-field-spinning-video">
				<label><?php esc_html_e( 'Spinning Book Video', 'my-book-showroom' ); ?></label>
				<div class="mbs-video-uploader">
					<input type="hidden" name="mbs_spinning_video_id[]"
					       class="mbs-spinning-video-id"
					       value="<?php echo esc_attr( $spinning_video_id ?: '' ); ?>">
					<button type="button" class="button mbs-upload-spinning-video">
						<?php echo $spinning_video_id
							? esc_html__( 'Replace Video', 'my-book-showroom' )
							: esc_html__( 'Upload Video', 'my-book-showroom' ); ?>
					</button>
					<span class="mbs-spinning-video-name"
					      style="margin-left:6px;<?php echo $spinning_video_id ? '' : 'display:none;'; ?>">
						<?php echo esc_html( $spinning_video_title ); ?>
					</span>
					<button type="button" class="button mbs-remove-spinning-video"
					        style="<?php echo $spinning_video_id ? '' : 'display:none;'; ?>">
						<?php esc_html_e( 'Remove', 'my-book-showroom' ); ?>
					</button>
				</div>
				<p class="description"><?php esc_html_e( 'Short looping video used for the spinning book cover animation.', 'my-book-showroom' ); ?></p>
			</div>
		<?php else : ?>
			<?php MBS_Pro_Gate::comment( 'Spinning book video' ); ?>
		<?php endif; ?>
		<?php
	}


	/**
	 * Render the per-format buy links section.
	 *
	 * @param int      $row_index        Row index used as the array key for buy link input names.
	 * @param object[] $format_buy_links Saved buy link rows for this format.
	 * @param bool     $is_pro           Whether PRO tier is active.
	 * @param bool     $is_pub           Whether PUB tier is active.
	 * @return void
	 * @since 1.0.0
	 */
	private function render_row_buy_links( int $row_index, array $format_buy_links, bool $is_pro = false, bool $is_pub = false ): void {
		$field_names = array(
			'id'           => 'mbs_fmt_bl_id['           . $row_index . '][]',
			'retailer_key' => 'mbs_fmt_bl_retailer_key[' . $row_index . '][]',
			'icon_id'      => 'mbs_fmt_bl_icon_id['      . $row_index . '][]',
			'label'        => 'mbs_fmt_bl_label['        . $row_index . '][]',
			'url'          => 'mbs_fmt_bl_url['          . $row_index . '][]',
			'embed'        => 'mbs_fmt_bl_embed['        . $row_index . '][]',
		);
		?>
		<!-- ── Buy Links ───────────────────────────────────────────── -->
		<h4 class="mbs-format-section-heading"><?php esc_html_e( 'Buy Links', 'my-book-showroom' ); ?></h4>
		<div class="mbs-field mbs-field-all mbs-format-buy-links-section">
			<div class="mbs-format-buy-links-rows" data-row-index="<?php echo esc_attr( $row_index ); ?>">
				<?php foreach ( $format_buy_links as $link ) {
					MBS_Buy_Link_Row_Renderer::render_row( $link, $field_names, $is_pro, $is_pub ); // sort controls enabled (Sprint 182)
				} ?>
			</div>
			<button type="button" class="button mbs-add-format-buy-link" style="margin-top:4px;">
				<?php esc_html_e( '+ Add Buy Link', 'my-book-showroom' ); ?>
			</button>
			<?php
			$template_field_names = array(
				'id'           => 'mbs_fmt_bl_id[__ROW_IDX__][]',
				'retailer_key' => 'mbs_fmt_bl_retailer_key[__ROW_IDX__][]',
				'icon_id'      => 'mbs_fmt_bl_icon_id[__ROW_IDX__][]',
				'label'        => 'mbs_fmt_bl_label[__ROW_IDX__][]',
				'url'          => 'mbs_fmt_bl_url[__ROW_IDX__][]',
				'embed'        => 'mbs_fmt_bl_embed[__ROW_IDX__][]',
			);
			echo '<div id="fmt-buy-link-row-template-' . absint( $row_index ) . '" style="display:none;">';
			MBS_Buy_Link_Row_Renderer::render_row( null, $template_field_names, $is_pro, $is_pub ); // sort controls enabled (Sprint 182)
			echo '</div>';
			?>
		</div>
		<?php
	}

	/**
	 * Render the Publisher Supply Chain section.
	 *
	 * Visible to PUB tier only. Always expanded (no details/summary wrapper).
	 * All six supply chain fields are rendered when $is_pub is true.
	 * Currency Code defaults to the global admin setting when no per-row value
	 * is stored. On Sale Date defaults to the row publication date when empty.
	 * Outputs an HTML comment placeholder when PUB is not active.
	 *
	 * @param array $ctx    Row context array.
	 * @param bool  $is_pub Whether the PUB tier is active.
	 * @return void
	 * @since 1.0.0
	 */
	private function render_row_supply_chain( array $ctx, bool $is_pub ): void {
		$price_type    = $ctx['price_type'];
		$currency_code = $ctx['currency_code'];
		$supplier_name = $ctx['supplier_name'];
		$discount_code = $ctx['discount_code'];
		$on_sale_date  = $ctx['on_sale_date'] ?: $ctx['pub_date']; // default to row pub date
		$returnability = $ctx['returnability'];

		// Default currency to global admin setting when no per-row value is stored.
		if ( '' === $currency_code ) {
			$currency_code = mbs_get_setting( 'general', 'currency', 'USD' );
		}

		$currency_options = array(
			'AED' => 'AED — UAE Dirham',
			'AUD' => 'AUD — Australian Dollar',
			'BRL' => 'BRL — Brazilian Real',
			'CAD' => 'CAD — Canadian Dollar',
			'CHF' => 'CHF — Swiss Franc',
			'CNY' => 'CNY — Chinese Yuan',
			'DKK' => 'DKK — Danish Krone',
			'EUR' => 'EUR — Euro',
			'GBP' => 'GBP — British Pound',
			'HKD' => 'HKD — Hong Kong Dollar',
			'INR' => 'INR — Indian Rupee',
			'JPY' => 'JPY — Japanese Yen',
			'KRW' => 'KRW — South Korean Won',
			'MXN' => 'MXN — Mexican Peso',
			'NOK' => 'NOK — Norwegian Krone',
			'NZD' => 'NZD — New Zealand Dollar',
			'SEK' => 'SEK — Swedish Krona',
			'SGD' => 'SGD — Singapore Dollar',
			'USD' => 'USD — US Dollar',
			'ZAR' => 'ZAR — South African Rand',
		);
		?>
		<!-- ── Publisher Supply Chain (PUB only) ─────────────────────────── -->
		<?php if ( $is_pub ) : ?>
		<div class="mbs-pub-supply-chain" style="margin-top:8px;padding:8px 0;">
			<p style="font-weight:600;margin:0 0 8px;">
				<?php esc_html_e( 'Publisher Supply Chain', 'my-book-showroom' ); ?>
			</p>
			<div class="mbs-field">
				<label><?php esc_html_e( 'Currency Code', 'my-book-showroom' ); ?></label>
				<select name="mbs_pub_currency_code[]">
					<?php foreach ( $currency_options as $cc_k => $cc_v ) : ?>
						<option value="<?php echo esc_attr( $cc_k ); ?>" <?php selected( $currency_code, $cc_k ); ?>><?php echo esc_html( $cc_v ); ?></option>
					<?php endforeach; ?>
				</select>
			</div>
			<div class="mbs-field">
				<label><?php esc_html_e( 'On Sale Date', 'my-book-showroom' ); ?></label>
				<input type="date" name="mbs_pub_on_sale_date[]" value="<?php echo esc_attr( $on_sale_date ); ?>">
			</div>
			<div class="mbs-field">
				<label><?php esc_html_e( 'Returnability', 'my-book-showroom' ); ?></label>
				<select name="mbs_pub_returnability[]">
					<option value=""><?php esc_html_e( '-- None --', 'my-book-showroom' ); ?></option>
					<option value="00" <?php selected( $returnability, '00' ); ?>><?php esc_html_e( 'Returnable', 'my-book-showroom' ); ?></option>
					<option value="01" <?php selected( $returnability, '01' ); ?>><?php esc_html_e( 'Non-returnable', 'my-book-showroom' ); ?></option>
					<option value="02" <?php selected( $returnability, '02' ); ?>><?php esc_html_e( 'On condition', 'my-book-showroom' ); ?></option>
				</select>
			</div>
			<div class="mbs-field">
				<label><?php esc_html_e( 'Price Type', 'my-book-showroom' ); ?></label>
				<select name="mbs_pub_price_type[]">
					<option value=""><?php esc_html_e( '-- None --', 'my-book-showroom' ); ?></option>
					<?php foreach ( array( '01' => __( 'RRP excl. tax', 'my-book-showroom' ), '02' => __( 'RRP incl. tax', 'my-book-showroom' ), '03' => __( 'Fixed retail price', 'my-book-showroom' ), '04' => __( 'Fixed retail excl. tax', 'my-book-showroom' ), '41' => __( 'Publishers retail price', 'my-book-showroom' ) ) as $pt_k => $pt_v ) : ?>
						<option value="<?php echo esc_attr( $pt_k ); ?>" <?php selected( $price_type, $pt_k ); ?>><?php echo esc_html( $pt_v ); ?></option>
					<?php endforeach; ?>
				</select>
			</div>
			<div class="mbs-field">
				<label><?php esc_html_e( 'Supplier', 'my-book-showroom' ); ?></label>
				<input type="text" name="mbs_pub_supplier_name[]" value="<?php echo esc_attr( $supplier_name ); ?>" class="regular-text" autocomplete="off" placeholder="<?php esc_attr_e( 'Supplier name', 'my-book-showroom' ); ?>">
			</div>
			<div class="mbs-field">
				<label><?php esc_html_e( 'Discount Code', 'my-book-showroom' ); ?></label>
				<input type="text" name="mbs_pub_discount_code[]" value="<?php echo esc_attr( $discount_code ); ?>" class="regular-text" autocomplete="off" placeholder="<?php esc_attr_e( 'e.g. TRADE', 'my-book-showroom' ); ?>">
				<p class="description"><?php esc_html_e( 'Optional — not included in completeness score.', 'my-book-showroom' ); ?></p>
			</div>
		</div>
		<?php else : ?>
			<?php MBS_Pro_Gate::comment( 'Publisher Supply Chain fields (Publisher tier required)' ); ?>
		<?php endif; ?>
		<?php
	}

	/**
	 * Render the in-person sales / Square Item ID field (POS-2).
	 * Only rendered when WooCommerce is active.
	 *
	 * @param object|null $row         Saved row or null for template.
	 * @param bool        $is_template True when rendering the JS clone template.
	 * @param int         $row_index   Row index used as the field's `for`/`id` attribute suffix.
	 * @return void
	 * @since 1.0.0
	 */
	private function render_row_pos_fields( array $ctx, int $row_index ): void {
		if ( ! class_exists( 'WooCommerce' ) ) {
			return;
		}
		$square_item_id = $ctx['square_item_id'];
		?>
		<!-- ── In-Person Sales (POS-2) ───────────────────────────── -->
		<div class="mbs-field mbs-field-all" style="margin-top:8px;">
			<label for="mbs_square_item_id_<?php echo esc_attr( $row_index ); ?>">
				<?php esc_html_e( 'Square Item ID', 'my-book-showroom' ); ?>
			</label>
			<input type="text"
			       id="mbs_square_item_id_<?php echo esc_attr( $row_index ); ?>"
			       name="mbs_square_item_id[]"
			       value="<?php echo esc_attr( $square_item_id ); ?>"
			       class="regular-text"
			       autocomplete="off"
			       placeholder="<?php esc_attr_e( 'Optional — Square catalog item ID', 'my-book-showroom' ); ?>">
			<p class="description">
				<?php esc_html_e( 'Paste the Square catalog item ID to link this edition for in-person sales via Square for WooCommerce.', 'my-book-showroom' ); ?>
			</p>
		</div>
		<?php
	}

	/**
	 * Render the collapsible row header: chevron toggle, format type badge,
	 * ISBN/price meta summary, and the Remove button.
	 * Clicking the header (outside the Remove button) expands or collapses
	 * the row body via JS (.mbs-format-row--open toggle on .mbs-format-row).
	 *
	 * @param array $ctx Row context from MBS_Format_Repeater_Data::build_row_context().
	 * @return void
	 * @since 2.3.0
	 */
	private function render_row_header( array $ctx, bool $is_pro = false ): void {
		$format_options = MBS_Format_Options::get_format_options();
		if ( 'other' === $ctx['format'] && '' !== $ctx['format_label'] ) {
			$badge_label = $ctx['format_label'];
		} elseif ( isset( $format_options[ $ctx['format'] ] ) ) {
			$badge_label = $format_options[ $ctx['format'] ];
		} else {
			$badge_label = __( 'New Format', 'my-book-showroom' );
		}

		$meta_parts = array();
		if ( '' !== $ctx['isbn13'] ) {
			$meta_parts[] = $ctx['isbn13'];
		}
		if ( '' !== $ctx['price'] ) {
			$meta_parts[] = '$' . $ctx['price'];
		}
		$meta_text = implode( ' · ', $meta_parts );
		?>
		<div class="mbs-format-row-header">
			<?php MBS_Sortable_UI::drag_handle( $is_pro ); ?>
			<button type="button"
			        class="mbs-format-row-toggle"
			        aria-expanded="false"
			        aria-label="<?php esc_attr_e( 'Toggle format details', 'my-book-showroom' ); ?>">
				<span class="mbs-format-row-chevron dashicons dashicons-arrow-down-alt2" aria-hidden="true"></span>
			</button>
			<span class="mbs-format-row-badge"><?php echo esc_html( $badge_label ); ?></span>
			<span class="mbs-format-row-meta"><?php echo esc_html( $meta_text ); ?></span>
			<?php MBS_Sortable_UI::reorder_buttons(
				__( 'Move format up', 'my-book-showroom' ),
				__( 'Move format down', 'my-book-showroom' )
			); ?>
			<button type="button" class="button button-small mbs-remove-format-row">
				<?php esc_html_e( 'Remove', 'my-book-showroom' ); ?>
			</button>
		</div>
		<?php
	}

}
