<?php
/**
 * REST API class file.
 *
 * @package WholesaleNode_For_WooCommerce
 */

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

// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery
// phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared

/**
 * Registers and handles REST API endpoints.
 */
class WOOWSNFree_API {

	const NS = 'woowsn/v1';

	/**
	 * @return void
	 */
	public static function register() {
		add_action( 'rest_api_init', array( __CLASS__, 'register_routes' ) );
	}

	/**
	 * @return void
	 */
	public static function register_routes() {
		if ( class_exists( 'WOOWSN_HidePrice_API' ) ) {
			return;
		}

		$perm = array( __CLASS__, 'can_manage' );

		register_rest_route( self::NS, '/settings/all', array(
			array( 'methods' => 'GET',  'callback' => array( __CLASS__, 'get_settings_all' ),  'permission_callback' => $perm ),
			array( 'methods' => 'POST', 'callback' => array( __CLASS__, 'save_settings_all' ), 'permission_callback' => $perm ),
		) );

		register_rest_route( self::NS, '/get-registration-form',  array( 'methods' => 'GET',  'callback' => array( __CLASS__, 'get_registration_form' ),  'permission_callback' => $perm ) );
		register_rest_route( self::NS, '/save-registration-form', array( 'methods' => 'POST', 'callback' => array( __CLASS__, 'save_registration_form' ), 'permission_callback' => $perm ) );
		register_rest_route( self::NS, '/registration-fields',    array( 'methods' => 'GET',  'callback' => array( __CLASS__, 'get_registration_fields' ), 'permission_callback' => $perm ) );

		register_rest_route( self::NS, '/quotes',                    array( 'methods' => 'GET',    'callback' => array( __CLASS__, 'get_quotes' ),          'permission_callback' => $perm ) );
		register_rest_route( self::NS, '/quotes/(?P<id>\d+)/status', array( 'methods' => 'POST',   'callback' => array( __CLASS__, 'update_quote_status' ), 'permission_callback' => $perm ) );
		register_rest_route( self::NS, '/quotes/(?P<id>\d+)/detail', array( 'methods' => 'GET',    'callback' => array( __CLASS__, 'get_quote_detail' ),    'permission_callback' => $perm ) );
		register_rest_route( self::NS, '/quotes/(?P<id>\d+)',        array( 'methods' => 'DELETE', 'callback' => array( __CLASS__, 'delete_quote' ),        'permission_callback' => $perm ) );

		register_rest_route( self::NS, '/hide-price/rules', array(
			array( 'methods' => 'GET',  'callback' => array( __CLASS__, 'get_hide_price_rules' ),   'permission_callback' => $perm ),
			array( 'methods' => 'POST', 'callback' => array( __CLASS__, 'create_hide_price_rule' ), 'permission_callback' => $perm ),
		) );
		register_rest_route( self::NS, '/hide-price/rules/(?P<id>\d+)', array(
			array( 'methods' => 'GET',    'callback' => array( __CLASS__, 'get_hide_price_rule' ),    'permission_callback' => $perm ),
			array( 'methods' => 'POST',   'callback' => array( __CLASS__, 'update_hide_price_rule' ), 'permission_callback' => $perm ),
			array( 'methods' => 'DELETE', 'callback' => array( __CLASS__, 'delete_hide_price_rule' ), 'permission_callback' => $perm ),
		) );
		register_rest_route( self::NS, '/hide-price/rules/(?P<id>\d+)/toggle-status', array( 'methods' => 'POST', 'callback' => array( __CLASS__, 'toggle_hide_price_rule' ), 'permission_callback' => $perm ) );
		register_rest_route( self::NS, '/hide-price/roles', array( 'methods' => 'GET', 'callback' => array( __CLASS__, 'get_roles' ), 'permission_callback' => $perm ) );

		register_rest_route( self::NS, '/discount-rules', array(
			array( 'methods' => 'GET',  'callback' => array( __CLASS__, 'get_discount_rules' ),   'permission_callback' => $perm ),
			array( 'methods' => 'POST', 'callback' => array( __CLASS__, 'create_discount_rule' ), 'permission_callback' => $perm ),
		) );
		register_rest_route( self::NS, '/discount-rules/(?P<id>\d+)', array(
			array( 'methods' => 'GET',    'callback' => array( __CLASS__, 'get_discount_rule' ),    'permission_callback' => $perm ),
			array( 'methods' => 'POST',   'callback' => array( __CLASS__, 'update_discount_rule' ), 'permission_callback' => $perm ),
			array( 'methods' => 'DELETE', 'callback' => array( __CLASS__, 'delete_discount_rule' ), 'permission_callback' => $perm ),
		) );
		register_rest_route( self::NS, '/discount-rules/status', array( 'methods' => 'POST', 'callback' => array( __CLASS__, 'toggle_discount_rule_status' ), 'permission_callback' => $perm ) );

		register_rest_route( self::NS, '/product-lists', array(
			array( 'methods' => 'GET',  'callback' => array( __CLASS__, 'get_product_lists' ),   'permission_callback' => $perm ),
			array( 'methods' => 'POST', 'callback' => array( __CLASS__, 'create_product_list' ), 'permission_callback' => $perm ),
		) );
		register_rest_route( self::NS, '/product-lists/(?P<id>\d+)', array(
			array( 'methods' => 'GET',    'callback' => array( __CLASS__, 'get_product_list' ),    'permission_callback' => $perm ),
			array( 'methods' => 'POST',   'callback' => array( __CLASS__, 'update_product_list' ), 'permission_callback' => $perm ),
			array( 'methods' => 'DELETE', 'callback' => array( __CLASS__, 'delete_product_list' ), 'permission_callback' => $perm ),
		) );

		register_rest_route( self::NS, '/productsearch', array( 'methods' => 'GET', 'callback' => array( __CLASS__, 'search_products' ), 'permission_callback' => $perm ) );
		register_rest_route( self::NS, '/user-roles',    array( 'methods' => 'GET', 'callback' => array( __CLASS__, 'get_roles' ),        'permission_callback' => $perm ) );

		register_rest_route( self::NS, '/wc-registration-settings', array(
			'methods'             => 'GET',
			'callback'            => array( __CLASS__, 'get_wc_registration_settings' ),
			'permission_callback' => $perm,
		) );

		register_rest_route( self::NS, '/user-groups/b2b-default',              array( 'methods' => 'GET',  'callback' => array( __CLASS__, 'get_b2b_default_group' ), 'permission_callback' => $perm ) );
		register_rest_route( self::NS, '/user-groups/free',                     array( 'methods' => 'GET',  'callback' => array( __CLASS__, 'get_free_groups' ),      'permission_callback' => $perm ) );
		register_rest_route( self::NS, '/user-groups/free/(?P<id>\d+)',         array( 'methods' => 'GET',  'callback' => array( __CLASS__, 'get_free_group' ),       'permission_callback' => $perm ) );
		register_rest_route( self::NS, '/user-groups/assign',                   array( 'methods' => 'POST', 'callback' => array( __CLASS__, 'assign_user_group' ),    'permission_callback' => $perm ) );
		register_rest_route( self::NS, '/user-groups/search-users',             array( 'methods' => 'GET',  'callback' => array( __CLASS__, 'search_users_groups' ),  'permission_callback' => $perm ) );
		register_rest_route( self::NS, '/user-groups/(?P<id>\d+)/members',      array( 'methods' => 'GET',  'callback' => array( __CLASS__, 'get_group_members' ),    'permission_callback' => $perm ) );
	}

