<?php
/**
 * Product editor meta boxes.
 *
 * @package WPDudeProducts
 */

declare(strict_types=1);

namespace WPDude\Products\Admin;

use WPDude\Products\Products\Post_Type;
use WPDude\Products\Products\Product_Meta;
use WPDude\Products\Support\Currencies;
use WPDude\Products\Support\Money;

/**
 * Product fields for standard and options physical products.
 */
final class Product_Meta_Box {

	/**
	 * Register hooks.
	 */
	public function register(): void {
		add_action( 'add_meta_boxes', array( $this, 'add' ) );
		add_action( 'save_post_' . Post_Type::POST_TYPE, array( $this, 'save' ), 10, 2 );
		add_action( 'admin_enqueue_scripts', array( $this, 'enqueue' ) );
		add_action( 'admin_notices', array( $this, 'sku_notices' ) );
	}

	/**
	 * Show SKU uniqueness errors after save.
	 */
	public function sku_notices(): void {
		if ( ! current_user_can( 'edit_posts' ) ) {
			return;
		}

		$key    = self::sku_notice_key();
		$errors = get_transient( $key );
		if ( ! is_array( $errors ) || empty( $errors ) ) {
			return;
		}
		delete_transient( $key );

		foreach ( $errors as $message ) {
			printf(
				'<div class="notice notice-error is-dismissible"><p>%s</p></div>',
				esc_html( (string) $message )
			);
		}
	}

	/**
	 * Transient key for current user SKU errors.
	 */
	private static function sku_notice_key(): string {
		return 'wpdude_dc_sku_errors_' . get_current_user_id();
	}

	/**
	 * Queue admin error notices for conflicting SKUs.
	 *
	 * @param list<string> $conflicts Conflicting SKU strings.
	 */
	private static function queue_sku_conflicts( array $conflicts ): void {
		if ( empty( $conflicts ) ) {
			return;
		}

		$messages = array();
		foreach ( $conflicts as $sku ) {
			$messages[] = sprintf(
				/* translators: %s: SKU code */
				__( 'SKU “%s” is already in use on another product or combination. It was not saved — each SKU must be unique across standard and options products.', 'dude-commerce' ),
				$sku
			);
		}

		set_transient( self::sku_notice_key(), $messages, MINUTE_IN_SECONDS );
	}

	/**
	 * Enqueue admin assets.
	 *
	 * @param string $hook Hook.
	 */
	public function enqueue( string $hook ): void {
		if ( ! in_array( $hook, array( 'post.php', 'post-new.php' ), true ) ) {
			return;
		}
		$screen = get_current_screen();
		if ( ! $screen || Post_Type::POST_TYPE !== $screen->post_type ) {
			return;
		}

		wp_enqueue_media();
		wp_enqueue_style(
			'wpdude-dc-admin-product',
			WPDUDE_DC_URL . 'assets/css/admin-product.css',
			array(),
			WPDUDE_DC_VERSION
		);
		wp_enqueue_script(
			'wpdude-dc-admin-product',
			WPDUDE_DC_URL . 'assets/js/admin-product.js',
			array( 'jquery' ),
			WPDUDE_DC_VERSION,
			true
		);
		wp_localize_script(
			'wpdude-dc-admin-product',
			'wpdudeDcAdmin',
			array(
				'maxOptions' => Product_Meta::MAX_OPTIONS,
				'i18n'       => array(
					'selectImages'      => __( 'Select gallery images', 'dude-commerce' ),
					'useImages'         => __( 'Use images', 'dude-commerce' ),
					'selectImage'       => __( 'Select combination image', 'dude-commerce' ),
					'useImage'          => __( 'Use image', 'dude-commerce' ),
					'noImage'           => __( 'No image', 'dude-commerce' ),
					'optionLimit'       => __( 'Maximum of 5 options.', 'dude-commerce' ),
					'needOptions'       => __( 'Add at least one option with values first.', 'dude-commerce' ),
					'namePlaceholder'   => __( 'e.g. Size', 'dude-commerce' ),
					'valuePlaceholder'  => __( 'e.g. S, M, L', 'dude-commerce' ),
					'remove'            => __( 'Remove', 'dude-commerce' ),
					'addValue'          => __( 'Add value', 'dude-commerce' ),
					'optionName'        => __( 'Option name', 'dude-commerce' ),
					'values'            => __( 'Values', 'dude-commerce' ),
					'generate'          => __( 'Generate combinations', 'dude-commerce' ),
					'select'            => __( 'Select', 'dude-commerce' ),
					'clear'             => __( 'Clear', 'dude-commerce' ),
					'needCombinations'  => __( 'Generate combinations first.', 'dude-commerce' ),
					'needSkuPrefix'     => __( 'Enter a SKU prefix first.', 'dude-commerce' ),
				),
			)
		);
	}

