<?php

namespace Limb_Chatbot\Includes\Api\V1\Controllers;

use Limb_Chatbot\Includes\Data_Objects\Chatbot;
use Limb_Chatbot\Includes\Data_Objects\Widget;
use Limb_Chatbot\Includes\Exceptions\Error_Codes;
use Limb_Chatbot\Includes\Exceptions\Exception;
use Limb_Chatbot\Includes\Factories\Widget_Config_Factory;
use Limb_Chatbot\Includes\Interfaces\Widget_Callback_Interface;
use Limb_Chatbot\Includes\Repositories\Widget_Repository;
use Limb_Chatbot\Includes\Services\Helper;
use Limb_Chatbot\Includes\Services\User_Roles;
use Limb_Chatbot\Includes\Services\Widget_Service;
use Limb_Chatbot\Includes\Widgets\Data\Form_Widget_Config;
use WP_Error;
use WP_REST_Request;
use WP_REST_Server;

/**
 * Widgets Controller
 *
 * Handles widget callback requests for form submissions and interactions.
 *
 * @since 1.0.11
 */
class Widgets_Controller extends Rest_Controller {
	/**
	 * Supported location param key.
	 *
	 * @var string
	 * @since 2.0.0
	 */
	private const LOCATION_PARAM_SCREEN = 'screen';

	/**
	 * Supported location operator.
	 *
	 * @var string
	 * @since 2.0.0
	 */
	private const LOCATION_OPERATOR_EQUALS = '===';

	/**
	 * Supported location values.
	 *
	 * @var string[]
	 * @since 2.0.0
	 */
	private const LOCATION_ALLOWED_VALUES = [ 'home', 'new_chat', 'chatbot_icon' ];


	/**
	 * Route prefix.
	 *
	 * @var string
	 * @since 1.0.11
	 */
	protected $rest_base = 'widgets';

	/**
	 * Widget repository instance.
	 *
	 * @var Widget_Repository
	 * @since 2.0.0
	 */
	protected Widget_Repository $repository;

	/**
	 * Widget centralized service
	 *
	 * @var Widget_Service
	 */
	private Widget_Service $service;

	/**
	 * Widgets_Controller constructor.
	 *
	 * @since 2.0.0
	 */
	public function __construct() {
		$this->config_factory = new Widget_Config_Factory();
		$this->repository     = new Widget_Repository();
		$this->service        = new Widget_Service();
	}

	/**
	 * Register widget routes.
	 *
	 * Keeps only list, batch upsert, batch delete, and callback endpoints.
	 *
	 * @return void
	 * @since 2.0.0
	 */
	public function register_routes(): void {
		register_rest_route( $this->namespace, '/' . $this->rest_base, [
			[
				'methods'             => WP_REST_Server::READABLE,
				'callback'            => [ $this, 'get_items' ],
				'permission_callback' => [ $this, 'permission_callback' ],
				'args'                => $this->get_collection_params(),
			],
			[
				'methods'             => WP_REST_Server::DELETABLE,
				'callback'            => [ $this, 'batch_delete' ],
				'permission_callback' => [ $this, 'permission_callback' ],
				'args'                => [
					'id' => [
						'type'              => 'array',
						'required'          => true,
						'sanitize_callback' => [ $this, 'sanitize_widget_ids' ],
						'validate_callback' => [ $this, 'validate_widget_ids' ],
					],
				],
			],
		] );

		register_rest_route( $this->namespace, '/' . $this->rest_base . '/batch', [
			[
				'methods'             => WP_REST_Server::EDITABLE,
				'callback'            => [ $this, 'batch' ],
				'permission_callback' => [ $this, 'permission_callback' ],
				'args'                => [
					'items' => [
						'type'              => 'array',
						'required'          => true,
						'sanitize_callback' => [ $this, 'sanitize_batch_items' ],
					],
				],
			],
		] );

		// Route for default chatbot
		register_rest_route( $this->namespace, $this->rest_base . '/(?P<widget_uuid>[a-zA-Z0-9_-]+)/callback', [
			'methods'             => 'POST',
			'callback'            => [ $this, 'handle_widget_callback' ],
			'permission_callback' => [ $this, 'check_public_permission' ],
			'args'                => $this->get_default_args(),
		] );
	}

