<?php
/**
 * Min/Max Quantities module.
 *
 * @package Kramar
 * @since   1.0.0
 */

declare( strict_types=1 );

namespace Kramar\Modules\Products;

// Prevent direct access.
defined( 'ABSPATH' ) || exit;

/**
 * Min/Max Quantities module.
 *
 * Enforces minimum/maximum/step quantity rules at product, variation, and
 * category levels, plus global order-level rules. Resolution order:
 * category → product → variation (each level overrides the previous).
 *
 * @since 1.0.0
 */
final class Min_Max_Quantities extends \Kramar\Module {

	/**
	 * {@inheritDoc}
	 */
	public function get_slug(): string {
		return 'min-max-quantities';
	}

	/**
	 * {@inheritDoc}
	 */
	public function get_category(): string {
		return 'products';
	}

	/**
	 * {@inheritDoc}
	 */
	public function get_name(): string {
		return __( 'Min/Max Quantities', 'kramar' );
	}

	/**
	 * {@inheritDoc}
	 */
	public function get_description(): string {
		return __( 'Enforce minimum, maximum, and step quantity rules per product, variation, and category. Optionally restrict total order quantity and order value.', 'kramar' );
	}

	/**
	 * {@inheritDoc}
	 */
	public function get_default_settings(): array {
		return [
			'enabled'         => false,
			'min_order_qty'   => 0,
			'max_order_qty'   => 0,
			'min_order_value' => 0,
			'max_order_value' => 0,
		];
	}

	/**
	 * {@inheritDoc}
	 */
	public function sanitize_settings( array $input ): array {
		return [
			'enabled'         => ! empty( $input['enabled'] ),
			'min_order_qty'   => max( 0, absint( $input['min_order_qty'] ?? 0 ) ),
			'max_order_qty'   => max( 0, absint( $input['max_order_qty'] ?? 0 ) ),
			'min_order_value' => max( 0, (float) ( $input['min_order_value'] ?? 0 ) ),
			'max_order_value' => max( 0, (float) ( $input['max_order_value'] ?? 0 ) ),
		];
	}

	/**
	 * {@inheritDoc}
	 */
	protected function run(): void {
		// Frontend quantity input attributes.
		add_filter( 'woocommerce_quantity_input_args', [ $this, 'filter_quantity_input_args' ], 10, 2 );
		add_filter( 'woocommerce_available_variation', [ $this, 'filter_available_variation' ], 10, 3 );

		// Cart validation.
		add_action( 'woocommerce_check_cart_items', [ $this, 'validate_cart_items' ] );

		// Product meta fields.
		add_action( 'woocommerce_product_options_inventory_product_data', [ $this, 'render_product_fields' ] );
		add_action( 'woocommerce_process_product_meta', [ $this, 'save_product_fields' ] );

		// Variation meta fields.
		add_action( 'woocommerce_variation_options_pricing', [ $this, 'render_variation_fields' ], 10, 3 );
		add_action( 'woocommerce_save_product_variation', [ $this, 'save_variation_fields' ], 10, 2 );

		// Category term meta fields.
		add_action( 'product_cat_add_form_fields', [ $this, 'render_category_add_fields' ] );
		add_action( 'product_cat_edit_form_fields', [ $this, 'render_category_edit_fields' ] );
		add_action( 'created_product_cat', [ $this, 'save_category_fields' ] );
		add_action( 'edited_product_cat', [ $this, 'save_category_fields' ] );
	}

	// -------------------------------------------------------------------------
	// Rule resolution
	// -------------------------------------------------------------------------

