<?php
/**
 * Settings API Controller
 *
 * @since   3.0.0
 * @package DokanKits\REST\Controllers\Version1
 */

namespace DokanKits\REST\Controllers\Version1;

use DokanKits\Core\Data\Models\Settings;
use DokanKits\Core\Data\Stores\SettingsDataStore;
use DokanKits\REST\Controllers\Controller;
use Exception;
use JsonException;
use Throwable;
use WP_Error;
use WP_REST_Request;
use WP_REST_Response;
use WP_REST_Server;

/**
 * Settings API Controller class.
 *
 * Handles API endpoints for managing plugin settings with consistent response patterns.
 *
 * @since 3.0.0
 */
class SettingsController extends Controller {
	/**
	 * Route base.
	 *
	 * @var string
	 */
	protected $rest_base = 'settings';

	/**
	 * Register routes.
	 *
	 * @since 3.0.0
	 *
	 * @return void
	 */
	public function register_routes() {
		// Collection endpoint: GET /settings, POST /settings.
		register_rest_route(
			$this->namespace,
			'/' . $this->rest_base,
			array(
				array(
					'methods'             => WP_REST_Server::READABLE,
					'callback'            => array( $this, 'get_settings' ),
					'permission_callback' => array( $this, 'admin_permissions_check' ),
					'args'                => $this->get_collection_params(),
				),
				array(
					'methods'             => WP_REST_Server::CREATABLE,
					'callback'            => array( $this, 'update_settings' ),
					'permission_callback' => array( $this, 'admin_permissions_check' ),
					'args'                => $this->get_bulk_update_params(),
				),
				'schema' => array( $this, 'get_item_schema' ),
			)
		);

		// Single setting endpoint: GET /settings/{setting_name}, PUT /settings/{setting_name}.
		register_rest_route(
			$this->namespace,
			'/' . $this->rest_base . '/(?P<setting_name>[a-zA-Z0-9._-]+)',
			array(
				array(
					'methods'             => WP_REST_Server::READABLE,
					'callback'            => array( $this, 'get_setting' ),
					'permission_callback' => array( $this, 'admin_permissions_check' ),
					'args'                => $this->get_single_setting_params(),
				),
				array(
					'methods'             => WP_REST_Server::EDITABLE,
					'callback'            => array( $this, 'update_setting' ),
					'permission_callback' => array( $this, 'admin_permissions_check' ),
					'args'                => $this->get_single_update_params(),
				),
				'schema' => array( $this, 'get_single_setting_schema' ),
			)
		);
	}

	/**
	 * Get all settings.
	 *
	 * @since 3.0.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 *
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
	 */
	public function get_settings( WP_REST_Request $request ) {
		try {
			// Get query parameters.
			$query_args = $this->prepare_query_args( $request );

			// Add force refresh parameter to ensure fresh data from database.
			$query_args['force_refresh'] = (bool) $request->get_param( 'force_refresh' );

			// Fetch settings using the data store.
			$settings_store = dokan_kits_get_container()->get( SettingsDataStore::class );
			$settings       = $settings_store->get_all( $query_args );

			// Prepare response data.
			$response_data = $this->prepare_settings_for_response( $settings, $request );

			/**
			 * Action fired after getting settings.
			 *
			 * @since 3.0.0
			 *
			 * @param array           $response_data The response data.
			 * @param WP_REST_Request $request       The request object.
			 */
			do_action( 'dokan_kits_after_get_settings', $response_data, $request );

			return $this->success(
				$response_data,
				sprintf(
				/* translators: %d: Number of settings retrieved */
					_n(
						'%d setting retrieved successfully.',
						'%d settings retrieved successfully.',
						count( $settings ),
						'dokan-kits'
					),
					count( $settings )
				)
			);

		} catch ( Exception $e ) {
			return $this->handle_exception( $e, 'get_settings' );
		}
	}