	/**
	 * Returns collection query parameters.
	 *
	 * @return array
	 * @since 2.0.0
	 */
	public function get_collection_params() {
		return array_merge( parent::get_collection_params(), [
			'orderby'   => [
				'type'    => 'string',
				'default' => 'order',
				'enum'    => [ 'id', 'type', 'order', 'created_at', 'updated_at' ],
			],
			'type'      => [
				'type' => 'string',
			],
			'published' => [
				'type' => 'integer',
			],
			'include'   => [
				'type'              => 'array',
				'sanitize_callback' => function ( $value ) {
					$res = [];
					foreach ( $value as $key => $item ) {
						$key         = sanitize_key( $key );
						$res[ $key ] = sanitize_text_field( $item );
					}

					return $res;
				},
				'validate_callback' => function ( $value ) {
					return ! empty( $value );
				}
			],
			'user_role' => [
				'type'              => 'string',
				'sanitize_callback' => [ $this, 'sanitize_user_role' ],
				'validate_callback' => [ $this, 'validate_user_role' ],
			],
		] );
	}

	/**
	 * Get default args for callback endpoint.
	 *
	 * @return array
	 * @since 2.0.0
	 */
	private function get_default_args() {
		return [
			'widget_uuid' => [
				'type'              => 'string',
				'required'          => true,
				'sanitize_callback' => 'sanitize_key',
				'validate_callback' => [ $this, 'validate_widget_uuid' ],
			],
			'params'      => [
				'type'              => 'object',
				'required'          => true,
				'sanitize_callback' => [ $this, 'sanitize_params' ],
				'validate_callback' => [ $this, 'validate_params' ],
			],
		];
	}

	/**
	 * Get widgets collection.
	 *
	 * @param  WP_REST_Request  $request  Incoming REST request.
	 *
	 * @return WP_Error|\WP_REST_Response
	 * @since 2.0.0
	 */
	public function get_items( $request ) {
		try {
			$items = $this->repository->get_items( $request->get_query_params() );

			return rest_ensure_response( $this->prepare_collection( $items, $request ) );
		} catch ( \Exception $e ) {
			Helper::log( $e, __METHOD__ );

			return Helper::get_wp_error( $e );
		}
	}

	/**
	 * Batch delete widgets.
	 *
	 * @param  WP_REST_Request  $request  Incoming REST request.
	 *
	 * @return WP_Error|\WP_REST_Response
	 * @since 2.0.0
	 */
	public function batch_delete( WP_REST_Request $request ) {
		try {
			$ids     = $request->get_param( 'id' );
			$deleted = $this->repository->batch_delete( $ids );

			return rest_ensure_response( [ 'deleted' => $deleted ] );
		} catch ( \Exception $e ) {
			Helper::log( $e, __METHOD__ );

			return Helper::get_wp_error( $e );
		}
	}

	/**
	 * Sanitize widget IDs payload.
	 *
	 * @param  mixed  $value  IDs payload.
	 *
	 * @return int[]
	 * @since 2.0.0
	 */
	public function sanitize_widget_ids( $value ): array {
		if ( ! is_array( $value ) ) {
			return [];
		}

		$ids = array_map( 'absint', $value );
		$ids = array_filter( array_unique( $ids ) );

		return array_values( $ids );
	}

	/**
	 * Validate widget IDs payload.
	 *
	 * @param  mixed  $value  IDs payload.
	 * @param  mixed  ...$_args  Additional callback arguments.
	 *
	 * @return bool
	 * @since 2.0.0
	 */
	public function validate_widget_ids( $value, ...$_args ): bool {
		if ( ! is_array( $value ) || empty( $value ) ) {
			return false;
		}

		foreach ( $value as $id ) {
			if ( ! is_numeric( $id ) || $id <= 0 ) {
				return false;
			}
		}

		return true;
	}

	/**
	 * Batch create/update widgets.
	 *
	 * @param  WP_REST_Request  $request  Incoming REST request.
	 *
	 * @return WP_Error|\WP_REST_Response
	 * @since 2.0.0
	 */
	public function batch( WP_REST_Request $request ) {
		try {
			$items = $request->get_param( 'items' );

			$validation = $this->validate_batch_items_payload( $items );
			if ( is_wp_error( $validation ) ) {
				return $validation;
			}

			return rest_ensure_response( $this->repository->batch( $items ) );
		} catch ( \Exception $e ) {
			Helper::log( $e, __METHOD__ );

			return Helper::get_wp_error( $e );
		}
	}