	/**
	 * @return bool
	 */
	public static function can_manage() {
		return current_user_can( 'manage_woocommerce' );
	}

	// ── Settings ──────────────────────────────────────────────────────────

	/**
	 * @return WP_REST_Response
	 */
	public static function get_settings_all() {
		return rest_ensure_response( array( 'success' => true, 'settings' => get_option( 'woowsn_free_settings_all', array() ) ) );
	}

	/**
	 * @param WP_REST_Request $request
	 * @return WP_REST_Response
	 */
	public static function save_settings_all( $request ) {
		$body = $request->get_json_params();
		update_option( 'woowsn_free_settings_all', $body );

		if ( ! empty( $body['hide_price'] ) && is_array( $body['hide_price'] ) ) {
			update_option( 'woowsn_hide_price_settings', $body['hide_price'] );
		}

		$quotes = $body['quotes'] ?? array();
		self::sync_quote_form_product_selection( ! empty( $quotes['show_product_selection'] ) );
		self::sync_quote_form_display_settings( $quotes );
		self::sync_quotes_settings( $quotes );
		return rest_ensure_response( array( 'success' => true ) );
	}

	/**
	 * Mirror display settings into woowsn_quotes_settings (pro option key).
	 *
	 * @param array $quotes Quote settings from request.
	 * @return void
	 */
	private static function sync_quotes_settings( $quotes ) {
		if ( empty( $quotes ) ) {
			return;
		}
		$existing = get_option( 'woowsn_quotes_settings', array() );
		$map = array(
			'displayMode'      => 'displaymode',
			'displayPosition'  => 'displayposition',
			'popupButtonText'  => 'popupbuttontext',
			'popupButtonClass' => 'popupbuttonclass',
		);
		$changed = false;
		foreach ( $map as $from => $to ) {
			if ( isset( $quotes[ $from ] ) ) {
				$val = sanitize_text_field( (string) $quotes[ $from ] );
				if ( ! isset( $existing[ $to ] ) || $existing[ $to ] !== $val ) {
					$existing[ $to ] = $val;
					$existing[ $from ] = $val;
					$changed = true;
				}
			}
		}
		if ( $changed ) {
			update_option( 'woowsn_quotes_settings', $existing );
		}
	}

	/**
	 * Sync quote-form display settings.
	 *
	 * @param array $quotes Quote settings.
	 * @return void
	 */
	private static function sync_quote_form_display_settings( $quotes ) {
		if ( empty( $quotes ) ) return;

		$form     = get_option( 'woowsn_quote_form', array() );
		$settings = isset( $form['settings'] ) && is_array( $form['settings'] ) ? $form['settings'] : array();

		$changed = false;
		$sync_keys = array(
			'enableOnSingleProduct',
			'displayPosition',
			'displayMode',
			'popupButtonText',
			'popupButtonClass',
		);

		foreach ( $sync_keys as $key ) {
			if ( isset( $quotes[ $key ] ) ) {
				$val = is_bool( $quotes[ $key ] ) ? $quotes[ $key ] : sanitize_text_field( (string) $quotes[ $key ] );
				if ( ! isset( $settings[ $key ] ) || $settings[ $key ] !== $val ) {
					$settings[ $key ] = $val;
					$changed = true;
				}
			}
		}

		if ( $changed ) {
			$form['settings'] = $settings;
			update_option( 'woowsn_quote_form', $form );
		}
	}

	/**
	 * Sync the show_product_selection toggle into the woowsn_quote_form option.
	 *
	 * @param bool $enabled Whether the product selection field is enabled.
	 * @return void
	 */
	private static function sync_quote_form_product_selection( $enabled ) {
		$form      = get_option( 'woowsn_quote_form', array() );
		$fields    = isset( $form['fields'] ) && is_array( $form['fields'] ) ? $form['fields'] : array();
		$field_id  = 'product_selection_free';
		$exists_at = null;

		foreach ( $fields as $i => $f ) {
			if ( isset( $f['id'] ) && $f['id'] === $field_id ) {
				$exists_at = $i;
				break;
			}
		}

		if ( $enabled && $exists_at === null ) {
			$fields[] = array(
				'id'            => $field_id,
				'type'          => 'product_selection',
				'name'          => 'current_product_id',
				'label'         => __( 'Select Product', 'wholesalenode-for-woocommerce' ),
				'placeholder'   => __( 'Search products...', 'wholesalenode-for-woocommerce' ),
				'required'      => false,
				'default'       => false,
				'locked'        => false,
				'productSource' => 'manual',
				'productIds'    => array(),
				'options'       => array(),
				'validation'    => array(),
				'conditions'    => array( 'roles' => array() ),
				'order'         => count( $fields ),
				'columns'       => null,
				'contains'      => array(),
				'groupStyle'    => null,
			);
		} elseif ( ! $enabled && $exists_at !== null ) {
			array_splice( $fields, $exists_at, 1 );
		} else {
			return;
		}

		$form['fields'] = array_values( $fields );
		update_option( 'woowsn_quote_form', $form );

		$free_form             = get_option( 'woowsn_free_quote_form', array( 'fields' => array(), 'settings' => array() ) );
		$free_form['settings'] = array_merge(
			isset( $free_form['settings'] ) ? (array) $free_form['settings'] : array(),
			array( 'show_product_selection' => $enabled )
		);
		update_option( 'woowsn_free_quote_form', $free_form );
	}