	/**
	 * Get a single setting.
	 *
	 * @since 3.0.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 *
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
	 */
	public function get_setting( WP_REST_Request $request ) {
		try {
			$setting_name = $this->sanitize_setting_name( $request->get_param( 'setting_name' ) );
			if ( empty( $setting_name ) ) {
				return $this->error(
					__( 'Setting name is required and must be valid.', 'dokan-kits' ),
					'invalid_setting_name',
					400
				);
			}

			// Validate setting name format and access.
			$validation_result = $this->validate_setting_access( $setting_name );
			if ( is_wp_error( $validation_result ) ) {
				return $validation_result;
			}

			// Get setting using data store with optional force load to ensure fresh data.
			$force_refresh  = (bool) $request->get_param( 'force_refresh' );
			$settings_store = dokan_kits_get_container()->get( SettingsDataStore::class );
			$setting        = $settings_store->get_settings_by_name( $setting_name, $force_refresh );

			if ( null === $setting ) {
				return $this->error(
					sprintf(
					/* translators: %s: setting name */
						__( 'Setting "%s" not found.', 'dokan-kits' ),
						$setting_name
					),
					'setting_not_found',
					404
				);
			}

			// Prepare response data.
			$response_data = $this->prepare_single_setting_for_response( $setting, $request );

			/**
			 * Filter the single setting response data.
			 *
			 * @since 3.0.0
			 *
			 * @param array           $response_data The response data.
			 * @param Settings        $setting       The setting object.
			 * @param WP_REST_Request $request       The request object.
			 */
			$response_data = apply_filters( 'dokan_kits_rest_single_setting_data', $response_data, $setting, $request );

			/**
			 * Action fired after getting a single setting.
			 *
			 * @since 3.0.0
			 *
			 * @param array           $response_data The response data.
			 * @param string          $setting_name  The setting name.
			 * @param WP_REST_Request $request       The request object.
			 */
			do_action( 'dokan_kits_after_get_single_setting', $response_data, $setting_name, $request );

			return $this->success(
				$response_data,
				sprintf(
				/* translators: %s: setting name */
					__( 'Setting "%s" retrieved successfully.', 'dokan-kits' ),
					$setting_name
				)
			);

		} catch ( Exception $e ) {
			return $this->handle_exception( $e, 'get_setting' );
		}
	}

	/**
	 * Update multiple settings.
	 *
	 * @since 3.0.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 *
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
	 */
	public function update_settings( WP_REST_Request $request ) {
		try {
			/**
			 * Action fired before updating settings.
			 *
			 * @since 3.0.0
			 *
			 * @param WP_REST_Request $request The request object.
			 */
			do_action( 'dokan_kits_before_update_settings', $request );

			// Get and validate settings data.
			$settings_data = $this->get_request_settings_data( $request );

			if ( empty( $settings_data ) ) {
				return $this->error(
					__( 'No settings data provided.', 'dokan-kits' ),
					'no_settings_data',
					400
				);
			}

			// Process each setting.
			$results = array();
			$errors  = array();

			foreach ( $settings_data as $setting_name => $setting_value ) {
				$update_result = $this->update_single_setting_internal( $setting_name, $setting_value );

				if ( is_wp_error( $update_result ) ) {
					$errors[ $setting_name ] = $update_result->get_error_message();
				} else {
					$results[ $setting_name ] = $setting_value;
				}
			}

			// Prepare response.
			$response_data = array(
				'updated' => $results,
				'errors'  => $errors,
				'total'   => count( $results ),
			);

			/**
			 * Filter the bulk update response data.
			 *
			 * @since 3.0.0
			 *
			 * @param array           $response_data The response data.
			 * @param array           $results       Successfully updated settings.
			 * @param array           $errors        Update errors.
			 * @param WP_REST_Request $request       The request object.
			 */
			$response_data = apply_filters( 'dokan_kits_rest_bulk_update_data', $response_data, $results, $errors, $request );

			/**
			 * Action fired after updating settings.
			 *
			 * @since 3.0.0
			 *
			 * @param array           $results The updated settings.
			 * @param array           $errors  The update errors.
			 * @param WP_REST_Request $request The request object.
			 */
			do_action( 'dokan_kits_after_update_settings', $results, $errors, $request );

			// Return response based on results.
			if ( ! empty( $errors ) && empty( $results ) ) {
				// All updates failed.
				return $this->error(
					__( 'All settings failed to update.', 'dokan-kits' ),
					'all_updates_failed',
					400,
					$response_data
				);
			}

			if ( ! empty( $errors ) ) {
				// Partial success.
				return $this->error(
					__( 'Some settings failed to update.', 'dokan-kits' ),
					'partial_update_failure',
					207, // Multi-Status.
					$response_data
				);
			}

			// All updates successful.
			return $this->success(
				$response_data,
				sprintf(
				/* translators: %d: Number of settings updated */
					_n(
						'%d setting updated successfully.',
						'%d settings updated successfully.',
						count( $results ),
						'dokan-kits'
					),
					count( $results )
				)
			);

		} catch ( Exception $e ) {
			return $this->handle_exception( $e, 'update_settings' );
		}
	}