	/**
	 * Add meta boxes.
	 */
	public function add(): void {
		add_meta_box(
			'wpdude_dc_details',
			__( 'Product details', 'dude-commerce' ),
			array( $this, 'render_details' ),
			Post_Type::POST_TYPE,
			'normal',
			'high'
		);
		add_meta_box(
			'wpdude_dc_gallery',
			__( 'Product gallery', 'dude-commerce' ),
			array( $this, 'render_gallery' ),
			Post_Type::POST_TYPE,
			'side',
			'default'
		);
		add_meta_box(
			'wpdude_dc_options',
			__( 'Options', 'dude-commerce' ),
			array( $this, 'render_options' ),
			Post_Type::POST_TYPE,
			'normal',
			'default'
		);
	}

	/**
	 * Render core product fields.
	 *
	 * @param \WP_Post $post Post.
	 */
	public function render_details( \WP_Post $post ): void {
		wp_nonce_field( 'wpdude_dc_save_product', 'wpdude_dc_product_nonce' );

		$type             = Product_Meta::get_type( $post->ID );
		$currency         = Product_Meta::get_currency( $post->ID );
		$price            = Product_Meta::minor_to_major_input( Product_Meta::get_price_minor( $post->ID ), $currency );
		$sale_price       = Product_Meta::minor_to_major_input( Product_Meta::get_sale_price_minor( $post->ID ), $currency );
		$sku              = Product_Meta::get_sku( $post->ID );
		$stock            = Product_Meta::get_stock( $post->ID );
		$sold_individually = Product_Meta::is_sold_individually( $post->ID );
		$shipping_type    = Product_Meta::get_shipping_type( $post->ID );
		$shipping_amount  = Product_Meta::minor_to_major_input( Product_Meta::get_shipping_amount_minor( $post->ID ), $currency );
		$is_standard = Product_Meta::TYPE_STANDARD === $type;
		?>
		<p>
			<label for="wpdude_dc_product_type"><strong><?php echo esc_html__( 'Product type', 'dude-commerce' ); ?></strong></label><br />
			<select name="wpdude_dc_product_type" id="wpdude_dc_product_type">
				<option value="standard" <?php selected( $type, Product_Meta::TYPE_STANDARD ); ?>>
					<?php echo esc_html__( 'Standard product', 'dude-commerce' ); ?>
				</option>
				<option value="options" <?php selected( $type, Product_Meta::TYPE_OPTIONS ); ?>>
					<?php echo esc_html__( 'Options product', 'dude-commerce' ); ?>
				</option>
			</select>
		</p>

		<p>
			<label for="wpdude_dc_currency"><strong><?php echo esc_html__( 'Currency', 'dude-commerce' ); ?></strong></label><br />
			<?php
			Currencies::render_select(
				'wpdude_dc_currency',
				'wpdude_dc_currency',
				$currency
			);
			?>
			<span class="description"><?php echo esc_html__( 'Used for this product’s prices and Stripe Checkout.', 'dude-commerce' ); ?></span>
		</p>

		<div class="wpdude-dc-standard-fields" <?php echo $is_standard ? '' : 'hidden'; ?>>
			<p>
				<label for="wpdude_dc_sku"><strong><?php echo esc_html__( 'SKU', 'dude-commerce' ); ?></strong></label><br />
				<input type="text" name="wpdude_dc_sku" id="wpdude_dc_sku" value="<?php echo esc_attr( $sku ); ?>" class="regular-text" />
				<span class="description"><?php echo esc_html__( 'Must be unique across all products (standard and options).', 'dude-commerce' ); ?></span>
			</p>
			<p>
				<label for="wpdude_dc_price"><strong><?php echo esc_html__( 'Price', 'dude-commerce' ); ?></strong> (<span class="wpdude-dc-currency-code"><?php echo esc_html( strtoupper( $currency ) ); ?></span>)</label><br />
				<input type="text" name="wpdude_dc_price" id="wpdude_dc_price" value="<?php echo esc_attr( $price ); ?>" class="regular-text" placeholder="10.00" />
				<span class="description"><?php echo esc_html__( 'Required. Must be greater than zero.', 'dude-commerce' ); ?></span>
			</p>
			<p>
				<label for="wpdude_dc_sale_price"><strong><?php echo esc_html__( 'Sale price', 'dude-commerce' ); ?></strong></label><br />
				<input type="text" name="wpdude_dc_sale_price" id="wpdude_dc_sale_price" value="<?php echo esc_attr( $sale_price ); ?>" class="regular-text" placeholder="8.00" />
				<span class="description"><?php echo esc_html__( 'Optional. Leave blank for no sale.', 'dude-commerce' ); ?></span>
			</p>
			<p>
				<label for="wpdude_dc_stock"><strong><?php echo esc_html__( 'Stock', 'dude-commerce' ); ?></strong></label><br />
				<input type="number" name="wpdude_dc_stock" id="wpdude_dc_stock" value="<?php echo esc_attr( (string) $stock ); ?>" class="small-text" min="0" step="1" />
			</p>
		</div>

		<p>
			<label>
				<input type="checkbox" name="wpdude_dc_sold_individually" value="yes" <?php checked( $sold_individually ); ?> />
				<?php echo esc_html__( 'Sold individually', 'dude-commerce' ); ?>
			</label>
			<span class="description"><?php echo esc_html__( 'Hide quantity on Buy Now and force quantity to 1.', 'dude-commerce' ); ?></span>
		</p>

		<p>
			<label for="wpdude_dc_shipping_type"><strong><?php echo esc_html__( 'Shipping', 'dude-commerce' ); ?></strong></label><br />
			<select name="wpdude_dc_shipping_type" id="wpdude_dc_shipping_type">
				<option value="free" <?php selected( $shipping_type, Product_Meta::SHIPPING_FREE ); ?>>
					<?php echo esc_html__( 'Free shipping', 'dude-commerce' ); ?>
				</option>
				<option value="flat" <?php selected( $shipping_type, Product_Meta::SHIPPING_FLAT ); ?>>
					<?php echo esc_html__( 'Flat rate shipping', 'dude-commerce' ); ?>
				</option>
			</select>
		</p>
		<p class="wpdude-dc-shipping-amount" <?php echo Product_Meta::SHIPPING_FLAT === $shipping_type ? '' : 'hidden'; ?>>
			<label for="wpdude_dc_shipping_amount"><strong><?php echo esc_html__( 'Shipping amount', 'dude-commerce' ); ?></strong> (<span class="wpdude-dc-currency-code"><?php echo esc_html( strtoupper( $currency ) ); ?></span>)</label><br />
			<input type="text" name="wpdude_dc_shipping_amount" id="wpdude_dc_shipping_amount" value="<?php echo esc_attr( $shipping_amount ); ?>" class="regular-text" placeholder="5.00" />
			<span class="description"><?php echo esc_html__( 'Charged once per order.', 'dude-commerce' ); ?></span>
		</p>
		<?php
	}