	// ── Registration form ─────────────────────────────────────────────────

	/**
	 * @return WP_REST_Response
	 */
	public static function get_registration_form() {
		$data = get_option( 'woowsn_free_registration_form', array( 'fields' => array() ) );
		if ( empty( $data['fields'] ) ) {
			$pro_data = get_option( 'woowsn_registration_form', array() );
			if ( ! empty( $pro_data['fields'] ) ) {
				$data = $pro_data;
			}
		}
		return rest_ensure_response( array( 'success' => true, 'data' => $data ) );
	}

	/**
	 * @param WP_REST_Request $request
	 * @return WP_REST_Response
	 */
	public static function save_registration_form( $request ) {
		$data = $request->get_json_params();
		update_option( 'woowsn_free_registration_form', $data );
		update_option( 'woowsn_registration_form', $data );
		return rest_ensure_response( array( 'success' => true ) );
	}

	/**
	 * @return WP_REST_Response
	 */
	public static function get_registration_fields() {
		return rest_ensure_response( array(
			array( 'id' => 'billing_first_name', 'name' => 'billing_first_name', 'type' => 'text',     'label' => __( 'First Name', 'wholesalenode-for-woocommerce' ), 'required' => true,  'default' => true, 'locked' => false, 'isWCDefault' => true ),
			array( 'id' => 'billing_last_name',  'name' => 'billing_last_name',  'type' => 'text',     'label' => __( 'Last Name',  'wholesalenode-for-woocommerce' ), 'required' => true,  'default' => true, 'locked' => false, 'isWCDefault' => true ),
			array( 'id' => 'billing_company',    'name' => 'billing_company',    'type' => 'text',     'label' => __( 'Company',    'wholesalenode-for-woocommerce' ), 'required' => false, 'default' => true, 'locked' => false, 'isWCDefault' => true ),
			array( 'id' => 'billing_email',      'name' => 'billing_email',      'type' => 'email',    'label' => __( 'Email',      'wholesalenode-for-woocommerce' ), 'required' => true,  'default' => true, 'locked' => false, 'isWCDefault' => true ),
			array( 'id' => 'billing_phone',      'name' => 'billing_phone',      'type' => 'tel',      'label' => __( 'Phone',      'wholesalenode-for-woocommerce' ), 'required' => false, 'default' => true, 'locked' => false, 'isWCDefault' => true ),
			array( 'id' => 'account_username',   'name' => 'account_username',   'type' => 'text',     'label' => __( 'Username',   'wholesalenode-for-woocommerce' ), 'required' => true,  'default' => true, 'locked' => false, 'isWCDefault' => true ),
			array( 'id' => 'account_password',   'name' => 'account_password',   'type' => 'password', 'label' => __( 'Password',   'wholesalenode-for-woocommerce' ), 'required' => true,  'default' => true, 'locked' => true,  'isWCDefault' => true ),
		) );
	}

	// ── Quotes ────────────────────────────────────────────────────────────

	/**
	 * @param WP_REST_Request $request
	 * @return WP_REST_Response
	 */
	public static function get_quotes( $request ) {
		global $wpdb;
		$table    = $wpdb->prefix . 'woowsn_quotes';
		$params   = $request->get_params();
		$per_page = max( 1, intval( $params['per_page'] ?? 20 ) );
		$page     = max( 1, intval( $params['page']     ?? 1 ) );
		$status   = sanitize_key( $params['status'] ?? '' );
		$search   = sanitize_text_field( $params['search'] ?? '' );
		$offset   = ( $page - 1 ) * $per_page;
		$has_st   = ( $status && 'all' !== $status );
		$has_sr   = (bool) $search;
		$like     = $has_sr ? '%' . $wpdb->esc_like( $search ) . '%' : '';

		if ( $has_st && $has_sr ) {
			$total = (int) $wpdb->get_var(
				$wpdb->prepare( 'SELECT COUNT(*) FROM %i WHERE status = %s AND form_data LIKE %s', $table, $status, $like )
			);
			$rows  = $wpdb->get_results(
				$wpdb->prepare( 'SELECT * FROM %i WHERE status = %s AND form_data LIKE %s ORDER BY created_at DESC LIMIT %d OFFSET %d', $table, $status, $like, $per_page, $offset ),
				ARRAY_A
			);
		} elseif ( $has_st ) {
			$total = (int) $wpdb->get_var(
				$wpdb->prepare( 'SELECT COUNT(*) FROM %i WHERE status = %s', $table, $status )
			);
			$rows  = $wpdb->get_results(
				$wpdb->prepare( 'SELECT * FROM %i WHERE status = %s ORDER BY created_at DESC LIMIT %d OFFSET %d', $table, $status, $per_page, $offset ),
				ARRAY_A
			);
		} elseif ( $has_sr ) {
			$total = (int) $wpdb->get_var(
				$wpdb->prepare( 'SELECT COUNT(*) FROM %i WHERE form_data LIKE %s', $table, $like )
			);
			$rows  = $wpdb->get_results(
				$wpdb->prepare( 'SELECT * FROM %i WHERE form_data LIKE %s ORDER BY created_at DESC LIMIT %d OFFSET %d', $table, $like, $per_page, $offset ),
				ARRAY_A
			);
		} else {
			$total = (int) $wpdb->get_var(
				$wpdb->prepare( 'SELECT COUNT(*) FROM %i', $table )
			);
			$rows  = $wpdb->get_results(
				$wpdb->prepare( 'SELECT * FROM %i ORDER BY created_at DESC LIMIT %d OFFSET %d', $table, $per_page, $offset ),
				ARRAY_A
			);
		}

		$quotes = array();
		foreach ( (array) $rows as $row ) {
			$quotes[] = array(
				'id'         => (int) $row['id'],
				'status'     => $row['status'],
				'created_at' => $row['created_at'],
				'updated_at' => $row['updated_at'] ?? '',
				'user_id'    => (int) $row['user_id'],
				'form_data'  => json_decode( $row['form_data'] ?? '{}', true ) ?: array(),
			);
		}

		return rest_ensure_response( array( 'success' => true, 'data' => $quotes, 'total' => $total, 'per_page' => $per_page, 'current_page' => $page ) );
	}