	/**
	 * Update a single setting.
	 *
	 * @since 3.0.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 *
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
	 */
	public function update_setting( WP_REST_Request $request ) {
		try {
			$setting_name  = $this->sanitize_setting_name( $request->get_param( 'setting_name' ) );
			$setting_value = $request->get_param( 'value' );

			if ( empty( $setting_name ) ) {
				return $this->error(
					__( 'Setting name is required and must be valid.', 'dokan-kits' ),
					'invalid_setting_name',
					400
				);
			}

			/**
			 * Action fired before updating a single setting.
			 *
			 * @since 3.0.0
			 *
			 * @param string          $setting_name  The setting name.
			 * @param mixed           $setting_value The setting value.
			 * @param WP_REST_Request $request       The request object.
			 */
			do_action( 'dokan_kits_before_update_single_setting', $setting_name, $setting_value, $request );

			// Update the setting.
			$update_result = $this->update_single_setting_internal( $setting_name, $setting_value );

			if ( is_wp_error( $update_result ) ) {
				return $update_result;
			}

			// Get the updated setting for response using data store with force load to ensure fresh data.
			$settings_store  = dokan_kits_get_container()->get( SettingsDataStore::class );
			$updated_setting = $settings_store->get_settings_by_name( $setting_name, true );

			// Clear any cached data to ensure consistency.
			if ( $updated_setting ) {
				$settings_store->clear_caches( $updated_setting );
			}
			if ( ! $updated_setting instanceof Settings ) {
				return $this->error(
					__( 'Failed to retrieve updated setting.', 'dokan-kits' ),
					'setting_retrieval_failed',
					500
				);
			}

			$response_data = $this->prepare_single_setting_for_response( $updated_setting, $request );

			/**
			 * Filter the single setting update response data.
			 *
			 * @since 3.0.0
			 *
			 * @param array           $response_data The response data.
			 * @param Settings        $setting       The updated setting object.
			 * @param WP_REST_Request $request       The request object.
			 */
			$response_data = apply_filters( 'dokan_kits_rest_single_setting_update_data', $response_data, $updated_setting, $request );

			/**
			 * Action fired after updating a single setting.
			 *
			 * @since 3.0.0
			 *
			 * @param array           $response_data The response data.
			 * @param string          $setting_name  The setting name.
			 * @param mixed           $setting_value The setting value.
			 * @param WP_REST_Request $request       The request object.
			 */
			do_action( 'dokan_kits_after_update_single_setting', $response_data, $setting_name, $setting_value, $request );

			return $this->success(
				$response_data,
				sprintf(
				/* translators: %s: setting name */
					__( 'Setting "%s" updated successfully.', 'dokan-kits' ),
					$setting_name
				)
			);

		} catch ( Exception $e ) {
			return $this->handle_exception( $e, 'update_setting' );
		}
	}