	/**
	 * Resolve effective min/max/step rules for a product.
	 *
	 * Category rules are applied first (highest min, lowest non-zero max across
	 * all assigned categories). Product-level meta overrides category rules if
	 * non-zero. Variation-level meta overrides product rules if the variation
	 * override flag is set.
	 *
	 * @since 1.0.0
	 *
	 * @param \WC_Product $product Product or variation object.
	 * @return array{min:int,max:int,step:int} Resolved rules.
	 */
	private function get_rules( \WC_Product $product ): array {
		$min  = 0;
		$max  = 0;
		$step = 1;

		// 1. Category level — use the parent product's categories for variations.
		$base_product = $product instanceof \WC_Product_Variation
			? wc_get_product( $product->get_parent_id() )
			: $product;

		if ( $base_product instanceof \WC_Product ) {
			foreach ( $base_product->get_category_ids() as $cat_id ) {
				$cat_min  = (int) get_term_meta( $cat_id, '_kramar_min_qty', true );
				$cat_max  = (int) get_term_meta( $cat_id, '_kramar_max_qty', true );
				$cat_step = (int) get_term_meta( $cat_id, '_kramar_step_qty', true );

				if ( $cat_min > 0 && $cat_min > $min ) {
					$min = $cat_min;
				}
				if ( $cat_max > 0 && ( 0 === $max || $cat_max < $max ) ) {
					$max = $cat_max;
				}
				if ( $cat_step > 1 ) {
					$step = $cat_step;
				}
			}
		}

		// 2. Product level overrides.
		$prod_min  = (int) $base_product?->get_meta( '_kramar_min_qty' );
		$prod_max  = (int) $base_product?->get_meta( '_kramar_max_qty' );
		$prod_step = (int) $base_product?->get_meta( '_kramar_step_qty' );

		if ( $prod_min > 0 ) {
			$min = $prod_min;
		}
		if ( $prod_max > 0 ) {
			$max = $prod_max;
		}
		if ( $prod_step > 1 ) {
			$step = $prod_step;
		}

		// 3. Variation level overrides (only when the override flag is enabled).
		if (
			$product instanceof \WC_Product_Variation &&
			'yes' === $product->get_meta( '_kramar_variation_minmax' )
		) {
			$var_min  = (int) $product->get_meta( '_kramar_min_qty' );
			$var_max  = (int) $product->get_meta( '_kramar_max_qty' );
			$var_step = (int) $product->get_meta( '_kramar_step_qty' );

			if ( $var_min > 0 ) {
				$min = $var_min;
			}
			if ( $var_max > 0 ) {
				$max = $var_max;
			}
			if ( $var_step > 1 ) {
				$step = $var_step;
			}
		}

		$step = max( 1, $step );

		// A maximum below the minimum satisfies no quantity at all. The product
		// page clamped the selector to the minimum, then the basket refused that
		// very quantity for exceeding the maximum, and checkout refused to
		// proceed — with no quantity the shopper could pick to get out of it, and
		// nothing anywhere saying the rule was impossible. Treat the maximum as
		// unset; the admin notice on the product screen reports the conflict.
		if ( $max > 0 && $min > 0 && $max < $min ) {
			$max = 0;
		}

		// Raise the minimum to a whole number of steps. WooCommerce Blocks —
		// which every block theme's quantity selector goes through — rounds the
		// minimum up to a multiple of the step and counts steps from zero, while
		// this module counted them from the minimum. With min 3 and step 4 the
		// selector offered only 4 and 8 and the server accepted only 3, 7 and 11,
		// so no reachable quantity was purchasable at all. Snapping the minimum
		// up makes both readings the same set: 4, 8, 12.
		// A step with no minimum still has one: WooCommerce Blocks treats the step
		// as an implicit floor and, worse, silently rounds a submitted quantity up
		// to the nearest multiple. The product page showed a perfectly valid "1",
		// the shopper added it, and four were charged with no notice anywhere. The
		// multiple-of correction below only ran when a minimum was already set.
		if ( $step > 1 && $min <= 0 ) {
			$min = $step;

			// Re-check the conflict this may have just created. Raising the minimum
			// to the step can push it past an explicit maximum — max 3 with step 5
			// left min 5 against max 3, no quantity satisfying both, and the basket
			// showing "requires a minimum of 5" and "allows a maximum of 3" at the
			// same time. The snap branch below already guards for this; because the
			// minimum set here is always a whole step, that branch never runs.
			if ( $max > 0 && $max < $min ) {
				$max = 0;
			}
		}

		if ( $step > 1 && $min > 0 && 0 !== $min % $step ) {
			$min = (int) ( ceil( $min / $step ) * $step );

			if ( $max > 0 && $max < $min ) {
				$max = 0;
			}
		}

		return [
			'min'  => $min,
			'max'  => $max,
			'step' => $step,
		];
	}

	// -------------------------------------------------------------------------
	// Frontend quantity input
	// -------------------------------------------------------------------------

