<?php
/**
 * File Path: includes/metaboxes/class-mbs-metabox-format-repeater.php
 *
 * My Book Showroom — Format & Pricing Repeater Metabox
 *
 * Handles the Formats & Pricing repeater in the book editor (Context 2).
 * Sprint Step 3: expanded with all new format row fields, JS-driven
 * field visibility per format type, PRO-gated cover image picker, and
 * audiobook-specific fields (narrator, duration, audio sample).
 *
 * @package MyBookShowroom
 * @since   1.0.0
 */
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

// phpcs:disable WordPress.Security.NonceVerification -- nonce verified in parent class verify_save() / check_admin_referer() before $_POST access; PHPCS cannot trace cross-method nonce verification
// phpcs:disable WordPress.Security.ValidatedSanitizedInput -- input sanitized at point of use (sanitize_text_field, absint, wp_kses_post, etc.); false positives from PHPCS not tracing sanitization helpers called before this method
class MBS_Metabox_Format_Repeater extends MBS_Metabox_Base { // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedClassFound -- mbs_/MBS_ prefix is correct; scanner not configured with plugin prefix

	public function __construct() {
		add_action( 'admin_notices', array( $this, 'show_conflict_notices' ) );
	}

	/**
	 * Display any ISBN/ASIN conflict warnings stored during the previous save.
	 *
	 * Reads the transient written by save() during the POST phase and outputs the
	 * notices on the following admin_notices hook (GET phase after PRG redirect).
	 * Deletes the transient immediately to prevent double-display.
	 *
	 * Uses $_GET['post'] for the post ID rather than global $post — the superglobal
	 * is always present and unambiguous on the post.php?post=N edit screen, whereas
	 * global $post can be reset by intervening hooks before admin_notices fires.
	 *
	 * @return void
	 */
	public function show_conflict_notices(): void {
		$screen = get_current_screen();
		if ( ! $screen || 'mbs_book' !== $screen->post_type ) {
			return;
		}

		$post_id = isset( $_GET['post'] ) ? absint( $_GET['post'] ) : 0;
		if ( ! $post_id ) {
			return;
		}

		$key     = 'mbs_format_repeater_notices_' . $post_id;
		$notices = get_transient( $key );
		if ( ! is_array( $notices ) || empty( $notices ) ) {
			return;
		}

		delete_transient( $key );

		foreach ( $notices as $msg ) {
			echo '<div class="notice notice-warning is-dismissible"><p>' . wp_kses_post( $msg ) . '</p></div>';
		}
	}


	// -------------------------------------------------------------------------
	// Render
	// -------------------------------------------------------------------------

	/**
	 * Render the Formats & Pricing metabox.
	 *
	 * R-03: All HTML output delegated to MBS_Metabox_Format_Repeater_Render.
	 */
	public function render( WP_Post $post ): void {
		( new MBS_Metabox_Format_Repeater_Render() )->render( $post );
	}

	// -------------------------------------------------------------------------
	// Save
	// -------------------------------------------------------------------------