	/**
	 * Prepare query arguments for settings retrieval.
	 *
	 * @since 3.0.0
	 *
	 * @param WP_REST_Request $request The request object.
	 *
	 * @return array Query arguments.
	 */
	protected function prepare_query_args( WP_REST_Request $request ): array {
		$args = array(
			'orderby' => $this->sanitize_text( $request->get_param( 'orderby' ) ),
			'order'   => strtoupper( $this->sanitize_text( $request->get_param( 'order' ) ) ),
			'limit'   => (int) $request->get_param( 'per_page' ),
			'offset'  => absint( $request->get_param( 'offset' ) ),
			'in'      => dokan_kits()->get_settings_groups(),
		);

		// Ensure valid order.
		if ( ! in_array( $args['order'], array( 'ASC', 'DESC' ), true ) ) {
			$args['order'] = 'ASC';
		}

		// Filter by group if provided.
		$group = $request->get_param( 'group' );
		if ( ! empty( $group ) ) {
			if ( is_string( $group ) ) {
				$args['in'] = array( $group );
			} elseif ( is_array( $group ) ) {
				$args['in'] = array_intersect( $group, dokan_kits()->get_settings_groups() );
			}
		}

		// Filter by search if provided.
		$search = $request->get_param( 'search' );
		if ( ! empty( $search ) ) {
			$args['search'] = $this->sanitize_text( $search );
		}

		/**
		 * Filter the query arguments for settings retrieval.
		 *
		 * @since 3.0.0
		 *
		 * @param array           $args    Query arguments.
		 * @param WP_REST_Request $request The request object.
		 */
		return apply_filters( 'dokan_kits_rest_settings_query_args', $args, $request );
	}

	/**
	 * Prepare settings for response.
	 *
	 * @since 3.0.0
	 *
	 * @param Settings[]      $settings The settings objects.
	 * @param WP_REST_Request $request  The request object.
	 *
	 * @return array Prepared settings data.
	 */
	protected function prepare_settings_for_response( array $settings, WP_REST_Request $request ): array {
		$data = array();

		foreach ( $settings as $setting ) {
			$item_data                  = $this->prepare_single_setting_for_response( $setting, $request );
			$data[ $item_data['name'] ] = maybe_unserialize( $item_data['value'] );
		}

		$response = array(
			'settings' => $data,
			'count'    => count( $data ),
		);

		/**
		 * Filter the prepared settings data for API response.
		 *
		 * @since 3.0.5
		 *
		 * @param array           $response The prepared response data.
		 * @param Settings[]      $settings Array of Settings objects.
		 * @param WP_REST_Request $request  The request object.
		 */
		return apply_filters( 'dokan_kits_rest_prepare_settings', $response, $settings, $request );
	}

	/**
	 * Prepare single setting for response.
	 *
	 * @since 3.0.0
	 *
	 * @param Settings        $setting The setting object.
	 * @param WP_REST_Request $request The request object.
	 *
	 * @return array Prepared setting data.
	 */
	protected function prepare_single_setting_for_response( Settings $setting, WP_REST_Request $request ): array {
		$data = array(
			'name'  => $setting->get_name(),
			'value' => $setting->get_value(),
		);

		/**
		 * Filter the setting data for API response.
		 *
		 * @since 3.0.0
		 *
		 * @param array           $data    Setting data.
		 * @param Settings        $setting Setting object.
		 * @param WP_REST_Request $request Request object.
		 */
		return apply_filters( 'dokan_kits_rest_prepare_setting', $data, $setting, $request );
	}