	/**
	 * Apply min/max/step to the quantity input args.
	 *
	 * @since 1.0.0
	 *
	 * @param array<string, mixed> $args    Default quantity input args.
	 * @param \WC_Product          $product The product.
	 * @return array<string, mixed> Modified args.
	 */
	public function filter_quantity_input_args( array $args, \WC_Product $product ): array {
		$rules = $this->get_rules( $product );

		if ( $rules['min'] > 0 ) {
			$args['min_value'] = $rules['min'];

			// Set the default rather than only correcting an existing one.
			// WooCommerce fills input_value in after this filter runs, so testing
			// isset() first left the box showing 1 beside a minimum of 4 — a value
			// the shopper cannot submit and did not choose.
			if ( ! isset( $args['input_value'] ) || (int) $args['input_value'] < $rules['min'] ) {
				$args['input_value'] = $rules['min'];
			}
		}

		if ( $rules['max'] > 0 ) {
			$args['max_value'] = $rules['max'];
		}

		$args['step'] = $rules['step'];

		return $args;
	}

	/**
	 * Pass min/max/step rules to variation data for JS switching.
	 *
	 * @since 1.0.0
	 *
	 * @param array<string, mixed>      $data      Variation data array.
	 * @param \WC_Product_Variable      $product   Parent product.
	 * @param \WC_Product_Variation     $variation Variation object.
	 * @return array<string, mixed> Extended variation data.
	 */
	public function filter_available_variation( array $data, \WC_Product_Variable $product, \WC_Product_Variation $variation ): array {
		$rules = $this->get_rules( $variation );

		$data['kramar_min_qty']  = $rules['min'] > 0 ? $rules['min'] : 1;
		$data['kramar_max_qty']  = $rules['max'];
		$data['kramar_step_qty'] = $rules['step'];

		return $data;
	}

	// -------------------------------------------------------------------------
	// Cart validation
	// -------------------------------------------------------------------------

	/**
	 * Validate per-item and order-level quantity/value rules in the cart.
	 *
	 * @since 1.0.0
	 */
	public function validate_cart_items(): void {
		$cart = WC()->cart;
		if ( ! $cart ) {
			return;
		}

		$total_qty   = 0;
		$total_value = 0.0;

		foreach ( $cart->get_cart() as $cart_item ) {
			$product = $cart_item['data'] ?? null;
			if ( ! $product instanceof \WC_Product ) {
				continue;
			}

			$qty   = (int) $cart_item['quantity'];
			$rules = $this->get_rules( $product );
			$name  = $product->get_name();

			if ( $rules['min'] > 0 && $qty < $rules['min'] ) {
				wc_add_notice(
					sprintf(
						/* translators: 1: product name, 2: minimum quantity */
						__( '%1$s requires a minimum quantity of %2$d.', 'kramar' ),
						'<strong>' . esc_html( $name ) . '</strong>',
						$rules['min']
					),
					'error'
				);
			}

			if ( $rules['max'] > 0 && $qty > $rules['max'] ) {
				wc_add_notice(
					sprintf(
						/* translators: 1: product name, 2: maximum quantity */
						__( '%1$s allows a maximum quantity of %2$d.', 'kramar' ),
						'<strong>' . esc_html( $name ) . '</strong>',
						$rules['max']
					),
					'error'
				);
			}

			// Steps count FROM the minimum, matching the min/step attributes this
			// module puts on the quantity input — a browser treats step as an
			// offset from min. Checking $qty % step instead measured from zero,
			// so with min=3 step=2 the browser allowed 3,5,7,9 while the server
			// only accepted 2,4,6,8: two disjoint sets, and no quantity at all
			// could satisfy both.
			$step_base = $rules['min'] > 0 ? $rules['min'] : 0;

			if ( $rules['step'] > 1 && 0 !== ( ( $qty - $step_base ) % $rules['step'] ) ) {
				if ( $step_base > 0 && 0 !== ( $step_base % $rules['step'] ) ) {
					$message = sprintf(
						/* translators: 1: product name, 2: step quantity, 3: minimum quantity */
						__( '%1$s must be purchased in multiples of %2$d, starting from %3$d.', 'kramar' ),
						'<strong>' . esc_html( $name ) . '</strong>',
						$rules['step'],
						$step_base
					);
				} else {
					$message = sprintf(
						/* translators: 1: product name, 2: step quantity */
						__( '%1$s must be purchased in multiples of %2$d.', 'kramar' ),
						'<strong>' . esc_html( $name ) . '</strong>',
						$rules['step']
					);
				}

				wc_add_notice( $message, 'error' );
			}

			$total_qty   += $qty;
			$total_value += (float) $product->get_price() * $qty;
		}

		// Order-level quantity checks.
		$min_order_qty = (int) $this->plugin->get_setting( $this->get_category(), $this->get_slug(), 'min_order_qty' );
		$max_order_qty = (int) $this->plugin->get_setting( $this->get_category(), $this->get_slug(), 'max_order_qty' );

		if ( $min_order_qty > 0 && $total_qty < $min_order_qty ) {
			wc_add_notice(
				sprintf(
					/* translators: %d: minimum total quantity */
					__( 'Your order must contain at least %d items in total.', 'kramar' ),
					$min_order_qty
				),
				'error'
			);
		}

		if ( $max_order_qty > 0 && $total_qty > $max_order_qty ) {
			wc_add_notice(
				sprintf(
					/* translators: %d: maximum total quantity */
					__( 'Your order cannot contain more than %d items in total.', 'kramar' ),
					$max_order_qty
				),
				'error'
			);
		}

		// Order-level value checks.
		$min_order_value = (float) $this->plugin->get_setting( $this->get_category(), $this->get_slug(), 'min_order_value' );
		$max_order_value = (float) $this->plugin->get_setting( $this->get_category(), $this->get_slug(), 'max_order_value' );

		if ( $min_order_value > 0 && $total_value < $min_order_value ) {
			wc_add_notice(
				sprintf(
					/* translators: %s: minimum order value formatted as currency */
					__( 'Your order must reach a minimum value of %s.', 'kramar' ),
					wp_kses_post( wc_price( $min_order_value ) )
				),
				'error'
			);
		}

		if ( $max_order_value > 0 && $total_value > $max_order_value ) {
			wc_add_notice(
				sprintf(
					/* translators: %s: maximum order value formatted as currency */
					__( 'Your order cannot exceed a value of %s.', 'kramar' ),
					wp_kses_post( wc_price( $max_order_value ) )
				),
				'error'
			);
		}
	}