	/**
	 * Render gallery meta box.
	 *
	 * @param \WP_Post $post Post.
	 */
	public function render_gallery( \WP_Post $post ): void {
		$ids = Product_Meta::get_gallery( $post->ID );
		?>
		<div id="wpdude-dc-gallery" class="wpdude-dc-gallery">
			<input type="hidden" name="wpdude_dc_gallery" id="wpdude_dc_gallery_ids" value="<?php echo esc_attr( implode( ',', $ids ) ); ?>" />
			<ul class="wpdude-dc-gallery-list">
				<?php foreach ( $ids as $id ) : ?>
					<?php
					$thumb = wp_get_attachment_image( $id, 'thumbnail' );
					if ( ! $thumb ) {
						continue;
					}
					?>
					<li data-id="<?php echo esc_attr( (string) $id ); ?>">
						<?php echo $thumb; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- wp_get_attachment_image(). ?>
						<button type="button" class="button-link wpdude-dc-gallery-remove" aria-label="<?php echo esc_attr__( 'Remove image', 'dude-commerce' ); ?>">&times;</button>
					</li>
				<?php endforeach; ?>
			</ul>
			<p>
				<button type="button" class="button" id="wpdude_dc_gallery_add"><?php echo esc_html__( 'Add images', 'dude-commerce' ); ?></button>
			</p>
			<p class="description"><?php echo esc_html__( 'Featured image is the default. Gallery images are additional product photos (not tied to options).', 'dude-commerce' ); ?></p>
		</div>
		<?php
	}