	/**
	 * Get settings data from request.
	 *
	 * @since 3.0.0
	 *
	 * @param WP_REST_Request $request The request object.
	 *
	 * @return array Settings data.
	 * @throws JsonException When JSON parsing fails.
	 */
	protected function get_request_settings_data( WP_REST_Request $request ): array {
		$params = $request->get_params();

		// Handle JSON body if available.
		if ( empty( $params ) && $request->get_body() ) {
			$params = json_decode( $request->get_body(), true, 512, JSON_THROW_ON_ERROR );
		}

		return $params['settings'] ?? array();
	}

	/**
	 * Update a single setting internally.
	 *
	 * @since 3.0.0
	 *
	 * @param string $setting_name  The setting name.
	 * @param mixed  $setting_value The setting value.
	 *
	 * @return true|WP_Error True on success, WP_Error on failure.
	 * @throws Exception When an error occurs during the update process.
	 */
	protected function update_single_setting_internal( string $setting_name, $setting_value ) {
		// Sanitize setting name.
		$setting_name = $this->sanitize_setting_name( $setting_name );
		if ( empty( $setting_name ) ) {
			return $this->error(
				__( 'Invalid setting name.', 'dokan-kits' ),
				'invalid_setting_name',
				400
			);
		}

		// Validate setting access.
		$validation_result = $this->validate_setting_access( $setting_name );
		if ( is_wp_error( $validation_result ) ) {
			return $validation_result;
		}

		// Parse setting name parts.
		$setting_parts = $this->parse_setting_name( $setting_name );
		if ( is_wp_error( $setting_parts ) ) {
			return $setting_parts;
		}

		if ( empty( $setting_parts['group'] ) ) {
			return $this->error(
				__( 'Invalid setting group.', 'dokan-kits' ),
				'invalid_setting_group',
			);
		}

		// Sanitize value.
		$sanitized_value = $this->sanitize_setting_value( $setting_value );

		// Get or create the setting using data store.
		$settings_store = dokan_kits_get_container()->get( SettingsDataStore::class );
		$setting        = $settings_store->get_settings_by_name( $setting_parts['group'] );

		if ( null === $setting ) {
			$setting = dokan_kits_get_container()->get( Settings::class );
			// Settings are stored per group (vendor/product/cart/shipping), not per option key.
			// Using the full dotted key here breaks subsequent reads/updates for fresh contexts (e.g., multisite sites).
			$setting->set_name( $setting_parts['group'] );
		}

		$full_value = $setting->get_value();
		if ( ! is_array( $full_value ) ) {
			$full_value = array();
		}

		$section_key = $setting_parts['section'];
		$section_value = ( isset( $full_value[ $section_key ] ) && is_array( $full_value[ $section_key ] ) )
			? $full_value[ $section_key ]
			: array();
		$section_value[ $setting_parts['option'] ] = $sanitized_value;

		$setting->update( $section_key, $section_value );

		$updated = $setting->save();
		if ( ! $updated ) {
			return $this->error(
				__( 'Failed to update setting.', 'dokan-kits' ),
				'setting_update_failed',
				500
			);
		}

		/**
		 * Action fired after a single setting is updated.
		 *
		 * @since 3.0.0
		 *
		 * @param string $setting_name  Setting name.
		 * @param mixed  $setting_value Setting value.
		 */
		do_action( 'dokan_kits_setting_updated', $setting_name, $sanitized_value );

		return true;
	}

	/**
	 * Sanitize setting name.
	 *
	 * @since 3.0.0
	 *
	 * @param string $setting_name The setting name.
	 *
	 * @return string Sanitized setting name.
	 */
	protected function sanitize_setting_name( string $setting_name ): string {
		return preg_replace( '/[^a-zA-Z0-9._-]/', '', $setting_name );
	}