	// -------------------------------------------------------------------------
	// Product meta fields
	// -------------------------------------------------------------------------

	/**
	 * Render min/max/step fields in the product Inventory tab.
	 *
	 * @since 1.0.0
	 */
	public function render_product_fields(): void {
		global $post;
		$product_id = $post->ID ?? 0;

		echo '<div class="options_group">';

		woocommerce_wp_text_input( [
			'id'                => '_kramar_min_qty',
			'label'             => __( 'Minimum quantity', 'kramar' ),
			'desc_tip'          => true,
			'description'       => __( 'Minimum quantity customers must purchase. 0 = no limit (inherits category rule if set).', 'kramar' ),
			'type'              => 'number',
			'custom_attributes' => [ 'min' => '0', 'step' => '1' ],
			'value'             => (string) (int) get_post_meta( $product_id, '_kramar_min_qty', true ),
		] );

		woocommerce_wp_text_input( [
			'id'                => '_kramar_max_qty',
			'label'             => __( 'Maximum quantity', 'kramar' ),
			'desc_tip'          => true,
			'description'       => __( 'Maximum quantity customers may purchase. 0 = no limit (inherits category rule if set).', 'kramar' ),
			'type'              => 'number',
			'custom_attributes' => [ 'min' => '0', 'step' => '1' ],
			'value'             => (string) (int) get_post_meta( $product_id, '_kramar_max_qty', true ),
		] );

		woocommerce_wp_text_input( [
			'id'                => '_kramar_step_qty',
			'label'             => __( 'Quantity step', 'kramar' ),
			'desc_tip'          => true,
			'description'       => __( 'Quantity must be a multiple of this value. 0 or 1 = no restriction.', 'kramar' ),
			'type'              => 'number',
			'custom_attributes' => [ 'min' => '0', 'step' => '1' ],
			'value'             => (string) (int) get_post_meta( $product_id, '_kramar_step_qty', true ),
		] );

		echo '</div>';
	}

	/**
	 * Save product-level min/max/step meta.
	 *
	 * @since 1.0.0
	 *
	 * @param int $product_id Post ID of the saved product.
	 */
	public function save_product_fields( int $product_id ): void {
		$fields = [ '_kramar_min_qty', '_kramar_max_qty', '_kramar_step_qty' ];

		foreach ( $fields as $field ) {
			$value = isset( $_POST[ $field ] ) ? absint( $_POST[ $field ] ) : 0; // phpcs:ignore WordPress.Security.NonceVerification.Missing
			update_post_meta( $product_id, $field, $value );
		}
	}

