<?php

namespace Bricksforge\Api;

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

use WP_REST_Controller;

include_once(BRICKSFORGE_PATH . '/includes/api/FormsHelper.php');

/**
 * REST_API Handler
 */
class Bricksforge extends WP_REST_Controller
{
    private $forms_helper;

    // API Keys to encrypt/decrypt (External API Loops)
    private $keys_to_encrypt = ['username', 'password', 'token', 'bearerToken', 'bearerRefreshToken', 'customHeaders', 'dynamicTokenData', 'queryParams'];

    /**
     * Constructor
     */
    public function __construct()
    {
        $this->namespace = 'bricksforge/v1';
        $this->forms_helper = new FormsHelper();
    }

    /**
     * Register the routes
     *
     * @return void
     */
    public function register_routes()
    {
        register_rest_route(
            $this->namespace,
            '/get_shortcode_content',
            array(
                array(
                    'methods'             => \WP_REST_Server::CREATABLE,
                    'callback'            => array($this, 'get_shortcode_content'),
                    'permission_callback' => array($this, 'allowed'),
                )
            )
        );
        register_rest_route(
            $this->namespace,
            '/get_user_roles',
            array(
                array(
                    'methods'             => \WP_REST_Server::READABLE,
                    'callback'            => array($this, 'get_user_roles'),
                    'permission_callback' => array($this, 'allow_permission'),
                )
            )
        );
        register_rest_route(
            $this->namespace,
            '/get_permissions_roles',
            array(
                array(
                    'methods'             => \WP_REST_Server::READABLE,
                    'callback'            => array($this, 'get_permissions_roles'),
                    'permission_callback' => array($this, 'allow_permission'),
                )
            )
        );
        register_rest_route(
            $this->namespace,
            '/save_permissions_roles',
            array(
                array(
                    'methods'             => \WP_REST_Server::CREATABLE,
                    'callback'            => array($this, 'save_permissions_roles'),
                    'permission_callback' => array($this, 'allow_permission'),
                )
            )
        );
        register_rest_route(
            $this->namespace,
            '/remove_user_role',
            array(
                array(
                    'methods'             => \WP_REST_Server::CREATABLE,
                    'callback'            => array($this, 'remove_user_role'),
                    'permission_callback' => array($this, 'allow_permission'),
                )
            )
        );
        register_rest_route(
            $this->namespace,
            '/get_global_classes',
            array(
                array(
                    'methods'             => \WP_REST_Server::READABLE,
                    'callback'            => array($this, 'get_global_classes'),
                    'permission_callback' => array($this, 'allow_permission'),
                )
            )
        );
        register_rest_route(
            $this->namespace,
            '/save_global_classes',
            array(
                array(
                    'methods'             => \WP_REST_Server::CREATABLE,
                    'callback'            => array($this, 'save_global_classes'),
                    'permission_callback' => array($this, 'allow_permission'),
                )
            )
        );
        register_rest_route(
            $this->namespace,
            '/save_tools',
            array(
                array(
                    'methods'             => \WP_REST_Server::CREATABLE,
                    'callback'            => array($this, 'save_tools'),
                    'permission_callback' => array($this, 'allow_permission'),
                )
            )
        );
        register_rest_route(
            $this->namespace,
            '/save_elements',
            array(
                array(
                    'methods'             => \WP_REST_Server::CREATABLE,
                    'callback'            => array($this, 'save_elements'),
                    'permission_callback' => array($this, 'allow_permission'),
                )
            )
        );
        register_rest_route(
            $this->namespace,
            '/save_popups',
            array(
                array(
                    'methods'             => \WP_REST_Server::CREATABLE,
                    'callback'            => array($this, 'save_popups'),
                    'permission_callback' => array($this, 'allow_permission'),
                )
            )
        );
        register_rest_route(
            $this->namespace,
            '/save_maintenance',
            array(
                array(
                    'methods'             => \WP_REST_Server::CREATABLE,
                    'callback'            => array($this, 'save_maintenance'),
                    'permission_callback' => array($this, 'allow_permission'),
                )
            )
        );
        register_rest_route(
            $this->namespace,
            '/save_whitelabel',
            array(
                array(
                    'methods'             => \WP_REST_Server::CREATABLE,
                    'callback'            => array($this, 'save_whitelabel'),
                    'permission_callback' => array($this, 'allow_permission'),
                )
            )
        );
        register_rest_route(
            $this->namespace,
            '/save_panel',
            array(
                array(
                    'methods'             => \WP_REST_Server::CREATABLE,
                    'callback'            => array($this, 'save_panel'),
                    'permission_callback' => array($this, 'allow_permission'),
                )
            )
        );
        register_rest_route(
            $this->namespace,
            '/save_settings',
            array(
                array(
                    'methods'             => \WP_REST_Server::CREATABLE,
                    'callback'            => array($this, 'save_settings'),
                    'permission_callback' => array($this, 'allow_permission'),
                )
            )
        );
        register_rest_route(
            $this->namespace,
            '/save_option',
            array(
                array(
                    'methods'             => \WP_REST_Server::CREATABLE,
                    'callback'            => array($this, 'save_option'),
                    'permission_callback' => array($this, 'allow_permission'),
                )
            )
        );
        register_rest_route(
            $this->namespace,
            '/get_option',
            array(
                array(
                    'methods'             => \WP_REST_Server::READABLE,
                    'callback'            => array($this, 'get_option'),
                    'permission_callback' => array($this, 'allow_permission'),
                )
            )
        );
        register_rest_route(
            $this->namespace,
            '/reset_to_default',
            array(
                array(
                    'methods'             => \WP_REST_Server::CREATABLE,
                    'callback'            => array($this, 'reset_to_default'),
                    'permission_callback' => array($this, 'allow_permission'),
                )
            )
        );
        register_rest_route(
            $this->namespace,
            '/export_settings',
            array(
                array(
                    'methods'             => \WP_REST_Server::CREATABLE,
                    'callback'            => array($this, 'export_settings'),
                    'permission_callback' => array($this, 'allow_permission'),
                )
            )
        );
        register_rest_route(
            $this->namespace,
            '/import_settings',
            array(
                array(
                    'methods'             => \WP_REST_Server::CREATABLE,
                    'callback'            => array($this, 'import_settings'),
                    'permission_callback' => array($this, 'allow_permission'),
                )
            )
        );
        register_rest_route(
            $this->namespace,
            '/form_submit',
            array(
                array(
                    'methods'             => \WP_REST_Server::CREATABLE,
                    'callback'            => array(new \Bricksforge\ProForms\Actions\Init, 'form_submit'),
                    'permission_callback' => array($this, 'allowed'),
                )
            )
        );
        register_rest_route(
            $this->namespace,
            '/render_form_data',
            array(
                array(
                    'methods'             => \WP_REST_Server::CREATABLE,
                    'callback'            => array($this, 'render_form_data'),
                    'permission_callback' => array($this, 'allowed'),
                )
            )
        );
        register_rest_route(
            $this->namespace,
            '/create_global_classes_backup',
            array(
                array(
                    'methods'             => \WP_REST_Server::CREATABLE,
                    'callback'            => array($this, 'create_global_classes_backup'),
                    'permission_callback' => array($this, 'allow_permission'),
                )
            )
        );
        register_rest_route(
            $this->namespace,
            '/restore_global_classes_backup',
            array(
                array(
                    'methods'             => \WP_REST_Server::CREATABLE,
                    'callback'            => array($this, 'restore_global_classes_backup'),
                    'permission_callback' => array($this, 'allow_permission'),
                )
            )
        );
        register_rest_route(
            $this->namespace,
            '/submissions',
            array(
                array(
                    'methods'             => \WP_REST_Server::READABLE,
                    'callback'            => array($this, 'get_submissions'),
                    'permission_callback' => array($this, 'allow_permission_lite'),
                )
            )
        );
        register_rest_route(
            $this->namespace,
            '/delete_submissions',
            array(
                array(
                    'methods'             => \WP_REST_Server::CREATABLE,
                    'callback'            => array($this, 'delete_submissions'),
                    'permission_callback' => array($this, 'allow_permission_lite'),
                )
            )
        );
        register_rest_route(
            $this->namespace,
            '/delete_submissions_table',
            array(
                array(
                    'methods'             => \WP_REST_Server::CREATABLE,
                    'callback'            => array($this, 'delete_submissions_table'),
                    'permission_callback' => array($this, 'allow_permission_lite'),
                )
            )
        );
        register_rest_route(
            $this->namespace,
            '/get_submissions_display_settings',
            array(
                array(
                    'methods'             => \WP_REST_Server::READABLE,
                    'callback'            => array($this, 'get_submissions_display_settings'),
                    'permission_callback' => array($this, 'allow_permission_lite'),
                )
            )
        );
        register_rest_route(
            $this->namespace,
            '/get_unread_submissions',
            array(
                array(
                    'methods'             => \WP_REST_Server::READABLE,
                    'callback'            => array($this, 'get_unread_submissions'),
                    'permission_callback' => array($this, 'allow_permission_lite'),
                )
            )
        );
        register_rest_route(
            $this->namespace,
            '/get_form_title',
            array(
                array(
                    'methods'             => \WP_REST_Server::READABLE,
                    'callback'            => array($this, 'get_form_title_by_id'),
                    'permission_callback' => array($this, 'allow_permission_lite'),
                )
            )
        );
        register_rest_route(
            $this->namespace,
            '/render_dynamic_data',
            array(
                array(
                    'methods'             => \WP_REST_Server::READABLE,
                    'callback'            => array($this, 'render_dynamic_data'),
                    'permission_callback' => array($this, 'allowed'),
                )
            )
        );

        // Register rest root for an openai api call
        register_rest_route(
            $this->namespace,
            '/ai',
            array(
                array(
                    'methods'             => \WP_REST_Server::CREATABLE,
                    'callback'            => array($this, 'ai_task'),
                    'permission_callback' => array($this, 'allow_permission_lite'),
                )
            )
        );

        // Form File Uploads
        register_rest_route(
            $this->namespace,
            '/form_file_upload',
            array(
                array(
                    'methods'             => \WP_REST_Server::CREATABLE,
                    'callback'            => array($this, 'form_file_upload'),
                    'permission_callback' => array($this, 'allowed'),
                )
            )
        );
        register_rest_route(
            $this->namespace,
            '/form_file_load_initial_files',
            array(
                array(
                    'methods'             => \WP_REST_Server::CREATABLE,
                    'callback'            => array($this, 'form_file_load_initial_files'),
                    'permission_callback' => array($this, 'allowed'),
                )
            )
        );
        register_rest_route(
            $this->namespace,
            '/clear_external_api_cache',
            array(
                array(
                    'methods'             => \WP_REST_Server::CREATABLE,
                    'callback'            => array($this, 'clear_external_api_cache'),
                    'permission_callback' => array($this, 'allow_permission'),
                )
            )
        );
        // utils/encrypt
        register_rest_route(
            $this->namespace,
            '/utils/encrypt',
            array(
                array(
                    'methods'             => \WP_REST_Server::CREATABLE,
                    'callback'            => array($this, 'encrypt'),
                    'permission_callback' => array($this, 'allow_permission'),
                )
            )
        );

        // utils/decrypt
        register_rest_route(
            $this->namespace,
            '/utils/decrypt',
            array(
                array(
                    'methods'             => \WP_REST_Server::CREATABLE,
                    'callback'            => array($this, 'decrypt'),
                    'permission_callback' => array($this, 'allow_permission'),
                )
            )
        );

        // utils/encrypt_rest_api_auth_data
        register_rest_route(
            $this->namespace,
            '/utils/encrypt_rest_api_auth_data',
            array(
                array(
                    'methods'             => \WP_REST_Server::CREATABLE,
                    'callback'            => array($this, 'encrypt_rest_api_auth_data'),
                    'permission_callback' => array($this, 'allow_permission'),
                )
            )
        );

        // utils/decrypt_rest_api_auth_data
        register_rest_route(
            $this->namespace,
            '/utils/decrypt_rest_api_auth_data',
            array(
                array(
                    'methods'             => \WP_REST_Server::CREATABLE,
                    'callback'            => array($this, 'decrypt_rest_api_auth_data'),
                    'permission_callback' => array($this, 'allow_permission'),
                )
            )
        );

        // fetch_api
        register_rest_route(
            $this->namespace,
            '/fetch_api',
            array(
                array(
                    'methods'             => \WP_REST_Server::CREATABLE,
                    'callback'            => array($this, 'fetch_api'),
                    'permission_callback' => array($this, 'allow_permission'),
                )
            )
        );
        // query_builder_load_items
        register_rest_route(
            $this->namespace,
            '/query_builder_load_items',
            array(
                array(
                    'methods'             => \WP_REST_Server::CREATABLE,
                    'callback'            => array($this, 'query_builder_load_items'),
                    'permission_callback' => array($this, 'allowed'),
                )
            )
        );

        // CPT Sync - Manual Sync Endpoint
        register_rest_route(
            $this->namespace,
            '/cpt-sync/manual',
            array(
                array(
                    'methods'             => \WP_REST_Server::CREATABLE,
                    'callback'            => array($this, 'handle_manual_cpt_sync'),
                    'permission_callback' => array($this, 'allow_permission'),
                )
            )
        );

        // CPT Sync - Progress Endpoint
        register_rest_route(
            $this->namespace,
            '/cpt-sync/progress/(?P<item_id>[a-zA-Z0-9-]+)',
            array(
                array(
                    'methods'             => \WP_REST_Server::READABLE,
                    'callback'            => array($this, 'handle_cpt_sync_progress'),
                    'permission_callback' => array($this, 'allow_permission'),
                )
            )
        );

        // CPT Sync - Delete CPTs and Data for specific item Endpoint (must be before webhook route)
        register_rest_route(
            $this->namespace,
            '/cpt-sync/delete-item/(?P<item_id>[^/]+)',
            array(
                array(
                    'methods'             => \WP_REST_Server::CREATABLE,
                    'callback'            => array($this, 'handle_delete_item_cpts'),
                    'permission_callback' => array($this, 'allow_permission'),
                )
            )
        );

        // CPT Sync - Webhook Endpoint
        register_rest_route(
            $this->namespace,
            '/cpt-sync/(?P<item_id>[a-zA-Z0-9-]+)',
            array(
                array(
                    'methods'             => \WP_REST_Server::CREATABLE,
                    'callback'            => array($this, 'handle_webhook_cpt_sync'),
                    'permission_callback' => '__return_true',  // Webhook auth via secret
                ),
                array(
                    'methods'             => \WP_REST_Server::READABLE,
                    'callback'            => array($this, 'handle_webhook_cpt_sync'),
                    'permission_callback' => '__return_true',  // Webhook auth via secret
                )
            )
        );

        // Stripe Webhook Endpoint
        register_rest_route(
            $this->namespace,
            '/stripe-webhook',
            array(
                array(
                    'methods'             => \WP_REST_Server::CREATABLE,
                    'callback'            => array($this, 'handle_stripe_webhook'),
                    'permission_callback' => '__return_true',  // Webhook auth via Stripe signature verification
                )
            )
        );

        // Mollie Webhook Endpoint
        register_rest_route(
            $this->namespace,
            '/mollie-webhook',
            array(
                array(
                    'methods'             => \WP_REST_Server::CREATABLE,
                    'callback'            => array($this, 'handle_mollie_webhook'),
                    'permission_callback' => '__return_true',  // Webhook auth via Mollie signature verification
                )
            )
        );

        // Generic Payment Status Check Endpoint
        register_rest_route(
            $this->namespace,
            '/payment-status/(?P<provider>[a-zA-Z0-9_]+)/(?P<session_identifier>[a-zA-Z0-9_]+)',
            array(
                array(
                    'methods'             => \WP_REST_Server::READABLE,
                    'callback'            => array($this, 'check_payment_status'),
                    'permission_callback' => '__return_true',  // Public endpoint, but requires valid session identifier
                )
            )
        );

        // Stripe Payment Status Check Endpoint (Backwards Compatibility)
        register_rest_route(
            $this->namespace,
            '/stripe-status/(?P<session_identifier>[a-zA-Z0-9_]+)',
            array(
                array(
                    'methods'             => \WP_REST_Server::READABLE,
                    'callback'            => array($this, 'check_stripe_status'),
                    'permission_callback' => '__return_true',  // Public endpoint, but requires valid session identifier
                )
            )
        );
    }