	/**
	 * Validate setting access.
	 *
	 * @since 3.0.0
	 *
	 * @param string $setting_name The setting name.
	 *
	 * @return true|WP_Error True if valid, WP_Error otherwise.
	 */
	protected function validate_setting_access( string $setting_name ) {
		// Parse setting name to validate format.
		$setting_parts = $this->parse_setting_name( $setting_name );
		if ( is_wp_error( $setting_parts ) ) {
			return $setting_parts;
		}

		// Check if group is allowed.
		if ( ! in_array( $setting_parts['group'], dokan_kits()->get_settings_groups(), true ) ) {
			return $this->error(
				sprintf(
				/* translators: 1: group name, 2: allowed groups */
					__( 'Invalid group "%1$s". Allowed groups: %2$s', 'dokan-kits' ),
					$setting_parts['group'],
					implode( ', ', dokan_kits()->get_settings_groups() )
				),
				'invalid_group',
				400
			);
		}

		/**
		 * Filter setting access validation.
		 *
		 * @since 3.0.0
		 *
		 * @param bool   $access        Whether access is allowed.
		 * @param string $setting_name  The setting name.
		 * @param array  $setting_parts Parsed setting parts.
		 */
		$access_allowed = apply_filters( 'dokan_kits_rest_setting_access', true, $setting_name, $setting_parts );
		if ( ! $access_allowed ) {
			return $this->error(
				sprintf(
				/* translators: %s: setting name */
					__( 'Access denied for setting "%s".', 'dokan-kits' ),
					$setting_name
				),
				'setting_access_denied',
				403
			);
		}

		return true;
	}

	/**
	 * Parse setting name into components.
	 *
	 * @since 3.0.0
	 *
	 * @param string $setting_name The setting name.
	 *
	 * @return array|WP_Error Parsed components or error.
	 */
	protected function parse_setting_name( string $setting_name ) {
		$parts = explode( '.', $setting_name, 3 );

		if ( count( $parts ) < 3 ) {
			return $this->error(
				__( 'Invalid setting name format. Expected: group.section.option', 'dokan-kits' ),
				'invalid_setting_format',
				400
			);
		}

		$settings = array(
			'group'   => $this->sanitize_key( $parts[0] ),
			'section' => $this->sanitize_key( $parts[1] ),
			'option'  => $this->sanitize_key( $parts[2] ),
		);

		/**
		 * Filter parsed setting name components.
		 *
		 * @since 3.0.5
		 *
		 * @param array  $settings     Array containing parsed setting components (group, section, option).
		 * @param string $setting_name Original setting name string.
		 */
		return apply_filters( 'dokan_kits_rest_parse_setting_name', $settings, $setting_name );
	}

	/**
	 * Sanitize setting value.
	 *
	 * @since 3.0.0
	 *
	 * @param mixed $value Setting value.
	 *
	 * @return mixed Sanitized value.
	 */
	protected function sanitize_setting_value( $value ) {
		return apply_filters( 'dokan_kits_rest_sanitize_setting_value', wc_clean( $value ), $value );
	}

	/**
	 * Handle exceptions in a standardized way.
	 *
	 * @since 3.0.0
	 *
	 * @param Throwable $exception The exception.
	 * @param string    $context   The context.
	 *
	 * @return WP_Error Error response.
	 */
	protected function handle_exception( Throwable $exception, string $context = '' ): WP_Error {
		// Log the exception.
		dokan_kits_logger()->error(
			sprintf( 'Exception in SettingsController::%s: %s', $context, $exception->getMessage() ),
			array(
				'exception' => $exception,
				'context'   => $context,
			)
		);

		// Handle specific exception types.
		if ( $exception instanceof JsonException ) {
			return $this->error(
				__( 'Invalid JSON data provided.', 'dokan-kits' ),
				'invalid_json',
				400
			);
		}

		// Generic server error.
		return $this->error(
			sprintf(
			/* translators: %s: context */
				__( 'Failed to %s: An unexpected error occurred.', 'dokan-kits' ),
				str_replace( '_', ' ', $context )
			),
			'internal_error',
			500
		);
	}