	/**
	 * Sanitize batch widget items payload.
	 *
	 * @param  mixed  $value  Raw batch items.
	 *
	 * @return array
	 * @since 2.0.0
	 */
	public function sanitize_batch_items( $value ): array {
		if ( ! is_array( $value ) ) {
			return [];
		}

		$items = [];
		foreach ( $value as $item ) {
			if ( ! is_array( $item ) ) {
				continue;
			}
			$items[] = $this->sanitize_widget_payload( $item );
		}

		return $items;
	}

	/**
	 * Sanitize widget payload for create/update.
	 *
	 * @param  array  $item  Raw item payload.
	 *
	 * @return array
	 * @since 2.0.0
	 */
	private function sanitize_widget_payload( array $item ): array {
		$type       = $this->sanitize_widget_type( $item['type'] ?? '' );
		$raw_config = $item['config'] ?? ( $item['data'] ?? [] );

		return [
			'id'         => isset( $item['id'] ) ? absint( $item['id'] ) : null,
			'uuid'       => $this->sanitize_widget_uuid( $item['uuid'] ?? '' ),
			'type'       => $type,
			'published'  => $this->sanitize_widget_published( $item['published'] ?? 1 ),
			'config'     => $this->sanitize_widget_config( $raw_config, $type ),
			'locations'  => $this->sanitize_widget_locations( $item['locations'] ?? [] ),
			'visibility' => $this->sanitize_widget_visibility( $item['visibility'] ?? [], $item['type'] ),
			'order'      => $this->sanitize_widget_order( $item['order'] ?? 0 ),
		];
	}

	/**
	 * Sanitize widget type.
	 *
	 * @param  mixed  $value  Type payload.
	 *
	 * @return string
	 * @since 2.0.0
	 */
	public function sanitize_widget_type( $value ): string {
		return sanitize_key( is_string( $value ) ? $value : '' );
	}

	/**
	 * Sanitize widget UUID.
	 *
	 * @param  mixed  $value  UUID payload.
	 *
	 * @return string
	 * @since 2.0.0
	 */
	public function sanitize_widget_uuid( $value ): string {
		return sanitize_text_field( is_string( $value ) ? $value : '' );
	}

	/**
	 * Sanitize widget published flag.
	 *
	 * @param  mixed  $value  Published payload.
	 *
	 * @return int
	 * @since 2.0.0
	 */
	public function sanitize_widget_published( $value ): int {
		return (int) (bool) $value;
	}

	/**
	 * Sanitize widget config based on widget type.
	 *
	 * @param  mixed  $value  Config payload.
	 * @param  string  $type  Widget type key.
	 *
	 * @return array
	 * @since 2.0.0
	 */
	public function sanitize_widget_config( $value, string $type ): array {
		if ( ! is_array( $value ) || ! $this->validate_widget_type( $type ) ) {
			return [];
		}

		$config = $this->config_factory->make( $type );

		return $config->sanitize( $value );
	}

	/**
	 * Validate widget type.
	 *
	 * @param  mixed  $value  Type payload.
	 * @param  mixed  ...$_args  Additional callback arguments.
	 *
	 * @return bool
	 * @since 2.0.0
	 */
	public function validate_widget_type( $value, ...$_args ): bool {
		return is_string( $value ) && in_array( $value, Widget_Config_Factory::SUPPORTED_TYPES, true );
	}

	/**
	 * Sanitize widget location rules.
	 *
	 * @param  mixed  $locations  Location groups payload.
	 *
	 * @return array
	 * @since 2.0.0
	 */
	public function sanitize_widget_locations( $locations ): array {
		$sanitized = [];
		if ( ! is_array( $locations ) ) {
			return $sanitized;
		}
		foreach ( $locations as $group ) {
			if ( ! is_array( $group ) ) {
				continue;
			}
			$group_rules = [];
			foreach ( $group as $rule ) {
				if ( ! is_array( $rule ) ) {
					continue;
				}
				$param    = sanitize_key( $rule['param'] ?? '' );
				$operator = sanitize_text_field( $rule['operator'] ?? '' );
				$value    = sanitize_key( $rule['value'] ?? '' );

				if ( self::LOCATION_PARAM_SCREEN !== $param ) {
					continue;
				}
				if ( self::LOCATION_OPERATOR_EQUALS !== $operator ) {
					continue;
				}
				if ( ! in_array( $value, self::LOCATION_ALLOWED_VALUES, true ) ) {
					continue;
				}

				$group_rules[] = [
					'param'    => self::LOCATION_PARAM_SCREEN,
					'operator' => self::LOCATION_OPERATOR_EQUALS,
					'value'    => $value,
				];
			}
			if ( ! empty( $group_rules ) ) {
				$sanitized[] = $group_rules;
			}
		}

		return $sanitized;
	}