    /**
     * Get Shortcode Content
     *
     * @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_shortcode_content($request)
    {
        $response = $request->get_body();

        if (!$response) {
            return;
        }

        $output = "";

        $template_id = $response;

        /**
         * Workaround: Bricks currently does not load styles if the template is loaded outside.
         */
        $elements = get_post_meta($template_id, BRICKS_DB_PAGE_CONTENT, true); #
        $inline_css = \Bricks\Templates::generate_inline_css($template_id, $elements);

        // NOTE: Not the perfect solution – but currently the way to go.
        $output .= "<style id=\"bricks-inline-css-template-{$template_id}\">{$inline_css}</style>";
        $output .= \Bricks\Frontend::render_data($elements);

        return json_encode($output);
    }

    /**
     * Get User Roles
     *
     * @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_user_roles($request)
    {
        global $wp_roles;

        $all_roles = $wp_roles->roles;
        $editable_roles = apply_filters('editable_roles', $all_roles);

        $response = rest_ensure_response($editable_roles);

        return $response;
    }

    /**
     * Get Permission Roles
     *
     * @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_permissions_roles($request)
    {
        $response = rest_ensure_response(get_option('brf_permissions_roles'));
        return $response;
    }

    /**
     * Save Permission Roles
     *
     * @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 save_permissions_roles($request)
    {
        global $wp_roles;

        $data = $request->get_body();

        $roles = json_decode($data);


        if (!$roles || !is_array($roles)) {
            return false;
        }

        update_option('brf_permissions_roles', $roles, 'no');

        return true;
    }

    /**
     * Removes a custom User Role
     *
     * @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 remove_user_role($request)
    {
        $role = $request->get_body();
        $role_data = json_decode($role);

        // Handle both string and object formats
        $role_value = is_string($role_data) ? $role_data : (is_object($role_data) && isset($role_data->value) ? $role_data->value : $role_data);

        if ($role_value != 'administrator' && $role_value != 'editor' && $role_value != 'author' && $role_value != 'contributor' && $role_value != 'subscriber') {
            remove_role($role_value);
        }
        return true;
    }

    /**
     * Get Global Classes
     *
     * @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_global_classes($request)
    {
        $response = rest_ensure_response(get_option('brf_global_classes'));
        return $response;
    }

    /**
     * Save Global Classes
     *
     * @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 save_global_classes($request)
    {

        $data = $request->get_body();
        $categories = json_decode($data)[0];
        $activated = json_decode($data)[1];
        $excluded_classes = isset(json_decode($data)[2]) ? json_decode($data)[2] : [];

        if (!isset($categories)) {
            return false;
        }

        if (count($categories) === 0) {
            update_option('brf_global_classes', $categories, "no");
            return false;
        }

        if (!isset($activated)) {
            return false;
        }

        if (!is_array($categories)) {
            return false;
        }

        update_option('brf_global_classes_activated', $activated);

        update_option('brf_global_classes_excluded', $excluded_classes);

        update_option('brf_global_classes', $categories, "no");

        $global_classes = get_option('bricks_global_classes') ? get_option('bricks_global_classes') : [];
        $global_classes_locked = get_option('bricks_global_classes_locked') ? get_option('bricks_global_classes_locked') : [];

        foreach ($categories as $category) {

            foreach ($category->classes as $class) {
                if (($key = array_search($class, $global_classes_locked)) !== false) {
                    unset($global_classes_locked[$key]);
                }

                foreach ($global_classes as $key => $row) {
                    if (isset($row["source"]) && $row["source"] === 'bricksforge') {
                        unset($global_classes[$key]);
                    }
                }
            }
        }

        $global_classes = array_values($global_classes);
        $global_classes_locked = array_values($global_classes_locked);

        foreach ($categories as $category) {

            $is_active = isset($category->active) ? $category->active : true;

            if (!$is_active) {
                continue;
            }

            if ($category->classes && !empty($category->classes)) {

                // Stop here if global classes are not activated
                if (!get_option('brf_global_classes_activated') || get_option('brf_global_classes_activated') == false) {
                    update_option('bricks_global_classes', $global_classes);
                    update_option('bricks_global_classes_locked', $global_classes_locked);
                    return false;
                }

                foreach ($category->classes as $class) {
                    array_push($global_classes, [
                        "id"       => $class,
                        "name"     => $class,
                        "settings" => array(),
                        "source"   => "bricksforge"
                    ]);

                    array_push($global_classes_locked, $class);
                }
            }
        }
        $global_classes = array_map("unserialize", array_unique(array_map("serialize", $global_classes)));
        $global_classes_locked = array_unique($global_classes_locked);
        $global_classes = array_values($global_classes);
        $global_classes_locked = array_values($global_classes_locked);

        update_option('bricks_global_classes', $global_classes);
        update_option('bricks_global_classes_locked', $global_classes_locked);

        $success = $this->render_css_files($categories);

        return $success === true;
    }

    /**
     * Save Tools
     *
     * @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 save_tools($request)
    {
        global $wp_roles;

        $data = $request->get_body();

        $tools = json_decode($data);

        if (!isset($tools) || !is_array($tools)) {
            return false;
        }

        update_option('brf_activated_tools', $tools);

        return true;
    }

    /**
     * Save Elements
     *
     * @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 save_elements($request)
    {
        global $wp_roles;

        $data = $request->get_body();

        $elements = json_decode($data);

        if (!isset($elements) || !is_array($elements)) {
            return false;
        }

        $elements = $this->handle_element_settings($elements);

        update_option('brf_activated_elements', $elements);

        return true;
    }

    /**
     * Save Popups
     *
     * @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 save_popups($request)
    {
        global $wp_roles;

        $data = $request->get_body();

        $elements = json_decode($data);

        if (is_null($elements) || !is_array($elements)) {
            return false;
        }

        update_option('brf_popups', $elements);

        return true;
    }

    /**
     * Save Maintenance Settings
     *
     * @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 save_maintenance($request)
    {
        global $wp_roles;

        $data = $request->get_body();

        $settings = json_decode($data);

        if (!$settings || !is_array($settings)) {
            return false;
        }

        update_option('brf_maintenance', $settings);

        return true;
    }

    /**
     * Save White Label Settings
     *
     * @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 save_whitelabel($request)
    {
        global $wp_roles;

        $data = $request->get_body();

        $settings = json_decode($data);

        if (!$settings || !is_array($settings)) {
            return false;
        }

        update_option('brf_whitelabel', $settings);

        return true;
    }

    /**
     * Save Panel Settings
     *
     * @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 save_panel($request)
    {
        global $wp_roles;

        $data = $request->get_body();

        $settings = json_decode($data);

        if (is_null($settings) || !is_array($settings)) {
            return false;
        }

        update_option('brf_panel', $settings, "no");

        return true;
    }

    /**
     * Save Settings
     *
     * @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 save_settings($request)
    {
        $data = $request->get_body();

        $settings = json_decode($data);

        update_option('brf_settings', $settings);

        return $settings;
    }

    /**
     * Save Option
     *
     * @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 save_option($request)
    {
        // Retrieve data from the body of the request
        $body = $request->get_body();
        $data = json_decode($body, true); // Use associative array

        // Check if we have valid data and enough elements for key-value pair
        if (is_array($data) && count($data) >= 2) {
            // Sanitize the data
            $key = sanitize_text_field($data[0]);
            $value = $data[1];
            $autoload = isset($data[2]) && $data[2] === 'no' ? "no" : "yes";

            // CRITICAL: For brf_external_api_loops, ensure we're working with arrays, not objects
            // This prevents serialization issues
            if ($key === 'brf_external_api_loops' && is_array($value)) {
                // Deep convert all objects to arrays to ensure consistent serialization
                $value = json_decode(json_encode($value), true);
            }


            // If key is "brf_tool_settings", we need some encryption for sensitive data
            if ($key === 'brf_tool_settings') {
                $value = $this->handle_tool_settings($value);
            }

            // If key is "brf_external_api_loops", trigger CPT rewrite flush
            if ($key === 'brf_external_api_loops') {
                // Set transient to trigger flush on next page load
                set_transient('brf_cpt_sync_needs_flush', true, 60);
            }

            // Get old value for comparison
            $old_value = get_option($key);
            // Convert stdClass to array if needed (handles both object and array of objects)
            if ($old_value !== false && $old_value !== null) {
                $old_value = json_decode(json_encode($old_value), true);
            }

            // Use standard WordPress update_option
            // Clear cache before updating to ensure we're working with fresh data
            wp_cache_delete($key, 'options');
            wp_cache_delete('alloptions', 'options');


            // Use standard WordPress update_option
            $update_status = update_option($key, $value, $autoload);

            // If update_option returned false, it means WordPress thinks the values are identical
            // But we still want to save, so use direct DB update
            if (!$update_status && $key === 'brf_external_api_loops') {
                global $wpdb;

                $serialized_value = maybe_serialize($value);
                $result = $wpdb->query($wpdb->prepare(
                    "UPDATE {$wpdb->options} SET option_value = %s WHERE option_name = %s",
                    $serialized_value,
                    $key
                ));

                if ($result !== false) {
                    $update_status = true;
                }
            }


            // Clear cache again after saving to ensure fresh data
            wp_cache_delete($key, 'options');
            wp_cache_delete('alloptions', 'options');
            wp_cache_flush();

            // Check if update was successful
            if (!$update_status) {
                return rest_ensure_response('No update processed');
            }
        } else {
            return new \WP_Error('invalid_data', 'Invalid data received', array('status' => 400));
        }

        return rest_ensure_response('Option successfully updated');
    }

    /**
     * Auto-save an external API loop item if it doesn't exist
     * This is used when fetching data for a newly created item
     *
     * @param array $item_data The item data to save
     * @return bool True if successful, false otherwise
     */
    private function auto_save_item($item_data)
    {
        if (empty($item_data) || !is_array($item_data)) {
            return false;
        }

        // Get existing items
        $items = get_option('brf_external_api_loops', []);

        // Convert to array format if needed
        if (!is_array($items)) {
            $items = json_decode(json_encode($items), true);
        }

        if (!is_array($items)) {
            $items = [];
        }

        // Check if item already exists
        $item_id = isset($item_data['id']) ? $item_data['id'] : null;
        if (empty($item_id)) {
            return false;
        }

        $item_exists = false;
        foreach ($items as $existing_item) {
            $existing_id = is_array($existing_item) ? (isset($existing_item['id']) ? $existing_item['id'] : null) : (isset($existing_item->id) ? $existing_item->id : null);
            if ($existing_id === $item_id) {
                $item_exists = true;
                break;
            }
        }

        // If item doesn't exist, add it
        if (!$item_exists) {
            // Ensure item_data is in array format
            $item_to_save = json_decode(json_encode($item_data), true);

            // Remove temporary properties that shouldn't be persisted
            unset($item_to_save['loading']);
            unset($item_to_save['syncing']);
            unset($item_to_save['syncingStep']);

            // Encrypt sensitive data if Utils class is available
            if (class_exists('\Bricksforge\Api\Utils')) {
                $utils = new \Bricksforge\Api\Utils();

                // Encrypt main data
                $this->encrypt_data($item_to_save, $utils);

                // Encrypt auth data if present
                if (isset($item_to_save['auth']) && !empty($item_to_save['auth'])) {
                    $this->encrypt_data($item_to_save['auth'], $utils);
                }
            }

            // Add item to array
            $items[] = $item_to_save;

            // Save to database
            wp_cache_delete('brf_external_api_loops', 'options');
            wp_cache_delete('alloptions', 'options');

            $result = update_option('brf_external_api_loops', $items);

            // If update_option failed, try direct DB update
            if (!$result) {
                global $wpdb;
                $serialized_value = maybe_serialize($items);
                $wpdb->query($wpdb->prepare(
                    "UPDATE {$wpdb->options} SET option_value = %s WHERE option_name = %s",
                    $serialized_value,
                    'brf_external_api_loops'
                ));
            }

            // Clear cache after saving
            wp_cache_delete('brf_external_api_loops', 'options');
            wp_cache_delete('alloptions', 'options');

            // Trigger CPT rewrite flush if needed
            set_transient('brf_cpt_sync_needs_flush', true, 60);

            return true;
        }

        return false;
    }

    private function handle_tool_settings($value)
    {
        $new_value = $value;

        $keys_to_encrypt = [
            14 => 'apiKey',  // Key ID => Key field name
        ];

        foreach ($keys_to_encrypt as $key_id => $key_field) {
            $current_key = null;
            $encrypted_key = null;

            foreach ($value as $item) {
                if ($item->id === $key_id) {
                    $current_key = isset($item->settings->$key_field) ? $item->settings->$key_field : null;
                    break;
                }
            }

            if ($current_key) {
                $stored_value = get_option('brf_tool_settings');

                $stored_key = null;

                if (is_array($stored_value)) {
                    foreach ($stored_value as $item) {
                        if ($item->id === $key_id) {
                            $stored_key = isset($item->settings->$key_field) ? $item->settings->$key_field : null;
                            break;
                        }
                    }
                }

                if ($stored_key === $current_key) {
                    // Key has not changed, no need to re-encrypt
                    $encrypted_key = $current_key;
                }

                if (!$encrypted_key) {
                    $encrypted_key = $this->encryptIfKeyChanged($current_key, $stored_key);
                }
            }

            // Update the encrypted key in the $new_value array
            foreach ($new_value as &$item) {
                if ($item->id === $key_id && isset($item->settings->$key_field)) {
                    $item->settings->$key_field = $encrypted_key;
                    break;
                }
            }
        }

        return $new_value;
    }

    private function handle_element_settings($value)
    {
        $new_value = $value;

        $keys_to_encrypt = [
            5 => ['hCaptchaKey', 'hCaptchaSecret', 'turnstileKey', 'turnstileSecret', 'stripePublishableKey', 'stripeSecretKey', 'stripeWebhookSecret', 'googleMapsApiKey'],
            1 => ['kitID']
        ];

        foreach ($keys_to_encrypt as $key_id => $key_fields) {
            $keyFieldUpdated = false;

            foreach ($key_fields as $key_field) {
                $current_key = null;
                $encrypted_key = null;

                foreach ($value as $item) {
                    if ($item->id === $key_id) {
                        $current_key = isset($item->settings->$key_field) ? $item->settings->$key_field : null;
                        break;
                    }
                }

                if ($current_key) {
                    $stored_value = get_option('brf_activated_elements');

                    $stored_key = null;

                    if (is_array($stored_value)) {
                        foreach ($stored_value as $item) {
                            if ($item->id === $key_id) {
                                $stored_key = isset($item->settings->$key_field) ? $item->settings->$key_field : null;
                                break;
                            }
                        }
                    }

                    if ($stored_key === $current_key) {
                        // Key has not changed, no need to re-encrypt
                        $encrypted_key = $current_key;
                    }

                    if (!$encrypted_key) {
                        $encrypted_key = $this->encryptIfKeyChanged($current_key, $stored_key);
                    }
                }

                // Update the encrypted key in the $new_value array
                foreach ($new_value as $index => $item) {
                    if ($item->id === $key_id && isset($item->settings->$key_field)) {
                        $new_value[$index]->settings->$key_field = $encrypted_key;
                        $keyFieldUpdated = true;
                    }
                }
            }

            if (!$keyFieldUpdated) {
                // If none of the key fields were updated, reset the encrypted values
                foreach ($new_value as $index => $item) {
                    if ($item->id === $key_id) {
                        foreach ($key_fields as $key_field) {
                            $new_value[$index]->settings->$key_field = null;
                        }
                    }
                }
            }
        }

        return $new_value;
    }


    private function encryptIfKeyChanged($current_key, $stored_key)
    {
        if ($current_key !== $stored_key) {
            $utils = new \Bricksforge\Api\Utils();
            return $utils->encrypt($current_key);
        }

        return $current_key;
    }

    /**
     * Get Option
     *
     * @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_option($request)
    {
        $key = $request->get_param('_key');

        // For brf_external_api_loops, read directly from DB to bypass cache
        // This ensures we always get the latest data
        if ($key === 'brf_external_api_loops') {
            global $wpdb;

            // Clear cache first
            wp_cache_delete($key, 'options');
            wp_cache_delete('alloptions', 'options');

            // Read directly from DB
            $db_value = $wpdb->get_var($wpdb->prepare(
                "SELECT option_value FROM {$wpdb->options} WHERE option_name = %s",
                $key
            ));

            if ($db_value !== null) {
                $value = maybe_unserialize($db_value);
                return $value;
            }

            // Fallback to get_option if DB read fails
            return get_option($key, []);
        }

        return get_option($key);
    }

    /**
     * Reset to default
     *
     * @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 reset_to_default($request)
    {
        delete_option('brf_permissions_roles');
        delete_option('brf_global_classes_activated');
        delete_option('brf_global_classes_excluded');
        delete_option('brf_global_classes');
        delete_option('brf_activated_tools');
        delete_option('brf_activated_elements');
        delete_option('brf_popups');
        delete_option('brf_maintenance');
        delete_option('brf_whitelabel');
        delete_option('brf_panel');
        delete_option('brf_panel_nodes');
        delete_option('brf_tool_settings');
        delete_option('brf_terminal_history');
        delete_option('brf_backend_designer');
        delete_option('brf_unread_submissions');
        delete_option('brf_email_designer_data');
        delete_option('brf_email_designer_themes');
        delete_option('brf_page_transitions');
        delete_option('brf_external_api_loops');
    }

    /**
     * Export all settings
     * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
     */
    public function export_settings()
    {
        $current_time = current_time('Y-m-d-H-i-s');
        $options = array(
            'brf_permissions_roles'        => get_option('brf_permissions_roles'),
            'brf_global_classes_activated' => get_option('brf_global_classes_activated'),
            'brf_global_classes_excluded'  => get_option('brf_global_classes_excluded'),
            'brf_global_classes'           => get_option('brf_global_classes'),
            'brf_activated_tools'          => get_option('brf_activated_tools'),
            'brf_activated_elements'       => get_option('brf_activated_elements'),
            'brf_popups'                   => get_option('brf_popups'),
            'brf_maintenance'              => get_option('brf_maintenance'),
            'brf_whitelabel'               => get_option('brf_whitelabel'),
            'brf_panel'                    => get_option('brf_panel'),
            'brf_panel_nodes'              => get_option('brf_panel_nodes'),
            'brf_tool_settings'            => get_option('brf_tool_settings'),
            'brf_terminal_history'         => get_option('brf_terminal_history'),
            'brf_backend_designer'         => get_option('brf_backend_designer'),
            'brf_email_designer_data'      => get_option('brf_email_designer_data'),
            'brf_email_designer_themes'    => get_option('brf_email_designer_themes'),
            'brf_page_transitions'         => get_option('brf_page_transitions'),
            'brf_external_api_loops'       => get_option('brf_external_api_loops'),
        );
        $json_data = json_encode($options);
        $file_name = 'bricksforge' . $current_time . '.json';

        // Set the appropriate headers
        header('Content-Type: application/json');
        header('Content-Disposition: attachment; filename="' . $file_name . '"');
        header('Content-Length: ' . strlen($json_data));

        // Send the file to the client
        echo $json_data;
        exit;
    }

    /**
     * Import settings
     *
     * @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 import_settings($request)
    {
        $settings = json_decode($request->get_body());

        if (is_null($settings) || !is_object($settings)) {
            return false;
        }

        update_option('brf_permissions_roles', $settings->brf_permissions_roles, 'no');
        update_option('brf_global_classes_activated', $settings->brf_global_classes_activated, 'no');
        update_option('brf_global_classes_excluded', $settings->brf_global_classes_excluded, 'no');
        update_option('brf_global_classes', $settings->brf_global_classes, 'no');
        update_option('brf_activated_tools', $settings->brf_activated_tools, 'no');
        update_option('brf_activated_elements', $settings->brf_activated_elements, 'no');
        update_option('brf_popups', $settings->brf_popups, 'no');
        update_option('brf_maintenance', $settings->brf_maintenance, 'no');
        update_option('brf_whitelabel', $settings->brf_whitelabel, 'no');
        update_option('brf_panel', $settings->brf_panel, 'no');
        update_option('brf_panel_nodes', $settings->brf_panel_nodes, 'no');
        update_option('brf_tool_settings', $settings->brf_tool_settings, 'no');
        update_option('brf_terminal_history', $settings->brf_terminal_history, 'no');
        update_option('brf_backend_designer', $settings->brf_backend_designer, 'no');
        update_option('brf_email_designer_data', $settings->brf_email_designer_data, 'no');
        update_option('brf_email_designer_themes', $settings->brf_email_designer_themes, 'no');
        update_option('brf_page_transitions', $settings->brf_page_transitions, 'no');
        update_option('brf_external_api_loops', $settings->brf_external_api_loops, 'no');

        return true;
    }

    public function get_submissions_display_settings()
    {
        return get_option('brf_submissions_display_settings');
    }

    public function get_unread_submissions()
    {
        return get_option('brf_unread_submissions');
    }

    /**
     * Checks if a given request has access to read the data.
     *
     * @param  WP_REST_Request $request Full details about the request.
     *
     * @return true|WP_Error True if the request has read access, WP_Error object otherwise.
     */
    public function allow_permission($request)
    {
        $nonce = $request->get_header('X-WP-Nonce');
        return is_user_logged_in() && wp_verify_nonce($nonce, 'wp_rest') && current_user_can('manage_options');
    }

    public function allow_permission_lite($request)
    {
        $nonce = $request->get_header('X-WP-Nonce');
        return is_user_logged_in() && wp_verify_nonce($nonce, 'wp_rest') && current_user_can('edit_posts');
    }

    /**
     * Allow Permission
     *
     * @param  WP_REST_Request $request Full details about the request.
     *
     * @return true|WP_Error True if the request has read access, WP_Error object otherwise.
     */
    public function allowed($request)
    {
        $nonce = $request->get_header('X-WP-Nonce');
        return wp_verify_nonce($nonce, 'wp_rest');
    }

    public static function clear_temp_directory()
    {
        $temp_directory = BRICKSFORGE_TEMP_DIR;
        // Default: 1 hour (3600 seconds)
        // Can be customized via 'bricksforge/pro_forms/temp_directory_cleanup_time' filter
        // Set to 0 for immediate cleanup, or any number of seconds for custom time
        $age_limit = apply_filters('bricksforge/pro_forms/temp_directory_cleanup_time', 3600);

        if (is_dir($temp_directory)) {
            $files = glob($temp_directory . '*', GLOB_MARK);
            $time_now = time(); // Current time

            foreach ($files as $file) {
                if (is_writable($file)) {
                    $file_mod_time = filemtime($file);
                    $age = $time_now - $file_mod_time;

                    // If that file is older than the age limit, delete it
                    if ($age > $age_limit) {
                        unlink($file);
                    }
                }
            }
        }
    }

    public function render_form_data($request)
    {
        $form_settings = \Bricks\Helpers::get_element_settings($request->get_param('postId'), $request->get_param('formId'));

        if (!isset($form_settings) || empty($form_settings)) {
            return false;
        }

        $form_actions = isset($form_settings['actions']) ? $form_settings['actions'] : null;
        $form_data = $request->get_body_params();
        $post_id = $request->get_param('postId');
        $dynamic_post_id = $request->get_param('dynamicPostId');

        $submit_conditions = array();
        $submissions_count = null;
        $wc_data = (object)[];

        $submitButtonHasCondition = isset($form_settings['submitButtonHasCondition']) && $form_settings['submitButtonHasCondition'] === true;
        $has_wc_add_to_cart_action = class_exists('WooCommerce') && isset($form_actions) && is_array($form_actions) && in_array('wc_add_to_cart', $form_actions);

        if ($submitButtonHasCondition) {
            foreach ($form_settings["submitButtonConditions"] as $condition) {
                $value1 = bricks_render_dynamic_data($condition['submitButtonConditionValue'], $dynamic_post_id ? $dynamic_post_id : $post_id);
                $value1 = $this->forms_helper->get_form_field_by_id($value1, $form_data);

                $value2 = bricks_render_dynamic_data($condition['submitButtonConditionValue2'], $dynamic_post_id ? $dynamic_post_id : $post_id);
                $value2 = $this->forms_helper->get_form_field_by_id($value2, $form_data);

                $submit_conditions[] = [
                    'condition' => $condition['submitButtonCondition'],
                    'operator'  => $condition['submitButtonConditionOperator'],
                    'dataType'  => isset($condition['submitButtonConditionType']) ? $condition['submitButtonConditionType'] : null,
                    'value'     => $value1,
                    'value2'    => $value2,
                    'post_id'   => isset($condition['submitButtonConditionPostId']) ? bricks_render_dynamic_data($condition['submitButtonConditionPostId'], $dynamic_post_id ? $dynamic_post_id : $post_id) : null,
                ];

                switch ($condition['submitButtonCondition']) {
                    case 'option':
                        $submit_conditions[count($submit_conditions) - 1]['data'] = get_option($condition['submitButtonConditionValue']);
                        break;
                    case 'post_meta':
                        $s_post_id = $this->forms_helper->get_form_field_by_id($submit_conditions[count($submit_conditions) - 1]['post_id'], $form_data);
                        $submit_conditions[count($submit_conditions) - 1]['data'] = get_post_meta($s_post_id, $condition['submitButtonConditionValue'], true);
                        break;
                    case 'storage_item':
                        $submit_conditions[count($submit_conditions) - 1]['data'] = get_option($condition['submitButtonConditionValue']);
                        break;
                    case 'form_field':
                        $fieldValue = $form_data["form-field-" . $condition['submitButtonConditionValue']];
                        $submit_conditions[count($submit_conditions) - 1]['data'] = isset($fieldValue) ? $fieldValue : $form_data["form-field-" . $condition['submitButtonConditionValue'] . '[]'];
                        break;
                    case 'submission_count_reached':
                        global $wpdb;
                        $table_name = $wpdb->prefix . BRICKSFORGE_SUBMISSIONS_DB_TABLE;
                        $form_id = sanitize_text_field($request->get_param('formId'));
                        $submissions_count = $wpdb->get_var(
                            $wpdb->prepare(
                                "SELECT COUNT(*) FROM $table_name WHERE form_id = %s",
                                $form_id
                            )
                        );
                        break;
                    case 'submission_field':
                        global $wpdb;
                        $table_name = $wpdb->prefix . BRICKSFORGE_SUBMISSIONS_DB_TABLE;
                        $form_id = sanitize_text_field($request->get_param('formId'));
                        $submissions = $wpdb->get_results(
                            $wpdb->prepare(
                                "SELECT fields FROM $table_name WHERE form_id = %s",
                                $form_id
                            )
                        );

                        if (!empty($submissions)) {
                            $submissions = json_decode(json_encode($submissions), true);

                            foreach ($submissions as $submission) {
                                $submission = json_decode($submission['fields'], true);

                                foreach ($submission['fields'] as $submissionField) {
                                    if ($submissionField['id'] == $condition['submitButtonConditionValue'] && $submissionField['value'] == $condition['submitButtonConditionValue2']) {
                                        $submit_conditions[count($submit_conditions) - 1]['data'][] = $submissionField;
                                    }
                                }
                            }
                        }
                        break;
                    default:
                        break;
                }
            }
        }

        if (isset($form_settings['submitButtonConditionsAlternativeText']) && !empty($form_settings['submitButtonConditionsAlternativeText'])) {
            $form_settings['submitButtonConditionsAlternativeText'] = bricks_render_dynamic_data($form_settings['submitButtonConditionsAlternativeText'], $dynamic_post_id ? $dynamic_post_id : $post_id);
        }

        // If uses add to cart, pass the current variant price to the form data
        if ($has_wc_add_to_cart_action) {
            $product = isset($form_settings['pro_forms_post_action_add_to_cart_product']) ? $form_settings['pro_forms_post_action_add_to_cart_product'] : null;
            $product_id = isset($form_settings['pro_forms_post_action_add_to_cart_product_id']) ? $form_settings['pro_forms_post_action_add_to_cart_product_id'] : null;

            if ($product !== 'custom') {
                $product_id = $product;
            } else {
                $product_id = $this->forms_helper->get_form_field_by_id($product_id, $form_data);
            }

            $custom_fields = isset($form_settings['pro_forms_post_action_add_to_cart_custom_fields']) ? $form_settings['pro_forms_post_action_add_to_cart_custom_fields'] : null;

            $wc_data->variation_price = $this->forms_helper->get_variation_price($product_id, $custom_fields, $form_data);
        }

        $output = [
            "postID"           => $dynamic_post_id ? $dynamic_post_id : $post_id,
            "formID"           => $request->get_param('formId'),
        ];

        if ($submitButtonHasCondition === true) {
            $output["submitConditions"] = $submit_conditions;
            $output['submitConditionsData'] = [
                'relation' => $form_settings['submitButtonConditionsRelation'],
                "submissionsCount" => $submissions_count ? $submissions_count : null,
                "submissionsMax" => isset($form_settings['submission_max']) ? $form_settings['submission_max'] : null,
                "submitButtonConditionAction" => $form_settings['submitButtonConditionAction'],
                "submitButtonConditionsAlternativeText" => isset($form_settings['submitButtonConditionsAlternativeText']) ? $form_settings['submitButtonConditionsAlternativeText'] : null,
                "submitButtonText" => isset($form_settings['submitButtonText']) ? $form_settings['submitButtonText'] : null,
            ];
        }

        if ($has_wc_add_to_cart_action === true) {
            $output["wc"] = $wc_data;
        }

        return $output;
    }

    public function form_file_upload()
    {
        require_once(ABSPATH . 'wp-admin/includes/file.php');

        $file_data = $_FILES;

        // SECURITY FIX: Get postId and formId from POST (safe for form identification)
        // Then retrieve allowed_file_types from form settings instead of trusting $_POST
        $post_id = isset($_POST['postId']) ? absint($_POST['postId']) : 0;
        $form_id = isset($_POST['formId']) ? sanitize_text_field($_POST['formId']) : '';
        $form_id_fallback = isset($_POST['formIdFallback']) ? sanitize_text_field($_POST['formIdFallback']) : '';

        // Build form structure to get allowed file types from form settings
        $form_structure = [];
        if ($post_id && $form_id) {
            $form_structure = $this->forms_helper->build_form_structure($post_id, $form_id);
        }

        if (empty($form_structure)) {
            $form_structure = $this->forms_helper->build_form_structure($post_id, $form_id_fallback);
        }

        // Parse max file size from string format (e.g., "10MB", "750KB")
        $max_file_size = 20971520; // Default: 20 MB
        if (isset($_POST['max_file_size'])) {
            $size_string = $_POST['max_file_size'];

            // Extract number and unit from string
            if (preg_match('/^(\d+(?:\.\d+)?)\s*(MB|KB|GB)?$/i', $size_string, $matches)) {
                $size_value = floatval($matches[1]);
                $unit = isset($matches[2]) ? strtoupper($matches[2]) : 'MB';

                // Convert to bytes
                switch ($unit) {
                    case 'GB':
                        $max_file_size = $size_value * 1024 * 1024 * 1024;
                        break;
                    case 'MB':
                        $max_file_size = $size_value * 1024 * 1024;
                        break;
                    case 'KB':
                        $max_file_size = $size_value * 1024;
                        break;
                }
            }
        }

        $uploaded_files = [];

        // Iterate over each file element in $_FILES
        foreach ($file_data as $key => $files) {
            // Default allowed file types (same as before)
            $default_types = array('image/jpeg', 'image/png', 'image/webp', 'image/gif', 'application/pdf', 'video/mp4', 'audio/mpeg');

            // Get allowed file types from form structure for this specific field
            $allowed_file_types = $this->get_allowed_file_types_from_settings($form_structure, $key, $default_types);

            // Handle multiple files for each field
            foreach ($files['name'] as $index => $name) {
                $file = array(
                    'name'     => $files['name'][$index],
                    'type'     => $files['type'][$index],
                    'tmp_name' => $files['tmp_name'][$index],
                    'error'    => $files['error'][$index],
                    'size'     => $files['size'][$index]
                );

                // Check if the file was uploaded successfully without errors
                if ($file['error'] === 0) {
                    // Check the file type using Fileinfo (reads actual file content)
                    $finfo = finfo_open(FILEINFO_MIME_TYPE);
                    $real_mime_type = finfo_file($finfo, $file['tmp_name']);
                    finfo_close($finfo);

                    // Additional validation: Check if extension matches MIME type
                    $file_info = wp_check_filetype_and_ext($file['tmp_name'], $file['name']);
                    $wp_allowed_mime_type = $file_info['type'];

                    // Manual extension to MIME type validation
                    $expected_mime_from_ext = $this->get_mime_type_from_extension($file['name']);

                    // Define allowed file types (now from form settings, not $_POST)
                    $allowed_types = $allowed_file_types;
                    $allowed_types = apply_filters('bricksforge/pro_forms/allowed_file_types', $allowed_types);

                    // Security check: Validate MIME types
                    // 1. Allowed types must be an array
                    // 2. Real MIME type must be allowed
                    // 3. If WordPress detected a type, it must also be allowed
                    // 4. Real MIME type must match expected MIME type from extension (if known)
                    if (
                        !is_array($allowed_types) ||
                        !in_array($real_mime_type, $allowed_types) ||
                        ($wp_allowed_mime_type !== false && !in_array($wp_allowed_mime_type, $allowed_types)) ||
                        ($expected_mime_from_ext && $real_mime_type !== $expected_mime_from_ext)
                    ) {
                        wp_send_json_error('File type not allowed or file extension does not match content');
                        continue;
                    }

                    // Check the file size
                    $file_size = filesize($file['tmp_name']);
                    $max_size = $max_file_size;
                    $max_size = apply_filters('bricksforge/pro_forms/max_file_size', $max_size);

                    // Skip the file if it is too large
                    if ($file_size > $max_size) {
                        wp_send_json_error('File size exceeds the maximum allowed size');
                        continue;
                    }

                    // We use wp_handle_upload to move the file to the destination path
                    // We use a filter to change the directory to
                    add_filter('upload_dir', array($this, 'temporary_change_upload_dir'));

                    $uploaded_file = wp_handle_upload($file, array('test_form' => false));

                    remove_filter('upload_dir', array($this, 'temporary_change_upload_dir'));

                    if (is_wp_error($uploaded_file)) {
                        wp_send_json_error($uploaded_file->get_error_message());
                    }

                    // We add the filename to the $uploaded_file. We need to strip the path from the filename
                    $original_filename = $file['name'];

                    if ($original_filename) {
                        $uploaded_file['originalFilename'] = $original_filename;
                    }

                    $original_filesize = $file['size'];
                    if ($original_filesize) {
                        $uploaded_file['originalFilesize'] = $original_filesize;
                    }

                    $this->clean_up_temp_files();

                    // Use lock to prevent race condition with cron cleanup
                    $lock_key = 'bricksforge_temp_upload_lock';
                    $lock_acquired = false;
                    $max_attempts = 10;
                    $attempt = 0;

                    // Try to acquire lock with retry mechanism
                    while (!$lock_acquired && $attempt < $max_attempts) {
                        if (!get_transient($lock_key)) {
                            set_transient($lock_key, 1, 5); // Short lock, 5 seconds
                            $lock_acquired = true;
                        } else {
                            usleep(100000); // Wait 0.1 seconds before retry
                            $attempt++;
                        }
                    }

                    if ($lock_acquired) {
                        $temp_files = get_option('brf_tmp_files', array());
                        $temp_files[] = $uploaded_file['file'];
                        update_option('brf_tmp_files', $temp_files);
                        delete_transient($lock_key);
                    } else {
                        // Fallback: just add without lock (better than failing upload)
                        $temp_files = get_option('brf_tmp_files', array());
                        $temp_files[] = $uploaded_file['file'];
                        update_option('brf_tmp_files', $temp_files);
                    }

                    $uploaded_files[] = $uploaded_file;

                    // Return the uploaded files
                    wp_send_json_success($uploaded_files);
                }
            }
        }

        // Check if any files were uploaded
        if (empty($uploaded_files)) {
            wp_send_json_error('No files uploaded');
        }

        // Return the uploaded files
        wp_send_json_success($uploaded_files);
    }

    public function temporary_change_upload_dir($dir)
    {
        return array(
            'path' => $dir['basedir'] . BRICKSFORGE_TEMP_DIR_PATH_FROM_WP_UPLOAD_DIR,
            'url' => $dir['baseurl'] . BRICKSFORGE_TEMP_DIR_PATH_FROM_WP_UPLOAD_DIR,
            'subdir' => BRICKSFORGE_TEMP_DIR_PATH_FROM_WP_UPLOAD_DIR,
        ) + $dir;
    }

    public function form_file_load_initial_files()
    {
        $file = $_POST['file'];

        if (!isset($file)) {
            wp_send_json_error('No file provided');
        }

        // Retrieve attachment ID from the file URL
        $attachment_id = attachment_url_to_postid($file);

        if (!$attachment_id) {
            wp_send_json_error('Invalid file URL');
        }

        // Get the file path and mime type
        $attachment_path = get_attached_file($attachment_id);
        $attachment_type = mime_content_type($attachment_path);
        $attachment_size = filesize($attachment_path);

        // Get file name
        $attachment_name = basename($attachment_path);

        // Read the file content
        $file_content = file_get_contents($attachment_path);

        // Create the file object
        $file_object = [
            'name' => $attachment_name,
            'type' => $attachment_type,
            'content' => base64_encode($file_content), // Encode content to base64
            'size' => $attachment_size
        ];

        // Send the file object as a JSON response
        wp_send_json_success($file_object);
    }

    public function clean_up_temp_files()
    {
        $temp_files = get_option('brf_tmp_files', array());

        // Get the current time once to avoid repeated calls
        $current_time = time();

        // Use array_filter to remove old files
        $temp_files = array_filter($temp_files, function ($file) use ($current_time) {
            // Default: 5 hours (18000 seconds)
            // Can be customized via 'bricksforge/pro_forms/temp_file_cleanup_time' filter
            // Set to 0 for immediate cleanup, or any number of seconds for custom time
            $time = apply_filters('bricksforge/pro_forms/temp_file_cleanup_time', 18000);

            if (file_exists($file) && filemtime($file) < $current_time - $time) {
                // Try to unlink and check for success
                if (@unlink($file)) {
                    return false; // Remove this file from the array
                } else {
                    // Handle error (e.g., log it)
                    error_log("Failed to delete temp file: $file");
                }
            }
            return true; // Keep this file in the array
        });

        update_option('brf_tmp_files', $temp_files);
    }

    /**
     * Get Global Classes Backups
     *
     * @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 create_global_classes_backup()
    {
        // Include GlobalClasses.php
        include_once(BRICKSFORGE_PATH . '/includes/global-classes/GlobalClasses.php');

        $global_classes = new \Bricksforge\GlobalClasses();

        $new_backup = $global_classes->create_global_classes_backup();

        return $new_backup;
    }

    /**
     * Restore a global classes backup
     * @param WP_REST_Request $request The request object.
     * @return bool True if the backup was restored, false otherwise.
     */
    public function restore_global_classes_backup($request)
    {

        // Get request param
        $params = $request->get_params();

        $backup_id = $params['id'];

        if (!$backup_id) {
            return false;
        }

        // Include GlobalClasses.php
        include_once(BRICKSFORGE_PATH . '/includes/global-classes/GlobalClasses.php');

        $global_classes = new \Bricksforge\GlobalClasses();

        $result = $global_classes->restore_global_classes(json_decode($backup_id));

        return $result;
    }

    /**
     * Get submissions
     * @param WP_REST_Request $request The request object.
     * @return array The submissions.
     */
    public function get_submissions($request)
    {
        $page = 1;
        $per_page = 9999999;
        $search = '';
        $order_by = 'id';
        $order = 'DESC';
        $form_id = $request->get_param('formId') ? $request->get_param('formId') : null;

        global $wpdb;
        $table_name = $wpdb->prefix . BRICKSFORGE_SUBMISSIONS_DB_TABLE;

        // Calculate offset based on current page and items per page
        $offset = ($page - 1) * $per_page;

        // Build the SQL query. Join with wp_posts to get the post title
        $sql = "SELECT s.*, p.post_title FROM $table_name s LEFT JOIN {$wpdb->posts} p ON s.post_id = p.ID";

        // Add form_id filter if provided
        if (!is_null($form_id)) {
            $sql .= " WHERE s.form_id = %s";
        }

        // Add search query if provided
        if (!empty($search)) {
            $sql .= " AND s.fields LIKE '%" . $wpdb->esc_like($search) . "%'";
        }

        // Add sorting criteria
        $sql .= " ORDER BY s.$order_by $order";

        // Add limit and offset
        $sql .= " LIMIT $per_page OFFSET $offset";

        // Prepare the SQL statement
        $prepared = $wpdb->prepare($sql, $form_id);

        // Execute the query
        $results = $wpdb->get_results($prepared);

        return $results;
    }

    /**
     * Delete submissions
     * @param WP_REST_Request $request The request object.
     * @return bool True if the submissions were deleted, false otherwise.
     */
    public function delete_submissions($request)
    {
        $request = $request->get_body();

        $request = json_decode($request);

        if (!isset($request->submissions) || empty($request->submissions)) {
            return false;
        }

        // Array of submissions
        $submissions = $request->submissions;

        if (!is_array($submissions)) {
            return false;
        }

        global $wpdb;
        $table_name = $wpdb->prefix . BRICKSFORGE_SUBMISSIONS_DB_TABLE;

        // Sanitize and prepare the array of IDs for the query
        $ids = array_map('intval', $submissions);
        $ids = array_filter($ids);
        $ids = array_unique($ids);

        if (empty($ids)) {
            return false;
        }

        // Use proper prepared statement with placeholders
        $placeholders = implode(',', array_fill(0, count($ids), '%d'));
        $sql = $wpdb->prepare("DELETE FROM $table_name WHERE id IN ($placeholders)", $ids);

        $wpdb->query($sql);

        return true;
    }

    /**
     * Delete the submissions table
     * @return bool True if the table was deleted, false otherwise.
     */
    public function delete_submissions_table()
    {
        global $wpdb;
        $table_name = $wpdb->prefix . BRICKSFORGE_SUBMISSIONS_DB_TABLE;

        // Delete all data in the table
        $wpdb->query("TRUNCATE TABLE `$table_name`");

        // Drop the table
        $wpdb->query("DROP TABLE IF EXISTS `$table_name`");

        if (get_option('brf_submissions_display_settings')) {
            delete_option('brf_submissions_display_settings');
        }

        return true;
    }

    public function get_form_title_by_id($request)
    {
        $form_settings = \Bricks\Helpers::get_element_settings($request->get_param('postId'), $request->get_param('formId'));

        if (!isset($form_settings) || empty($form_settings)) {
            return false;
        }
        if (isset($form_settings['submission_form_title']) && !empty($form_settings['submission_form_title'])) {
            return bricks_render_dynamic_data($form_settings['submission_form_title'], $request->get_param('postId'));
        }

        return false;
    }

    /**
     * AI
     * @return void
     * @since 0.9.9
     */
    public function ai_task($request)
    {
        $body = $request->get_body();
        $body = json_decode($body);
        $prompt = $body->prompt;

        if (!$prompt) {
            return false;
        }

        $ai = new \Bricksforge\AI();
        $result = $ai->run($prompt);

        return $result;
    }

    /**
     * Render CSS files for the global classes
     */
    public function render_css_files($categories)
    {
        clearstatcache();

        // Create file structure if not exists
        if (!file_exists(BRICKSFORGE_CUSTOM_STYLES_FILE)) {
            if (!is_dir(BRICKSFORGE_CUSTOM_STYLES_DIR)) {
                mkdir(BRICKSFORGE_CUSTOM_STYLES_DIR, 0755, true);
            }

            touch(BRICKSFORGE_CUSTOM_STYLES_FILE);
        }

        if (!$categories || empty($categories)) {
            return false;
        }

        // Reset the file
        file_put_contents(BRICKSFORGE_CUSTOM_STYLES_FILE, ' ');

        $css_content = file_get_contents(BRICKSFORGE_CUSTOM_STYLES_FILE);

        $pattern = '/(?:[\.]{1})([a-zA-Z_]+[\w_]*)(?:[\s\.\,\{\>#\:]{0})/im';

        foreach ($categories as $category) {

            if (isset($category->active) && $category->active == false) {
                continue;
            }

            $prefix = $category->prefix;
            if (is_null($prefix)) {
                $css_content .= PHP_EOL . $category->code;
            } else {
                $category->code = preg_replace($pattern, '.' . $prefix . '-${1}', $category->code);
                $css_content .= PHP_EOL . $category->code;
            }
        }

        $result = file_put_contents(BRICKSFORGE_CUSTOM_STYLES_FILE, $css_content);

        return $result;
    }

    /**
     * Sanitize a value
     * @param mixed $value The value to sanitize.
     * @return mixed The sanitized value.
     */
    public function sanitize_value($value)
    {
        if (is_array($value)) {
            foreach ($value as $key => $sub_value) {
                $value[$key] = $this->sanitize_value($sub_value);
            }
        } elseif (is_numeric($value)) {
            $value = preg_replace('/[^0-9]/', '', $value);
        } else {
            $value = sanitize_text_field($value);
        }
        return $value;
    }

    /**
     * Render dynamic data
     * @param WP_REST_Request $request The request object.
     * @return string|false The rendered dynamic data or false if the value is empty.
     */
    public function render_dynamic_data($request)
    {
        $value = $request->get_param('_value');
        $post_id = $request->get_param('_post_id');

        if (empty($value)) {
            return false;
        }

        if (empty($post_id)) {
            $post_id = null;
        }

        return bricks_render_dynamic_data($value, $post_id);
    }

    /**
     * Clear the cache for an external API loop item
     * @param WP_REST_Request $request The request object.
     * @return bool True if the cache was cleared, false otherwise.
     */
    public function clear_external_api_cache($request)
    {
        $body = json_decode($request->get_body(), true);
        $item_id = $body['item_id'] ?? null;

        if (empty($item_id)) {
            return false;
        }

        try {
            \Bricksforge\ExternalApiLoops::clear_cache($item_id);
        } catch (\Exception $e) {
            return false;
        }

        return true;
    }

    /**
     * Encrypt a value
     * @param WP_REST_Request $request The request object.
     * @return string|false The encrypted value or false if the value is not encrypted.
     */
    public function encrypt($request)
    {
        $value = $request->get_param('value');

        if (empty($value)) {
            return false;
        }

        if (!class_exists('\Bricksforge\Api\Utils')) {
            return false;
        }

        $utils = new \Bricksforge\Api\Utils();

        if (!$utils->is_encrypted($value)) {
            return $utils->encrypt($value);
        }

        return $value;
    }

    /**
     * Decrypt a value
     * @param WP_REST_Request $request The request object.
     * @return string|false The decrypted value or false if the value is not encrypted.
     */
    public function decrypt($request)
    {
        $value = $request->get_param('value');

        if (empty($value)) {
            return false;
        }

        if (!class_exists('\Bricksforge\Api\Utils')) {
            return false;
        }

        $utils = new \Bricksforge\Api\Utils();

        if ($utils->is_encrypted($value)) {
            return $utils->decrypt($value);
        }

        return $value;
    }
    public function encrypt_rest_api_auth_data($request)
    {
        $data = $request->get_param('data');

        if (empty($data)) {
            return false;
        }

        // Ensure auth property exists (initialize if missing)
        if (!isset($data['auth'])) {
            $data['auth'] = [
                'method' => 'none',
                'username' => '',
                'password' => '',
                'token' => '',
            ];
        }

        if (!class_exists('\Bricksforge\Api\Utils')) {
            return false;
        }

        $utils = new \Bricksforge\Api\Utils();

        // Encrypt main data
        $this->encrypt_data($data, $utils);

        // Encrypt 'auth' data (only if auth exists and is not empty)
        if (isset($data['auth']) && !empty($data['auth'])) {
            $this->encrypt_data($data['auth'], $utils);
        }

        return $data;
    }

    public function decrypt_rest_api_auth_data($request)
    {
        $data = $request->get_param('data');

        if (empty($data)) {
            return false;
        }

        // Ensure auth property exists (initialize if missing)
        if (!isset($data['auth'])) {
            $data['auth'] = [
                'method' => 'none',
                'username' => '',
                'password' => '',
                'token' => '',
            ];
        }

        if (!class_exists('\Bricksforge\Api\Utils')) {
            return false;
        }

        $utils = new \Bricksforge\Api\Utils();

        // Decrypt main data
        $this->decrypt_data($data, $utils);

        // Decrypt 'auth' data (only if auth exists and is not empty)
        if (isset($data['auth']) && !empty($data['auth'])) {
            $this->decrypt_data($data['auth'], $utils);
        }

        return $data;
    }

    private function decrypt_data(&$data, $utils)
    {
        foreach ($data as $key => &$value) {
            if (isset($value) && in_array($key, $this->keys_to_encrypt)) {
                if (is_array($value) && ($key === 'customHeaders' || $key === 'dynamicTokenData' || $key === 'queryParams')) {
                    foreach ($value as &$item) {
                        $item['key'] = $utils->decrypt($item['key']);
                        $item['value'] = $utils->decrypt($item['value']);
                    }
                    unset($item); // Unset reference after loop
                } else {
                    $value = $utils->decrypt($value);
                }
            }
        }
        unset($value); // Unset reference after loop
    }

    private function encrypt_data(&$data, $utils)
    {
        foreach ($data as $key => &$value) {
            if (isset($value) && in_array($key, $this->keys_to_encrypt)) {
                if (is_array($value) && ($key === 'customHeaders' || $key === 'dynamicTokenData' || $key === 'queryParams')) {
                    foreach ($value as &$item) {
                        $item['key'] = $utils->encrypt($item['key']);
                        $item['value'] = $utils->encrypt($item['value']);
                    }
                    unset($item); // Unset reference after loop
                } else {
                    $value = $utils->encrypt($value);
                }
            }
        }
        unset($value); // Unset reference after loop
    }

    public function fetch_api($request)
    {
        // Decode the request body
        $body = json_decode($request->get_body(), true);

        // Validate the item_id
        if (empty($body['item_id'])) {
            return new \WP_Error('missing_item_id', 'Item ID is required', ['status' => 400]);
        }

        $item_id = sanitize_text_field($body['item_id']);
        $item_data = isset($body['item_data']) ? $body['item_data'] : null;

        try {
            // CRITICAL: Clear all caches before fetching to ensure fresh data
            // Clear static cache
            \Bricksforge\ExternalApiLoops::clear_cache($item_id);

            // CRITICAL: Clear WordPress option cache to ensure we get the latest items
            // This is especially important for newly created items
            wp_cache_delete('brf_external_api_loops', 'options');
            wp_cache_delete('alloptions', 'options');

            // Initialize the ExternalApiLoops instance
            $instance = new \Bricksforge\ExternalApiLoops(true);

            // Collect items and active items
            $instance->get_items();
            $instance->collect_active_items();

            // Check if item exists, if not and we have item_data, auto-save it
            $item = $instance->get_item($item_id);
            if (empty($item) && !empty($item_data)) {
                // Auto-save the item if it doesn't exist
                $this->auto_save_item($item_data);

                // Clear cache again after saving
                wp_cache_delete('brf_external_api_loops', 'options');
                wp_cache_delete('alloptions', 'options');

                // Reload items
                $instance->get_items();
                $instance->collect_active_items();

                // Verify item was saved
                $item = $instance->get_item($item_id);
                if (empty($item)) {
                    return new \WP_Error('item_save_failed', 'Failed to save item automatically.', ['status' => 500]);
                }
            }

            // Run the API request with force_request = true to bypass all caches
            // The run_api_request method will clear caches when force_request = true
            $response = $instance->run_api_request($item_id, null, true);

            // Check if the response is valid
            // run_api_request returns null if item is not found, false on error, or WP_Error on API errors
            if ($response === false || $response === null) {
                // Check if item exists
                $item = $instance->get_item($item_id);
                if (empty($item)) {
                    return new \WP_Error('item_not_found', 'Item not found. Please save the item before fetching.', ['status' => 404]);
                }
                return new \WP_Error('api_request_failed', 'API request failed', ['status' => 500]);
            }

            // Check if response is a WP_Error
            if (is_wp_error($response)) {
                return $response;
            }

            // Get original unfiltered response for pagination detection
            // We need to fetch it again without rootPath filtering
            $item = $instance->get_item($item_id);
            $original_response = null;

            if (!empty($item) && isset($item->rootPath) && trim($item->rootPath) !== '') {
                // Get the original response before rootPath filtering
                // We'll use a temporary approach: fetch without rootPath
                $temp_root_path = $item->rootPath;
                $item->rootPath = ''; // Temporarily remove rootPath

                // Get the original response
                $original_response = $instance->run_api_request($item_id, null, true);

                // Restore rootPath
                $item->rootPath = $temp_root_path;
            }

            // Return both filtered and original response
            if ($original_response !== null && !is_wp_error($original_response)) {
                return [
                    'filtered' => $response,
                    'original' => $original_response,
                    'hasOriginal' => true
                ];
            }

            return $response;
        } catch (\Exception $e) {
            // Log the exception
            error_log("Bricksforge: Exception occurred - " . $e->getMessage());
            return new \WP_Error('exception_occurred', 'An error occurred while processing the request', ['status' => 500]);
        }
    }

    /**
     * Load items for the query builder
     * @param WP_REST_Request $request The request object.
     * @return WP_REST_Response The response object.
     */
    public function query_builder_load_items($request)
    {
        $body = json_decode($request->get_body(), true);
        $item_id = $body['item_id'];
        $count = $body['count'];
        $post_id = $body['post_id'] ?? get_the_ID();
        $query_element_id = $body['query_element_id'] ?? null;

        if (!isset($item_id) || empty($item_id)) {
            return new \WP_Error('missing_item_id', 'Item ID is required', ['status' => 400]);
        }

        // We need to force the query to be a loop. Otherwise, Bricks would create IDs instead of classes.
        // After processing, we set the force_is_looping back to false.
        $filter_callback = function ($force_is_looping, $query_element_id, $query_element) {
            return true;
        };
        add_filter('bricks/query/force_is_looping', $filter_callback, 10, 3);

        try {
            $instance = new \Bricksforge\ExternalApiLoops(true);

            $instance->get_items();
            $instance->collect_active_items();

            $items = $instance->active_items;

            $item_ids = array_column($items, 'id');

            // Find the index of the item with the given $item_id
            $index = array_search($item_id, $item_ids);

            // Check if the item was found
            if ($index === false) {
                remove_filter('bricks/query/force_is_looping', $filter_callback, 10);
                return new \WP_Error('item_not_found', 'Item not found', ['status' => 404]);
            }

            // Retrieve the item using the found index
            $item = $items[$index];

            if (empty($item)) {
                remove_filter('bricks/query/force_is_looping', $filter_callback, 10);
                return new \WP_Error('item_not_found', 'Item not found', ['status' => 404]);
            }

            $loading_type = $item->loadingType ?? "all";

            if ($loading_type === "all") {
                remove_filter('bricks/query/force_is_looping', $filter_callback, 10);
                return false;
            }

            $result = false;
            switch ($loading_type) {
                case "infinite":
                    $result = $this->handle_infinite_loading_type($item, $count, $instance, $post_id, $query_element_id);
                    break;
                case "pagination":
                    $result = $this->handle_pagination_loading_type($item, $count, $instance, $post_id);
                    break;
            }

            // Always remove the filter before returning
            remove_filter('bricks/query/force_is_looping', $filter_callback, 10);

            return $result;
        } catch (\Exception $e) {
            // Ensure filter is removed even on exception
            remove_filter('bricks/query/force_is_looping', $filter_callback, 10);
            throw $e;
        }
    }

    /**
     * Handle the infinite loading type
     * @param WP_REST_Request $request The request object.
     * @return WP_REST_Response The response object.
     */
    private function handle_infinite_loading_type($item, $count, $instance, $post_id, $query_element_id)
    {
        $total_key = $item->totalKey ?? "total";
        $limit_key = $item->limitKey ?? "limit";
        $offset_key = $item->offsetKey ?? "offset";

        $url = $item->url;

        if (!isset($url)) {
            return false;
        }

        $item_label = $item->label ?? "";
        $item_id = $item->id ?? "";

        $items_to_load = $item->itemsToLoad ?? 10;

        $loading_details = [
            $offset_key => $count,
            $limit_key => $items_to_load
        ];

        $response = $instance->run_api_request($item_id, null, true, $loading_details);

        if (empty($response)) {
            return false;
        }

        \Bricks\Database::$page_data['preview_or_post_id'] = $post_id;
        \Bricks\Theme_Styles::load_set_styles($post_id);

        $data = \Bricks\Helpers::get_element_data($post_id, $query_element_id);

        // STEP: Build the flat list index
        $indexed_elements = [];

        foreach ($data['elements'] as $element) {
            $indexed_elements[$element['id']] = $element;
        }

        if (! array_key_exists($query_element_id, $indexed_elements)) {
            return rest_ensure_response(
                [
                    'html'   => '',
                    'styles' => '',
                    'error'  => 'Element not found',
                ]
            );
        }

        // STEP: Set the query element pagination
        $query_element = $indexed_elements[$query_element_id];

        // Remove the parent
        if (! empty($query_element['parent'])) {
            $query_element['parent']       = 0;
            $query_element['_noRootClass'] = 1;
        }

        // Remove the settings.hasLoop and query.loop.settings.hasLoop
        unset($query_element['settings']['hasLoop']);
        unset($query_element['query']['loop']['settings']['hasLoop']);

        // We add the attributes to the query element
        $query_element = $this->add_attributes_to_query_element($query_element, $item_id, "infinite");

        // STEP: Get the query loop elements (main and children)
        $loop_elements = [$query_element];

        $children = $query_element['children'];

        while (! empty($children)) {
            $child_id = array_shift($children);

            if (array_key_exists($child_id, $indexed_elements)) {
                $loop_elements[] = $indexed_elements[$child_id];

                if (! empty($indexed_elements[$child_id]['children'])) {
                    $children = array_merge($children, $indexed_elements[$child_id]['children']);
                }
            }
        }

        // Test
        $rendered_elements = [];
        $html = "";

        // Für jedes Element aus deiner API, erstelle ein Element basierend auf dem Loop-Template
        foreach ($response as $i) {
            $rendered_loop_elements = $this->replace_api_data_in_elements($loop_elements, $i, $item_label, $instance);
            $html .= \Bricks\Frontend::render_data($rendered_loop_elements);
        }

        // We add the offset and limit to the url
        return [
            'html'   => $html,
        ];
    }

    private function add_attributes_to_query_element($query_element, $item_id, $loading_type)
    {
        $current_attributes = isset($query_element['settings']) && isset($query_element['settings']['_attributes']) ? $query_element['settings']['_attributes'] : [];
        //$current_classes = isset($query_element['settings']) && isset($query_element['settings']['_cssClasses']) ? $query_element['settings']['_cssClasses'] : [];

        $additional_attributes = [
            [
                'id' => \Bricks\Helpers::generate_random_id(false),
                'name' => 'data-brf-query-id',
                'value' => $query_element['id']
            ],
            [
                'id' => \Bricks\Helpers::generate_random_id(false),
                'name' => 'data-brf-item-id',
                'value' => $item_id
            ],
            [
                'id' => \Bricks\Helpers::generate_random_id(false),
                'name' => 'data-brf-loading-type',
                'value' => $loading_type
            ]
        ];

        // Classes are saved as string. Example: class1 class2 class3
        //$additional_classes = "brxe-ebruzb";
        //$query_element['settings']['_cssClasses'] = $current_classes . " " . $additional_classes;

        $query_element['settings']['_attributes'] = array_merge($current_attributes, $additional_attributes);



        return $query_element;
    }

    /**
     * Replace API data in multiple elements (array of elements)
     * 
     * @param array $elements Array of elements to process
     * @param mixed $i Current API item data
     * @param string $item_label Item label
     * @param object $instance ExternalApiLoops instance
     * @return array Processed elements
     */
    private function replace_api_data_in_elements($elements, $i, $item_label, $instance)
    {
        $processed_elements = [];

        foreach ($elements as $element) {
            $processed_elements[] = $this->replace_api_data_in_element($element, $i, $item_label, $instance);
        }

        return $processed_elements;
    }

    /**
     * Replace API data in a single element
     * 
     * @param array $query_element Single element to process
     * @param mixed $i Current API item data
     * @param string $item_label Item label
     * @param object $instance ExternalApiLoops instance
     * @return array Processed element
     */
    private function replace_api_data_in_element($query_element, $i, $item_label, $instance)
    {
        // We replace dynamic data with regex
        // {brf_api_my-variable} to the given value

        $pattern = '/{brf_api_(.*?)}/';

        // Convert array to string to perform regex replacement
        $element_string = json_encode($query_element);

        // Replace all API variables using preg_replace_callback to access matches
        $element_string = preg_replace_callback($pattern, function ($matches) use ($instance, $item_label, $i) {
            return $instance->render_variable_from_static_json($matches[0], $item_label, $i);
        }, $element_string);

        // Convert back to array
        $replaced_element = json_decode($element_string, true);

        return $replaced_element;
    }

    /**
     * Handle the pagination loading type
     * @param WP_REST_Request $request The request object.
     * @return WP_REST_Response The response object.
     */
    private function handle_pagination_loading_type($item, $count, $instance, $post_id)
    {
        return $item;
    }

    /**
     * Get allowed file types from form structure for a specific field
     * SECURITY: This reads from form settings, not from user input
     *
     * @param array $form_structure The form structure containing field information
     * @param string $input_name The input name (e.g., 'form-field-988a15')
     * @param array $default_types Default allowed file types if field not found
     * @return array Array of allowed MIME types
     */
    private function get_allowed_file_types_from_settings($form_structure, $input_name, $default_types = array())
    {
        // Default allowed file types
        if (empty($default_types)) {
            $default_types = array('image/jpeg', 'image/png', 'image/webp', 'image/gif', 'application/pdf', 'video/mp4', 'audio/mpeg');
        }

        // If no form structure, try to use allowed_file_types from POST as fallback
        // This is useful when the form structure cannot be retrieved (e.g., templates)
        if (empty($form_structure)) {
            if (isset($_POST['allowed_file_types']) && !empty($_POST['allowed_file_types'])) {
                return $this->parse_allowed_file_types_from_string($_POST['allowed_file_types']);
            }
            return $default_types;
        }

        // Extract field ID from input name (e.g., 'form-field-988a15' -> '988a15')
        $field_id = str_replace('form-field-', '', $input_name);

        // Recursively search through form structure (including nested children) to find the field
        $field_settings = $this->find_field_settings_recursive($form_structure, $field_id);

        if ($field_settings && isset($field_settings['fileUploadAllowedTypes'])) {
            $types_string = $field_settings['fileUploadAllowedTypes'];

            // If types string is empty, return defaults
            if (empty($types_string)) {
                return $default_types;
            }

            $types = strtolower($types_string);
            $types = array_map(function ($type) {
                $type = trim($type);
                // Remove dots from extensions
                if (substr($type, 0, 1) === '.') {
                    $type = substr($type, 1);
                }

                // Convert extensions to MIME types (similar to form_file_upload logic)
                if (in_array($type, ['mp4', 'mov', 'avi', 'wmv'])) {
                    return 'video/' . $type;
                }
                if (in_array($type, ['mp3', 'wav', 'ogg'])) {
                    return 'audio/' . $type;
                }
                if (in_array($type, ['pdf', 'zip'])) {
                    return 'application/' . $type;
                }
                if (in_array($type, ['jpg', 'jpeg', 'png', 'gif', 'webp'])) {
                    return 'image/' . $type;
                }

                return $type;
            }, explode(',', $types));

            // Expand official MIME types to include their real detected equivalents
            // finfo_file() often detects exotic files as octet-stream or text/plain
            $types = $this->expand_mime_types_for_detection($types);

            return array_unique($types);
        }

        // If we didn't find the specific field or it has no allowed types, return defaults
        return $default_types;
    }

    /**
     * Recursively search through form structure to find field settings by field_id
     * This handles nested children in steps, blocks, conditional-wrappers, etc.
     *
     * @param array $elements Array of form elements
     * @param string $field_id The field ID to search for
     * @return array|null The field settings if found, null otherwise
     */
    private function find_field_settings_recursive($elements, $field_id)
    {
        foreach ($elements as $element) {
            // Check if this element matches the field_id
            if (isset($element['field_id']) && $element['field_id'] === $field_id) {
                return isset($element['settings']) ? $element['settings'] : null;
            }

            // Recursively search through children
            if (isset($element['children']) && !empty($element['children'])) {
                $found = $this->find_field_settings_recursive($element['children'], $field_id);
                if ($found !== null) {
                    return $found;
                }
            }
        }

        return null;
    }

    /**
     * Flatten form fields recursively for debugging purposes
     * Shows all fields including those nested in steps/blocks/conditional-wrappers
     *
     * @param array $elements Array of form elements
     * @param int $depth Current nesting depth
     * @return array Flattened array of field info
     */
    private function flatten_form_fields_for_debug($elements, $depth = 0)
    {
        $result = [];
        foreach ($elements as $element) {
            $info = [
                'depth' => $depth,
                'field_id' => $element['field_id'] ?? 'N/A',
                'name' => $element['name'] ?? 'N/A',
                'has_fileUploadAllowedTypes' => isset($element['settings']['fileUploadAllowedTypes']),
                'fileUploadAllowedTypes' => $element['settings']['fileUploadAllowedTypes'] ?? 'N/A',
                'has_children' => isset($element['children']) && !empty($element['children']),
            ];
            $result[] = $info;

            // Recursively add children
            if (isset($element['children']) && !empty($element['children'])) {
                $result = array_merge($result, $this->flatten_form_fields_for_debug($element['children'], $depth + 1));
            }
        }
        return $result;
    }

    /**
     * Expand official MIME types to include their real detected equivalents
     * finfo_file() detects many exotic file types as application/octet-stream or text/plain
     *
     * @param array $types Original MIME types from settings
     * @return array Expanded MIME types including detected equivalents
     */
    private function expand_mime_types_for_detection($types)
    {
        // Mapping of official MIME types to their real detected equivalents
        // finfo_file() often returns generic types like octet-stream or text/plain
        $mime_expansions = [
            // ===================
            // CAD/3D FILES
            // ===================
            'model/stl' => ['application/octet-stream', 'text/plain', 'application/sla', 'application/vnd.ms-pki.stl'],
            'model/obj' => ['text/plain', 'application/octet-stream'],
            'model/step' => ['text/plain', 'application/octet-stream'],
            'application/step' => ['text/plain', 'application/octet-stream'],
            'model/iges' => ['text/plain', 'application/octet-stream'],
            'application/iges' => ['text/plain', 'application/octet-stream'],
            'image/vnd.dwg' => ['application/octet-stream', 'application/acad', 'application/x-dwg'],
            'image/vnd.dxf' => ['text/plain', 'application/octet-stream', 'application/dxf'],
            'model/vnd.dwf' => ['application/octet-stream', 'application/x-dwf'],
            'model/gltf+json' => ['application/json', 'text/plain'],
            'model/gltf-binary' => ['application/octet-stream'],
            'model/3mf' => ['application/octet-stream', 'application/vnd.ms-package.3dmanufacturing-3dmodel+xml'],
            'application/acad' => ['application/octet-stream'],
            'application/x-dwg' => ['application/octet-stream'],
            'application/dxf' => ['text/plain', 'application/octet-stream'],
            'application/x-dxf' => ['text/plain', 'application/octet-stream'],

            // ===================
            // MICROSOFT OFFICE
            // ===================
            // Modern Office formats (OOXML) are ZIP-based
            'application/vnd.openxmlformats-officedocument.wordprocessingml.document' => ['application/zip', 'application/octet-stream'], // .docx
            'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' => ['application/zip', 'application/octet-stream'], // .xlsx
            'application/vnd.openxmlformats-officedocument.presentationml.presentation' => ['application/zip', 'application/octet-stream'], // .pptx
            // Legacy Office formats
            'application/msword' => ['application/octet-stream'], // .doc
            'application/vnd.ms-excel' => ['application/octet-stream'], // .xls
            'application/vnd.ms-powerpoint' => ['application/octet-stream'], // .ppt

            // ===================
            // ARCHIVES
            // ===================
            'application/x-rar-compressed' => ['application/octet-stream', 'application/vnd.rar'],
            'application/vnd.rar' => ['application/octet-stream', 'application/x-rar-compressed'],
            'application/x-7z-compressed' => ['application/octet-stream'],
            'application/x-tar' => ['application/octet-stream'],
            'application/gzip' => ['application/octet-stream', 'application/x-gzip'],
            'application/x-bzip2' => ['application/octet-stream'],

            // ===================
            // EBOOKS
            // ===================
            'application/epub+zip' => ['application/zip', 'application/octet-stream'],
            'application/x-mobipocket-ebook' => ['application/octet-stream'], // .mobi

            // ===================
            // VECTOR GRAPHICS
            // ===================
            'image/svg+xml' => ['text/plain', 'text/xml', 'application/xml', 'text/html'],
            'application/postscript' => ['application/octet-stream', 'text/plain'], // .eps, .ai (some)
            'application/illustrator' => ['application/pdf', 'application/octet-stream'], // .ai

            // ===================
            // DATA/TEXT FORMATS
            // ===================
            'text/csv' => ['text/plain', 'application/csv'],
            'application/json' => ['text/plain'],
            'application/xml' => ['text/plain', 'text/xml'],
            'text/xml' => ['text/plain', 'application/xml'],
            'application/javascript' => ['text/plain', 'text/javascript'],
            'text/javascript' => ['text/plain', 'application/javascript'],
            'text/markdown' => ['text/plain'],
            'application/x-yaml' => ['text/plain'],
            'text/yaml' => ['text/plain'],

            // ===================
            // FONTS
            // ===================
            'font/ttf' => ['application/octet-stream', 'application/x-font-ttf'],
            'font/otf' => ['application/octet-stream', 'application/x-font-otf'],
            'font/woff' => ['application/octet-stream', 'application/font-woff'],
            'font/woff2' => ['application/octet-stream', 'application/font-woff2'],
            'application/x-font-ttf' => ['application/octet-stream', 'font/ttf'],
            'application/x-font-otf' => ['application/octet-stream', 'font/otf'],

            // ===================
            // EXECUTABLES/INSTALLERS (if intentionally allowed)
            // ===================
            'application/x-msdownload' => ['application/octet-stream'], // .exe, .dll
            'application/x-msi' => ['application/octet-stream'], // .msi
            'application/vnd.apple.installer+xml' => ['application/octet-stream', 'application/xml'], // .pkg
            'application/x-apple-diskimage' => ['application/octet-stream'], // .dmg
        ];

        $expanded = $types;

        foreach ($types as $type) {
            $type_lower = strtolower(trim($type));
            if (isset($mime_expansions[$type_lower])) {
                $expanded = array_merge($expanded, $mime_expansions[$type_lower]);
            }
        }

        return array_unique($expanded);
    }

    /**
     * Parse allowed file types from a string (from POST or accept attribute)
     * Converts extensions and MIME types into a proper array
     *
     * @param string $types_string Comma-separated list of extensions/MIME types
     * @return array Array of MIME types
     */
    private function parse_allowed_file_types_from_string($types_string)
    {
        if (empty($types_string)) {
            return array('image/jpeg', 'image/png', 'image/webp', 'image/gif', 'application/pdf', 'video/mp4', 'audio/mpeg');
        }

        $types = strtolower($types_string);
        $types = array_map(function ($type) {
            $type = trim($type);
            // Remove dots from extensions
            if (substr($type, 0, 1) === '.') {
                $type = substr($type, 1);
            }

            // Convert extensions to MIME types
            if (in_array($type, ['mp4', 'mov', 'avi', 'wmv'])) {
                return 'video/' . $type;
            }
            if (in_array($type, ['mp3', 'wav', 'ogg'])) {
                return 'audio/' . $type;
            }
            if (in_array($type, ['pdf', 'zip'])) {
                return 'application/' . $type;
            }
            if (in_array($type, ['jpg', 'jpeg', 'png', 'gif', 'webp'])) {
                return 'image/' . ($type === 'jpg' ? 'jpeg' : $type);
            }

            return $type;
        }, explode(',', $types));

        // Expand official MIME types to include their real detected equivalents
        $types = $this->expand_mime_types_for_detection($types);

        return array_unique($types);
    }

    /**
     * Get expected MIME type from file extension
     *
     * @param string $filename
     * @return string|null
     */
    private function get_mime_type_from_extension($filename)
    {
        $extension = strtolower(pathinfo($filename, PATHINFO_EXTENSION));

        $mime_types = array(
            'jpg' => 'image/jpeg',
            'jpeg' => 'image/jpeg',
            'png' => 'image/png',
            'gif' => 'image/gif',
            'webp' => 'image/webp',
            'pdf' => 'application/pdf',
            'mp4' => 'video/mp4',
            'mov' => 'video/quicktime',
            'avi' => 'video/x-msvideo',
            'wmv' => 'video/x-ms-wmv',
            'mp3' => 'audio/mpeg',
            'wav' => 'audio/wav',
            'ogg' => 'audio/ogg',
            'zip' => 'application/zip',
        );

        return isset($mime_types[$extension]) ? $mime_types[$extension] : null;
    }

    /**
     * Handle manual CPT sync request
     *
     * @param WP_REST_Request $request The request object
     * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure
     */
    public function handle_manual_cpt_sync($request)
    {
        $body = json_decode($request->get_body(), true);
        $item_id = $body['item_id'] ?? null;

        if (empty($item_id)) {
            return new \WP_Error('missing_item_id', 'Item ID is required', ['status' => 400]);
        }

        try {
            // Load CptSync class if not already loaded
            if (!class_exists('Bricksforge\CptSync')) {
                require_once BRICKSFORGE_PATH . '/includes/external-api-loops/CptSync.php';
            }

            $cpt_sync = new \Bricksforge\CptSync();
            // Always force fetch on manual sync to ensure we get all paginated pages
            $result = $cpt_sync->sync_api_to_cpt($item_id, true);

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

            return rest_ensure_response([
                'success' => true,
                'stats' => $result,
                'message' => 'Sync completed successfully'
            ]);
        } catch (\Exception $e) {
            error_log("Bricksforge CPT Sync Error: " . $e->getMessage());
            return new \WP_Error('sync_failed', $e->getMessage(), ['status' => 500]);
        }
    }

    /**
     * Handle webhook CPT sync request
     *
     * @param WP_REST_Request $request The request object
     * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure
     */
    public function handle_webhook_cpt_sync($request)
    {
        $item_id = $request->get_param('item_id');

        if (empty($item_id)) {
            return new \WP_Error('missing_item_id', 'Item ID is required', ['status' => 400]);
        }

        // Get the item to check webhook secret
        $items = get_option('brf_external_api_loops', []);
        $item = null;

        // Handle both array and object formats
        foreach ($items as $i) {
            $current_id = is_array($i) ? (isset($i['id']) ? $i['id'] : null) : (isset($i->id) ? $i->id : null);
            if ($current_id === $item_id) {
                $item = $i;
                break;
            }
        }

        if (!$item) {
            return new \WP_Error('item_not_found', 'API item not found', ['status' => 404]);
        }

        // Convert to object if it's an array for easier access
        if (is_array($item)) {
            $item = json_decode(json_encode($item));
        }

        // Verify webhook is enabled
        if (!isset($item->cptSettings->webhookEnabled) || !$item->cptSettings->webhookEnabled) {
            return new \WP_Error('webhook_disabled', 'Webhook is not enabled for this item', ['status' => 403]);
        }

        // Verify webhook secret (always required)
        $stored_secret = $item->cptSettings->webhookSecret ?? '';

        if (empty($stored_secret)) {
            return new \WP_Error('webhook_not_configured', 'Webhook secret is not configured. Please configure a webhook secret in the backend.', ['status' => 403]);
        }

        $provided_secret = $request->get_header('X-Webhook-Secret');
        if (!$provided_secret) {
            $provided_secret = $request->get_header('X_Webhook_Secret');
        }

        if (empty($provided_secret)) {
            return new \WP_Error('missing_secret', 'Webhook secret is required. Please provide X-Webhook-Secret header.', ['status' => 401]);
        }

        if (!hash_equals($stored_secret, $provided_secret)) {
            return new \WP_Error('unauthorized', 'Invalid webhook secret', ['status' => 401]);
        }

        // Perform sync
        try {
            // Load CptSync class if not already loaded
            if (!class_exists('Bricksforge\CptSync')) {
                require_once BRICKSFORGE_PATH . '/includes/external-api-loops/CptSync.php';
            }

            $cpt_sync = new \Bricksforge\CptSync();
            $result = $cpt_sync->sync_api_to_cpt($item_id);

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

            return rest_ensure_response([
                'success' => true,
                'stats' => $result,
                'message' => 'Webhook sync completed successfully'
            ]);
        } catch (\Exception $e) {
            error_log("Bricksforge CPT Webhook Sync Error: " . $e->getMessage());
            return new \WP_Error('sync_failed', $e->getMessage(), ['status' => 500]);
        }
    }

    /**
     * Handle CPT sync progress request
     *
     * @param WP_REST_Request $request The request object
     * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure
     */
    public function handle_cpt_sync_progress($request)
    {
        $item_id = $request->get_param('item_id');

        if (empty($item_id)) {
            return new \WP_Error('missing_item_id', 'Item ID is required', ['status' => 400]);
        }

        $progress_key = 'brf_cpt_sync_progress_' . $item_id;
        $progress = get_transient($progress_key);

        if ($progress === false) {
            return rest_ensure_response([
                'syncing' => false,
                'step' => '',
                'current' => 0,
                'total' => 0,
            ]);
        }

        return rest_ensure_response($progress);
    }

    /**
     * Handle delete CPTs and data for specific item request
     *
     * @param WP_REST_Request $request The request object
     * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure
     */
    public function handle_delete_item_cpts($request)
    {
        $item_id = $request->get_param('item_id');

        if (empty($item_id)) {
            return new \WP_Error('missing_item_id', 'Item ID is required', ['status' => 400]);
        }

        try {
            // Load CptSync class if not already loaded
            if (!class_exists('Bricksforge\CptSync')) {
                require_once BRICKSFORGE_PATH . '/includes/external-api-loops/CptSync.php';
            }

            $cpt_sync = new \Bricksforge\CptSync();
            $result = $cpt_sync->delete_cpts_for_item($item_id);

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

            return rest_ensure_response([
                'success' => true,
                'stats' => $result,
                'message' => 'CPTs and data deleted successfully for this item'
            ]);
        } catch (\Exception $e) {
            error_log("Bricksforge Delete Item CPTs Error: " . $e->getMessage());
            return new \WP_Error('delete_failed', $e->getMessage(), ['status' => 500]);
        }
    }

    /**
     * Handle Stripe webhook events
     *
     * @param WP_REST_Request $request The request object
     * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure
     * @since 3.1.8
     */
    public function handle_stripe_webhook($request)
    {
        // Include Stripe library
        require_once BRICKSFORGE_PATH . '/includes/vendor/stripe-php/init.php';

        try {
            // Get webhook secret from element settings
            $activated_elements = get_option('brf_activated_elements');
            $stripe_settings = null;

            if (is_array($activated_elements)) {
                foreach ($activated_elements as $element) {
                    if (isset($element->id) && $element->id === 5) {
                        $stripe_settings = isset($element->settings) ? $element->settings : null;
                        break;
                    }
                }
            }

            if (empty($stripe_settings) || empty($stripe_settings->stripeWebhookSecret)) {
                error_log('Bricksforge Stripe Webhook: Webhook secret not configured');
                return new \WP_Error('webhook_error', 'Webhook not configured', ['status' => 400]);
            }

            $utils = new \Bricksforge\Api\Utils();
            $webhook_secret = $utils->decrypt($stripe_settings->stripeWebhookSecret);

            // Get the raw POST body
            $payload = $request->get_body();
            $sig_header = $request->get_header('stripe-signature');

            if (empty($sig_header)) {
                error_log('Bricksforge Stripe Webhook: Missing signature header');
                return new \WP_Error('webhook_error', 'Missing signature', ['status' => 400]);
            }

            // Verify webhook signature
            try {
                $event = \Stripe\Webhook::constructEvent(
                    $payload,
                    $sig_header,
                    $webhook_secret
                );
            } catch (\UnexpectedValueException $e) {
                // Invalid payload
                return new \WP_Error('webhook_error', 'Invalid payload', ['status' => 400]);
            } catch (\Stripe\Exception\SignatureVerificationException $e) {
                // Invalid signature
                return new \WP_Error('webhook_error', 'Invalid signature', ['status' => 400]);
            }

            // Handle the event
            switch ($event->type) {
                case 'checkout.session.completed':
                    $session = $event->data->object;

                    // Get session identifier from metadata
                    $session_identifier = isset($session->metadata->brf_session_identifier) ? $session->metadata->brf_session_identifier : '';

                    if (empty($session_identifier)) {
                        break;
                    }

                    // Verify the session ID matches our stored identifier
                    $stored_identifier = get_transient('brf_stripe_session_' . $session->id);

                    if ($stored_identifier !== $session_identifier) {
                        break;
                    }

                    // Retrieve stored form data
                    $transient_key = 'brf_stripe_session_' . $session_identifier;
                    $form_data = get_transient($transient_key);

                    if (empty($form_data)) {
                        break;
                    }

                    // Execute remaining form actions after payment confirmation
                    $pending_actions_key = 'brf_stripe_pending_actions_' . $session_identifier;
                    $pending_actions_data = get_transient($pending_actions_key);

                    $action_results = [];

                    if (!empty($pending_actions_data)) {
                        $form_actions = isset($pending_actions_data['form_actions']) ? $pending_actions_data['form_actions'] : [];
                        $has_actions = !empty($form_actions);

                        if ($has_actions) {
                            error_log('Bricksforge Stripe: Executing ' . count($form_actions) . ' pending actions');

                            // Execute all pending actions
                            try {
                                // Include Base class (Init.php is already loaded during WordPress boot)
                                if (!class_exists('\Bricksforge\ProForms\Actions\Base')) {
                                    include_once(BRICKSFORGE_PATH . '/includes/elements/pro-forms/actions/Base.php');
                                }

                                // Recreate the Base object with stored data
                                $base = new \Bricksforge\ProForms\Actions\Base(
                                    $pending_actions_data['form_settings'],
                                    $pending_actions_data['form_data'],
                                    $pending_actions_data['form_files'],
                                    $pending_actions_data['post_id'],
                                    $pending_actions_data['form_id'],
                                    $pending_actions_data['dynamic_post_id'],
                                    $pending_actions_data['form_structure'],
                                    $pending_actions_data['post_context']
                                );

                                // Execute each pending action
                                foreach ($form_actions as $action_name) {
                                    error_log('Bricksforge Stripe: Executing action: ' . $action_name);

                                    // Use a simple switch to execute actions
                                    switch ($action_name) {
                                        case 'email':
                                            include_once(BRICKSFORGE_PATH . '/includes/elements/pro-forms/actions/Email.php');
                                            $action = new \Bricksforge\ProForms\Actions\Email();
                                            $action->run($base);
                                            break;

                                        case 'webhook':
                                            include_once(BRICKSFORGE_PATH . '/includes/elements/pro-forms/actions/Webhook.php');
                                            $action = new \Bricksforge\ProForms\Actions\Webhook();
                                            $action->run($base);
                                            break;

                                        case 'redirect':
                                            include_once(BRICKSFORGE_PATH . '/includes/elements/pro-forms/actions/Redirect.php');
                                            $action = new \Bricksforge\ProForms\Actions\Redirect();
                                            $result = $action->run($base);
                                            if ($result) {
                                                $action_results['redirect'] = $result;
                                            }
                                            break;

                                        case 'reload':
                                            include_once(BRICKSFORGE_PATH . '/includes/elements/pro-forms/actions/Reload.php');
                                            $action = new \Bricksforge\ProForms\Actions\Reload();
                                            $action->run($base);
                                            break;

                                        case 'update_post_meta':
                                            include_once(BRICKSFORGE_PATH . '/includes/elements/pro-forms/actions/Update_Post_Meta.php');
                                            $action = new \Bricksforge\ProForms\Actions\Update_Post_Meta();
                                            $action->run($base);
                                            break;

                                        case 'add_post_meta':
                                            include_once(BRICKSFORGE_PATH . '/includes/elements/pro-forms/actions/Add_Post_Meta.php');
                                            $action = new \Bricksforge\ProForms\Actions\Add_Post_Meta();
                                            $action->run($base);
                                            break;

                                        case 'delete_post_meta':
                                            include_once(BRICKSFORGE_PATH . '/includes/elements/pro-forms/actions/Delete_Post_Meta.php');
                                            $action = new \Bricksforge\ProForms\Actions\Delete_Post_Meta();
                                            $action->run($base);
                                            break;

                                        case 'add_option':
                                            include_once(BRICKSFORGE_PATH . '/includes/elements/pro-forms/actions/Add_Option.php');
                                            $action = new \Bricksforge\ProForms\Actions\Add_Option();
                                            $action->run($base);
                                            break;

                                        case 'update_option':
                                            include_once(BRICKSFORGE_PATH . '/includes/elements/pro-forms/actions/Update_Option.php');
                                            $action = new \Bricksforge\ProForms\Actions\Update_Option();
                                            $action->run($base);
                                            break;

                                        case 'delete_option':
                                            include_once(BRICKSFORGE_PATH . '/includes/elements/pro-forms/actions/Delete_Option.php');
                                            $action = new \Bricksforge\ProForms\Actions\Delete_Option();
                                            $action->run($base);
                                            break;

                                        case 'update_user_meta':
                                            include_once(BRICKSFORGE_PATH . '/includes/elements/pro-forms/actions/Update_User_Meta.php');
                                            $action = new \Bricksforge\ProForms\Actions\Update_User_Meta();
                                            $action->run($base);
                                            break;

                                        case 'add_user_meta':
                                            include_once(BRICKSFORGE_PATH . '/includes/elements/pro-forms/actions/Add_User_Meta.php');
                                            $action = new \Bricksforge\ProForms\Actions\Add_User_Meta();
                                            $action->run($base);
                                            break;

                                        case 'delete_user_meta':
                                            include_once(BRICKSFORGE_PATH . '/includes/elements/pro-forms/actions/Delete_User_Meta.php');
                                            $action = new \Bricksforge\ProForms\Actions\Delete_User_Meta();
                                            $action->run($base);
                                            break;

                                        case 'post_create':
                                            include_once(BRICKSFORGE_PATH . '/includes/elements/pro-forms/actions/Create_Post.php');
                                            $action = new \Bricksforge\ProForms\Actions\Create_Post();
                                            $action->run($base);
                                            break;

                                        case 'post_update':
                                            include_once(BRICKSFORGE_PATH . '/includes/elements/pro-forms/actions/Update_Post.php');
                                            $action = new \Bricksforge\ProForms\Actions\Update_Post();
                                            $action->run($base);
                                            break;

                                        case 'post_delete':
                                            include_once(BRICKSFORGE_PATH . '/includes/elements/pro-forms/actions/Delete_Post.php');
                                            $action = new \Bricksforge\ProForms\Actions\Delete_Post();
                                            $action->run($base);
                                            break;

                                        case 'mailchimp':
                                            include_once(BRICKSFORGE_PATH . '/includes/elements/pro-forms/actions/Mailchimp.php');
                                            $action = new \Bricksforge\ProForms\Actions\Mailchimp();
                                            $action->run($base);
                                            break;

                                        case 'create_submission':
                                            include_once(BRICKSFORGE_PATH . '/includes/elements/pro-forms/actions/Create_Submission.php');
                                            $action = new \Bricksforge\ProForms\Actions\Create_Submission();
                                            $action->run($base);
                                            break;

                                        case 'login':
                                            include_once(BRICKSFORGE_PATH . '/includes/elements/pro-forms/actions/Login.php');
                                            $action = new \Bricksforge\ProForms\Actions\Login();
                                            $action->run($base);
                                            break;

                                        case 'register':
                                            include_once(BRICKSFORGE_PATH . '/includes/elements/pro-forms/actions/Register.php');
                                            $action = new \Bricksforge\ProForms\Actions\Register();
                                            $action->run($base);
                                            break;

                                        case 'confetti':
                                            // Confetti is a frontend-only action, just mark it in results
                                            $base->set_result([
                                                'action'  => 'confetti',
                                                'status' => 'success',
                                            ]);
                                            break;

                                        default:
                                            error_log('Bricksforge Stripe: Unknown action: ' . $action_name);
                                            break;
                                    }
                                }

                                error_log('Bricksforge Stripe: All pending actions executed successfully');

                                // Get Thank You URL from original form data
                                $thank_you_url = isset($form_data['thank_you_url']) ? $form_data['thank_you_url'] : null;

                                // Mark actions as completed
                                set_transient($pending_actions_key, array_merge($pending_actions_data, [
                                    'status' => 'completed',
                                    'completed_at' => time(),
                                    'results' => $base->results ?? [],
                                    'thank_you_url' => $thank_you_url
                                ]), 10 * MINUTE_IN_SECONDS); // Keep for 10 minutes for status polling

                            } catch (\Exception $e) {
                                error_log('Bricksforge Stripe: Error executing pending actions - ' . $e->getMessage());

                                // Get Thank You URL from original form data
                                $thank_you_url = isset($form_data['thank_you_url']) ? $form_data['thank_you_url'] : null;

                                // Mark as completed even if error (payment was successful)
                                set_transient($pending_actions_key, array_merge($pending_actions_data, [
                                    'status' => 'completed',
                                    'completed_at' => time(),
                                    'error' => $e->getMessage(),
                                    'thank_you_url' => $thank_you_url
                                ]), 10 * MINUTE_IN_SECONDS);
                            }
                        } else {
                            // No actions to execute (only Stripe payment), mark as completed immediately
                            error_log('Bricksforge Stripe: No pending actions, marking as completed');

                            // Get Thank You URL from original form data
                            $thank_you_url = isset($form_data['thank_you_url']) ? $form_data['thank_you_url'] : null;

                            set_transient($pending_actions_key, array_merge($pending_actions_data, [
                                'status' => 'completed',
                                'completed_at' => time(),
                                'results' => [],
                                'thank_you_url' => $thank_you_url
                            ]), 10 * MINUTE_IN_SECONDS);
                        }
                    } else {
                        // No pending actions data found - this shouldn't happen but handle it gracefully
                        error_log('Bricksforge Stripe: No pending actions data found for session ' . $session_identifier);
                    }

                    // Store payment information for potential future use
                    $payment_data = [
                        'session_id' => $session->id,
                        'payment_status' => $session->payment_status,
                        'amount_total' => $session->amount_total,
                        'currency' => $session->currency,
                        'customer_email' => $session->customer_details->email ?? '',
                        'payment_intent' => $session->payment_intent ?? '',
                        'completed_at' => time(),
                    ];

                    // Store payment data as a transient for 7 days (for potential retrieval)
                    set_transient('brf_stripe_payment_' . $session->id, $payment_data, 7 * DAY_IN_SECONDS);

                    // Fire action hook for developers to extend functionality
                    do_action('bricksforge/stripe/payment_completed', $session, $form_data, $payment_data, $action_results);

                    // Log successful webhook processing
                    error_log('Bricksforge Stripe: Webhook processed successfully for session ' . $session->id);

                    // Clean up transients
                    delete_transient($transient_key);
                    delete_transient('brf_stripe_session_' . $session->id);
                    delete_option('brf_stripe_session_' . $session->id);

                    break;

                case 'checkout.session.expired':
                    $session = $event->data->object;
                    error_log('Bricksforge Stripe: Checkout session expired ' . $session->id);

                    // Clean up expired session data
                    $session_identifier = isset($session->metadata->brf_session_identifier) ? $session->metadata->brf_session_identifier : '';
                    if (!empty($session_identifier)) {
                        delete_transient('brf_stripe_session_' . $session_identifier);
                        delete_transient('brf_stripe_session_' . $session->id);
                        delete_option('brf_stripe_session_' . $session->id);
                    }
                    break;

                default:
                    // Unhandled event type
                    error_log('Bricksforge Stripe Webhook: Unhandled event type ' . $event->type);
            }

            // Return 200 OK to Stripe
            return rest_ensure_response([
                'success' => true,
                'message' => 'Webhook received',
            ]);
        } catch (\Exception $e) {
            error_log('Bricksforge Stripe Webhook Error: ' . $e->getMessage());
            return new \WP_Error('webhook_error', 'Webhook processing failed', ['status' => 500]);
        }
    }

    /**
     * Handle Mollie Webhook
     * Called by Mollie when payment status changes
     *
     * @param WP_REST_Request $request The request object
     * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure
     * @since 3.1.8
     */
    public function handle_mollie_webhook($request)
    {
        // Include Mollie SDK
        require_once BRICKSFORGE_PATH . '/includes/vendor/mollie/init.php';

        try {
            // Get Mollie API key from element settings
            $activated_elements = get_option('brf_activated_elements');
            $mollie_settings = null;

            if (is_array($activated_elements)) {
                foreach ($activated_elements as $element) {
                    if (isset($element->id) && $element->id === 5) {
                        $mollie_settings = isset($element->settings) ? $element->settings : null;
                        break;
                    }
                }
            }

            // Get the payment ID from the webhook request
            // Mollie sends POST with 'id' parameter containing the payment ID
            $payment_id = $request->get_param('id');

            // If no payment ID, this is likely a test ping from Mollie - respond with 200 OK
            if (empty($payment_id)) {
                return rest_ensure_response([
                    'success' => true,
                    'message' => 'Webhook endpoint active',
                ]);
            }

            if (empty($mollie_settings) || empty($mollie_settings->mollieApiKey)) {
                error_log('Bricksforge Mollie Webhook: API key not configured');
                // Still return 200 to acknowledge receipt, but log the error
                return rest_ensure_response([
                    'success' => false,
                    'message' => 'Webhook received but API key not configured',
                ]);
            }

            $utils = new \Bricksforge\Api\Utils();
            $api_key = $utils->decrypt($mollie_settings->mollieApiKey);

            // Verify webhook signature if configured
            $webhook_secret = null;
            if (!empty($mollie_settings->mollieWebhookSecret)) {
                $webhook_secret = $utils->decrypt($mollie_settings->mollieWebhookSecret);

                // Get the signature header
                $signature = $request->get_header('x-mollie-signature');

                if (!empty($signature) && !empty($webhook_secret)) {
                    try {
                        $validator = new \Mollie\Api\Webhooks\SignatureValidator($webhook_secret);
                        $isValid = $validator->validatePayload($request->get_body(), $signature);

                        if (!$isValid) {
                            error_log('Bricksforge Mollie Webhook: Invalid signature');
                            return new \WP_Error('webhook_error', 'Invalid signature', ['status' => 400]);
                        }
                    } catch (\Mollie\Api\Exceptions\InvalidSignatureException $e) {
                        error_log('Bricksforge Mollie Webhook: Signature verification failed - ' . $e->getMessage());
                        return new \WP_Error('webhook_error', 'Invalid signature', ['status' => 400]);
                    }
                }
            }

            // Initialize Mollie client
            $mollie = new \Mollie\Api\MollieApiClient();
            $mollie->setApiKey($api_key);

            // Retrieve the payment from Mollie API
            $payment = $mollie->payments->get($payment_id);

            // Get session identifier from payment metadata
            $session_identifier = isset($payment->metadata->brf_session_identifier) ? $payment->metadata->brf_session_identifier : '';

            if (empty($session_identifier)) {
                error_log('Bricksforge Mollie Webhook: No session identifier in metadata');
                return rest_ensure_response(['success' => true, 'message' => 'Webhook received - no session identifier']);
            }

            // Check if payment is paid
            if (!$payment->isPaid()) {
                // Payment not completed yet, acknowledge webhook but don't process
                error_log('Bricksforge Mollie Webhook: Payment ' . $payment_id . ' not paid yet, status: ' . $payment->status);
                return rest_ensure_response(['success' => true, 'message' => 'Webhook received - payment pending']);
            }

            // Verify the session identifier exists
            $stored_session_identifier = get_transient('brf_mollie_payment_' . $payment_id);

            if ($stored_session_identifier !== $session_identifier) {
                error_log('Bricksforge Mollie Webhook: Session identifier mismatch');
                return rest_ensure_response(['success' => true, 'message' => 'Webhook received - session mismatch']);
            }

            // Retrieve stored form data
            $transient_key = 'brf_mollie_session_' . $session_identifier;
            $form_data = get_transient($transient_key);

            if (empty($form_data)) {
                error_log('Bricksforge Mollie Webhook: Form data not found for session ' . $session_identifier);
                return rest_ensure_response(['success' => true, 'message' => 'Webhook received - form data expired']);
            }

            // Execute remaining form actions after payment confirmation
            $pending_actions_key = 'brf_mollie_pending_actions_' . $session_identifier;
            $pending_actions_data = get_transient($pending_actions_key);

            $action_results = [];

            if (!empty($pending_actions_data)) {
                $form_actions = isset($pending_actions_data['form_actions']) ? $pending_actions_data['form_actions'] : [];
                $has_actions = !empty($form_actions);

                if ($has_actions) {
                    error_log('Bricksforge Mollie: Executing ' . count($form_actions) . ' pending actions');

                    // Execute all pending actions
                    try {
                        // Include Base class
                        if (!class_exists('\Bricksforge\ProForms\Actions\Base')) {
                            include_once(BRICKSFORGE_PATH . '/includes/elements/pro-forms/actions/Base.php');
                        }

                        // Recreate the Base object with stored data
                        $base = new \Bricksforge\ProForms\Actions\Base(
                            $pending_actions_data['form_settings'],
                            $pending_actions_data['form_data'],
                            $pending_actions_data['form_files'],
                            $pending_actions_data['post_id'],
                            $pending_actions_data['form_id'],
                            $pending_actions_data['dynamic_post_id'],
                            $pending_actions_data['form_structure'],
                            $pending_actions_data['post_context']
                        );

                        // Execute each pending action (same as Stripe webhook)
                        foreach ($form_actions as $action_name) {
                            error_log('Bricksforge Mollie: Executing action: ' . $action_name);

                            switch ($action_name) {
                                case 'email':
                                    include_once(BRICKSFORGE_PATH . '/includes/elements/pro-forms/actions/Email.php');
                                    $action = new \Bricksforge\ProForms\Actions\Email();
                                    $action->run($base);
                                    break;

                                case 'webhook':
                                    include_once(BRICKSFORGE_PATH . '/includes/elements/pro-forms/actions/Webhook.php');
                                    $action = new \Bricksforge\ProForms\Actions\Webhook();
                                    $action->run($base);
                                    break;

                                case 'redirect':
                                    include_once(BRICKSFORGE_PATH . '/includes/elements/pro-forms/actions/Redirect.php');
                                    $action = new \Bricksforge\ProForms\Actions\Redirect();
                                    $result = $action->run($base);
                                    if ($result) {
                                        $action_results['redirect'] = $result;
                                    }
                                    break;

                                case 'reload':
                                    include_once(BRICKSFORGE_PATH . '/includes/elements/pro-forms/actions/Reload.php');
                                    $action = new \Bricksforge\ProForms\Actions\Reload();
                                    $action->run($base);
                                    break;

                                case 'update_post_meta':
                                    include_once(BRICKSFORGE_PATH . '/includes/elements/pro-forms/actions/Update_Post_Meta.php');
                                    $action = new \Bricksforge\ProForms\Actions\Update_Post_Meta();
                                    $action->run($base);
                                    break;

                                case 'add_post_meta':
                                    include_once(BRICKSFORGE_PATH . '/includes/elements/pro-forms/actions/Add_Post_Meta.php');
                                    $action = new \Bricksforge\ProForms\Actions\Add_Post_Meta();
                                    $action->run($base);
                                    break;

                                case 'delete_post_meta':
                                    include_once(BRICKSFORGE_PATH . '/includes/elements/pro-forms/actions/Delete_Post_Meta.php');
                                    $action = new \Bricksforge\ProForms\Actions\Delete_Post_Meta();
                                    $action->run($base);
                                    break;

                                case 'add_option':
                                    include_once(BRICKSFORGE_PATH . '/includes/elements/pro-forms/actions/Add_Option.php');
                                    $action = new \Bricksforge\ProForms\Actions\Add_Option();
                                    $action->run($base);
                                    break;

                                case 'update_option':
                                    include_once(BRICKSFORGE_PATH . '/includes/elements/pro-forms/actions/Update_Option.php');
                                    $action = new \Bricksforge\ProForms\Actions\Update_Option();
                                    $action->run($base);
                                    break;

                                case 'delete_option':
                                    include_once(BRICKSFORGE_PATH . '/includes/elements/pro-forms/actions/Delete_Option.php');
                                    $action = new \Bricksforge\ProForms\Actions\Delete_Option();
                                    $action->run($base);
                                    break;

                                case 'update_user_meta':
                                    include_once(BRICKSFORGE_PATH . '/includes/elements/pro-forms/actions/Update_User_Meta.php');
                                    $action = new \Bricksforge\ProForms\Actions\Update_User_Meta();
                                    $action->run($base);
                                    break;

                                case 'add_user_meta':
                                    include_once(BRICKSFORGE_PATH . '/includes/elements/pro-forms/actions/Add_User_Meta.php');
                                    $action = new \Bricksforge\ProForms\Actions\Add_User_Meta();
                                    $action->run($base);
                                    break;

                                case 'delete_user_meta':
                                    include_once(BRICKSFORGE_PATH . '/includes/elements/pro-forms/actions/Delete_User_Meta.php');
                                    $action = new \Bricksforge\ProForms\Actions\Delete_User_Meta();
                                    $action->run($base);
                                    break;

                                case 'post_create':
                                    include_once(BRICKSFORGE_PATH . '/includes/elements/pro-forms/actions/Create_Post.php');
                                    $action = new \Bricksforge\ProForms\Actions\Create_Post();
                                    $action->run($base);
                                    break;

                                case 'post_update':
                                    include_once(BRICKSFORGE_PATH . '/includes/elements/pro-forms/actions/Update_Post.php');
                                    $action = new \Bricksforge\ProForms\Actions\Update_Post();
                                    $action->run($base);
                                    break;

                                case 'post_delete':
                                    include_once(BRICKSFORGE_PATH . '/includes/elements/pro-forms/actions/Delete_Post.php');
                                    $action = new \Bricksforge\ProForms\Actions\Delete_Post();
                                    $action->run($base);
                                    break;

                                case 'mailchimp':
                                    include_once(BRICKSFORGE_PATH . '/includes/elements/pro-forms/actions/Mailchimp.php');
                                    $action = new \Bricksforge\ProForms\Actions\Mailchimp();
                                    $action->run($base);
                                    break;

                                case 'create_submission':
                                    include_once(BRICKSFORGE_PATH . '/includes/elements/pro-forms/actions/Create_Submission.php');
                                    $action = new \Bricksforge\ProForms\Actions\Create_Submission();
                                    $action->run($base);
                                    break;

                                case 'login':
                                    include_once(BRICKSFORGE_PATH . '/includes/elements/pro-forms/actions/Login.php');
                                    $action = new \Bricksforge\ProForms\Actions\Login();
                                    $action->run($base);
                                    break;

                                case 'register':
                                    include_once(BRICKSFORGE_PATH . '/includes/elements/pro-forms/actions/Register.php');
                                    $action = new \Bricksforge\ProForms\Actions\Register();
                                    $action->run($base);
                                    break;

                                case 'confetti':
                                    // Confetti is a frontend-only action, just mark it in results
                                    $base->set_result([
                                        'action'  => 'confetti',
                                        'status' => 'success',
                                    ]);
                                    break;

                                default:
                                    error_log('Bricksforge Mollie: Unknown action: ' . $action_name);
                                    break;
                            }
                        }

                        error_log('Bricksforge Mollie: All pending actions executed successfully');

                        // Get Thank You URL from original form data
                        $thank_you_url = isset($form_data['thank_you_url']) ? $form_data['thank_you_url'] : null;

                        // Mark actions as completed
                        set_transient($pending_actions_key, array_merge($pending_actions_data, [
                            'status' => 'completed',
                            'completed_at' => time(),
                            'results' => $base->results ?? [],
                            'thank_you_url' => $thank_you_url
                        ]), 10 * MINUTE_IN_SECONDS);
                    } catch (\Exception $e) {
                        error_log('Bricksforge Mollie: Error executing pending actions - ' . $e->getMessage());

                        // Get Thank You URL from original form data
                        $thank_you_url = isset($form_data['thank_you_url']) ? $form_data['thank_you_url'] : null;

                        // Mark as completed even if error (payment was successful)
                        set_transient($pending_actions_key, array_merge($pending_actions_data, [
                            'status' => 'completed',
                            'completed_at' => time(),
                            'error' => $e->getMessage(),
                            'thank_you_url' => $thank_you_url
                        ]), 10 * MINUTE_IN_SECONDS);
                    }
                } else {
                    // No actions to execute, mark as completed immediately
                    error_log('Bricksforge Mollie: No pending actions, marking as completed');

                    $thank_you_url = isset($form_data['thank_you_url']) ? $form_data['thank_you_url'] : null;

                    set_transient($pending_actions_key, array_merge($pending_actions_data, [
                        'status' => 'completed',
                        'completed_at' => time(),
                        'results' => [],
                        'thank_you_url' => $thank_you_url
                    ]), 10 * MINUTE_IN_SECONDS);
                }
            } else {
                error_log('Bricksforge Mollie: No pending actions data found for session ' . $session_identifier);
            }

            // Store payment information
            $payment_data = [
                'payment_id' => $payment->id,
                'payment_status' => $payment->status,
                'amount' => $payment->amount->value,
                'currency' => $payment->amount->currency,
                'method' => $payment->method ?? '',
                'completed_at' => time(),
            ];

            // Store payment data for 7 days
            set_transient('brf_mollie_payment_data_' . $payment->id, $payment_data, 7 * DAY_IN_SECONDS);

            // Fire action hook for developers
            do_action('bricksforge/mollie/payment_completed', $payment, $form_data, $payment_data, $action_results);

            // Log successful webhook processing
            error_log('Bricksforge Mollie: Webhook processed successfully for payment ' . $payment->id);

            // Clean up transients
            delete_transient($transient_key);
            delete_transient('brf_mollie_payment_' . $payment->id);

            // Return 200 OK to Mollie
            return rest_ensure_response([
                'success' => true,
                'message' => 'Webhook received',
            ]);
        } catch (\Mollie\Api\Exceptions\ApiException $e) {
            error_log('Bricksforge Mollie Webhook API Error: ' . $e->getMessage());
            return new \WP_Error('webhook_error', 'API error', ['status' => 500]);
        } catch (\Exception $e) {
            error_log('Bricksforge Mollie Webhook Error: ' . $e->getMessage());
            return new \WP_Error('webhook_error', 'Webhook processing failed', ['status' => 500]);
        }
    }

    /**
     * Check Payment Status and Action Completion (Generic)
     * Works with any payment provider (Stripe, PayPal, Mollie, etc.)
     *
     * @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
     * @since 3.1.8
     */
    public function check_payment_status($request)
    {
        $provider = $request->get_param('provider');
        $session_identifier = $request->get_param('session_identifier');

        if (empty($provider) || empty($session_identifier)) {
            return new \WP_Error('invalid_request', 'Missing provider or session identifier', ['status' => 400]);
        }

        // Validate provider (only alphanumeric and underscore allowed)
        if (!preg_match('/^[a-zA-Z0-9_]+$/', $provider)) {
            return new \WP_Error('invalid_provider', 'Invalid provider name', ['status' => 400]);
        }

        // Check if pending actions exist
        $pending_actions_key = 'brf_' . $provider . '_pending_actions_' . $session_identifier;
        $pending_actions_data = get_transient($pending_actions_key);

        if (empty($pending_actions_data)) {
            return rest_ensure_response([
                'status' => 'not_found',
                'message' => 'Session not found or expired',
            ]);
        }

        $status = isset($pending_actions_data['status']) ? $pending_actions_data['status'] : 'pending';

        $response = [
            'status' => $status,
        ];

        if ($status === 'completed') {
            $response['completed_at'] = $pending_actions_data['completed_at'] ?? time();
            $response['results'] = $pending_actions_data['results'] ?? [];
            $response['thank_you_url'] = $pending_actions_data['thank_you_url'] ?? null;
        } elseif ($status === 'pending') {
            // FIRST: Check actual payment status with the provider
            // This helps detect cancelled/abandoned payments before showing webhook warnings
            $real_payment_status = $this->check_real_payment_status($provider, $pending_actions_data);

            if ($real_payment_status !== null) {
                // Payment was cancelled, expired, or failed at the provider
                if (in_array($real_payment_status, ['cancelled', 'expired', 'failed', 'open'])) {
                    $response['status'] = 'cancelled';
                    $response['payment_status'] = $real_payment_status;
                    $response['message'] = __('Payment was cancelled or not completed. Please try again.', 'bricksforge');
                    return rest_ensure_response($response);
                }

                // Payment is actually paid but webhook hasn't processed yet
                if ($real_payment_status === 'paid') {
                    $response['payment_status'] = 'paid';
                    $response['message'] = __('Payment received, processing...', 'bricksforge');
                }
            }

            // Check if webhook is configured - if not, add warning (only if payment status is unknown or paid)
            $activated_elements = get_option('brf_activated_elements');
            $webhook_configured = false;

            // Provider-specific webhook secret field names
            $webhook_secret_field_map = [
                'stripe' => 'stripeWebhookSecret',
                'paypal' => 'paypalWebhookSecret',
                'mollie' => 'mollieWebhookSecret',
                // Add more providers here as needed
            ];

            $webhook_field = isset($webhook_secret_field_map[$provider]) ? $webhook_secret_field_map[$provider] : null;

            if ($webhook_field && is_array($activated_elements)) {
                foreach ($activated_elements as $element) {
                    if (isset($element->id) && $element->id === 5) { // Pro Forms element
                        $settings = isset($element->settings) ? $element->settings : null;
                        if (!empty($settings) && !empty($settings->{$webhook_field})) {
                            $webhook_configured = true;
                        }
                        break;
                    }
                }
            }

            if (!$webhook_configured && $real_payment_status !== 'paid') {
                $provider_name = ucfirst($provider);
                $response['warning'] = 'webhook_not_configured';
                $response['message'] = $provider_name . ' webhook is not configured. Payment cannot be verified automatically.';
            }

            // Check how long the session has been pending
            $created_at = isset($pending_actions_data['created_at']) ? $pending_actions_data['created_at'] : time();
            $pending_duration = time() - $created_at;

            $response['pending_duration'] = $pending_duration;
        }

        return rest_ensure_response($response);
    }

    /**
     * Check real payment status with the payment provider
     *
     * @param string $provider The payment provider (stripe, mollie, etc.)
     * @param array $pending_actions_data The stored pending actions data
     * @return string|null The real payment status or null if unable to check
     * @since 3.1.8
     */
    private function check_real_payment_status($provider, $pending_actions_data)
    {
        $activated_elements = get_option('brf_activated_elements');
        $provider_settings = null;

        if (is_array($activated_elements)) {
            foreach ($activated_elements as $element) {
                if (isset($element->id) && $element->id === 5) {
                    $provider_settings = isset($element->settings) ? $element->settings : null;
                    break;
                }
            }
        }

        if (empty($provider_settings)) {
            return null;
        }

        $utils = new \Bricksforge\Api\Utils();

        try {
            if ($provider === 'mollie') {
                return $this->check_mollie_payment_status($pending_actions_data, $provider_settings, $utils);
            } elseif ($provider === 'stripe') {
                return $this->check_stripe_payment_status($pending_actions_data, $provider_settings, $utils);
            }
        } catch (\Exception $e) {
            error_log('Bricksforge Payment Status Check Error: ' . $e->getMessage());
            return null;
        }

        return null;
    }

    /**
     * Check Mollie payment status
     *
     * @param array $pending_actions_data The stored pending actions data
     * @param object $provider_settings The provider settings
     * @param object $utils The utils instance
     * @return string|null The payment status
     */
    private function check_mollie_payment_status($pending_actions_data, $provider_settings, $utils)
    {
        if (empty($provider_settings->mollieApiKey)) {
            return null;
        }

        $payment_id = isset($pending_actions_data['payment_id']) ? $pending_actions_data['payment_id'] : null;

        if (empty($payment_id)) {
            return null;
        }

        // Include Mollie SDK
        require_once BRICKSFORGE_PATH . '/includes/vendor/mollie/init.php';

        $api_key = $utils->decrypt($provider_settings->mollieApiKey);
        $mollie = new \Mollie\Api\MollieApiClient();
        $mollie->setApiKey($api_key);

        $payment = $mollie->payments->get($payment_id);

        // Mollie payment statuses: open, canceled, pending, authorized, expired, failed, paid
        // Map to our simplified statuses
        switch ($payment->status) {
            case 'paid':
                return 'paid';
            case 'canceled':
                return 'cancelled';
            case 'expired':
                return 'expired';
            case 'failed':
                return 'failed';
            case 'open':
                return 'open'; // User hasn't completed payment yet
            case 'pending':
            case 'authorized':
            default:
                return null; // Still processing
        }
    }

    /**
     * Check Stripe payment status
     *
     * @param array $pending_actions_data The stored pending actions data
     * @param object $provider_settings The provider settings
     * @param object $utils The utils instance
     * @return string|null The payment status
     */
    private function check_stripe_payment_status($pending_actions_data, $provider_settings, $utils)
    {
        if (empty($provider_settings->stripeSecretKey)) {
            return null;
        }

        $checkout_session_id = isset($pending_actions_data['checkout_session_id']) ? $pending_actions_data['checkout_session_id'] : null;

        if (empty($checkout_session_id)) {
            return null;
        }

        // Include Stripe SDK
        require_once BRICKSFORGE_PATH . '/includes/vendor/stripe-php/init.php';

        $secret_key = $utils->decrypt($provider_settings->stripeSecretKey);
        \Stripe\Stripe::setApiKey($secret_key);

        $session = \Stripe\Checkout\Session::retrieve($checkout_session_id);

        // Stripe Checkout Session statuses: open, complete, expired
        // payment_status: paid, unpaid, no_payment_required
        switch ($session->status) {
            case 'complete':
                if ($session->payment_status === 'paid') {
                    return 'paid';
                }
                return null;
            case 'expired':
                return 'expired';
            case 'open':
                return 'open'; // User hasn't completed payment yet
            default:
                return null;
        }
    }

    /**
     * Check Stripe Payment Status and Action Completion (Backwards Compatibility)
     *
     * @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
     * @since 3.1.8
     * @deprecated Use check_payment_status() instead
     */
    public function check_stripe_status($request)
    {
        // Add provider parameter for generic method
        $request->set_param('provider', 'stripe');
        return $this->check_payment_status($request);
    }
}