	public function save( int $post_id, WP_Post $post ): void {
		if ( ! $this->verify_save( $post_id, 'mbs_format_repeater_nonce', 'mbs_format_repeater_nonce' ) ) {
			return;
		}

		// Resolve currency once — used by clean_price() for zero-decimal validation.
		$_mbs_save_currency = mbs_get_setting( 'general', 'currency', 'USD' );

		// F-020 Phase 7 -- Supply chain fields: all gated on PUB tier only.
		$is_pub = mbs_is_pub();
		$pub_price_types     = $is_pub ? wp_unslash( $_POST['mbs_pub_price_type']    ?? array() ) : array();
		$pub_currency_codes  = $is_pub ? wp_unslash( $_POST['mbs_pub_currency_code'] ?? array() ) : array();
		$pub_supplier_names  = $is_pub ? wp_unslash( $_POST['mbs_pub_supplier_name'] ?? array() ) : array();
		$pub_discount_codes  = $is_pub ? wp_unslash( $_POST['mbs_pub_discount_code'] ?? array() ) : array();
		$pub_on_sale_dates   = $is_pub ? wp_unslash( $_POST['mbs_pub_on_sale_date']  ?? array() ) : array();
		$pub_returnabilities = $is_pub ? wp_unslash( $_POST['mbs_pub_returnability'] ?? array() ) : array();

		// Collect all submitted arrays.
		$format_row_ids    = wp_unslash( $_POST['mbs_format_row_id']    ?? array() );
		$format_sort_orders = wp_unslash( $_POST['mbs_format_sort_order'] ?? array() );
		$format_types      = wp_unslash( $_POST['mbs_format']           ?? array() );
		$format_labels     = wp_unslash( $_POST['mbs_format_label']     ?? array() );

		// Buy links per format row: mbs_fmt_bl_url[row_index][] and mbs_fmt_bl_id[row_index][].
		// Collected as-is (nested arrays); individual rows processed after the format row is saved.
		$fmt_bl_urls_all   = isset( $_POST['mbs_fmt_bl_url']   ) ? (array) $_POST['mbs_fmt_bl_url']   : array();
		$fmt_bl_ids_all    = isset( $_POST['mbs_fmt_bl_id']   )  ? (array) $_POST['mbs_fmt_bl_id']    : array();
		$fmt_bl_labels_all = isset( $_POST['mbs_fmt_bl_label'] )
			? array_map( function( $col ) {
				return array_map( 'sanitize_text_field', array_map( 'wp_unslash', (array) $col ) );
			  }, (array) $_POST['mbs_fmt_bl_label'] )
			: array();
		$fmt_bl_icon_ids_all = isset( $_POST['mbs_fmt_bl_icon_id'] )
			? array_map( function( $col ) {
				return array_map( 'absint', (array) $col );
			  }, (array) $_POST['mbs_fmt_bl_icon_id'] )
			: array();
		$subtitles         = wp_unslash( $_POST['mbs_subtitle']         ?? array() );
		$pub_dates         = wp_unslash( $_POST['mbs_pub_date']         ?? array() );
		$edition_numbers   = wp_unslash( $_POST['mbs_edition_number_row'] ?? array() );
		$edition_types     = wp_unslash( $_POST['mbs_edition_type_row']   ?? array() );
		$isbn13s           = wp_unslash( $_POST['mbs_isbn13']           ?? array() );
		$asins             = wp_unslash( $_POST['mbs_asin']             ?? array() );
		$prices            = wp_unslash( $_POST['mbs_price']            ?? array() );
		$page_lengths      = wp_unslash( $_POST['mbs_page_length']      ?? array() );
		$narrators         = wp_unslash( $_POST['mbs_narrator']         ?? array() );
		$duration_hhs      = wp_unslash( $_POST['mbs_duration_hh']      ?? array() );
		$duration_mms      = wp_unslash( $_POST['mbs_duration_mm']      ?? array() );
		$file_formats      = wp_unslash( $_POST['mbs_file_format']      ?? array() );
		$cover_image_ids    = wp_unslash( $_POST['mbs_cover_image_id']    ?? array() );
		$audio_sample_ids   = wp_unslash( $_POST['mbs_audio_sample_id']   ?? array() );
		$spinning_video_ids  = wp_unslash( $_POST['mbs_spinning_video_id']  ?? array() );
		$square_item_ids     = class_exists( 'WooCommerce' )
			? wp_unslash( $_POST['mbs_square_item_id'] ?? array() )
			: array(); // POS-2: Square Item ID — only available when WC is active.

		$count = max(
			count( $format_row_ids ),
			count( $format_types ),
			count( $format_labels ),
			count( $isbn13s ),
			count( $asins ),
			count( $prices )
		);

		$allowed_file_formats = array( 'epub', 'mobi', 'pdf', 'other' );
		$keep_ids             = array();
		$notices              = array(); // FV-006/FV-007: user-facing conflict messages.

		for ( $i = 0; $i < $count; $i++ ) {
			$format = sanitize_text_field( $format_types[ $i ] ?? '' );
			if ( '' === $format ) {
				continue; // Skip rows with no format selected.
			}

			// V8-B-001: Validate format type against registered allowlist.
			if ( ! array_key_exists( $format, MBS_Format_Options::get_format_options() ) ) {
				continue; // Skip unknown format type.
			}

			$row_id = ! empty( $format_row_ids[ $i ] ) ? (int) $format_row_ids[ $i ] : 0;

			// Pub date: accept only valid Y-m-d values.
			$pub_date_raw = sanitize_text_field( $pub_dates[ $i ] ?? '' );
			$pub_date     = '';
			if ( '' !== $pub_date_raw ) {
				$date_obj = DateTime::createFromFormat( 'Y-m-d', $pub_date_raw );
				if ( $date_obj && $date_obj->format( 'Y-m-d' ) === $pub_date_raw ) {
					$pub_date = $pub_date_raw;
				}
			}

			// Duration: store NULL when field is blank (0 is a valid value).
			$duration_hh_raw = $duration_hhs[ $i ] ?? '';
			$duration_mm_raw = $duration_mms[ $i ] ?? '';
			$duration_hh     = '' !== $duration_hh_raw ? absint( $duration_hh_raw ) : null;
			$duration_mm     = '' !== $duration_mm_raw ? min( 59, absint( $duration_mm_raw ) ) : null;

			// File format: whitelist check.
			$file_format = sanitize_text_field( $file_formats[ $i ] ?? '' );
			if ( ! in_array( $file_format, $allowed_file_formats, true ) ) {
				$file_format = '';
			}

			// FV-007: Normalize ISBN and check for duplicates before saving.
			$isbn13_raw  = $isbn13s[ $i ] ?? '';
			$isbn13_norm = '' !== $isbn13_raw ? ( MBS_ISBN_Helper::normalize_isbn( $isbn13_raw ) ?: null ) : null;
			if ( null !== $isbn13_norm ) {
				$isbn_conflict = MBS_Format_Row_DAO::get_format_by_isbn( $isbn13_norm, $row_id, $post_id );
				if ( $isbn_conflict ) {
					/* translators: %d: WordPress post ID used as a fallback book identifier */
					$conflict_title = get_the_title( $isbn_conflict->post_id ) ?: sprintf( __( 'Book #%d', 'my-book-showroom' ), $isbn_conflict->post_id ); // phpcs:ignore WordPress.WP.I18n.MissingTranslatorsComment -- single numeric placeholder, no translator context needed
					$conflict_fmt   = MBS_Format_Options::get_format_label( $isbn_conflict->format );
					$notices[]      = sprintf(
						/* translators: 1: formatted ISBN-13, 2: book title, 3: format label */
						__( 'ISBN %1$s is already in use in <strong>%2$s</strong> (%3$s). The ISBN was not saved for this format row — please resolve the conflict.', 'my-book-showroom' ),
						esc_html( MBS_ISBN_Helper::format_isbn( $isbn13_norm ) ),
						esc_html( $conflict_title ),
						esc_html( $conflict_fmt )
					);
					$isbn13_norm = null; // Do not store the duplicate.
				}
			}

			// FV-006: Normalize ASIN and check for duplicates before saving.
			$asin_raw  = sanitize_text_field( $asins[ $i ] ?? '' );
			$asin_norm = '' !== $asin_raw ? ( MBS_ISBN_Helper::normalize_asin( $asin_raw ) ?: null ) : null;
			if ( null !== $asin_norm ) {
				$asin_conflict = MBS_Format_Row_DAO::get_format_by_asin( $asin_norm, $row_id, $post_id );
				if ( $asin_conflict ) {
					$conflict_title = get_the_title( $asin_conflict->post_id ) ?: sprintf( __( 'Book #%d', 'my-book-showroom' ), $asin_conflict->post_id ); // phpcs:ignore WordPress.WP.I18n.MissingTranslatorsComment -- single numeric placeholder, no translator context needed
					$conflict_fmt   = MBS_Format_Options::get_format_label( $asin_conflict->format );
					$notices[]      = sprintf(
						/* translators: 1: ASIN, 2: book title, 3: format label */
						__( 'ASIN %1$s is already in use in <strong>%2$s</strong> (%3$s). The ASIN was not saved for this format row — please resolve the conflict.', 'my-book-showroom' ),
						esc_html( $asin_norm ),
						esc_html( $conflict_title ),
						esc_html( $conflict_fmt )
					);
					$asin_norm = null; // Do not store the duplicate.
				}
			}

			$data = array(
				'format'          => $format,
				'sort_order'      => absint( $format_sort_orders[ $i ] ?? $i ),
				'format_label'    => sanitize_text_field( $format_labels[ $i ] ?? '' ) ?: null,
				'subtitle'        => sanitize_text_field( $subtitles[ $i ] ?? '' ) ?: null,
				'pub_date'        => $pub_date ?: null,
				'edition_number'  => absint( $edition_numbers[ $i ] ?? 0 ) ?: null,
				'edition_type'    => sanitize_text_field( $edition_types[ $i ] ?? '' ) ?: null,
				'isbn13'          => $isbn13_norm,
				'asin'            => $asin_norm,
				'price'           => MBS_Formatting::clean_price( $prices[ $i ] ?? '', $_mbs_save_currency ),
				'page_length'     => absint( $page_lengths[ $i ] ?? 0 ) ?: null,
				'narrator'        => sanitize_text_field( $narrators[ $i ] ?? '' ) ?: null,
				'duration_hh'     => $duration_hh,
				'duration_mm'     => $duration_mm,
				'file_format'     => $file_format ?: null,
				'cover_image_id'    => absint( $cover_image_ids[ $i ] ?? 0 ) ?: null,
				'audio_sample_id'   => absint( $audio_sample_ids[ $i ] ?? 0 ) ?: null,
				'spinning_video_id' => absint( $spinning_video_ids[ $i ] ?? 0 ) ?: null,
				'square_item_id'    => class_exists( 'WooCommerce' )
					? ( sanitize_text_field( $square_item_ids[ $i ] ?? '' ) ?: null )
					: null, // POS-2
			);

			// F-020 Phase 7 -- PUB-only supply chain fields.
			if ( $is_pub ) {
				$cc_raw = strtoupper( sanitize_text_field( $pub_currency_codes[ $i ] ?? '' ) );
				$data['currency_code'] = preg_match( '/^[A-Z]{3}$/', $cc_raw ) ? $cc_raw : null;

				$osd_raw = sanitize_text_field( $pub_on_sale_dates[ $i ] ?? '' );
				$osd_obj = $osd_raw ? \DateTime::createFromFormat( 'Y-m-d', $osd_raw ) : false;
				$data['on_sale_date'] = ( $osd_obj && $osd_obj->format( 'Y-m-d' ) === $osd_raw ) ? $osd_raw : null;

				$ret_raw   = sanitize_text_field( $pub_returnabilities[ $i ] ?? '' );
				$ret_allow = array( '', '00', '01', '02' );
				$data['returnability'] = in_array( $ret_raw, $ret_allow, true ) ? ( $ret_raw ?: null ) : null;

				$pt_raw   = sanitize_text_field( $pub_price_types[ $i ] ?? '' );
				$pt_allow = array( '', '01', '02', '03', '04', '41' );
				$data['price_type'] = in_array( $pt_raw, $pt_allow, true ) ? ( $pt_raw ?: null ) : null;

				$data['supplier_name'] = sanitize_text_field( $pub_supplier_names[ $i ] ?? '' ) ?: null;
				$data['discount_code'] = sanitize_text_field( $pub_discount_codes[ $i ] ?? '' ) ?: null;
			}

			if ( $row_id > 0 ) {
				MBS_Format_Row_DAO::update( $row_id, $data );
				$keep_ids[] = $row_id;
			} else {
				$data['post_id'] = $post_id;
				$new_id = MBS_Format_Row_DAO::insert( $data );
				if ( $new_id ) {
					$row_id     = $new_id; // resolve final ID for buy link save below
					$keep_ids[] = $row_id;
				}
			}

			// Save buy links for this format row.
			// mbs_fmt_bl_url[$i][] and mbs_fmt_bl_id[$i][] are indexed by format row position.
			if ( $row_id > 0 ) {
				$bl_urls    = isset( $fmt_bl_urls_all[ $i ] )   ? (array) wp_unslash( $fmt_bl_urls_all[ $i ] )   : array();
				$bl_ids_raw = isset( $fmt_bl_ids_all[ $i ] )   ? (array) wp_unslash( $fmt_bl_ids_all[ $i ] )   : array();
				$keep_bl    = array();

				foreach ( $bl_urls as $j => $url_raw ) {
					$url = esc_url_raw( trim( $url_raw ) );
					if ( empty( $url ) ) {
						continue;
					}
					$bl_id        = absint( $bl_ids_raw[ $j ] ?? 0 );
					$retailer_key = MBS_Metabox_Buy_Links::detect_retailer_key( $url );

					$bl_data = array(
						'post_id'            => $post_id,
						'url'                => $url,
						'retailer_key'       => $retailer_key,
						'format_row_id'      => $row_id,
						'sort_order'         => $j,
						'link_type'          => 'buy',
						'link_label'         => $fmt_bl_labels_all[ $i ][ $j ]   ?? '',
						'icon_attachment_id' => $fmt_bl_icon_ids_all[ $i ][ $j ] ?: null,
					);

					if ( $bl_id > 0 ) {
						MBS_Buy_Link_DAO::save( array_merge( $bl_data, array( 'id' => $bl_id ) ) );
						$keep_bl[] = $bl_id;
					} else {
						$new_bl = MBS_Buy_Link_DAO::save( $bl_data );
						if ( $new_bl ) {
							$keep_bl[] = $new_bl;
						}
					}
				}

				// Soft-delete removed buy links for this specific format row.
				MBS_Buy_Link_DAO::soft_delete_missing_for_format_row( $row_id, $keep_bl );
			}
		}

		MBS_Format_Row_DAO::soft_delete_missing_for_post( $post_id, $keep_ids );

		// Soft-delete any format-level buy links whose parent format row was just removed.
		MBS_Buy_Link_DAO::soft_delete_orphaned_for_post( $post_id, $keep_ids );

		// FV-006/FV-007: Surface any duplicate ISBN/ASIN conflicts as dismissible admin notices.
		// Transient is read by show_conflict_notices() on the subsequent GET after the PRG redirect.
		if ( ! empty( $notices ) ) {
			set_transient( 'mbs_format_repeater_notices_' . $post_id, $notices, 60 );
		}
	}

	/**
	 * Render one editor section within the unified book editor shell.
	 *
	 * Passthrough — single-section handler (Sprint 169).
	 *
	 * @since  1.0.0
	 * @param  string  $section_key  Section key (ignored for single-section handlers).
	 * @param  WP_Post $post         Current post object.
	 * @return void
	 */
	public function render_section( string $section_key, WP_Post $post ): void {
		$this->render( $post );
	}

	/**
	 * Save one editor section within the unified book editor shell.
	 *
	 * Passthrough — single-section handler (Sprint 169).
	 *
	 * @since  1.0.0
	 * @param  string  $section_key  Section key (ignored for single-section handlers).
	 * @param  int     $post_id      Post ID.
	 * @param  WP_Post $post         Post object.
	 * @return void
	 */
	public function save_section( string $section_key, int $post_id, WP_Post $post ): void {
		$this->save( $post_id, $post );
	}

}