	/**
	 * Sanitize widget visibility payload.
	 *
	 * @param  mixed  $visibility  Visibility payload.
	 * @param $type
	 *
	 * @return array
	 * @since 2.0.0
	 */
	public function sanitize_widget_visibility( $visibility, $type ): array {
		if ( $type == Form_Widget_Config::TYPE ) {
			return [ 'roles' => [ User_Roles::ROLE_EVERYONE ] ];
		}
		if ( ! is_array( $visibility ) || ! array_key_exists( 'roles',
				$visibility ) || ! is_array( $visibility['roles'] ) ) {
			return [];
		}

		$roles = [];
		foreach ( $visibility['roles'] as $role ) {
			if ( ! is_string( $role ) ) {
				continue;
			}
			$roles[] = sanitize_key( $role );
		}

		return [ 'roles' => array_values( array_unique( $roles ) ) ];
	}

	/**
	 * Sanitize widget order.
	 *
	 * @param  mixed  $value  Order payload.
	 *
	 * @return int
	 * @since 2.0.0
	 */
	public function sanitize_widget_order( $value ): int {
		return absint( $value );
	}

	/**
	 * Validate batch widget items payload.
	 *
	 * @param  mixed  $value  Batch items payload.
	 * @param  mixed  ...$_args  Additional callback arguments.
	 *
	 * @return bool|WP_Error
	 * @since 2.0.0
	 */
	public function validate_batch_items_payload( $value, ...$_args ) {
		if ( ! is_array( $value ) || empty( $value ) ) {
			return new WP_Error(
				Error_Codes::VALIDATION_INVALID_VALUE,
				__( 'Items must be a non-empty array.', 'limb-chatbot' ),
				[ 'status' => 400 ]
			);
		}

		foreach ( $value as $index => $item ) {
			if ( ! is_array( $item ) ) {
				return new WP_Error(
					Error_Codes::VALIDATION_INVALID_VALUE,
					sprintf(
					/* translators: %d is the item index in the batch payload. */
						__( 'Widget item at index %d must be an object.', 'limb-chatbot' ),
						(int) $index
					),
					[ 'status' => 400 ]
				);
			}
			$valid = $this->validate_widget_payload( $item, (int) $index );
			if ( is_wp_error( $valid ) ) {
				return $valid;
			}
		}

		return true;
	}

	/**
	 * Validate sanitized widget payload.
	 *
	 * @param  array  $item   Sanitized widget item.
	 * @param  int    $index  Position in the batch (for error messages).
	 *
	 * @return bool|WP_Error
	 * @since 2.0.0
	 */
	private function validate_widget_payload( array $item, int $index = 0 ) {
		$field_errors = $this->collect_widget_field_errors( $item );
		if ( ! empty( $field_errors ) ) {
			return $this->widget_validation_wp_error( $item, $index, $field_errors );
		}

		return true;
	}

	/**
	 * Build a WP_Error with `widget` context and `fields` map for API clients (dot paths: `config.*`, `locations`, etc.).
	 *
	 * @param  array<string, string>  $fields  Field key => message.
	 */
	private function widget_validation_wp_error( array $item, int $index, array $fields ): WP_Error {
		$type       = isset( $item['type'] ) ? (string) $item['type'] : '';
		$type_label = ucwords( str_replace( '_', ' ', $type ) ) ?: __( 'Unknown', 'limb-chatbot' );
		$first      = reset( $fields );
		$message    = is_string( $first ) && $first !== ''
			? $first
			: sprintf(
				/* translators: %s: human-readable widget type */
				__( 'The "%s" widget could not be saved.', 'limb-chatbot' ),
				$type_label
			);

		return new WP_Error(
			Error_Codes::VALIDATION_INVALID_VALUE,
			$message,
			[
				'status' => 400,
				'widget' => [
					'index' => $index,
					'uuid'  => isset( $item['uuid'] ) ? (string) $item['uuid'] : '',
					'type'  => $type,
					'label' => $type_label,
				],
				'fields' => $fields,
			]
		);
	}