	/**
	 * Get the query params for collections.
	 *
	 * @since 3.0.0
	 *
	 * @return array Collection parameters.
	 */
	public function get_collection_params(): array {
		$params = array(
			'orderby'       => array(
				'description' => __( 'Order by field.', 'dokan-kits' ),
				'type'        => 'string',
				'default'     => 'name',
				'enum'        => array( 'id', 'name' ),
			),
			'order'         => array(
				'description' => __( 'Order direction.', 'dokan-kits' ),
				'type'        => 'string',
				'default'     => 'ASC',
				'enum'        => array( 'ASC', 'DESC' ),
			),
			'per_page'      => array(
				'description' => __( 'Number of items to fetch.', 'dokan-kits' ),
				'type'        => 'integer',
				'default'     => -1,
				'minimum'     => -1,
			),
			'offset'        => array(
				'description' => __( 'Number of items to skip.', 'dokan-kits' ),
				'type'        => 'integer',
				'default'     => 0,
				'minimum'     => 0,
			),
			'group'         => array(
				'description' => __( 'Filter settings by group.', 'dokan-kits' ),
				'type'        => array( 'string', 'array' ),
				'items'       => array(
					'type' => 'string',
					'enum' => dokan_kits()->get_settings_groups(),
				),
			),
			'search'        => array(
				'description' => __( 'Search settings by name.', 'dokan-kits' ),
				'type'        => 'string',
			),
			'force_refresh' => array(
				'description' => __( 'Force refresh data from database bypassing cache.', 'dokan-kits' ),
				'type'        => 'boolean',
				'default'     => true,
			),
		);

		/**
		 * Filter settings collection endpoint parameters.
		 *
		 * @since 3.0.5
		 *
		 * @param array $params Collection parameters.
		 */
		return apply_filters( 'dokan_kits_rest_settings_collection_params', $params );
	}

	/**
	 * Get parameters for single setting retrieval.
	 *
	 * @since 3.0.0
	 *
	 * @return array Single setting parameters.
	 */
	public function get_single_setting_params(): array {
		$params = array(
			'setting_name'  => array(
				'description' => __( 'Setting name in format: group.section.option', 'dokan-kits' ),
				'type'        => 'string',
				'required'    => true,
				'pattern'     => '^[a-zA-Z0-9._-]+',
			),
			'force_refresh' => array(
				'description' => __( 'Force refresh data from database bypassing cache.', 'dokan-kits' ),
				'type'        => 'boolean',
				'default'     => true,
			),
		);

		/**
		 * Filter single setting endpoint parameters.
		 *
		 * @since 3.0.5
		 *
		 * @param array $params Single setting parameters.
		 */
		return apply_filters( 'dokan_kits_rest_single_setting_params', $params );
	}

	/**
	 * Get parameters for bulk settings update.
	 *
	 * @since 3.0.0
	 *
	 * @return array Bulk update parameters.
	 */
	public function get_bulk_update_params(): array {
		$params = array(
			'settings' => array(
				'description' => __( 'Settings data to update as key-value pairs.', 'dokan-kits' ),
				'type'        => 'object',
				'required'    => true,
				'properties'  => array(
					'additionalProperties' => array(
						'type' => array( 'string', 'number', 'boolean', 'array', 'object' ),
					),
				),
			),
		);

		/**
		 * Filter bulk update endpoint parameters.
		 *
		 * @since 3.0.5
		 *
		 * @param array $params Bulk update parameters.
		 */
		return apply_filters( 'dokan_kits_rest_bulk_update_params', $params );
	}