	// -------------------------------------------------------------------------
	// Variation meta fields
	// -------------------------------------------------------------------------

	/**
	 * Render min/max/step fields on the variation pricing panel.
	 *
	 * @since 1.0.0
	 *
	 * @param int      $loop           Variation loop index.
	 * @param array<string, mixed> $variation_data Variation data.
	 * @param \WP_Post $variation      Variation post object.
	 */
	public function render_variation_fields( int $loop, array $variation_data, \WP_Post $variation ): void {
		$var_id  = $variation->ID;
		$enabled = get_post_meta( $var_id, '_kramar_variation_minmax', true );
		$min     = (int) get_post_meta( $var_id, '_kramar_min_qty', true );
		$max     = (int) get_post_meta( $var_id, '_kramar_max_qty', true );
		$step    = (int) get_post_meta( $var_id, '_kramar_step_qty', true );
		?>
		<?php $cb_id = 'kramar-minmax-toggle-' . $loop; ?>
		<div class="kramar-variation-minmax" style="padding:10px 0 0;clear:both;">
			<label for="<?php echo esc_attr( $cb_id ); ?>">
				<input
					type="checkbox"
					id="<?php echo esc_attr( $cb_id ); ?>"
					name="kramar_variation_minmax[<?php echo esc_attr( (string) $loop ); ?>]"
					value="yes"
					<?php checked( $enabled, 'yes' ); ?>
					onchange="this.closest('.kramar-variation-minmax').querySelector('.kramar-minmax-fields').style.display=this.checked?'flex':'none';"
				/>
				<?php esc_html_e( 'Override quantity rules for this variation', 'kramar' ); ?>
			</label>

			<div class="kramar-minmax-fields" style="display:<?php echo 'yes' === $enabled ? 'flex' : 'none'; ?>;gap:12px;margin-top:8px;">
				<label style="font-size:13px;">
					<?php esc_html_e( 'Min', 'kramar' ); ?>
					<input type="number" name="kramar_var_min_qty[<?php echo esc_attr( (string) $loop ); ?>]" value="<?php echo esc_attr( (string) $min ); ?>" min="0" step="1" style="width:60px;margin-left:4px;" />
				</label>
				<label style="font-size:13px;">
					<?php esc_html_e( 'Max', 'kramar' ); ?>
					<input type="number" name="kramar_var_max_qty[<?php echo esc_attr( (string) $loop ); ?>]" value="<?php echo esc_attr( (string) $max ); ?>" min="0" step="1" style="width:60px;margin-left:4px;" />
				</label>
				<label style="font-size:13px;">
					<?php esc_html_e( 'Step', 'kramar' ); ?>
					<input type="number" name="kramar_var_step_qty[<?php echo esc_attr( (string) $loop ); ?>]" value="<?php echo esc_attr( (string) $step ); ?>" min="0" step="1" style="width:60px;margin-left:4px;" />
				</label>
			</div>
		</div>
		<?php
	}

	/**
	 * Save variation-level min/max/step and override flag.
	 *
	 * @since 1.0.0
	 *
	 * @param int $variation_id Variation post ID.
	 * @param int $loop         Variation loop index.
	 */
	public function save_variation_fields( int $variation_id, int $loop ): void {
		// phpcs:disable WordPress.Security.NonceVerification.Missing
		$enabled = isset( $_POST['kramar_variation_minmax'][ $loop ] ) ? 'yes' : 'no';
		update_post_meta( $variation_id, '_kramar_variation_minmax', $enabled );

		$min  = isset( $_POST['kramar_var_min_qty'][ $loop ] ) ? absint( $_POST['kramar_var_min_qty'][ $loop ] ) : 0;
		$max  = isset( $_POST['kramar_var_max_qty'][ $loop ] ) ? absint( $_POST['kramar_var_max_qty'][ $loop ] ) : 0;
		$step = isset( $_POST['kramar_var_step_qty'][ $loop ] ) ? absint( $_POST['kramar_var_step_qty'][ $loop ] ) : 0;

		update_post_meta( $variation_id, '_kramar_min_qty', $min );
		update_post_meta( $variation_id, '_kramar_max_qty', $max );
		update_post_meta( $variation_id, '_kramar_step_qty', $step );
		// phpcs:enable WordPress.Security.NonceVerification.Missing
	}

	// -------------------------------------------------------------------------
	// Category term meta fields
	// -------------------------------------------------------------------------