	/**
	 * Merge config, locations, visibility, and order validation messages.
	 *
	 * @return array<string, string>
	 */
	private function collect_widget_field_errors( array $item ): array {
		$type = $item['type'] ?? '';
		if ( ! $this->validate_widget_type( $type ) ) {
			return [
				'type' => sprintf(
					/* translators: %s: raw type string from the request */
					__( 'Widget type "%s" is not supported.', 'limb-chatbot' ),
					is_string( $type ) ? $type : ''
				),
			];
		}

		$config_handler = $this->config_factory->make( $type );
		$fields         = [];
		foreach ( $config_handler->get_validation_errors( $item['config'] ?? [] ) as $key => $message ) {
			$fields[ 'config.' . $key ] = $message;
		}

		return array_merge(
			$fields,
			$this->get_widget_locations_validation_errors( $item['locations'] ?? [] ),
			$this->get_widget_visibility_validation_errors( $item['visibility'] ?? [], $type ),
			$this->get_widget_order_validation_errors( $item['order'] ?? 0 )
		);
	}

	/**
	 * @return array<string, string>
	 */
	private function get_widget_locations_validation_errors( $value ): array {
		if ( $this->validate_widget_locations( $value ) ) {
			return [];
		}

		return [
			'locations' => __( 'Display location rules are invalid. Each rule must use Screen with a supported value (Home, Start chat, or Chat icon).', 'limb-chatbot' ),
		];
	}

	/**
	 * @return array<string, string>
	 */
	private function get_widget_visibility_validation_errors( $value, string $type ): array {
		if ( $this->validate_widget_visibility( $value, $type ) ) {
			return [];
		}

		return [
			'visibility' => __( 'Select at least one valid user role for who can see this widget.', 'limb-chatbot' ),
		];
	}

	/**
	 * @return array<string, string>
	 */
	private function get_widget_order_validation_errors( $value ): array {
		if ( $this->validate_widget_order( $value ) ) {
			return [];
		}

		return [
			'order' => __( 'Sort order must be zero or a positive number.', 'limb-chatbot' ),
		];
	}

	/**
	 * Validate widget config based on widget type.
	 *
	 * @param  mixed  $value  Config payload.
	 * @param  string  $type  Widget type key.
	 *
	 * @return bool
	 * @since 2.0.0
	 */
	public function validate_widget_config( $value, string $type ): bool {
		if ( ! is_array( $value ) || ! $this->validate_widget_type( $type ) ) {
			return false;
		}

		$config = $this->config_factory->make( $type );

		return empty( $config->get_validation_errors( $value ) );
	}

	/**
	 * Validate widget location rules.
	 *
	 * @param  mixed  $value  Location rules payload.
	 * @param  mixed  ...$_args  Additional callback arguments.
	 *
	 * @return bool
	 * @since 2.0.0
	 */
	public function validate_widget_locations( $value, ...$_args ): bool {
		if ( ! is_array( $value ) ) {
			return false;
		}

		foreach ( $value as $group ) {
			if ( ! is_array( $group ) ) {
				return false;
			}

			foreach ( $group as $rule ) {
				if ( ! is_array( $rule ) ) {
					return false;
				}

				$param    = sanitize_key( $rule['param'] ?? '' );
				$operator = sanitize_text_field( $rule['operator'] ?? '' );
				$value    = sanitize_key( $rule['value'] ?? '' );

				if ( self::LOCATION_PARAM_SCREEN !== $param ) {
					return false;
				}
				if ( self::LOCATION_OPERATOR_EQUALS !== $operator ) {
					return false;
				}
				if ( ! in_array( $value, self::LOCATION_ALLOWED_VALUES, true ) ) {
					return false;
				}
			}
		}

		return true;
	}

	/**
	 * Validate widget visibility payload.
	 *
	 * @param  mixed  $value  Visibility payload.
	 * @param  mixed  ...$_args  Additional callback arguments.
	 *
	 * @return bool
	 * @since 2.0.0
	 */
	public function validate_widget_visibility( $value, ...$_args ): bool {
		if ( $_args[0] == Form_Widget_Config::TYPE ) {
			return true;
		}
		if ( ! is_array( $value ) || ! array_key_exists( 'roles', $value ) || ! is_array( $value['roles'] ) ) {
			return false;
		}
		foreach ( $value['roles'] as $role ) {
			if ( ! is_string( $role ) || ! User_Roles::instance()->get_by_key( $role ) ) {
				return false;
			}
		}

		return true;
	}

	/**
	 * Validate widget order.
	 *
	 * @param  mixed  $value  Order payload.
	 * @param  mixed  ...$_args  Additional callback arguments.
	 *
	 * @return bool
	 * @since 2.0.0
	 */
	public function validate_widget_order( $value, ...$_args ): bool {
		return is_numeric( $value ) && (int) $value >= 0;
	}