	/**
	 * @param WP_REST_Request $request
	 * @return WP_REST_Response|WP_Error
	 */
	public static function get_quote_detail( $request ) {
		global $wpdb;
		$row = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}woowsn_quotes WHERE id = %d", (int) $request['id'] ), ARRAY_A );
		if ( ! $row ) {
			return new WP_Error( 'not_found', __( 'Quote not found.', 'wholesalenode-for-woocommerce' ), array( 'status' => 404 ) );
		}

		$fd           = json_decode( $row['form_data'] ?? '{}', true ) ?: array();
		$product_data = array();

		if ( ! empty( $fd['current_product_id'] ) && function_exists( 'wc_get_product' ) ) {
			$product = wc_get_product( (int) $fd['current_product_id'] );
			if ( $product ) {
				$product_data = array(
					'product_name'     => $product->get_name(),
					'product_sku'      => $product->get_sku(),
					'product_price'    => wc_price( $product->get_price() ),
					'product_image'    => wp_get_attachment_url( $product->get_image_id() ) ?: '',
					'product_url'      => get_edit_post_link( $product->get_id(), 'url' ),
					'product_view_url' => get_permalink( $product->get_id() ),
				);
			}
		}

		return rest_ensure_response( array(
			'success' => true,
			'data'    => array_merge( array(
				'id'          => (int) $row['id'],
				'status'      => $row['status'],
				'created_at'  => $row['created_at'],
				'updated_at'  => $row['updated_at'] ?? '',
				'user_id'     => (int) $row['user_id'],
				'customer_id' => (int) $row['user_id'],
				'form_data'   => $fd,
				'thread'      => array(),
			), $product_data ),
		) );
	}

	/**
	 * @param WP_REST_Request $request
	 * @return WP_REST_Response|WP_Error
	 */
	public static function update_quote_status( $request ) {
		global $wpdb;
		$body    = $request->get_json_params();
		$status  = sanitize_key( $body['status'] ?? 'pending' );
		$allowed = array( 'pending', 'processing', 'completed', 'cancelled', 'reviewed', 'closed' );
		if ( ! in_array( $status, $allowed, true ) ) {
			return new WP_Error( 'invalid_status', __( 'Invalid status.', 'wholesalenode-for-woocommerce' ), array( 'status' => 400 ) );
		}
		$wpdb->update( $wpdb->prefix . 'woowsn_quotes', array( 'status' => $status ), array( 'id' => (int) $request['id'] ) );
		return rest_ensure_response( array( 'success' => true, 'status' => $status ) );
	}

	/**
	 * @param WP_REST_Request $request
	 * @return WP_REST_Response
	 */
	public static function delete_quote( $request ) {
		global $wpdb;
		$wpdb->delete( $wpdb->prefix . 'woowsn_quotes', array( 'id' => (int) $request['id'] ) );
		return rest_ensure_response( array( 'success' => true ) );
	}

	// ── Rules table helpers ───────────────────────────────────────────────

	/**
	 * Decode JSON columns and merge config blob to top-level.
	 *
	 * @param array $row Raw DB row.
	 * @return array
	 */
	private static function hydrate_rule( $row ) {
		$json_cols = array( 'user_roles', 'user_ids', 'user_list_ids', 'product_ids', 'product_list_ids', 'category_ids', 'tag_ids' );
		foreach ( $json_cols as $col ) {
			$row[ $col ] = ! empty( $row[ $col ] ) ? json_decode( $row[ $col ], true ) : array();
			if ( ! is_array( $row[ $col ] ) ) {
				$row[ $col ] = array();
			}
		}
		$config = ! empty( $row['config'] ) ? json_decode( $row['config'], true ) : array();
		if ( is_array( $config ) ) {
			foreach ( $config as $k => $v ) {
				if ( ! isset( $row[ $k ] ) ) {
					$row[ $k ] = $v;
				}
			}
		}
		return $row;
	}

	/**
	 * Map params to DB columns.
	 *
	 * @param string $rule_type Rule type.
	 * @param array  $params    Request params.
	 * @param bool   $is_update Whether this is an update.
	 * @return array
	 */
	private static function prepare_rule_data( $rule_type, $params, $is_update = false ) {
		$data = array();

		if ( ! $is_update ) {
			$data['rule_type']  = $rule_type;
			$data['created_at'] = current_time( 'mysql' );
			$data['created_by'] = get_current_user_id();
		}

		$direct = array( 'title', 'description', 'status', 'priority' );
		foreach ( $direct as $col ) {
			if ( isset( $params[ $col ] ) ) {
				$data[ $col ] = is_int( $params[ $col ] ) ? $params[ $col ] : sanitize_text_field( $params[ $col ] );
			}
		}

		$json_cols = array( 'user_roles', 'user_ids', 'user_list_ids', 'product_ids', 'product_list_ids', 'category_ids', 'tag_ids' );
		foreach ( $json_cols as $col ) {
			if ( isset( $params[ $col ] ) ) {
				$data[ $col ] = wp_json_encode( is_array( $params[ $col ] ) ? array_values( $params[ $col ] ) : array() );
			}
		}

		// Everything else goes into config — but NEVER include 'config' itself
		// (it comes back from hydrate_rule when editing, causing infinite nesting).
		$reserved = array_merge(
			$direct,
			$json_cols,
			array( 'rule_type', 'id', 'created_at', 'updated_at', 'created_by', 'config' )
		);
		$config = array();
		foreach ( $params as $k => $v ) {
			if ( ! in_array( $k, $reserved, true ) ) {
				$config[ $k ] = $v;
			}
		}
		if ( ! empty( $config ) ) {
			$data['config'] = wp_json_encode( $config );
		}

		if ( $is_update ) {
			$data['updated_at'] = current_time( 'mysql' );
		}

		return $data;
	}

	// ── Hide Price Rules ──────────────────────────────────────────────────

	/**
	 * @param WP_REST_Request $request
	 * @return WP_REST_Response
	 */
	public static function get_hide_price_rules( $request ) {
		global $wpdb;
		$params   = $request->get_params();
		$per_page = max( 1, intval( $params['per_page'] ?? 20 ) );
		$page     = max( 1, intval( $params['page']     ?? 1 ) );
		$table    = $wpdb->prefix . 'woowsn_rules';
		$total    = (int) $wpdb->get_var( $wpdb->prepare( 'SELECT COUNT(*) FROM %i WHERE rule_type = %s', $table, 'hide_price' ) );
		$offset   = ( $page - 1 ) * $per_page;
		$rows     = $wpdb->get_results( $wpdb->prepare( 'SELECT * FROM %i WHERE rule_type = %s ORDER BY priority ASC, id ASC LIMIT %d OFFSET %d', $table, 'hide_price', $per_page, $offset ), ARRAY_A );
		return rest_ensure_response( array( 'success' => true, 'data' => array_values( array_map( array( __CLASS__, 'hydrate_rule' ), (array) $rows ) ), 'total' => $total, 'per_page' => $per_page, 'current_page' => $page ) );
	}

	/**
	 * @param WP_REST_Request $request
	 * @return WP_REST_Response|WP_Error
	 */
	public static function get_hide_price_rule( $request ) {
		global $wpdb;
		$table = $wpdb->prefix . 'woowsn_rules';
		$row   = $wpdb->get_row( $wpdb->prepare( 'SELECT * FROM %i WHERE id = %d AND rule_type = %s', $table, (int) $request['id'], 'hide_price' ), ARRAY_A );
		if ( ! $row ) {
			return new WP_Error( 'not_found', __( 'Rule not found.', 'wholesalenode-for-woocommerce' ), array( 'status' => 404 ) );
		}
		$rule         = self::hydrate_rule( $row );
		$rule['name'] = $rule['title'];
		return rest_ensure_response( array( 'success' => true, 'data' => $rule ) );
	}

	/**
	 * @param WP_REST_Request $request
	 * @return WP_REST_Response
	 */
	public static function create_hide_price_rule( $request ) {
		return self::upsert_hide_price_rule( $request, null );
	}

	/**
	 * @param WP_REST_Request $request
	 * @return WP_REST_Response
	 */
	public static function update_hide_price_rule( $request ) {
		return self::upsert_hide_price_rule( $request, (int) $request['id'] );
	}

	/**
	 * @param WP_REST_Request  $request
	 * @param int|null         $rule_id
	 * @return WP_REST_Response
	 */
	private static function upsert_hide_price_rule( $request, $rule_id ) {
		global $wpdb;
		$b           = $request->get_json_params();
		$b['title']  = $b['name'] ?? $b['title'] ?? '';
		$b['status'] = in_array( $b['status'] ?? '', array( 'active', 'inactive' ), true ) ? $b['status'] : 'active';
		$data        = self::prepare_rule_data( 'hide_price', $b, (bool) $rule_id );
		$table       = $wpdb->prefix . 'woowsn_rules';
		if ( $rule_id ) {
			$wpdb->update( $table, $data, array( 'id' => $rule_id ) );
		} else {
			$wpdb->insert( $table, $data );
			$rule_id = $wpdb->insert_id;
		}
		return rest_ensure_response( array( 'success' => true, 'data' => array( 'id' => $rule_id ) ) );
	}

	/**
	 * @param WP_REST_Request $request
	 * @return WP_REST_Response
	 */
	public static function delete_hide_price_rule( $request ) {
		global $wpdb;
		$wpdb->delete( $wpdb->prefix . 'woowsn_rules', array( 'id' => (int) $request['id'], 'rule_type' => 'hide_price' ) );
		return rest_ensure_response( array( 'success' => true ) );
	}

	/**
	 * @param WP_REST_Request $request
	 * @return WP_REST_Response|WP_Error
	 */
	public static function toggle_hide_price_rule( $request ) {
		global $wpdb;
		$table = $wpdb->prefix . 'woowsn_rules';
		$id    = (int) $request['id'];
		$row   = $wpdb->get_row( $wpdb->prepare( 'SELECT status FROM %i WHERE id = %d', $table, $id ), ARRAY_A );
		if ( ! $row ) {
			return new WP_Error( 'not_found', __( 'Rule not found.', 'wholesalenode-for-woocommerce' ), array( 'status' => 404 ) );
		}
		$new = ( 'active' === $row['status'] ) ? 'inactive' : 'active';
		$wpdb->update( $table, array( 'status' => $new, 'updated_at' => current_time( 'mysql' ) ), array( 'id' => $id ) );
		return rest_ensure_response( array( 'success' => true, 'status' => $new ) );
	}

	// ── Discount Rules ────────────────────────────────────────────────────

	/**
	 * @param WP_REST_Request $request
	 * @return WP_REST_Response
	 */
	public static function get_discount_rules( $request ) {
		global $wpdb;
		$params   = $request->get_params();
		$per_page = max( 1, intval( $params['per_page'] ?? 20 ) );
		$page     = max( 1, intval( $params['page']     ?? 1 ) );
		$table    = $wpdb->prefix . 'woowsn_rules';
		$total    = (int) $wpdb->get_var( $wpdb->prepare( 'SELECT COUNT(*) FROM %i WHERE rule_type = %s', $table, 'discount' ) );
		$offset   = ( $page - 1 ) * $per_page;
		$rows     = $wpdb->get_results( $wpdb->prepare( 'SELECT * FROM %i WHERE rule_type = %s ORDER BY priority ASC, id ASC LIMIT %d OFFSET %d', $table, 'discount', $per_page, $offset ), ARRAY_A );
		return rest_ensure_response( array( 'success' => true, 'data' => array_values( array_map( array( __CLASS__, 'hydrate_rule' ), (array) $rows ) ), 'total' => $total, 'per_page' => $per_page, 'current_page' => $page ) );
	}

	/**
	 * @param WP_REST_Request $request
	 * @return WP_REST_Response|WP_Error
	 */
	public static function get_discount_rule( $request ) {
		global $wpdb;
		$table = $wpdb->prefix . 'woowsn_rules';
		$row   = $wpdb->get_row( $wpdb->prepare( 'SELECT * FROM %i WHERE id = %d AND rule_type = %s', $table, (int) $request['id'], 'discount' ), ARRAY_A );
		if ( ! $row ) {
			return new WP_Error( 'not_found', __( 'Rule not found.', 'wholesalenode-for-woocommerce' ), array( 'status' => 404 ) );
		}
		return rest_ensure_response( self::hydrate_rule( $row ) );
	}

	/**
	 * @param WP_REST_Request $request
	 * @return WP_REST_Response
	 */
	public static function create_discount_rule( $request ) {
		return self::upsert_discount_rule( $request, null );
	}

	/**
	 * @param WP_REST_Request $request
	 * @return WP_REST_Response
	 */
	public static function update_discount_rule( $request ) {
		return self::upsert_discount_rule( $request, (int) $request['id'] );
	}

	/**
	 * @param WP_REST_Request  $request
	 * @param int|null         $rule_id
	 * @return WP_REST_Response
	 */
	private static function upsert_discount_rule( $request, $rule_id ) {
		global $wpdb;
		$b = $request->get_json_params();
		$allowed = array( 'percentage', 'fixed_product', 'fixed_cart', 'quantity_percentage', 'quantity_fixed', 'bogo' );
		if ( ! in_array( $b['discount_type'] ?? '', $allowed, true ) ) {
			$b['discount_type'] = 'percentage';
		}
		$b['status']   = in_array( $b['status'] ?? '', array( 'active', 'inactive' ), true ) ? $b['status'] : 'active';
		$b['apply_to'] = 'product';
		$data  = self::prepare_rule_data( 'discount', $b, (bool) $rule_id );
		$table = $wpdb->prefix . 'woowsn_rules';
		if ( $rule_id ) {
			$wpdb->update( $table, $data, array( 'id' => $rule_id ) );
		} else {
			$wpdb->insert( $table, $data );
			$rule_id = $wpdb->insert_id;
		}
		return rest_ensure_response( array( 'success' => true, 'id' => $rule_id ) );
	}

	/**
	 * @param WP_REST_Request $request
	 * @return WP_REST_Response
	 */
	public static function toggle_discount_rule_status( $request ) {
		global $wpdb;
		$b      = $request->get_json_params();
		$id     = intval( $b['id'] ?? 0 );
		$status = in_array( $b['status'] ?? '', array( 'active', 'inactive' ), true ) ? $b['status'] : 'active';
		$wpdb->update( $wpdb->prefix . 'woowsn_rules', array( 'status' => $status, 'updated_at' => current_time( 'mysql' ) ), array( 'id' => $id, 'rule_type' => 'discount' ) );
		return rest_ensure_response( array( 'success' => true ) );
	}

	/**
	 * @param WP_REST_Request $request
	 * @return WP_REST_Response
	 */
	public static function delete_discount_rule( $request ) {
		global $wpdb;
		$wpdb->delete( $wpdb->prefix . 'woowsn_rules', array( 'id' => (int) $request['id'], 'rule_type' => 'discount' ) );
		return rest_ensure_response( array( 'success' => true ) );
	}

	// ── Product Lists ─────────────────────────────────────────────────────

	/**
	 * @param WP_REST_Request $request
	 * @return WP_REST_Response
	 */
	public static function get_product_lists( $request ) {
		global $wpdb;
		$params   = $request->get_params();
		$per_page = max( 1, intval( $params['per_page'] ?? 20 ) );
		$page     = max( 1, intval( $params['page']     ?? 1 ) );
		$table    = $wpdb->prefix . 'woowsn_product_lists';
		$total    = (int) $wpdb->get_var( $wpdb->prepare( 'SELECT COUNT(*) FROM %i', $table ) );
		$offset   = ( $page - 1 ) * $per_page;
		$rows     = $wpdb->get_results( $wpdb->prepare( 'SELECT * FROM %i ORDER BY title ASC LIMIT %d OFFSET %d', $table, $per_page, $offset ), ARRAY_A );
		return rest_ensure_response( array( 'success' => true, 'data' => array_values( array_map( array( __CLASS__, 'format_product_list' ), (array) $rows ) ), 'total' => $total, 'per_page' => $per_page, 'current_page' => $page ) );
	}

	/**
	 * @param WP_REST_Request $request
	 * @return WP_REST_Response|WP_Error
	 */
	public static function get_product_list( $request ) {
		global $wpdb;
		$table = $wpdb->prefix . 'woowsn_product_lists';
		$row   = $wpdb->get_row( $wpdb->prepare( 'SELECT * FROM %i WHERE id = %d', $table, (int) $request['id'] ), ARRAY_A );
		if ( ! $row ) {
			return new WP_Error( 'not_found', __( 'List not found.', 'wholesalenode-for-woocommerce' ), array( 'status' => 404 ) );
		}
		return rest_ensure_response( self::format_product_list( $row ) );
	}

	/**
	 * @param WP_REST_Request $request
	 * @return WP_REST_Response
	 */
	public static function create_product_list( $request ) {
		return self::upsert_product_list( $request, null );
	}

	/**
	 * @param WP_REST_Request $request
	 * @return WP_REST_Response
	 */
	public static function update_product_list( $request ) {
		return self::upsert_product_list( $request, (int) $request['id'] );
	}

	/**
	 * @param WP_REST_Request  $request
	 * @param int|null         $list_id
	 * @return WP_REST_Response
	 */
	private static function upsert_product_list( $request, $list_id ) {
		global $wpdb;
		$b      = $request->get_json_params();
		$status = in_array( $b['status'] ?? '', array( 'active', 'inactive' ), true ) ? $b['status'] : 'active';
		$pids   = array_values( array_map( 'intval', (array) ( $b['product_ids'] ?? array() ) ) );
		$data   = array(
			'title'        => sanitize_text_field( $b['title']       ?? '' ),
			'description'  => sanitize_text_field( $b['description'] ?? '' ),
			'type'         => 'manual',
			'product_ids'  => wp_json_encode( $pids ),
			'category_ids' => wp_json_encode( array() ),
			'tag_ids'      => wp_json_encode( array() ),
			'status'       => $status,
		);
		$table = $wpdb->prefix . 'woowsn_product_lists';
		if ( $list_id ) {
			$data['updated_at'] = current_time( 'mysql' );
			$wpdb->update( $table, $data, array( 'id' => $list_id ) );
		} else {
			$data['created_by'] = get_current_user_id();
			$data['created_at'] = current_time( 'mysql' );
			$wpdb->insert( $table, $data );
			$list_id = $wpdb->insert_id;
		}
		return rest_ensure_response( array( 'success' => true, 'id' => $list_id ) );
	}

	/**
	 * @param WP_REST_Request $request
	 * @return WP_REST_Response
	 */
	public static function delete_product_list( $request ) {
		global $wpdb;
		$wpdb->delete( $wpdb->prefix . 'woowsn_product_lists', array( 'id' => (int) $request['id'] ) );
		return rest_ensure_response( array( 'success' => true ) );
	}

	/**
	 * @param array $row Raw DB row.
	 * @return array
	 */
	private static function format_product_list( $row ) {
		return array(
			'id'          => (int) $row['id'],
			'title'       => $row['title'],
			'description' => $row['description'] ?? '',
			'type'        => $row['type']   ?? 'manual',
			'status'      => $row['status'] ?? 'active',
			'product_ids' => json_decode( $row['product_ids']  ?? '[]', true ) ?: array(),
			'category_ids'=> json_decode( $row['category_ids'] ?? '[]', true ) ?: array(),
			'tag_ids'     => json_decode( $row['tag_ids']      ?? '[]', true ) ?: array(),
			'created_at'  => $row['created_at'] ?? '',
			'updated_at'  => $row['updated_at'] ?? '',
		);
	}

	// ── Supporting data ───────────────────────────────────────────────────

	/**
	 * @param WP_REST_Request $request
	 * @return WP_REST_Response
	 */
	public static function search_products( $request ) {
		$search = sanitize_text_field( $request->get_param( 'search' ) ?? '' );
		if ( ! $search || ! function_exists( 'wc_get_products' ) ) {
			return rest_ensure_response( array() );
		}
		$data = array();
		foreach ( wc_get_products( array( 'limit' => 20, 'status' => 'publish', 's' => $search, 'return' => 'objects' ) ) as $p ) {
			$data[] = array(
				'value' => $p->get_id(),
				'label' => $p->get_name() . ' (#' . $p->get_id() . ')',
				'id'    => $p->get_id(),
				'name'  => $p->get_name(),
				'sku'   => $p->get_sku(),
			);
		}
		return rest_ensure_response( $data );
	}

	/**
	 * Get the b2b_default group ID, ensuring the row is seeded.
	 *
	 * @return WP_REST_Response
	 */
	public static function get_b2b_default_group() {
		global $wpdb;
		$table = $wpdb->prefix . 'woowsn_user_groups';

		if ( class_exists( 'WOOWSNFree_Activation' ) ) {
			WOOWSNFree_Activation::seed_b2b_group();
		}

		$b2b_default_id = (int) $wpdb->get_var(
			$wpdb->prepare( 'SELECT id FROM %i WHERE slug = %s LIMIT 1', $table, 'b2b_default' )
		);
		$b2b_parent_id = (int) $wpdb->get_var(
			$wpdb->prepare( 'SELECT id FROM %i WHERE slug = %s LIMIT 1', $table, 'b2b' )
		);

		return rest_ensure_response( array(
			'success'       => true,
			'b2b_id'        => $b2b_parent_id,
			'b2b_default_id'=> $b2b_default_id,
		) );
	}

	/**
	 * Get all user groups with member counts.
	 *
	 * @return WP_REST_Response
	 */
	public static function get_free_groups() {
		global $wpdb;
		$table = $wpdb->prefix . 'woowsn_user_groups';

		if ( class_exists( 'WOOWSNFree_Activation' ) ) {
			WOOWSNFree_Activation::seed_b2b_group();
		}

		$rows = $wpdb->get_results( $wpdb->prepare( 'SELECT * FROM %i ORDER BY parent_id ASC, sort_order ASC, id ASC', $table ), ARRAY_A );

		$data = array();
		foreach ( (array) $rows as $row ) {
			$member_count = (int) $wpdb->get_var(
				$wpdb->prepare( "SELECT COUNT(*) FROM {$wpdb->usermeta} WHERE meta_key = %s AND meta_value = %d", 'woowsn_user_group', (int) $row['id'] ) // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key, WordPress.DB.SlowDBQuery.slow_db_query_meta_value
			);

			$parent_slug = '';
			if ( (int) $row['parent_id'] > 0 ) {
				$parent_slug = (string) $wpdb->get_var(
					$wpdb->prepare( 'SELECT slug FROM %i WHERE id = %d', $table, (int) $row['parent_id'] )
				);
			}

			$data[] = array(
				'id'           => (int) $row['id'],
				'title'        => $row['title'],
				'slug'         => $row['slug'],
				'description'  => $row['description'] ?? '',
				'group_type'   => $row['group_type'],
				'parent_id'    => (int) $row['parent_id'],
				'parent_slug'  => $parent_slug,
				'status'       => $row['status'],
				'is_system'    => $row['group_type'] === 'system',
				'member_count' => $member_count,
			);
		}

		return rest_ensure_response( array( 'success' => true, 'data' => $data ) );
	}

	/**
	 * Get a single user group.
	 *
	 * @param WP_REST_Request $request Request object.
	 * @return WP_REST_Response|WP_Error
	 */
	public static function get_free_group( $request ) {
		global $wpdb;
		$table = $wpdb->prefix . 'woowsn_user_groups';
		$id    = (int) $request['id'];
		$row   = $wpdb->get_row( $wpdb->prepare( 'SELECT * FROM %i WHERE id = %d', $table, $id ), ARRAY_A );
		if ( ! $row ) {
			return new WP_Error( 'not_found', __( 'Group not found.', 'wholesalenode-for-woocommerce' ), array( 'status' => 404 ) );
		}

		$member_count = (int) $wpdb->get_var(
			$wpdb->prepare( "SELECT COUNT(*) FROM {$wpdb->usermeta} WHERE meta_key = %s AND meta_value = %d", 'woowsn_user_group', $id ) // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key, WordPress.DB.SlowDBQuery.slow_db_query_meta_value
		);
		$parent_slug = '';
		if ( (int) $row['parent_id'] > 0 ) {
			$parent_slug = (string) $wpdb->get_var(
				$wpdb->prepare( 'SELECT slug FROM %i WHERE id = %d', $table, (int) $row['parent_id'] )
			);
		}

		return rest_ensure_response( array(
			'success' => true,
			'data'    => array(
				'id'           => (int) $row['id'],
				'title'        => $row['title'],
				'slug'         => $row['slug'],
				'description'  => $row['description'] ?? '',
				'group_type'   => $row['group_type'],
				'parent_id'    => (int) $row['parent_id'],
				'parent_slug'  => $parent_slug,
				'status'       => $row['status'],
				'is_system'    => $row['group_type'] === 'system',
				'member_count' => $member_count,
			),
		) );
	}

	/**
	 * Get the members of a user group.
	 *
	 * @param WP_REST_Request $request Request object.
	 * @return WP_REST_Response
	 */
	public static function get_group_members( $request ) {
		$group_id = (int) $request['id'];
		$users    = get_users( array(
			'meta_key'   => 'woowsn_user_group', // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
			'meta_value' => $group_id, // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_value
			'fields'     => array( 'ID', 'user_email', 'display_name' ),
			'orderby'    => 'display_name',
			'order'      => 'ASC',
		) );

		$data = array();
		foreach ( $users as $u ) {
			$data[] = array( 'id' => (int) $u->ID, 'name' => $u->display_name, 'email' => $u->user_email );
		}
		return rest_ensure_response( $data );
	}

	/**
	 * Assign or remove a user from a group.
	 *
	 * @param WP_REST_Request $request Request object.
	 * @return WP_REST_Response|WP_Error
	 */
	public static function assign_user_group( $request ) {
		$params   = $request->get_json_params();
		$user_id  = (int) ( $params['user_id']  ?? 0 );
		$group_id = (int) ( $params['group_id'] ?? 0 );

		if ( ! $user_id || ! get_userdata( $user_id ) ) {
			return new WP_Error( 'invalid_user', __( 'Invalid user.', 'wholesalenode-for-woocommerce' ), array( 'status' => 400 ) );
		}

		if ( $group_id === 0 ) {
			delete_user_meta( $user_id, 'woowsn_user_group' );
		} else {
			update_user_meta( $user_id, 'woowsn_user_group', $group_id );
		}

		return rest_ensure_response( array( 'success' => true, 'user_id' => $user_id, 'group_id' => $group_id ) );
	}

	/**
	 * Search users by name or email.
	 *
	 * @param WP_REST_Request $request Request object.
	 * @return WP_REST_Response
	 */
	public static function search_users_groups( $request ) {
		$search = sanitize_text_field( $request->get_param( 'search' ) ?? '' );
		if ( strlen( $search ) < 2 ) {
			return rest_ensure_response( array() );
		}

		$users = get_users( array(
			'search'         => '*' . $search . '*',
			'search_columns' => array( 'user_login', 'user_email', 'display_name' ),
			'number'         => 20,
			'orderby'        => 'display_name',
			'order'          => 'ASC',
			'fields'         => array( 'ID', 'user_email', 'display_name' ),
		) );

		$data = array();
		foreach ( $users as $u ) {
			$data[] = array( 'id' => (int) $u->ID, 'name' => $u->display_name, 'email' => $u->user_email );
		}
		return rest_ensure_response( $data );
	}

	/**
	 * @return WP_REST_Response
	 */
	public static function get_roles() {
		global $wp_roles;
		$data = array();
		foreach ( $wp_roles->roles as $key => $role ) {
			$data[] = array( 'id' => $key, 'value' => $key, 'name' => $role['name'], 'label' => $role['name'] );
		}
		return rest_ensure_response( array( 'success' => true, 'data' => $data ) );
	}

	public static function get_wc_registration_settings() {
		return rest_ensure_response( array(
			'generate_username' => get_option( 'woocommerce_registration_generate_username' ) === 'yes',
			'generate_password' => get_option( 'woocommerce_registration_generate_password' ) === 'yes',
		) );
	}
}