	/**
	 * Render fields on the Add Category form.
	 *
	 * @since 1.0.0
	 */
	public function render_category_add_fields(): void {
		?>
		<div class="form-field">
			<h3 style="margin-bottom:0;"><?php esc_html_e( 'Quantity Rules (Kramar)', 'kramar' ); ?></h3>
		</div>
		<div class="form-field">
			<label for="kramar_cat_min_qty"><?php esc_html_e( 'Minimum quantity', 'kramar' ); ?></label>
			<input type="number" name="kramar_cat_min_qty" id="kramar_cat_min_qty" value="0" min="0" step="1" />
			<p><?php esc_html_e( 'Minimum quantity for products in this category. 0 = no limit.', 'kramar' ); ?></p>
		</div>
		<div class="form-field">
			<label for="kramar_cat_max_qty"><?php esc_html_e( 'Maximum quantity', 'kramar' ); ?></label>
			<input type="number" name="kramar_cat_max_qty" id="kramar_cat_max_qty" value="0" min="0" step="1" />
			<p><?php esc_html_e( 'Maximum quantity for products in this category. 0 = no limit.', 'kramar' ); ?></p>
		</div>
		<div class="form-field">
			<label for="kramar_cat_step_qty"><?php esc_html_e( 'Quantity step', 'kramar' ); ?></label>
			<input type="number" name="kramar_cat_step_qty" id="kramar_cat_step_qty" value="0" min="0" step="1" />
			<p><?php esc_html_e( 'Quantity must be a multiple of this value. 0 or 1 = no restriction.', 'kramar' ); ?></p>
		</div>
		<?php
	}

	/**
	 * Render fields on the Edit Category form.
	 *
	 * @since 1.0.0
	 *
	 * @param \WP_Term $term The term being edited.
	 */
	public function render_category_edit_fields( \WP_Term $term ): void {
		$min  = (int) get_term_meta( $term->term_id, '_kramar_min_qty', true );
		$max  = (int) get_term_meta( $term->term_id, '_kramar_max_qty', true );
		$step = (int) get_term_meta( $term->term_id, '_kramar_step_qty', true );
		?>
		<tr class="form-field">
			<th colspan="2"><h3 style="margin-bottom:0;"><?php esc_html_e( 'Quantity Rules (Kramar)', 'kramar' ); ?></h3></th>
		</tr>
		<tr class="form-field">
			<th><label for="kramar_cat_min_qty"><?php esc_html_e( 'Minimum quantity', 'kramar' ); ?></label></th>
			<td>
				<input type="number" name="kramar_cat_min_qty" id="kramar_cat_min_qty" value="<?php echo esc_attr( (string) $min ); ?>" min="0" step="1" />
				<p class="description"><?php esc_html_e( 'Minimum quantity for products in this category. 0 = no limit.', 'kramar' ); ?></p>
			</td>
		</tr>
		<tr class="form-field">
			<th><label for="kramar_cat_max_qty"><?php esc_html_e( 'Maximum quantity', 'kramar' ); ?></label></th>
			<td>
				<input type="number" name="kramar_cat_max_qty" id="kramar_cat_max_qty" value="<?php echo esc_attr( (string) $max ); ?>" min="0" step="1" />
				<p class="description"><?php esc_html_e( 'Maximum quantity for products in this category. 0 = no limit.', 'kramar' ); ?></p>
			</td>
		</tr>
		<tr class="form-field">
			<th><label for="kramar_cat_step_qty"><?php esc_html_e( 'Quantity step', 'kramar' ); ?></label></th>
			<td>
				<input type="number" name="kramar_cat_step_qty" id="kramar_cat_step_qty" value="<?php echo esc_attr( (string) $step ); ?>" min="0" step="1" />
				<p class="description"><?php esc_html_e( 'Quantity must be a multiple of this value. 0 or 1 = no restriction.', 'kramar' ); ?></p>
			</td>
		</tr>
		<?php
	}

	/**
	 * Save category term meta.
	 *
	 * @since 1.0.0
	 *
	 * @param int $term_id The saved term ID.
	 */
	public function save_category_fields( int $term_id ): void {
		// phpcs:disable WordPress.Security.NonceVerification.Missing
		$min  = isset( $_POST['kramar_cat_min_qty'] ) ? absint( $_POST['kramar_cat_min_qty'] ) : 0;
		$max  = isset( $_POST['kramar_cat_max_qty'] ) ? absint( $_POST['kramar_cat_max_qty'] ) : 0;
		$step = isset( $_POST['kramar_cat_step_qty'] ) ? absint( $_POST['kramar_cat_step_qty'] ) : 0;
		// phpcs:enable WordPress.Security.NonceVerification.Missing

		update_term_meta( $term_id, '_kramar_min_qty', $min );
		update_term_meta( $term_id, '_kramar_max_qty', $max );
		update_term_meta( $term_id, '_kramar_step_qty', $step );
	}