	/**
	 * Sanitize user role query argument.
	 *
	 * @param  mixed  $value  Role key.
	 *
	 * @return string
	 * @since 2.0.0
	 */
	public function sanitize_user_role( $value ): string {
		return sanitize_key( is_string( $value ) ? $value : '' );
	}

	/**
	 * Validate user role query argument.
	 *
	 * @param  mixed  $value  Role key.
	 * @param  mixed  ...$_args  Additional callback arguments.
	 *
	 * @return bool
	 * @since 2.0.0
	 */
	public function validate_user_role( $value, ...$_args ): bool {
		if ( ! is_string( $value ) || '' === $value ) {
			return true;
		}

		return null !== User_Roles::instance()->get_by_key( $value );
	}

	/**
	 * Validate widget ID.
	 *
	 * @param  string  $value  Widget ID to validate.
	 *
	 * @return WP_Error|bool True if valid, false otherwise.
	 * @since 1.0.11
	 */
	public function validate_widget_uuid( $value ) {
		$widget = Widget::find_by_uuid( $value );
		if ( ! $widget instanceof Widget ) {
			return new WP_Error( Error_Codes::NOT_FOUND, __( 'Widget not found', 'limb-chatbot' ) );
		}
		$config_handler = $this->config_factory->make( $widget->get_type() );

		return $config_handler instanceof Widget_Callback_Interface;
	}

	/**
	 * Sanitize callback params object.
	 *
	 * @param  array  $value  Params array.
	 *
	 * @return array
	 * @since 1.0.11
	 */
	public function sanitize_params( $value ): array {
		if ( ! is_array( $value ) ) {
			return [];
		}

		$sanitized = [];
		foreach ( $value as $key => $val ) {
			$clean_key = sanitize_key( $key );
			if ( is_string( $val ) ) {
				$sanitized[ $clean_key ] = sanitize_text_field( $val );
			} elseif ( is_array( $val ) ) {
				$sanitized[ $clean_key ] = wp_kses_post_deep( $val );
			} elseif ( is_bool( $val ) || is_int( $val ) || is_float( $val ) ) {
				$sanitized[ $clean_key ] = $val;
			} else {
				$sanitized[ $clean_key ] = '';
			}
		}

		return $sanitized;
	}

	/**
	 * Validate callback params object.
	 *
	 * @param  mixed  $value  Params to validate.
	 *
	 * @return bool|WP_Error
	 * @since 1.0.11
	 */
	public function validate_params( $value, WP_REST_Request $request ) {
		if ( ! is_array( $value ) && ! is_object( $value ) ) {
			return new WP_Error(
				'invalid_params_type',
				__( 'Params must be an object or array', 'limb-chatbot' ),
				[ 'status' => 400 ]
			);
		}

		return true;
	}

	/**
	 * Handle widget callback request.
	 *
	 * @param  WP_REST_Request  $request  REST request object.
	 *
	 * @return WP_Error|\WP_REST_Response
	 * @since 1.0.11
	 */
	public function handle_widget_callback( WP_REST_Request $request ) {
		try {
			$widget_uuid = $request->get_param( 'widget_uuid' );
			$widget      = Widget::find_by_uuid( $widget_uuid );
			$params      = $request->get_param( 'params' );

			$result = $this->service->execute_callback( $widget, $params );

			return rest_ensure_response( $result );
		} catch ( \Exception $e ) {
			Helper::log( $e, __METHOD__ );

			return Helper::get_wp_error( $e );
		}
	}

	/**
	 * Check public permission (allow all).
	 *
	 * @return bool Always true for widget callbacks.
	 * @since 1.0.11
	 */
	public function check_public_permission(): bool {
		return true;
	}

	/**
	 * Get widget item by ID from chatbot.
	 *
	 * @param  Chatbot  $chatbot  Chatbot instance.
	 * @param  string  $widget_id  Widget ID to find.
	 *
	 * @return Widget Widget payload or legacy object fallback.
	 * @throws Exception
	 * @since 1.0.11
	 */
	private function get_widget_item( Chatbot $chatbot, string $widget_id ) {
		$widget = $this->repository->get_item_by_uuid( $widget_id );
		if ( ! $widget instanceof Widget && is_numeric( $widget_id ) ) {
			$widget = $this->repository->get_item( (int) $widget_id );
		}

		if ( $widget instanceof Widget ) {
			return $widget;
		}

		throw new Exception( Error_Codes::NOT_FOUND, __( 'Widget not found', 'limb-chatbot' ) );
	}
}