	/**
	 * Get parameters for single setting update.
	 *
	 * @since 3.0.0
	 *
	 * @return array Single update parameters.
	 */
	public function get_single_update_params(): array {
		$params = array(
			'setting_name' => array(
				'description' => __( 'Setting name in format: group.section.option', 'dokan-kits' ),
				'type'        => 'string',
				'required'    => true,
				'pattern'     => '^[a-zA-Z0-9._-]+',
			),
			'value'        => array(
				'description' => __( 'Setting value to update.', 'dokan-kits' ),
				'type'        => array( 'string', 'number', 'boolean', 'array', 'object' ),
				'required'    => true,
			),
		);

		/**
		 * Filter single update endpoint parameters.
		 *
		 * @since 3.0.5
		 *
		 * @param array $params Single update parameters.
		 */
		return apply_filters( 'dokan_kits_rest_single_update_params', $params );
	}

	/**
	 * Get item schema for settings collection.
	 *
	 * @since 3.0.0
	 *
	 * @return array Item schema.
	 */
	public function get_item_schema() {
		if ( $this->schema ) {
			return $this->add_additional_fields_schema( $this->schema );
		}

		$schema = array(
			'$schema'    => 'http://json-schema.org/draft-04/schema#',
			'title'      => 'dokan_kits_settings_collection',
			'type'       => 'object',
			'properties' => array(
				'success' => array(
					'description' => __( 'Whether the request was successful.', 'dokan-kits' ),
					'type'        => 'boolean',
					'context'     => array( 'view', 'edit' ),
					'readonly'    => true,
				),
				'message' => array(
					'description' => __( 'Human-readable message describing the result.', 'dokan-kits' ),
					'type'        => 'string',
					'context'     => array( 'view', 'edit' ),
					'readonly'    => true,
				),
				'data'    => array(
					'description' => __( 'Settings collection data.', 'dokan-kits' ),
					'type'        => 'object',
					'context'     => array( 'view', 'edit' ),
					'properties'  => array(
						'settings' => array(
							'description'          => __( 'Settings as key-value pairs.', 'dokan-kits' ),
							'type'                 => 'object',
							'additionalProperties' => array(
								'type' => array( 'string', 'number', 'boolean', 'array', 'object' ),
							),
						),
						'count'    => array(
							'description' => __( 'Number of settings in the collection.', 'dokan-kits' ),
							'type'        => 'integer',
							'minimum'     => 0,
						),
					),
				),
			),
		);

		/**
		 * Filter settings collection schema.
		 *
		 * @since 3.0.5
		 *
		 * @param array $schema Settings collection schema.
		 */
		$schema = apply_filters( 'dokan_kits_rest_settings_collection_schema', $schema );

		$this->schema = $schema;

		return $this->add_additional_fields_schema( $this->schema );
	}

	/**
	 * Get schema for single setting response.
	 *
	 * @since 3.0.0
	 *
	 * @return array Single setting schema.
	 */
	public function get_single_setting_schema(): array {
		$schema = array(
			'$schema'    => 'http://json-schema.org/draft-04/schema#',
			'title'      => 'dokan_kits_single_setting',
			'type'       => 'object',
			'properties' => array(
				'success' => array(
					'description' => __( 'Whether the request was successful.', 'dokan-kits' ),
					'type'        => 'boolean',
					'readonly'    => true,
				),
				'message' => array(
					'description' => __( 'Human-readable message describing the result.', 'dokan-kits' ),
					'type'        => 'string',
					'readonly'    => true,
				),
				'data'    => array(
					'description' => __( 'Single setting data.', 'dokan-kits' ),
					'type'        => 'object',
					'properties'  => array(
						'name'  => array(
							'description' => __( 'Setting name.', 'dokan-kits' ),
							'type'        => 'string',
							'readonly'    => true,
						),
						'value' => array(
							'description' => __( 'Setting value.', 'dokan-kits' ),
							'type'        => array( 'string', 'number', 'boolean', 'array', 'object' ),
						),
					),
				),
			),
		);

		/**
		 * Filter single setting schema.
		 *
		 * @since 3.0.5
		 *
		 * @param array $schema Single setting schema.
		 */
		return apply_filters( 'dokan_kits_rest_single_setting_schema', $schema );
	}
}