	// -------------------------------------------------------------------------
	// Admin settings UI
	// -------------------------------------------------------------------------

	/**
	 * {@inheritDoc}
	 */
	public function render_extra_settings( string $option_name ): void {
		$slug = $this->get_slug();
		$cat  = $this->get_category();

		$min_order_qty   = (int) $this->plugin->get_setting( $cat, $slug, 'min_order_qty' );
		$max_order_qty   = (int) $this->plugin->get_setting( $cat, $slug, 'max_order_qty' );
		$min_order_value = (float) $this->plugin->get_setting( $cat, $slug, 'min_order_value' );
		$max_order_value = (float) $this->plugin->get_setting( $cat, $slug, 'max_order_value' );

		$currency = get_woocommerce_currency_symbol();
		?>
		<div class="kramar-module__extra">
			<div class="kramar-settings-stack">

				<p>
					<label for="kramar-mmq-min-order-qty"><?php esc_html_e( 'Minimum order quantity', 'kramar' ); ?></label>
					<input
						type="number"
						id="kramar-mmq-min-order-qty"
						name="<?php echo esc_attr( $option_name ); ?>[<?php echo esc_attr( $cat ); ?>][<?php echo esc_attr( $slug ); ?>][min_order_qty]"
						value="<?php echo esc_attr( (string) $min_order_qty ); ?>"
						min="0"
						step="1"
						style="width:100px;"
					/>
					<span class="description"><?php esc_html_e( 'Minimum total number of items across the whole order. 0 = no limit.', 'kramar' ); ?></span>
				</p>

				<p>
					<label for="kramar-mmq-max-order-qty"><?php esc_html_e( 'Maximum order quantity', 'kramar' ); ?></label>
					<input
						type="number"
						id="kramar-mmq-max-order-qty"
						name="<?php echo esc_attr( $option_name ); ?>[<?php echo esc_attr( $cat ); ?>][<?php echo esc_attr( $slug ); ?>][max_order_qty]"
						value="<?php echo esc_attr( (string) $max_order_qty ); ?>"
						min="0"
						step="1"
						style="width:100px;"
					/>
					<span class="description"><?php esc_html_e( 'Maximum total number of items across the whole order. 0 = no limit.', 'kramar' ); ?></span>
				</p>

				<p>
					<label for="kramar-mmq-min-order-value">
						<?php
						printf(
							/* translators: %s: currency symbol */
							esc_html__( 'Minimum order value (%s)', 'kramar' ),
							esc_html( $currency )
						);
						?>
					</label>
					<input
						type="number"
						id="kramar-mmq-min-order-value"
						name="<?php echo esc_attr( $option_name ); ?>[<?php echo esc_attr( $cat ); ?>][<?php echo esc_attr( $slug ); ?>][min_order_value]"
						value="<?php echo esc_attr( (string) $min_order_value ); ?>"
						min="0"
						step="0.01"
						style="width:120px;"
					/>
					<span class="description"><?php esc_html_e( 'Minimum cart subtotal before coupons and shipping. 0 = no limit.', 'kramar' ); ?></span>
				</p>

				<p>
					<label for="kramar-mmq-max-order-value">
						<?php
						printf(
							/* translators: %s: currency symbol */
							esc_html__( 'Maximum order value (%s)', 'kramar' ),
							esc_html( $currency )
						);
						?>
					</label>
					<input
						type="number"
						id="kramar-mmq-max-order-value"
						name="<?php echo esc_attr( $option_name ); ?>[<?php echo esc_attr( $cat ); ?>][<?php echo esc_attr( $slug ); ?>][max_order_value]"
						value="<?php echo esc_attr( (string) $max_order_value ); ?>"
						min="0"
						step="0.01"
						style="width:120px;"
					/>
					<span class="description"><?php esc_html_e( 'Maximum cart subtotal before coupons and shipping. 0 = no limit.', 'kramar' ); ?></span>
				</p>

			</div>
		</div>
		<?php
	}
}