	/**
	 * Render options meta box (options products).
	 *
	 * @param \WP_Post $post Post.
	 */
	public function render_options( \WP_Post $post ): void {
		$type         = Product_Meta::get_type( $post->ID );
		$options      = Product_Meta::get_options( $post->ID );
		$combinations = Product_Meta::get_combinations( $post->ID );
		$currency     = Product_Meta::get_currency( $post->ID );
		$is_options   = Product_Meta::TYPE_OPTIONS === $type;
		?>
		<div id="wpdude-dc-options-wrap" <?php echo $is_options ? '' : 'hidden'; ?>>
			<p class="description">
				<?php echo esc_html__( 'Add up to 5 options (e.g. Size, Colour). Combinations are generated from all value mixes. Set each combination image next to its SKU.', 'dude-commerce' ); ?>
			</p>

			<div id="wpdude-dc-option-defs">
				<?php
				if ( empty( $options ) ) {
					$options = array(
						array(
							'name'   => '',
							'values' => array(
								array(
									'value'    => '',
									'image_id' => 0,
								),
							),
						),
					);
				}
				foreach ( $options as $opt_index => $option ) :
					?>
					<div class="wpdude-dc-option-row" data-option-index="<?php echo esc_attr( (string) $opt_index ); ?>">
						<p>
							<label><strong><?php echo esc_html__( 'Option name', 'dude-commerce' ); ?></strong></label><br />
							<input type="text" name="wpdude_dc_option_name[<?php echo esc_attr( (string) $opt_index ); ?>]" value="<?php echo esc_attr( $option['name'] ); ?>" class="regular-text wpdude-dc-option-name" placeholder="<?php echo esc_attr__( 'e.g. Size', 'dude-commerce' ); ?>" />
						</p>
						<p><strong><?php echo esc_html__( 'Values', 'dude-commerce' ); ?></strong>
							<span class="description"><?php echo esc_html__( '— one row per value, or type S, M, L in one row', 'dude-commerce' ); ?></span>
						</p>
						<ul class="wpdude-dc-option-values">
							<?php foreach ( $option['values'] as $entry ) : ?>
								<li class="wpdude-dc-option-value-row">
									<input type="text" name="wpdude_dc_option_value[<?php echo esc_attr( (string) $opt_index ); ?>][]" value="<?php echo esc_attr( $entry['value'] ); ?>" class="regular-text wpdude-dc-option-value-label" placeholder="<?php echo esc_attr__( 'e.g. S, M, L', 'dude-commerce' ); ?>" />
									<button type="button" class="button-link-delete wpdude-dc-remove-value"><?php echo esc_html__( 'Remove', 'dude-commerce' ); ?></button>
								</li>
							<?php endforeach; ?>
						</ul>
						<p>
							<button type="button" class="button wpdude-dc-add-value"><?php echo esc_html__( 'Add value', 'dude-commerce' ); ?></button>
							<button type="button" class="button-link-delete wpdude-dc-remove-option"><?php echo esc_html__( 'Remove option', 'dude-commerce' ); ?></button>
						</p>
						<hr />
					</div>
				<?php endforeach; ?>
			</div>

			<p>
				<button type="button" class="button" id="wpdude_dc_add_option"><?php echo esc_html__( 'Add option', 'dude-commerce' ); ?></button>
				<button type="button" class="button button-primary" id="wpdude_dc_generate_combinations"><?php echo esc_html__( 'Generate combinations', 'dude-commerce' ); ?></button>
			</p>

			<div class="wpdude-dc-combo-defaults" id="wpdude-dc-combo-defaults">
				<p class="description">
					<?php echo esc_html__( 'Defaults speed up entry only. Each combination row can still be edited. SKUs must stay unique across all products.', 'dude-commerce' ); ?>
				</p>
				<div class="wpdude-dc-combo-defaults__fields">
					<label>
						<span><?php echo esc_html__( 'Price', 'dude-commerce' ); ?></span>
						<input type="text" id="wpdude_dc_combo_default_price" class="small-text" placeholder="10.00" autocomplete="off" />
					</label>
					<label>
						<span><?php echo esc_html__( 'Sale price', 'dude-commerce' ); ?></span>
						<input type="text" id="wpdude_dc_combo_default_sale" class="small-text" autocomplete="off" />
					</label>
					<label>
						<span><?php echo esc_html__( 'Stock', 'dude-commerce' ); ?></span>
						<input type="number" id="wpdude_dc_combo_default_stock" class="small-text" min="0" step="1" value="0" autocomplete="off" />
					</label>
					<label class="wpdude-dc-combo-defaults__check">
						<input type="checkbox" id="wpdude_dc_same_price_all" value="1" />
						<?php echo esc_html__( 'Same price for all', 'dude-commerce' ); ?>
					</label>
					<label class="wpdude-dc-combo-defaults__check">
						<input type="checkbox" id="wpdude_dc_same_stock_all" value="1" />
						<?php echo esc_html__( 'Same stock for all', 'dude-commerce' ); ?>
					</label>
				</div>
				<p class="wpdude-dc-combo-defaults__actions">
					<button type="button" class="button" id="wpdude_dc_combo_apply_all"><?php echo esc_html__( 'Apply to all', 'dude-commerce' ); ?></button>
					<button type="button" class="button" id="wpdude_dc_combo_fill_empty"><?php echo esc_html__( 'Fill empty only', 'dude-commerce' ); ?></button>
				</p>
				<div class="wpdude-dc-combo-defaults__sku">
					<label>
						<span><?php echo esc_html__( 'SKU prefix', 'dude-commerce' ); ?></span>
						<input type="text" id="wpdude_dc_combo_sku_prefix" class="regular-text" placeholder="<?php echo esc_attr__( 'e.g. TEE', 'dude-commerce' ); ?>" autocomplete="off" />
					</label>
					<label class="wpdude-dc-combo-defaults__check">
						<input type="checkbox" id="wpdude_dc_combo_sku_overwrite" value="1" />
						<?php echo esc_html__( 'Overwrite existing SKUs', 'dude-commerce' ); ?>
					</label>
					<button type="button" class="button" id="wpdude_dc_combo_generate_skus"><?php echo esc_html__( 'Generate SKUs', 'dude-commerce' ); ?></button>
				</div>
			</div>

			<p class="description"><?php echo esc_html__( 'Set SKU, price, stock, and image on each combination row. SKUs must be unique across all products.', 'dude-commerce' ); ?></p>

			<table class="widefat striped wpdude-dc-combinations-table" id="wpdude-dc-combinations-table">
				<thead>
					<tr>
						<th><?php echo esc_html__( 'Combination', 'dude-commerce' ); ?></th>
						<th><?php echo esc_html__( 'SKU', 'dude-commerce' ); ?></th>
						<th><?php echo esc_html__( 'Price', 'dude-commerce' ); ?></th>
						<th><?php echo esc_html__( 'Sale price', 'dude-commerce' ); ?></th>
						<th><?php echo esc_html__( 'Stock', 'dude-commerce' ); ?></th>
						<th><?php echo esc_html__( 'Image', 'dude-commerce' ); ?></th>
					</tr>
				</thead>
				<tbody id="wpdude-dc-combinations-body">
					<?php if ( empty( $combinations ) ) : ?>
						<tr class="wpdude-dc-no-combinations">
							<td colspan="6"><?php echo esc_html__( 'No combinations yet. Add options and click Generate combinations.', 'dude-commerce' ); ?></td>
						</tr>
					<?php else : ?>
						<?php foreach ( $combinations as $i => $combination ) : ?>
							<?php
							$label = array();
							foreach ( $combination['options'] as $name => $value ) {
								$label[] = $name . ': ' . $value;
							}
							$price_in = Product_Meta::minor_to_major_input( (int) $combination['price'], $currency );
							$sale_in  = Product_Meta::minor_to_major_input( (int) $combination['sale_price'], $currency );
							$image_id = (int) ( $combination['image_id'] ?? 0 );
							$thumb    = $image_id ? wp_get_attachment_image( $image_id, array( 40, 40 ) ) : '';
							?>
							<tr>
								<td>
									<strong><?php echo esc_html( implode( ' / ', $label ) ); ?></strong>
									<input type="hidden" name="wpdude_dc_combo_key[<?php echo esc_attr( (string) $i ); ?>]" value="<?php echo esc_attr( (string) $combination['key'] ); ?>" />
									<?php foreach ( $combination['options'] as $opt_name => $opt_value ) : ?>
										<input type="hidden" name="wpdude_dc_combo_opt[<?php echo esc_attr( (string) $i ); ?>][<?php echo esc_attr( $opt_name ); ?>]" value="<?php echo esc_attr( $opt_value ); ?>" />
									<?php endforeach; ?>
								</td>
								<td>
									<input type="text" name="wpdude_dc_combo_sku[<?php echo esc_attr( (string) $i ); ?>]" value="<?php echo esc_attr( (string) $combination['sku'] ); ?>" class="regular-text" />
								</td>
								<td>
									<input type="text" name="wpdude_dc_combo_price[<?php echo esc_attr( (string) $i ); ?>]" value="<?php echo esc_attr( $price_in ); ?>" class="small-text" placeholder="10.00" />
								</td>
								<td>
									<input type="text" name="wpdude_dc_combo_sale_price[<?php echo esc_attr( (string) $i ); ?>]" value="<?php echo esc_attr( $sale_in ); ?>" class="small-text" />
								</td>
								<td>
									<input type="number" name="wpdude_dc_combo_stock[<?php echo esc_attr( (string) $i ); ?>]" value="<?php echo esc_attr( (string) (int) $combination['stock'] ); ?>" class="small-text" min="0" step="1" />
								</td>
								<td class="wpdude-dc-combo-image-cell">
									<input type="hidden" name="wpdude_dc_combo_image[<?php echo esc_attr( (string) $i ); ?>]" value="<?php echo esc_attr( (string) $image_id ); ?>" class="wpdude-dc-combo-image-id" />
									<span class="wpdude-dc-combo-image-preview"><?php echo $thumb ? $thumb : esc_html__( 'No image', 'dude-commerce' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></span>
									<button type="button" class="button wpdude-dc-combo-image-select"><?php echo esc_html__( 'Select', 'dude-commerce' ); ?></button>
									<button type="button" class="button-link wpdude-dc-combo-image-clear"><?php echo esc_html__( 'Clear', 'dude-commerce' ); ?></button>
								</td>
							</tr>
						<?php endforeach; ?>
					<?php endif; ?>
				</tbody>
			</table>
		</div>
		<p id="wpdude-dc-options-standard-note" <?php echo $is_options ? 'hidden' : ''; ?>>
			<?php echo esc_html__( 'Switch product type to Options product to configure options and combinations.', 'dude-commerce' ); ?>
		</p>
		<?php
	}

	/**
	 * Save product meta.
	 *
	 * @param int      $post_id Post ID.
	 * @param \WP_Post $post    Post.
	 */
	public function save( int $post_id, \WP_Post $post ): void {
		unset( $post );

		if ( ! isset( $_POST['wpdude_dc_product_nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( (string) $_POST['wpdude_dc_product_nonce'] ) ), 'wpdude_dc_save_product' ) ) {
			return;
		}
		if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
			return;
		}
		if ( ! current_user_can( 'edit_post', $post_id ) ) {
			return;
		}

		$currency = Currencies::sanitize(
			sanitize_text_field( wp_unslash( (string) ( $_POST['wpdude_dc_currency'] ?? Product_Meta::get_store_currency() ) ) ),
			Product_Meta::get_store_currency()
		);

		$type = sanitize_text_field( wp_unslash( (string) ( $_POST['wpdude_dc_product_type'] ?? Product_Meta::TYPE_STANDARD ) ) );
		if ( ! in_array( $type, Product_Meta::supported_types(), true ) ) {
			$type = Product_Meta::TYPE_STANDARD;
		}

		$shipping_type = sanitize_text_field( wp_unslash( (string) ( $_POST['wpdude_dc_shipping_type'] ?? Product_Meta::SHIPPING_FREE ) ) );
		if ( ! in_array( $shipping_type, array( Product_Meta::SHIPPING_FREE, Product_Meta::SHIPPING_FLAT ), true ) ) {
			$shipping_type = Product_Meta::SHIPPING_FREE;
		}

		$shipping_amount = 0;
		if ( Product_Meta::SHIPPING_FLAT === $shipping_type ) {
			$shipping_amount = Money::to_minor(
				sanitize_text_field( wp_unslash( (string) ( $_POST['wpdude_dc_shipping_amount'] ?? '0' ) ) ),
				$currency
			);
		}

		Product_Meta::update( $post_id, 'product_type', $type );
		Product_Meta::update( $post_id, 'currency', $currency );
		Product_Meta::update( $post_id, 'sold_individually', ! empty( $_POST['wpdude_dc_sold_individually'] ) ? 'yes' : 'no' );
		Product_Meta::update( $post_id, 'shipping_type', $shipping_type );
		Product_Meta::update( $post_id, 'shipping_amount', $shipping_amount );

		$gallery_raw = sanitize_text_field( wp_unslash( (string) ( $_POST['wpdude_dc_gallery'] ?? '' ) ) );
		$gallery_ids = array();
		if ( '' !== $gallery_raw ) {
			foreach ( explode( ',', $gallery_raw ) as $id ) {
				$id = absint( $id );
				if ( $id > 0 ) {
					$gallery_ids[] = $id;
				}
			}
		}
		Product_Meta::update( $post_id, 'gallery', array_values( array_unique( $gallery_ids ) ) );

		if ( Product_Meta::TYPE_STANDARD === $type ) {
			$price = Money::to_minor(
				sanitize_text_field( wp_unslash( (string) ( $_POST['wpdude_dc_price'] ?? '0' ) ) ),
				$currency
			);
			$sale = Money::to_minor(
				sanitize_text_field( wp_unslash( (string) ( $_POST['wpdude_dc_sale_price'] ?? '0' ) ) ),
				$currency
			);
			if ( $sale >= $price ) {
				$sale = 0;
			}

			$sku_result = Product_Meta::resolve_unique_skus(
				array( sanitize_text_field( wp_unslash( (string) ( $_POST['wpdude_dc_sku'] ?? '' ) ) ) ),
				Product_Meta::map_existing_skus( $post_id )
			);
			self::queue_sku_conflicts( $sku_result['conflicts'] );

			$sku = $sku_result['skus'][0] ?? '';
			if ( ! empty( $sku_result['conflicts'] ) ) {
				// Keep the previously saved SKU rather than clearing it.
				$sku = Product_Meta::get_sku( $post_id );
			}

			Product_Meta::update( $post_id, 'sku', $sku );
			Product_Meta::update( $post_id, 'price', $price );
			Product_Meta::update( $post_id, 'sale_price', $sale );
			Product_Meta::update( $post_id, 'stock', max( 0, absint( $_POST['wpdude_dc_stock'] ?? 0 ) ) );
			Product_Meta::update( $post_id, 'options', array() );
			Product_Meta::update( $post_id, 'combinations', array() );
			return;
		}

		Product_Meta::update( $post_id, 'sku', '' );
		Product_Meta::update( $post_id, 'price', 0 );
		Product_Meta::update( $post_id, 'sale_price', 0 );
		Product_Meta::update( $post_id, 'stock', 0 );

		$names        = isset( $_POST['wpdude_dc_option_name'] ) && is_array( $_POST['wpdude_dc_option_name'] ) ? wp_unslash( $_POST['wpdude_dc_option_name'] ) : array(); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
		$value_labels = isset( $_POST['wpdude_dc_option_value'] ) && is_array( $_POST['wpdude_dc_option_value'] ) ? wp_unslash( $_POST['wpdude_dc_option_value'] ) : array(); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized

		$options = array();
		$indexes = array_keys( $names );
		$count   = 0;
		foreach ( $indexes as $opt_index ) {
			if ( $count >= Product_Meta::MAX_OPTIONS ) {
				break;
			}
			$name = sanitize_text_field( (string) ( $names[ $opt_index ] ?? '' ) );
			if ( '' === $name ) {
				continue;
			}

			$labels = isset( $value_labels[ $opt_index ] ) && is_array( $value_labels[ $opt_index ] ) ? $value_labels[ $opt_index ] : array();
			$values = array();
			$seen   = array();
			foreach ( $labels as $label ) {
				$parts = array_filter( array_map( 'trim', explode( ',', (string) $label ) ) );
				foreach ( $parts as $part ) {
					$part = sanitize_text_field( $part );
					if ( '' === $part ) {
						continue;
					}
					$key = strtolower( $part );
					if ( isset( $seen[ $key ] ) ) {
						continue;
					}
					$seen[ $key ] = true;
					$values[]     = array(
						'value'    => $part,
						'image_id' => 0,
					);
				}
			}
			if ( empty( $values ) ) {
				continue;
			}
			$options[] = array(
				'name'   => $name,
				'values' => $values,
			);
			++$count;
		}
		Product_Meta::update( $post_id, 'options', $options );

		$keys         = isset( $_POST['wpdude_dc_combo_key'] ) && is_array( $_POST['wpdude_dc_combo_key'] ) ? wp_unslash( $_POST['wpdude_dc_combo_key'] ) : array(); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
		$combo_opts   = isset( $_POST['wpdude_dc_combo_opt'] ) && is_array( $_POST['wpdude_dc_combo_opt'] ) ? wp_unslash( $_POST['wpdude_dc_combo_opt'] ) : array(); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
		$combo_skus   = isset( $_POST['wpdude_dc_combo_sku'] ) && is_array( $_POST['wpdude_dc_combo_sku'] ) ? wp_unslash( $_POST['wpdude_dc_combo_sku'] ) : array(); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
		$combo_prices = isset( $_POST['wpdude_dc_combo_price'] ) && is_array( $_POST['wpdude_dc_combo_price'] ) ? wp_unslash( $_POST['wpdude_dc_combo_price'] ) : array(); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
		$combo_sales  = isset( $_POST['wpdude_dc_combo_sale_price'] ) && is_array( $_POST['wpdude_dc_combo_sale_price'] ) ? wp_unslash( $_POST['wpdude_dc_combo_sale_price'] ) : array(); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
		$combo_stocks = isset( $_POST['wpdude_dc_combo_stock'] ) && is_array( $_POST['wpdude_dc_combo_stock'] ) ? wp_unslash( $_POST['wpdude_dc_combo_stock'] ) : array(); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
		$combo_images = isset( $_POST['wpdude_dc_combo_image'] ) && is_array( $_POST['wpdude_dc_combo_image'] ) ? wp_unslash( $_POST['wpdude_dc_combo_image'] ) : array(); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized

		$combinations  = array();
		$proposed_skus = array();
		foreach ( $keys as $index => $key ) {
			$key = sanitize_text_field( (string) $key );
			if ( '' === $key ) {
				continue;
			}

			$opts = array();
			if ( isset( $combo_opts[ $index ] ) && is_array( $combo_opts[ $index ] ) ) {
				foreach ( $combo_opts[ $index ] as $opt_name => $opt_value ) {
					$opts[ sanitize_text_field( (string) $opt_name ) ] = sanitize_text_field( (string) $opt_value );
				}
			}

			$price = Money::to_minor( sanitize_text_field( (string) ( $combo_prices[ $index ] ?? '0' ) ), $currency );
			$sale  = Money::to_minor( sanitize_text_field( (string) ( $combo_sales[ $index ] ?? '0' ) ), $currency );
			if ( $sale >= $price ) {
				$sale = 0;
			}

			$proposed_skus[] = (string) ( $combo_skus[ $index ] ?? '' );
			$combinations[]  = array(
				'key'        => $key,
				'options'    => $opts,
				'sku'        => '', // filled after uniqueness check.
				'price'      => $price,
				'sale_price' => $sale,
				'stock'      => max( 0, absint( $combo_stocks[ $index ] ?? 0 ) ),
				'image_id'   => absint( $combo_images[ $index ] ?? 0 ),
			);
		}

		if ( empty( $combinations ) && ! empty( $options ) ) {
			$combinations = Product_Meta::merge_combinations( $options, array() );
			$proposed_skus = array();
			foreach ( $combinations as $combination ) {
				$proposed_skus[] = (string) ( $combination['sku'] ?? '' );
			}
		}

		$sku_result = Product_Meta::resolve_unique_skus(
			$proposed_skus,
			Product_Meta::map_existing_skus( $post_id )
		);
		self::queue_sku_conflicts( $sku_result['conflicts'] );

		$previous_by_key = array();
		foreach ( Product_Meta::get_combinations( $post_id ) as $previous ) {
			$previous_by_key[ (string) ( $previous['key'] ?? '' ) ] = (string) ( $previous['sku'] ?? '' );
		}

		$accepted_keys = array();
		foreach ( $sku_result['skus'] as $accepted ) {
			if ( '' !== $accepted ) {
				$accepted_keys[ Product_Meta::sku_key( $accepted ) ] = true;
			}
		}

		foreach ( $combinations as $i => $combination ) {
			$sku = $sku_result['skus'][ $i ] ?? '';
			if ( '' === $sku && isset( $sku_result['conflicts'] ) && '' !== Product_Meta::normalize_sku( (string) ( $proposed_skus[ $i ] ?? '' ) ) ) {
				$previous = Product_Meta::normalize_sku( $previous_by_key[ $combination['key'] ] ?? '' );
				if ( '' !== $previous && ! isset( $accepted_keys[ Product_Meta::sku_key( $previous ) ] ) ) {
					$sku = $previous;
					$accepted_keys[ Product_Meta::sku_key( $previous ) ] = true;
				}
			}
			$combinations[ $i ]['sku'] = $sku;
		}

		Product_Meta::update( $post_id, 'combinations', $combinations );
	}
}
