<?php

namespace WPUF\UserDirectory\Api;

use WP_Error;
use WP_REST_Request;
use WP_REST_Response;
use WP_REST_Server;
use WP_User;
use Exception;

class User_Listing extends WPUF_Pro_REST_Controller {

    /**
     * Route name
     *
     * @since 4.2.0
     *
     * @var string
     */
    protected $rest_base = 'user_directory';

    public function register_routes() {
        register_rest_route(
            $this->namespace,
            '/' . $this->rest_base,
            [
                [
                    'methods'             => WP_REST_Server::CREATABLE,
                    'callback'            => [ $this, 'create_item' ],
                    'permission_callback' => [ $this, 'permissions_check' ],
                    'args'                => $this->get_endpoint_args_for_item_schema( true ),
                ],
                [
                    'methods'             => WP_REST_Server::READABLE,
                    'callback'            => [ $this, 'get_items' ],
                    'permission_callback' => [ $this, 'permissions_check' ],
                ],
            ]
        );
        // Duplicate endpoint
        register_rest_route(
            $this->namespace,
            '/' . $this->rest_base . '/duplicate',
            [
                [
                    'methods'             => WP_REST_Server::CREATABLE,
                    'callback'            => [ $this, 'duplicate_item' ],
                    'permission_callback' => [ $this, 'permissions_check' ],
                ],
            ]
        );
        // Delete endpoint
        register_rest_route(
            $this->namespace,
            '/' . $this->rest_base . '/(?P<id>\\d+)',
            [
                [
                    'methods'             => WP_REST_Server::DELETABLE,
                    'callback'            => [ $this, 'delete_item' ],
                    'permission_callback' => [ $this, 'permissions_check' ],
                ],
            ]
        );
        // Update endpoint
        register_rest_route(
            $this->namespace,
            '/' . $this->rest_base . '/(?P<id>\\d+)',
            [
                [
                    'methods'             => WP_REST_Server::EDITABLE, // PUT/PATCH/POST
                    'callback'            => [ $this, 'update_item' ],
                    'permission_callback' => [ $this, 'permissions_check' ],
                ],
            ]
        );
        // Add user count endpoint
        register_rest_route(
            $this->namespace,
            '/' . $this->rest_base . '/user_count',
            [
                [
                    'methods'             => WP_REST_Server::READABLE,
                    'callback'            => [ $this, 'get_user_count' ],
                    'permission_callback' => [ $this, 'permissions_check' ],
                    'args'                => [
                        'roles' => [
                            'description' => __( 'Comma-separated list of user roles.', 'wpuf-pro' ),
                            'type'        => 'string',
                            'required'    => false,
                        ],
                        'exclude_users' => [
                            'description' => __( 'Comma-separated list of user IDs to exclude.', 'wpuf-pro' ),
                            'type'        => 'string',
                            'required'    => false,
                        ],
                        'max_item' => [
                            'description' => __( 'Maximum number of users to count.', 'wpuf-pro' ),
                            'type'        => 'integer',
                            'required'    => false,
                        ],
                    ],
                ],
            ]
        );
        // Add single directory fetch endpoint
        register_rest_route(
            $this->namespace,
            '/' . $this->rest_base . '/(?P<id>\\d+)',
            [
                [
                    'methods'             => WP_REST_Server::READABLE,
                    'callback'            => [ $this, 'get_item' ],
                    'permission_callback' => [ $this, 'permissions_check' ],
                ],
            ]
        );
        // Add user search endpoint (public)
        register_rest_route(
            $this->namespace,
            '/' . $this->rest_base . '/search',
            [
                [
                    'methods'             => WP_REST_Server::READABLE,
                    'callback'            => [ $this, 'get_users_search' ],
                    'permission_callback' => '__return_true',
                    'args'                => [
                        'search'            => [
                            'description' => __( 'Search term.', 'wpuf-pro' ),
                            'type'        => 'string',
                            'required'    => false,
                        ],
                        'searchable_fields' => [
                            'description' => __( 'Fields to search (array).', 'wpuf-pro' ),
                            'type'        => 'array',
                            'items'       => [ 'type' => 'string' ],
                            'required'    => false,
                        ],
                        'roles'             => [
                            'description' => __( 'Comma-separated list of user roles.', 'wpuf-pro' ),
                            'type'        => 'string',
                            'required'    => false,
                        ],
                        'per_page'          => [
                            'description' => __( 'Users per page.', 'wpuf-pro' ),
                            'type'        => 'integer',
                            'required'    => false,
                        ],
                        'page'              => [
                            'description' => __( 'Page number.', 'wpuf-pro' ),
                            'type'        => 'integer',
                            'required'    => false,
                        ],
                        'orderby'           => [
                            'description' => __( 'Order by field.', 'wpuf-pro' ),
                            'type'        => 'string',
                            'required'    => false,
                        ],
                        'order'             => [
                            'description' => __( 'Order direction.', 'wpuf-pro' ),
                            'type'        => 'string',
                            'required'    => false,
                        ],
                        'base_url'          => [
                            'description' => __( 'Base URL for pagination links.', 'wpuf-pro' ),
                            'type'        => 'string',
                            'required'    => false,
                        ],
                        'directory_layout'  => [
                            'description' => __( 'Directory layout to use.', 'wpuf-pro' ),
                            'type'        => 'string',
                            'required'    => false,
                        ],
                        'search_by'         => [
                            'description' => __( 'Field to search by (user_login, user_email, display_name, or meta field).', 'wpuf-pro' ),
                            'type'        => 'string',
                            'required'    => false,
                        ],
                        'users_per_row'     => [
                            'description' => __( 'Number of users to display per row in grid layouts.', 'wpuf-pro' ),
                            'type'        => 'integer',
                            'required'    => false,
                        ],
                    ],
                ],
            ]
        );

        // Add preview endpoint for block editor
        register_rest_route(
            $this->namespace,
            '/' . $this->rest_base . '/preview',
            [
                [
                    'methods'             => WP_REST_Server::READABLE,
                    'callback'            => [ $this, 'get_users_preview' ],
                    'permission_callback' => '__return_true',
                    'args'                => [
                        'roles'             => [
                            'description' => __( 'Comma-separated list of user roles.', 'wpuf-pro' ),
                            'type'        => 'string',
                            'required'    => false,
                        ],
                        'per_page'          => [
                            'description' => __( 'Users per page.', 'wpuf-pro' ),
                            'type'        => 'integer',
                            'required'    => false,
                        ],
                        'page'              => [
                            'description' => __( 'Page number.', 'wpuf-pro' ),
                            'type'        => 'integer',
                            'required'    => false,
                        ],
                        'orderby'           => [
                            'description' => __( 'Order by field.', 'wpuf-pro' ),
                            'type'        => 'string',
                            'required'    => false,
                        ],
                        'order'             => [
                            'description' => __( 'Order direction.', 'wpuf-pro' ),
                            'type'        => 'string',
                            'required'    => false,
                        ],
                        'exclude_users'     => [
                            'description' => __( 'Exclude user IDs (array).', 'wpuf-pro' ),
                            'type'        => 'array',
                            'items'       => [ 'type' => 'integer' ],
                            'required'    => false,
                        ],
                        'searchable_fields' => [
                            'description' => __( 'Fields to search (array).', 'wpuf-pro' ),
                            'type'        => 'array',
                            'items'       => [ 'type' => 'string' ],
                            'required'    => false,
                        ],
                    ],
                ],
            ]
        );
    }

    /**
     * Create a new item.
     *
     * @since 4.2.0
     *
     * @param WP_REST_Request $request Full details about the request.
     *
     * @return WP_Error|WP_REST_Response
     */
    public function create_item( $request ) {
        $params = $request->get_json_params();

        // Sanitize fields
        $post_title = ! empty( $params['directory_title'] ) ? sanitize_text_field( $params['directory_title'] ) : __( 'Unnamed Directory', 'wpuf-pro' );

        // Include all fields that need to be saved
        $post_content = [
            'roles', 'directory_layout', 'profile_layout', 'profile_size', 'avatar_size', 'profile_base', 'profile_tabs',
            'profile_tabs_labels', 'profile_tabs_order', 'excluded_users', 'exclude_users', 'about_tabs_content', 'social_fields', 'enable_search', 'search_placeholder',
            'searchable_fields', 'max_item', 'max_item_per_page', 'columns', 'single_profile_permalink', 'show_avatar_in_table',
            'orderby', 'order', 'default_sort_by', 'default_sort_order', 'users_per_page', 'max_users', 'user_limit_type',
            'default_active_tab', 'enable_tabs', 'posts_per_tab', 'profile_fields', 'custom_fields',
            'show_avatar', 'show_email', 'show_phone', 'show_user_bio', 'show_username', 'show_website'
        ];

        $meta_input = [];

        foreach ( $post_content as $field ) {
            if ( isset( $params[ $field ] ) ) {
                // Special handling for complex fields
                if ( $field === 'excluded_users' || $field === 'about_tabs_content' || $field === 'profile_tabs_labels' || $field === 'profile_tabs_order' ) {
                    // These fields contain complex objects/arrays, store as-is after validation
                    $meta_input[ $field ] = $params[ $field ];
                } elseif ( in_array( $field, ['show_avatar_in_table', 'enable_search', 'enable_tabs', 'show_avatar', 'show_email', 'show_phone', 'show_user_bio', 'show_username', 'show_website'] ) ) {
                    // Handle boolean fields
                    $meta_input[ $field ] = filter_var( $params[ $field ], FILTER_VALIDATE_BOOLEAN );
                } elseif ( is_array( $params[ $field ] ) ) {
                    $meta_input[ $field ] = array_map( 'sanitize_text_field', $params[ $field ] );
                } else {
                    $meta_input[ $field ] = sanitize_text_field( $params[ $field ] );
                }
            }
        }

        // Insert post
        $postarr = [
            'post_title'   => $post_title,
            'post_type'    => 'wpuf_user_listing',
            'post_status'  => 'publish',
            'post_content' => wp_json_encode( $meta_input ),
        ];
        $post_id = wp_insert_post( $postarr );

        if ( is_wp_error( $post_id ) ) {
            return $post_id;
        }
        if ( ! $post_id ) {
            return new \WP_Error( 'save_failed', __( 'Failed to save directory settings.', 'wpuf-pro' ), [ 'status' => 500 ] );
        }

        return rest_ensure_response( [
            'success' => true,
            'message' => __( 'Directory settings saved.', 'wpuf-pro' ),
            'post_id' => $post_id,
            'data'    => [ 'post_title' => $post_title, 'post_content' => $meta_input ],
        ] );
    }

    /**
     * Get a paginated list of user directories.
     *
     * @param WP_REST_Request $request
     * @return WP_REST_Response|WP_Error
     */
    public function get_items( $request ) {
        $per_page = ! empty( $request['per_page'] ) ? (int) sanitize_text_field( $request['per_page'] ) : 10;
        $page     = ! empty( $request['page'] ) ? (int) sanitize_text_field( $request['page'] ) : 1;
        $status   = ! empty( $request['status'] ) ? sanitize_text_field( $request['status'] ) : 'any';
        $search   = ! empty( $request['s'] ) ? sanitize_text_field( $request['s'] ) : '';
        $offset   = ( $page - 1 ) * $per_page;

        $query_args = [
            'post_type'      => 'wpuf_user_listing',
            'post_status'    => $status,
            'posts_per_page' => $per_page,
            'offset'         => $offset,
            'orderby'        => 'ID',
            'order'          => 'DESC',
        ];

        if ( $search ) {
            $query_args['s'] = $search;
            // Restrict search to post_title only
            add_filter( 'posts_search', function( $search_sql, $wp_query ) {
                global $wpdb;
                if ( empty( $search_sql ) ) {
                    return $search_sql;
                }
                $q = $wp_query->query_vars;
                if ( ! empty( $q['s'] ) ) {
                    $like = '%' . $wpdb->esc_like( $q['s'] ) . '%';
                    $search_sql = $wpdb->prepare( " AND ({$wpdb->posts}.post_title LIKE %s) ", $like );
                }
                return $search_sql;
            }, 10, 2 );
        }

        $query = new \WP_Query( $query_args );

        if ( $search ) {
            // Remove the filter after query
            remove_all_filters( 'posts_search' );
        }

        $directories = [];

        if ( $query->have_posts() ) {
            while ( $query->have_posts() ) {
                $query->the_post();
                $directories[] = [
                    'ID'           => get_the_ID(),
                    'post_title'   => get_the_title(),
                    'post_status'  => get_post_status(),
                    'post_content' => get_the_content(),
                ];
            }
        }
        wp_reset_postdata();

        $total_items = $query->found_posts;
        $total_pages = $per_page > 0 ? (int) ceil($total_items / $per_page) : 1;

        return rest_ensure_response([
            'success' => true,
            'result'  => $directories,
            'pagination' => [
                'total_pages'  => $total_pages,
                'total_items'  => $total_items,
                'per_page'     => $per_page,
                'current_page' => $page,
            ],
        ]);
    }

    /**
     * Duplicate a directory post.
     *
     * @param WP_REST_Request $request
     * @return WP_REST_Response|WP_Error
     */
    public function duplicate_item( $request ) {
        $params       = $request->get_json_params();
        $directory_id = isset( $params['directory_id'] ) ? intval( $params['directory_id'] ) : 0;

        if ( ! $directory_id ) {
            return rest_ensure_response(
                [
                    'success' => false,
                    'message' => __( 'Invalid directory ID.', 'wpuf-pro' ),
                ]
            );
        }

        $post = get_post( $directory_id );

        if ( ! $post || $post->post_type !== 'wpuf_user_listing' ) {
            return rest_ensure_response(
                [
                    'success' => false,
                    'message' => __( 'Directory not found!.', 'wpuf-pro' ),
                ]
            );
        }

        $new_title   = $post->post_title . ' (#' . $post->ID . ')';

        $new_postarr = [
            'post_title'   => $new_title,
            'post_type'    => 'wpuf_user_listing',
            'post_status'  => 'publish',
            'post_content' => $post->post_content,
        ];

        $new_post_id = wp_insert_post( $new_postarr );

        if ( is_wp_error( $new_post_id ) ) {
            return rest_ensure_response(
                [
                    'success' => false,
                    'message' => __( 'Failed to duplicate directory.', 'wpuf-pro' ),
                ]
            );
        }

        return rest_ensure_response(
            [
                'success' => true,
                'message' => __( 'Directory duplicated.', 'wpuf-pro' ),
                'post_id' => $new_post_id,
                'data'    => [ 'post_title' => $new_title ],
            ]
        );
    }

    /**
     * Delete a directory post.
     *
     * @param WP_REST_Request $request
     * @return WP_REST_Response|WP_Error
     */
    public function delete_item( $request ) {
        $id = (int) $request['id'];
        $post = get_post( $id );

        if ( ! $post || $post->post_type !== 'wpuf_user_listing' ) {
            return rest_ensure_response([
                'success' => false,
                'message' => __( 'Directory not found.', 'wpuf-pro' ),
            ]);
        }

        $deleted = wp_delete_post( $id, true );

        if ( $deleted ) {
            return rest_ensure_response([
                'success' => true,
                'message' => __( 'Directory deleted.', 'wpuf-pro' ),
            ]);
        } else {
            return rest_ensure_response([
                'success' => false,
                'message' => __( 'Failed to delete directory.', 'wpuf-pro' ),
            ]);
        }
    }

    /**
     * Update a directory post.
     *
     * @param WP_REST_Request $request
     * @return WP_REST_Response|WP_Error
     */
    public function update_item( $request ) {
        $id = (int) $request['id'];
        $params = $request->get_json_params();

        $post = get_post( $id );
        if ( ! $post || $post->post_type !== 'wpuf_user_listing' ) {
            return rest_ensure_response([
                'success' => false,
                'message' => __( 'Directory not found.', 'wpuf-pro' ),
            ]);
        }

        // Sanitize fields
        $post_title = ! empty( $params['directory_title'] ) ? sanitize_text_field( $params['directory_title'] ) : $post->post_title;

        // Include all fields that need to be saved
        $post_content = [
            'roles', 'directory_layout', 'profile_layout', 'profile_size', 'avatar_size', 'profile_base', 'profile_tabs',
            'profile_tabs_labels', 'profile_tabs_order', 'excluded_users', 'exclude_users', 'about_tabs_content', 'social_fields', 'enable_search', 'search_placeholder',
            'searchable_fields', 'max_item', 'max_item_per_page', 'columns', 'single_profile_permalink', 'show_avatar_in_table',
            'orderby', 'order', 'default_sort_by', 'default_sort_order', 'users_per_page', 'max_users', 'user_limit_type',
            'default_active_tab', 'enable_tabs', 'posts_per_tab', 'profile_fields', 'custom_fields',
            'show_avatar', 'show_email', 'show_phone', 'show_user_bio', 'show_username', 'show_website'
        ];

        $meta_input = [];
        foreach ( $post_content as $field ) {
            if ( isset( $params[ $field ] ) ) {
                // Special handling for complex fields
                if ( $field === 'excluded_users' || $field === 'about_tabs_content' || $field === 'profile_tabs_labels' || $field === 'profile_tabs_order' ) {
                    // These fields contain complex objects/arrays, store as-is after validation
                    $meta_input[ $field ] = $params[ $field ];
                } elseif ( in_array( $field, ['show_avatar_in_table', 'enable_search', 'enable_tabs', 'show_avatar', 'show_email', 'show_phone', 'show_user_bio', 'show_username', 'show_website'] ) ) {
                    // Handle boolean fields
                    $meta_input[ $field ] = filter_var( $params[ $field ], FILTER_VALIDATE_BOOLEAN );
                } elseif ( is_array( $params[ $field ] ) ) {
                    $meta_input[ $field ] = array_map( 'sanitize_text_field', $params[ $field ] );
                } else {
                    $meta_input[ $field ] = sanitize_text_field( $params[ $field ] );
                }
            }
        }

        // Update post
        $postarr = [
            'ID'           => $id,
            'post_title'   => $post_title,
            'post_content' => wp_json_encode( $meta_input ),
        ];
        $updated_id = wp_update_post( $postarr );
        if ( is_wp_error( $updated_id ) ) {
            return $updated_id;
        }
        if ( ! $updated_id ) {
            return new \WP_Error( 'update_failed', __( 'Failed to update directory.', 'wpuf-pro' ), [ 'status' => 500 ] );
        }
        return rest_ensure_response([
            'success' => true,
            'message' => __( 'Directory updated.', 'wpuf-pro' ),
            'post_id' => $id,
            'data'    => [ 'post_title' => $post_title, 'post_content' => $meta_input ],
        ]);
    }

    /**
     * Get the count of users for given roles and exclusions.
     *
     * @since 4.2.0
     *
     * @param WP_REST_Request $request
     * @return WP_REST_Response|WP_Error
     */
    public function get_user_count( $request ) {
        $roles = $request->get_param( 'roles' );
        $exclude_users = $request->get_param( 'exclude_users' );
        $max_item = $request->get_param( 'max_item' );

        $args = [ 'count_total' => true ];

        // Handle roles
        if ( $roles && strtolower( $roles ) !== 'all' ) {
            $args['role__in'] = array_map( 'sanitize_text_field', array_map( 'trim', explode( ',', $roles ) ) );
        }

        // Handle excluded users
        if ( $exclude_users ) {
            $excluded_user_ids = array_map( 'intval', array_map( 'trim', explode( ',', $exclude_users ) ) );
            $excluded_user_ids = array_filter( $excluded_user_ids ); // Remove zeros/false values
            if ( ! empty( $excluded_user_ids ) ) {
                $args['exclude'] = $excluded_user_ids;
            }
        }

        $user_query = new \WP_User_Query( $args );
        $count = isset( $user_query->total_users ) ? (int) $user_query->total_users : 0;

        // Apply max_item limit if set and greater than 0
        if ( $max_item && intval( $max_item ) > 0 ) {
            $count = min( $count, intval( $max_item ) );
        }

        return rest_ensure_response([
            'success' => true,
            'count'   => $count,
        ]);
    }

    /**
     * Get a single directory by ID.
     *
     * @param WP_REST_Request $request
     * @return WP_REST_Response|WP_Error
     */
    public function get_item( $request ) {
        $id = (int) $request['id'];
        $post = get_post( $id );

        if ( ! $post || $post->post_type !== 'wpuf_user_listing' ) {
            return rest_ensure_response([
                'success' => false,
                'message' => __( 'Directory not found.', 'wpuf-pro' ),
            ]);
        }

        // Parse the post_content JSON to get directory settings
        $directory_settings = [];
        if ( ! empty( $post->post_content ) ) {
            $parsed_content = json_decode( $post->post_content, true );
            if ( is_array( $parsed_content ) ) {
                $directory_settings = $parsed_content;
            }
        }

        // Merge with defaults to ensure all settings are present
        $defaults = self::get_directory_defaults();
        $directory_settings = wp_parse_args( $directory_settings, $defaults );

        return rest_ensure_response([
            'success' => true,
            'result'  => [
                'ID'           => $post->ID,
                'post_title'   => $post->post_title,
                'post_status'  => $post->post_status,
                'post_content' => $post->post_content,
                // Include parsed settings for easy access
                'roles'                => $directory_settings['roles'] ?? 'all',
                'directory_layout'     => $directory_settings['directory_layout'] ?? 'layout-1',
                'profile_layout'       => $directory_settings['profile_layout'] ?? 'layout-1',
                'profile_size'         => $directory_settings['profile_size'] ?? 'thumbnail',
                'avatar_size'          => $directory_settings['avatar_size'] ?? '32',
                'profile_base'         => $directory_settings['profile_base'] ?? 'username',
                'max_item'             => $directory_settings['max_item'] ?? -1,
                'max_item_per_page'    => $directory_settings['max_item_per_page'] ?? 10,
                'enable_search'        => $directory_settings['enable_search'] ?? true,
                'search_placeholder'   => $directory_settings['search_placeholder'] ?? 'Search Users',
                'searchable_fields'    => $directory_settings['searchable_fields'] ?? [ 'user_login', 'user_email', 'display_name' ],
                'exclude_users'        => $directory_settings['exclude_users'] ?? [],
                'columns'              => $directory_settings['columns'] ?? [ 'display_name', 'user_email' ],
                'profile_tabs'         => $directory_settings['profile_tabs'] ?? 'about,comments,posts',
            ],
        ]);
    }

    /**
     * Convert layout name to row variant name.
     *
     * @since 4.2.0
     *
     * @param string $layout Layout name (e.g., 'layout-1', 'layout-2')
     * @return string Row variant name (e.g., 'row-1', 'row-2')
     */
    private function layout_to_row_variant( $layout ) {
        // Convert 'layout-X' to 'row-X'
        return str_replace( 'layout-', 'row-', $layout );
    }

    /**
     * Render user rows as HTML using the appropriate row template.
     *
     * @since 4.2.0
     *
     * @param array $users Array of WP_User objects.
     * @param array $args  Arguments for rendering (columns, layout, avatar_size, etc).
     * @return string Rendered HTML rows
     */
    protected function render_user_rows( $users, $args = [] ) {
        // Allow filtering of layout and row template
        // Handle both 'directory_layout' and 'directory_layouts' for compatibility
        $layout      = ! empty( $args['directory_layout'] ) ? sanitize_key( $args['directory_layout'] ) : 'layout-1';
        $row_variant = ! empty( $args['row_variant'] ) ? sanitize_key( $args['row_variant'] ) : 'row-1';

        // Auto-map directory layout to row variant if not explicitly set
        if ( empty( $args['row_variant'] ) ) {
            $row_variant = $this->layout_to_row_variant( $layout );
        }

        $columns     = ! empty( $args['columns'] ) ? (array) $args['columns'] : [ 'avatar', 'display_name', 'user_email' ];
        $avatar_size = ! empty( $args['avatar_size'] ) ? intval( $args['avatar_size'] ) : 32;

        // Allow filters for extensibility
        $layout      = apply_filters( 'wpuf_ud_rest_row_layout', $layout, $args );
        $row_variant = apply_filters( 'wpuf_ud_rest_row_variant', $row_variant, $args );
        $columns     = apply_filters( 'wpuf_ud_rest_row_columns', $columns, $args );
        $avatar_size = apply_filters( 'wpuf_ud_rest_row_avatar_size', $avatar_size, $args );

        // Handle empty users array
        if ( empty( $users ) ) {
            $search_term = ! empty( $args['search'] ) ? sanitize_text_field( $args['search'] ) : '';
            $no_users_message = ! empty( $search_term )
                ? __( 'No users found matching your search criteria.', 'wpuf-pro' )
                : __( 'No users found.', 'wpuf-pro' );

            // Return layout-appropriate structure
            if ( $layout === 'layout-1' ) {
                // Table layout: return table row
                return '<tr class="wpuf-user-directory-no-users">
                    <td colspan="' . count( $columns ) . '" class="wpuf-text-center wpuf-py-8 wpuf-px-4">
                        <div class="wpuf-text-gray-500 wpuf-text-lg wpuf-mb-2">' . esc_html( $no_users_message ) . '</div>
                        <div class="wpuf-text-gray-400 wpuf-text-sm">' . __( 'Try adjusting your search terms or filters.', 'wpuf-pro' ) . '</div>
                    </td>
                </tr>';
            } else {
                // List layouts: return empty string since we handle this via full_html in API response
                return '<li class="wpuf-user-card wpuf-col-span-full">
                    <div class="wpuf-no-users-container !wpuf-flex !wpuf-items-center !wpuf-justify-center !wpuf-min-h-[400px]">
                        <div class="wpuf-no-users-found !wpuf-flex !wpuf-flex-col !wpuf-items-center !wpuf-justify-center !wpuf-text-center">
                            <h3 class="!wpuf-text-base !wpuf-font-semibold !wpuf-text-gray-900 !wpuf-mb-2">' . esc_html( $no_users_message ) . '</h3>
                            <p class="!wpuf-text-sm !wpuf-text-gray-500">' . __( 'Try adjusting your search or filter to find what you\'re looking for.', 'wpuf-pro' ) . '</p>
                        </div>
                    </div>
                </li>';
            }
        }

        // Handle table layout differently - generate table rows instead of grid items
        if ( $layout === 'table' ) {
            return $this->render_table_rows_for_api( $users, $args );
        }

        // Build path to row template for grid layouts
        $template_path = WPUF_UD_TEMPLATES . "/shortcodes/directory/template-parts/{$row_variant}.php";
        if ( ! file_exists( $template_path ) ) {
            return '';
        }

        $html = '';
        foreach ( $users as $user ) {
            ob_start();

            // Ensure user object is complete - WP_User_Query might return incomplete objects
            if ( isset( $user->ID ) ) {
                // Always re-fetch to ensure we have all user properties
                $complete_user = get_userdata( $user->ID );
                if ( $complete_user ) {
                    $user = $complete_user;
                }
            }

            // Double-check critical properties
            if ( ! isset( $user->user_login ) || empty( $user->user_login ) ) {
                // Skip this user if we can't get their login
                continue;
            }

            // Prepare all data needed by the row template
            $all_data = [
                'profile_permalink' => ! empty( $args['profile_base'] ) ? $args['profile_base'] : 'username',
                'profile_base' => ! empty( $args['profile_base'] ) ? $args['profile_base'] : 'username', // Include both for compatibility
                'base_url' => ! empty( $args['base_url'] ) ? $args['base_url'] : '',
                'directory_layout' => $layout,
                'avatar_size' => $avatar_size,
                'meta_field_columns' => ! empty( $args['meta_field_columns'] ) ? $args['meta_field_columns'] : [],
                'social_fields' => ! empty( $args['social_fields'] ) ? $args['social_fields'] : [],
            ];


            // Variables available in template
            /** @var WP_User $user */
            // $columns, $avatar_size, $all_data are available
            include $template_path;
            $html .= ob_get_clean();
        }
        return $html;
    }

    /**
     * Validate and normalize block instance ID
     *
     * @since 4.2.0
     *
     * @param string $block_id Block instance ID from request.
     *
     * @return array Array with 'is_valid', 'is_block', 'normalized_id' keys.
     */
    private function validate_block_id( $block_id ) {
        $result = [
            'is_valid' => false,
            'is_block' => false,
            'normalized_id' => '',
        ];

        if ( empty( $block_id ) || ! is_string( $block_id ) ) {
            return $result;
        }

        $result['normalized_id'] = sanitize_text_field( $block_id );
        $result['is_valid'] = ! empty( $result['normalized_id'] );

        // Block IDs don't start with "shortcode_"
        $result['is_block'] = $result['is_valid'] && strpos( $result['normalized_id'], 'shortcode_' ) !== 0;

        return $result;
    }

    /**
     * Render users for block context using extracted method from Blocks class
     *
     * This method handles rendering users for blocks, ensuring visual consistency
     * with the main block rendering.
     *
     * @since 4.2.0
     *
     * @param array $users Array of WP_User objects.
     * @param array $filters Filters and settings for rendering.
     * @param int $page_id Post ID containing the block.
     * @param string $block_id Block instance ID.
     *
     * @return string Rendered HTML for user list items.
     */
    protected function render_users_for_block( $users, $filters, $page_id, $block_id, $base_url = '' ) {
        // Validate inputs
        if ( empty( $users ) ) {
            $search_term = ! empty( $filters['search'] ) ? sanitize_text_field( $filters['search'] ) : '';
            $no_users_message = ! empty( $search_term )
                ? __( 'No users found matching your search criteria.', 'wpuf-pro' )
                : __( 'No users found.', 'wpuf-pro' );

            return '<li class="wpuf-user-card wpuf-col-span-full">
                <div class="wpuf-user-directory-no-users wpuf-text-center wpuf-py-8 wpuf-px-4">
                    <div class="wpuf-text-gray-500 wpuf-text-lg wpuf-mb-2">' . esc_html( $no_users_message ) . '</div>
                    <div class="wpuf-text-gray-400 wpuf-text-sm">' . __( 'Try adjusting your search terms or filters.', 'wpuf-pro' ) . '</div>
                </div>
            </li>';
        }

        // Get the Blocks class instance to use the extracted method
        $blocks_instance = new \WPUF\UserDirectory\Blocks();

        // Get template block from the page content
        $template_block = $this->get_template_block_from_page( $page_id, $block_id );

        // Prepare attributes array for block rendering
        $attributes = [
            'roles'              => $filters['roles'] ?? 'all',
            'max_item_per_page'  => $filters['max_item_per_page'] ?? 10,
            'orderby'            => $filters['orderby'] ?? 'display_name',
            'order'              => $filters['order'] ?? 'asc',
            'directory_layout'   => $filters['directory_layout'] ?? 'layout-1',
            'enable_search'      => $filters['enable_search'] ?? true,
            'search_placeholder' => $filters['search_placeholder'] ?? __( 'Search Users', 'wpuf-pro' ),
            'profile_base'       => $filters['profile_base'] ?? 'user',
            'usersPerRow'        => $filters['users_per_row'] ?? 3, // Add users per row for grid layouts
            'base_url'           => $base_url, // Pass base_url for correct profile URL generation
            'block_id'           => $block_id, // Add block_id for context lookup
            'page_id'            => $page_id,  // Add page_id for context lookup
        ];



        // Handle missing template block with graceful fallback
        if ( ! $template_block ) {
            // Create a basic fallback template structure
            $template_block = [
                'blockName' => 'wpuf-ud/directory-item',
                'innerBlocks' => [],
                'attrs' => []
            ];


        }

        // Handle table layout differently - call table-specific method
        if ( $attributes['directory_layout'] === 'table' ) {
            try {
                return $this->render_table_rows_for_api( $users, $attributes );
            } catch ( Exception $e ) {
                return $this->render_simple_user_fallback( $users );
            }
        }

        // Call the public method directly for grid layouts
        try {
            return $blocks_instance->render_user_list_items( $users, $template_block, $attributes, 'ajax' );
        } catch ( Exception $e ) {
            // Fallback to simple user display on any rendering error

            return $this->render_simple_user_fallback( $users );
        }
    }

    /**
     * Render simple user fallback when block rendering fails
     *
     * @since 4.2.0
     *
     * @param array $users Array of WP_User objects.
     *
     * @return string Simple HTML for user list.
     */
    private function render_simple_user_fallback( $users ) {
        $html = '';
        foreach ( $users as $user ) {
            // $html .= '<li class="wpuf-user-card">';
            $html .= '<div class="wpuf-user-fallback">';
            $html .= '<h3>' . esc_html( $user->display_name ) . '</h3>';
            $html .= '<p>' . esc_html( $user->user_email ) . '</p>';
            $html .= '</div>';
            // $html .= '</li>';
        }
        return $html;
    }

    /**
     * Get template block structure from page content with enhanced parsing
     *
     * @since 4.2.0
     *
     * @param int $page_id Post ID containing the block.
     * @param string $block_id Block instance ID.
     *
     * @return array|null Template block data or null if not found.
     */
    private function get_template_block_from_page( $page_id, $block_id ) {
        // Validate inputs
        if ( empty( $page_id ) || empty( $block_id ) ) {
            return null;
        }

        $post = get_post( $page_id );
        if ( ! $post || empty( $post->post_content ) ) {
            return null;
        }

        // Parse blocks with error handling
        $blocks = parse_blocks( $post->post_content );
        if ( empty( $blocks ) ) {
            return null;
        }

        // Recursively search for the directory block
        // First try to find by block_id if it exists in attributes
        $user_directory_block = $this->find_block_recursively( $blocks, 'wpuf-ud/directory', $block_id );

        // If not found by ID, find the first directory block (common case)
        if ( ! $user_directory_block ) {
            $user_directory_block = $this->find_first_block_by_name( $blocks, 'wpuf-ud/directory' );
        }

        if ( ! $user_directory_block ) {
            return null;
        }

        // Look for user-directory-item within the user-directory block
        $template_block = $this->find_user_template_block( $user_directory_block );

        if ( $template_block ) {
            // Validate that the template block has inner blocks (user components)
            if ( empty( $template_block['innerBlocks'] ) ) {
                // Return a basic structure that will trigger fallback rendering
                return [
                    'blockName' => 'wpuf-ud/directory-item',
                    'innerBlocks' => [],
                    'attrs' => []
                ];
            }

            return $template_block;
        }

        return null;
    }

    /**
     * Recursively find a block by name and block_instance_id
     *
     * @since 4.2.0
     *
     * @param array $blocks Array of blocks to search.
     * @param string $block_name Block name to find.
     * @param string $block_id Block instance ID to match.
     *
     * @return array|null Found block or null.
     */
    private function find_block_recursively( $blocks, $block_name, $block_id ) {
        foreach ( $blocks as $block ) {
            // Check if this is the block we're looking for
            if (
                isset( $block['blockName'], $block['attrs']['block_instance_id'] ) &&
                $block_name === $block['blockName'] &&
                $block_id === $block['attrs']['block_instance_id']
            ) {
                return $block;
            }

            // Search in inner blocks recursively
            if ( ! empty( $block['innerBlocks'] ) ) {
                $found = $this->find_block_recursively( $block['innerBlocks'], $block_name, $block_id );
                if ( $found ) {
                    return $found;
                }
            }
        }

        return null;
    }

    /**
     * Find first block by name (without ID matching)
     *
     * @since 4.2.0
     *
     * @param array $blocks Array of blocks to search.
     * @param string $block_name Block name to find.
     *
     * @return array|null Found block or null.
     */
    private function find_first_block_by_name( $blocks, $block_name ) {
        foreach ( $blocks as $block ) {
            // Check if this is the block we're looking for
            if ( isset( $block['blockName'] ) && $block_name === $block['blockName'] ) {
                return $block;
            }

            // Search in inner blocks recursively
            if ( ! empty( $block['innerBlocks'] ) ) {
                $found = $this->find_first_block_by_name( $block['innerBlocks'], $block_name );
                if ( $found ) {
                    return $found;
                }
            }
        }

        return null;
    }

    /**
     * Find user-template block within a user-directory block
     *
     * @since 4.2.0
     *
     * @param array $user_directory_block User directory block data.
     *
     * @return array|null User template block or null.
     */
    private function find_user_template_block( $user_directory_block ) {
        if ( empty( $user_directory_block['innerBlocks'] ) ) {
            return null;
        }

        foreach ( $user_directory_block['innerBlocks'] as $inner_block ) {
            if ( isset( $inner_block['blockName'] ) && $inner_block['blockName'] === 'wpuf-ud/directory-item' ) {
                return $inner_block;
            }

            // Search nested blocks (in case template is nested deeper)
            if ( ! empty( $inner_block['innerBlocks'] ) ) {
                $found = $this->find_user_template_block( $inner_block );
                if ( $found ) {
                    return $found;
                }
            }
        }

        return null;
    }

    /**
     * Get default directory settings (mirrors settings-schema.json).
     *
     * @since 4.2.0
     * @return array
     */
    private static function get_directory_defaults() {
        return [
            'directory_layout'         => 'layout-1',
            'profile_layout'           => 'layout-1',
            'profile_size'             => 'thumbnail',
            'avatar_size'              => '32',
            'roles'                    => 'all',
            'profile_base'             => 'username',
            'max_item'                 => - 1,
            'max_item_per_page'        => 10,
            'enable_search'            => true,
            'search_placeholder'       => 'Search Users',
            'searchable_fields'        => [ 'user_login', 'user_email', 'display_name' ],
            'excluded_users'           => [],
            'columns'                  => [ 'display_name', 'user_email' ],
            'profile_tabs'             => [],
            'about_tabs_content'       => [],
            'social_fields'            => [],
            'single_profile_permalink' => '',
        ];
    }

    /**
     * Public REST endpoint for user search (with pagination)
     *
     * @since 4.2.0
     *
     * @param WP_REST_Request $request
     *
     * @return WP_REST_Response|WP_Error
     */
    public function get_users_search( $request ) {
        $params = $request->get_params();

        // New: Get block_id, page_id, and base_url
        $block_id = ! empty( $params['block_id'] ) ? sanitize_text_field( $params['block_id'] ) : '';
        $page_id  = ! empty( $params['page_id'] ) ? sanitize_text_field( $params['page_id'] ) : '';
        $base_url = ! empty( $params['base_url'] ) ? esc_url_raw( $params['base_url'] ) : '';



        // Fetch directory settings based on block_id and page_id
        $directory_settings = [];

        $default_settings = self::get_directory_defaults();

        if ( $page_id && $block_id ) {
            $post = get_post( $page_id );
            if ( $post ) {
                // Check if this is a shortcode (block_id starts with "shortcode_")
                if ( strpos( $block_id, 'shortcode_' ) === 0 ) {
                    // Handle shortcode scenario - parse shortcode attributes from post content
                    // Check for both shortcode patterns: wpuf_user_listing and wpuf_user_listing_id
                    $shortcode_pattern = '/\[wpuf_user_listing([^\]]*)\]/';
                    $shortcode_id_pattern = '/\[wpuf_user_listing_id="(\d+)"\]/';

                    // First check for wpuf_user_listing_id shortcode
                    if ( preg_match( $shortcode_id_pattern, $post->post_content, $id_matches ) ) {
                        // Found wpuf_user_listing_id shortcode - load from database
                        $directory_id = $id_matches[1];
                        $directory_post = get_post( $directory_id );

                        if ( $directory_post && $directory_post->post_type === 'wpuf_user_listing' ) {
                            // Parse stored settings from database
                            $stored_settings = [];
                            if ( ! empty( $directory_post->post_content ) ) {
                                $stored_settings = json_decode( $directory_post->post_content, true ) ?: [];
                            }

                            // Map stored settings to directory settings format
                            // Check multiple possible keys for layout
                            $layout = $stored_settings['directory_layout']
                                ?? $stored_settings['layout']
                                ?? $stored_settings['template']
                                ?? 'layout-1';

                            $directory_settings = [
                                'directory_layout'     => $layout,
                                'profile_layout'       => $stored_settings['profile_layout'] ?? 'layout-1',
                                'avatar_size'          => $stored_settings['avatar_size'] ?? '32',
                                'roles'                => $stored_settings['roles'] ?? 'all',
                                'profile_base'         => $stored_settings['profile_base'] ?? $stored_settings['profile_permalink'] ?? 'username',
                                'max_item_per_page'    => $stored_settings['max_item_per_page'] ?? $stored_settings['per_page'] ?? 10,
                                'enable_search'        => $stored_settings['enable_search'] ?? true,
                                'search_placeholder'   => $stored_settings['search_placeholder'] ?? 'Search Users',
                                'searchable_fields'    => $stored_settings['searchable_fields'] ?? [ 'user_login', 'user_email', 'display_name' ],
                                'exclude_users'        => $stored_settings['exclude_users'] ?? [],
                                'excluded_users'       => $stored_settings['excluded_users'] ?? [],
                                'columns'              => $stored_settings['columns'] ?? [ 'avatar', 'display_name', 'user_email' ],
                                'orderby'              => $stored_settings['orderby'] ?? 'id',
                                'order'                => $stored_settings['order'] ?? 'desc',
                                'enable_frontend_sorting' => $stored_settings['enable_frontend_sorting'] ?? true,
                                'social_fields'        => $stored_settings['social_fields'] ?? [],
                                'show_avatar_in_table' => $stored_settings['show_avatar_in_table'] ?? true,
                            ];
                        }
                    } elseif ( preg_match_all( $shortcode_pattern, $post->post_content, $matches, PREG_SET_ORDER ) ) {
                        // For now, use the first shortcode found
                        // TODO: In the future, we could match by position or other criteria
                        $shortcode_atts_string = isset( $matches[0][1] ) ? $matches[0][1] : '';
                        $shortcode_atts = [];

                        // Parse shortcode attributes
                        if ( ! empty( $shortcode_atts_string ) ) {
                            $shortcode_atts = shortcode_parse_atts( $shortcode_atts_string );
                            // Ensure we have an array
                            if ( ! is_array( $shortcode_atts ) ) {
                                $shortcode_atts = [];
                            }
                        }

                        // Map shortcode attributes to directory settings
                        $shortcode_defaults = [
                            'id'                => '',
                            'role'              => 'all',
                            'per_page'          => 10,
                            'directory_layout'  => 'layout-1',
                            'profile_layout'    => 'layout-1',
                            'default_tabs'      => 'about,comments,posts',
                            'gallery_img_size'  => 'thumbnail',
                            'avatar_size'       => '32',
                            'profile_permalink' => 'username',
                            'orderby'           => 'id',
                            'order'             => 'desc',
                        ];

                        $parsed_atts = shortcode_atts( $shortcode_defaults, $shortcode_atts, 'wpuf_user_listing' );

                        // CRITICAL FIX: Check if shortcode has ID parameter
                        // If it does, load settings from database like the initial render does
                        if ( ! empty( $parsed_atts['id'] ) ) {
                            $directory_id = $parsed_atts['id'];
                            $directory_post = get_post( $directory_id );

                            if ( $directory_post && $directory_post->post_type === 'wpuf_user_listing' ) {
                                // Parse stored settings from database
                                $stored_settings = [];
                                if ( ! empty( $directory_post->post_content ) ) {
                                    $stored_settings = json_decode( $directory_post->post_content, true ) ?: [];
                                }

                                // Map stored settings to directory settings format
                                // Handle different key names for profile permalink setting
                                $profile_base = $stored_settings['profile_permalink'] ?? $stored_settings['profile_base'] ?? 'username';

                                $directory_settings = [
                                    'directory_layout'     => $stored_settings['directory_layout'] ?? 'layout-1',
                                    'profile_layout'       => $stored_settings['profile_layout'] ?? 'layout-1',
                                    'avatar_size'          => $stored_settings['avatar_size'] ?? '32',
                                    'roles'                => $stored_settings['roles'] ?? 'all',
                                    'profile_base'         => $profile_base,
                                    'profile_permalink'    => $profile_base, // Include both keys for compatibility
                                    'max_item_per_page'    => $stored_settings['max_item_per_page'] ?? $stored_settings['per_page'] ?? 10,
                                    'enable_search'        => $stored_settings['enable_search'] ?? true,
                                    'search_placeholder'   => $stored_settings['search_placeholder'] ?? 'Search Users',
                                    'searchable_fields'    => $stored_settings['searchable_fields'] ?? [ 'user_login', 'user_email', 'display_name' ],
                                    'exclude_users'        => $this->extract_user_ids_from_excluded_users( $stored_settings['excluded_users'] ?? [] ),
                                    'excluded_users'       => $stored_settings['excluded_users'] ?? [],
                                    'columns'              => $stored_settings['columns'] ?? [ 'avatar', 'display_name', 'user_email' ],
                                    'orderby'              => $stored_settings['orderby'] ?? 'id',
                                    'order'                => $stored_settings['order'] ?? 'desc',
                                    'enable_frontend_sorting' => $stored_settings['enable_frontend_sorting'] ?? true,
                                    'show_avatar_in_table' => $stored_settings['show_avatar_in_table'] ?? true,
                                    'social_fields'        => $stored_settings['social_fields'] ?? [],
                                ];
                            } else {
                                // Fallback to parsed attributes if directory post not found
                                $directory_settings = [
                                    'directory_layout'     => $parsed_atts['directory_layout'],
                                    'avatar_size'          => $parsed_atts['avatar_size'],
                                    'roles'                => $parsed_atts['role'],
                                    'profile_base'         => $parsed_atts['profile_permalink'],
                                    'max_item_per_page'    => intval( $parsed_atts['per_page'] ),
                                    'enable_search'        => true,
                                    'search_placeholder'   => 'Search Users',
                                    'searchable_fields'    => [ 'user_login', 'user_email', 'display_name' ],
                                    'exclude_users'        => [],
                                    'columns'              => [ 'avatar', 'display_name', 'user_email' ],
                                ];
                            }
                        } else {
                            // Original logic for shortcodes without ID
                            $directory_settings = [
                                'directory_layout'     => $parsed_atts['directory_layout'],
                                'avatar_size'          => $parsed_atts['avatar_size'],
                                'roles'                => $parsed_atts['role'],
                                'profile_base'         => $parsed_atts['profile_permalink'],
                                'max_item_per_page'    => intval( $parsed_atts['per_page'] ),
                                'enable_search'        => true,
                                'search_placeholder'   => 'Search Users',
                                'searchable_fields'    => [ 'user_login', 'user_email', 'display_name' ],
                                'exclude_users'        => [],
                                'columns'              => [ 'avatar', 'display_name', 'user_email' ],
                            ];
                        }
                    }
                } else {
                    // Handle block scenario - parse block attributes
                    $blocks = parse_blocks( $post->post_content );
                    foreach ( $blocks as $block ) {
                        if (
                            isset( $block['blockName'], $block['attrs']['block_instance_id'] ) &&
                            'wpuf-ud/directory' === $block['blockName'] &&
                            $block_id === $block['attrs']['block_instance_id']
                        ) {
                            // Merge block attrs over defaults
                            $directory_settings = shortcode_atts( $default_settings, $block['attrs'] );
                            break;
                        }
                    }
                }
            }
        }

        $filters = [];
        // Map REST params to wpuf_ud_get_users args
        if ( ! empty( $params['roles'] ) && 'all' !== $params['roles'] ) {
            $filters['roles'] = array_map( 'trim', explode( ',', $params['roles'] ) );
        }
        		if ( ! empty( $params['exclude_roles'] ) ) {
			$filters['exclude_roles'] = array_map( 'trim', explode( ',', $params['exclude_roles'] ) );
		}
		if ( ! empty( $params['exclude_users'] ) ) {
			$filters['exclude_users'] = array_map( 'trim', explode( ',', $params['exclude_users'] ) );
		}
        // Fix per_page = 1 issue at the source before processing
        if ( ! empty( $params['max_item_per_page'] ) && intval( $params['max_item_per_page'] ) === 1 ) {
            // Override problematic max_item_per_page=1 from AJAX requests
            unset( $params['max_item_per_page'] );
        }

        // Also check for per_page = 1 directly
        if ( ! empty( $params['per_page'] ) && intval( $params['per_page'] ) === 1 ) {
            // Override problematic per_page=1 from AJAX requests
            unset( $params['per_page'] );
        }

        if ( ! empty( $params['per_page'] ) ) {
            $per_page = intval( $params['per_page'] );
        } elseif ( ! empty( $params['max_item_per_page'] ) ) {
            $per_page = intval( $params['max_item_per_page'] );
        } elseif ( ! empty( $directory_settings['max_item_per_page'] ) ) {
            $per_page = intval( $directory_settings['max_item_per_page'] );
        } else {
            $per_page = 10;
        }

        // Ensure per_page is at least 1 and reasonable
        $per_page = max( 1, $per_page );

        // Fix pagination issue when per_page is 1 by using a reasonable value
        if ( $per_page === 1 ) {
            // Default to 10 users per page when per_page was incorrectly set to 1
            $per_page = 10;
        }
        $page     = ! empty( $params['page'] ) ? intval( $params['page'] ) : 1;
        $offset   = ( $page - 1 ) * $per_page;
        $filters['per_page'] = $per_page;
        $filters['offset']   = $offset;
        if ( ! empty( $params['orderby'] ) ) {
            $filters['orderby'] = sanitize_key( $params['orderby'] );
        }
        if ( ! empty( $params['order'] ) ) {
            $filters['order'] = strtoupper( $params['order'] ) === 'ASC' ? 'ASC' : 'DESC';
        }
        if ( ! empty( $params['search'] ) ) {
            $filters['search'] = sanitize_text_field( $params['search'] );
        }

        // Handle search_by parameter for single column search
        $search_by = ! empty( $params['search_by'] ) ? sanitize_text_field( $params['search_by'] ) : '';

        if ( ! empty( $search_by ) ) {
            $core_fields = [ 'user_login', 'user_email', 'display_name' ];

            if ( in_array( $search_by, $core_fields, true ) ) {
                // Use core field search
                $filters['searchable_fields'] = [ $search_by ];
            } else {
                // Use meta field search
                $filters['search_meta_field'] = sanitize_key( $search_by );
            }
        } else {
            // Default to all core fields when no search_by specified
            if ( ! empty( $params['searchable_fields'] ) && is_array( $params['searchable_fields'] ) ) {
                $filters['searchable_fields'] = array_map( 'sanitize_key', $params['searchable_fields'] );
            } else {
                $filters['searchable_fields'] = [ 'user_login', 'user_email', 'display_name' ];
            }
        }

        // Handle users_per_row parameter for grid layouts
        $users_per_row = ! empty( $params['users_per_row'] ) ? intval( $params['users_per_row'] ) : 3;
        $filters['users_per_row'] = $users_per_row;

        // Merge directory settings into filters if needed
        if ( $directory_settings ) {
            $filters = array_merge( $directory_settings, $filters );
        }

        // Handle direct layout parameter (fallback for shortcodes)
        if ( empty( $filters['directory_layout'] ) && ! empty( $params['directory_layout'] ) ) {
            $filters['directory_layout'] = sanitize_text_field( $params['directory_layout'] );
        }

        // Handle max_item parameter from request (for consistent pagination)
        if ( ! empty( $params['max_item'] ) ) {
            $filters['max_item'] = intval( $params['max_item'] );
        }

        $result = function_exists( 'wpuf_ud_get_users' ) ? wpuf_ud_get_users( $filters ) : [ 'users' => [], 'total' => 0 ];

        $users       = $result['users'];
        $total       = $result['total'];

        // For shortcodes with max_item: ensure pagination calculation respects the max_item limit
        $is_shortcode_request = ! empty( $params['block_id'] ) && strpos( $params['block_id'], 'shortcode_' ) === 0;
        if ( $is_shortcode_request && ! empty( $directory_settings['max_item'] ) && $directory_settings['max_item'] > 0 ) {
            $max_item = intval( $directory_settings['max_item'] );
            $total = min( $total, $max_item );
        }

        $total_pages = $per_page > 0 ? (int) ceil( $total / $per_page ) : 1;

        // Prepare args for row rendering
        // Handle both 'directory_layout' and 'directory_layouts' for compatibility
        $directory_layout = ! empty( $filters['directory_layout'] ) ? $filters['directory_layout'] : 'layout-1';
        $row_variant = ! empty( $filters['row_variant'] ) ? $filters['row_variant'] : 'row-1';

        // Auto-map directory layout to row variant if not explicitly set
        if ( empty( $filters['row_variant'] ) ) {
            $row_variant = $this->layout_to_row_variant( $directory_layout );
        }

        // Validate and determine request type
        $block_validation = $this->validate_block_id( $block_id );
        $is_block_request = $block_validation['is_block'];

        if ( $is_block_request ) {
            // For blocks: use the extracted method for consistency with block rendering
            $rows_html = $this->render_users_for_block( $users, $filters, $page_id, $block_id, $base_url );
        } else {
            // For shortcodes: use the existing template-based rendering
            // Get meta field columns - check both new and legacy
            $meta_field_columns = [];
            $columns = ! empty( $filters['columns'] ) ? $filters['columns'] : [];

            // If no columns specified, try to get from legacy settings for backward compatibility
            if ( empty( $columns ) && empty( $directory_id ) ) {
                $legacy_settings = get_option( 'wpuf_userlisting', [] );
                if ( ! empty( $legacy_settings['fields'] ) ) {
                    $legacy_columns = [];
                    foreach ( $legacy_settings['fields'] as $field ) {
                        if ( $field['type'] === 'meta' && isset( $field['in_table'] ) && $field['in_table'] === 'yes' ) {
                            $legacy_columns[] = $field['meta'];
                        }
                    }
                    if ( ! empty( $legacy_columns ) ) {
                        $columns = $legacy_columns;
                    }
                }
            }

            // If still no columns, use defaults
            if ( empty( $columns ) ) {
                $columns = [ 'avatar', 'display_name', 'user_email' ];
            }

            // Check if we have the old default columns and replace with new defaults
            if ( !empty( $columns ) ) {
                $basic_fields = ['avatar', 'name', 'email', 'display_name', 'user_email', 'user_login', 'role', 'posts', 'joined', 'website', 'user_url'];
                $has_custom_meta = false;
                foreach ( $columns as $col ) {
                    if ( !in_array( $col, $basic_fields ) ) {
                        $has_custom_meta = true;
                        break;
                    }
                }

                // If we only have user_login and display_name (old defaults), replace with new defaults
                if ( !$has_custom_meta && in_array( 'user_login', $columns ) && in_array( 'display_name', $columns ) && count( $columns ) <= 3 ) {
                    // Replace user_login with user_email
                    $columns = array_map( function( $col ) {
                        return $col === 'user_login' ? 'user_email' : $col;
                    }, $columns );
                }
            }

            // Handle show_avatar_in_table setting for layout-1
            if ( $directory_layout === 'layout-1' && isset( $filters['show_avatar_in_table'] ) ) {
                $show_avatar_in_table = filter_var( $filters['show_avatar_in_table'], FILTER_VALIDATE_BOOLEAN );
                $has_avatar = in_array( 'avatar', $columns );

                if ( $show_avatar_in_table && ! $has_avatar ) {
                    // Add avatar to the beginning if it should be shown but isn't in columns
                    array_unshift( $columns, 'avatar' );
                } elseif ( ! $show_avatar_in_table && $has_avatar ) {
                    // Remove avatar if it shouldn't be shown but is in columns
                    $columns = array_values( array_diff( $columns, [ 'avatar' ] ) );
                }
            }

            // Get meta field columns - will check both new and legacy
            $meta_field_columns = $this->get_meta_field_columns_for_directory( $directory_id );

            // Always check for meta field columns (whether from new or legacy)
            if ( ! empty( $meta_field_columns ) ) {
                foreach ( $meta_field_columns as $meta_key => $label ) {
                    if ( ! in_array( $meta_key, $columns ) ) {
                        $columns[] = $meta_key;
                    }
                }

                // Enrich users with meta field data
                foreach ( $users as $user ) {
                    foreach ( $meta_field_columns as $meta_key => $label ) {
                        $user->$meta_key = get_user_meta( $user->ID, $meta_key, true );
                    }
                }
            }

            // Get profile permalink setting - check both possible keys
            $profile_base = ! empty( $filters['profile_base'] ) ? $filters['profile_base'] :
                           ( ! empty( $filters['profile_permalink'] ) ? $filters['profile_permalink'] : 'username' );

            // Get avatar size with legacy fallback
            $avatar_size = 32; // Default
            if ( ! empty( $params['avatar_size'] ) ) {
                $avatar_size = intval( $params['avatar_size'] );
            } elseif ( ! empty( $filters['avatar_size'] ) ) {
                $avatar_size = intval( $filters['avatar_size'] );
            } elseif ( empty( $directory_id ) ) {
                // For legacy mode, get from wpuf settings
                $legacy_avatar_size = wpuf_get_option( 'avatar_size', 'user_directory', '120' );
                $avatar_size = intval( $legacy_avatar_size );
            }

            // For table layout, use the specialized table rendering method
            if ( $directory_layout === 'table' ) {
                // Add block context for table layout
                $row_args = [
                    'directory_layout' => $directory_layout,
                    'show_avatar' => isset( $params['show_avatar'] ) ? $params['show_avatar'] : true,
                    'avatar_size' => $avatar_size,
                    'avatar_shape' => isset( $params['avatar_shape'] ) ? $params['avatar_shape'] : 'circle',
                    'avatar_fallback_type' => isset( $params['avatar_fallback_type'] ) ? $params['avatar_fallback_type'] : 'initial',
                    'profile_base' => $profile_base,
                    'base_url' => $base_url,
                    'block_id' => $block_id, // Add block_id for context lookup
                    'page_id' => $page_id,   // Add page_id for context lookup
                ];

                $rows_html = $this->render_table_rows_for_api( $users, $row_args );
            } else {
                // For other layouts, use the existing render_user_rows method
                $row_args = [
                    'directory_layout' => $directory_layout,
                    'row_variant'      => $row_variant,
                    'columns'          => $columns,
                    'meta_field_columns' => $meta_field_columns,
                    'avatar_size'      => $avatar_size,
                    'profile_base'     => $profile_base,
                    'base_url'         => $base_url,
                    'social_fields'    => ! empty( $filters['social_fields'] ) ? $filters['social_fields'] : [],
                ];


                $rows_html = $this->render_user_rows( $users, $row_args );
            }
        }

        // Render pagination HTML
        ob_start();
        $pagination = [
            'total_pages'  => $total_pages,
            'total_items'  => $total,
            'per_page'     => $per_page,
            'current_page' => $page,
        ];

        // Ensure base_url and query_args are properly set for pagination template
        if ( ! $base_url ) {
            // Fallback to current page if no base_url provided
            $base_url = $_SERVER['REQUEST_URI'] ?? '';

            // Ensure we have a valid URL before parsing
            if ( ! empty( $base_url ) ) {
                $parsed_url = wp_parse_url( $base_url );

                // Ensure wp_parse_url didn't return null
                if ( is_array( $parsed_url ) && isset( $parsed_url['path'] ) ) {
                    $base_url = $parsed_url['path'];
                } else {
                    // Fallback if parsing fails
                    $base_url = '/';
                }
            } else {
                // Fallback if no REQUEST_URI available
                $base_url = '/';
            }
        }

        // Extract query args from the current request (excluding pagination-related ones)
        $query_args = $params;
        unset( $query_args['page'], $query_args['base_url'], $query_args['block_id'], $query_args['page_id'] );

        // Ensure directory_layout is preserved in query args for pagination
        if ( ! empty( $directory_layout ) && empty( $query_args['directory_layout'] ) ) {
            $query_args['directory_layout'] = $directory_layout;
        }

        // Determine context and use appropriate pagination template
        // Check if this is a shortcode context (not a block context)
        $is_shortcode = empty( $block_id ) || strpos( $block_id, 'shortcode_' ) === 0;

        if ( $is_shortcode ) {
            // Use shortcode-specific pagination template
            $layout = $directory_layout; // Layout is already available from earlier
            include WPUF_UD_TEMPLATES . '/shortcodes/directory/template-parts/pagination-shortcode.php';
        } else {
            // Use default block pagination template
            include WPUF_UD_TEMPLATES . '/blocks/user-directory/components/pagination.php';
        }

        $pagination_html = ob_get_clean();

        // Prepare args for container rendering
        $container_args = [
            'directory_layout'   => $directory_layout,
            'roles'              => $filters['roles'] ?? 'all',
            'max_item_per_page'  => $filters['max_item_per_page'] ?? 10,
            'orderby'            => $filters['orderby'] ?? 'display_name',
            'order'              => $filters['order'] ?? 'asc',
            'enable_search'      => $filters['enable_search'] ?? true,
            'search_placeholder' => $filters['search_placeholder'] ?? __('Search Users', 'wpuf-pro'),
            'profile_base'       => $filters['profile_base'] ?? 'user',
            'users_per_row'      => $filters['users_per_row'] ?? 3, // Add users per row for grid layouts
            'base_url'           => $base_url,
            'block_id'           => $block_id,
            'page_id'            => $page_id,
            'search'             => $params['search'] ?? '',
        ];

        $response_data = [
            'success'         => true,
            'rows_html'       => $rows_html,
            'container_html'  => $this->render_block_container($users, $container_args), // NEW: Complete container
            'pagination_html' => $pagination_html,
            'usercount'       => $total,
            'announce'        => sprintf( __( '%d users found.', 'wpuf-pro' ), $total ),
        ];

        // For empty results in list layouts, provide a full container replacement
        if ( $total === 0 && $directory_layout !== 'layout-1' && $directory_layout !== 'table' ) {
            $search_term = ! empty( $params['search'] ) ? sanitize_text_field( $params['search'] ) : '';
            $no_users_message = ! empty( $search_term )
                ? __( 'No users found matching your search criteria.', 'wpuf-pro' )
                : __( 'No users found.', 'wpuf-pro' );

            $svg_icon = '<svg class="!wpuf-w-12 !wpuf-h-12 !wpuf-text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 715.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 919.288 0M15 7a3 3 0 11-6 0 3 3 0 616 0zm6 3a2 2 0 11-4 0 2 2 0 414 0zM7 10a2 2 0 11-4 0 2 2 0 414 0z"></path></svg>';

            $response_data['full_html'] = '<div class="wpuf-no-users-container !wpuf-flex !wpuf-items-center !wpuf-justify-center !wpuf-min-h-[400px]">
                    <div class="wpuf-no-users-found !wpuf-flex !wpuf-flex-col !wpuf-items-center !wpuf-justify-center !wpuf-text-center">
                        <h3 class="!wpuf-text-base !wpuf-font-semibold !wpuf-text-gray-900 !wpuf-mb-2">' . esc_html( $no_users_message ) . '</h3>
                        <p class="!wpuf-text-sm !wpuf-text-gray-500">' . esc_html( __( 'Try adjusting your search or filter to find what you\'re looking for.', 'wpuf-pro' ) ) . '</p>
                    </div>
                </div>';
        }

        return rest_ensure_response( $response_data );
    }

    /**
     * Preview endpoint for block editor - returns raw user data
     *
     * @since 4.2.0
     *
     * @param WP_REST_Request $request
     * @return WP_REST_Response|WP_Error
     */
    public function get_users_preview( $request ) {
        $params = $request->get_params();

        $filters = [];
        // Map REST params to wpuf_ud_get_users args
        if ( ! empty( $params['roles'] ) && 'all' !== $params['roles'] ) {
            $filters['roles'] = array_map( 'trim', explode( ',', $params['roles'] ) );
        }
        // Use per_page from params or default to 5 for preview
        $per_page = ! empty( $params['per_page'] ) ? intval( $params['per_page'] ) : 5;
        $page     = ! empty( $params['page'] ) ? intval( $params['page'] ) : 1;
        $offset   = ( $page - 1 ) * $per_page;
        $filters['per_page'] = $per_page;
        $filters['offset']   = $offset;
        if ( ! empty( $params['orderby'] ) ) {
            $filters['orderby'] = sanitize_key( $params['orderby'] );
        }
        if ( ! empty( $params['order'] ) ) {
            $filters['order'] = strtoupper( $params['order'] ) === 'ASC' ? 'ASC' : 'DESC';
        }
        // Handle exclude roles
        if ( ! empty( $params['exclude_roles'] ) ) {
            $filters['exclude_roles'] = array_map( 'trim', explode( ',', $params['exclude_roles'] ) );
        }
        // Handle exclude users
        if ( ! empty( $params['exclude_users'] ) && is_array( $params['exclude_users'] ) ) {
            $filters['exclude_users'] = array_map( 'intval', $params['exclude_users'] );
        }
        // Handle search
        if ( ! empty( $params['search'] ) ) {
            $filters['search'] = sanitize_text_field( $params['search'] );
        }

        // Handle search_by parameter for single column search
        $search_by = ! empty( $params['search_by'] ) ? sanitize_text_field( $params['search_by'] ) : '';

        if ( ! empty( $search_by ) ) {
            $core_fields = [ 'user_login', 'user_email', 'display_name' ];

            if ( in_array( $search_by, $core_fields, true ) ) {
                // Use core field search
                $filters['searchable_fields'] = [ $search_by ];
            } else {
                // Use meta field search
                $filters['search_meta_field'] = sanitize_key( $search_by );
            }
        } else {
            // Default to all core fields when no search_by specified
            if ( ! empty( $params['searchable_fields'] ) && is_array( $params['searchable_fields'] ) ) {
                $filters['searchable_fields'] = array_map( 'sanitize_key', $params['searchable_fields'] );
            }
        }

        // Handle searchable fields
        if ( ! empty( $params['searchable_fields'] ) && is_array( $params['searchable_fields'] ) ) {
            $filters['searchable_fields'] = array_map( 'sanitize_key', $params['searchable_fields'] );
        }

        // Handle avatar size parameter
        $avatar_size = ! empty( $params['avatar_size'] ) ? intval( $params['avatar_size'] ) : 64;
        // Ensure avatar size is within reasonable bounds
        $avatar_size = max( 32, min( 300, $avatar_size ) );

        $result = function_exists( 'wpuf_ud_get_users' ) ? wpuf_ud_get_users( $filters ) : [ 'users' => [], 'total' => 0 ];

        $users = $result['users'];
        $total = $result['total'];

        // Prepare user data for preview
        $preview_users = [];
        foreach ( $users as $user ) {
            $user_data = [
                'id'          => $user->ID,
                'username'    => $user->user_login,
                'display_name' => $user->display_name,
                'user_email'  => $user->user_email,
                'user_url'    => $user->user_url,
                'bio'         => $user->description,
                'avatar'      => get_avatar_url( $user->ID, [ 'size' => $avatar_size ] ),
                'first_name'  => $user->first_name,
                'last_name'   => $user->last_name,
                'nickname'    => $user->nickname,
                'roles'       => $user->roles,
                'registered_date' => $user->user_registered,
            ];
            // Add custom meta fields if they exist
            $custom_meta = get_user_meta( $user->ID );
            foreach ( $custom_meta as $key => $value ) {
                if ( is_array( $value ) && count( $value ) === 1 ) {
                    $user_data[ $key ] = $value[0];
                } else {
                    $user_data[ $key ] = $value;
                }
            }
            $preview_users[] = $user_data;
        }

        return rest_ensure_response([
            'success' => true,
            'users'   => $preview_users,
            'total'   => $total,
            'per_page' => $per_page,
            'page'     => $page,
        ]);
    }

    /**
     * Get meta field columns marked for table display
     *
     * @since 4.2.0
     *
     * @param string $directory_id Directory post ID
     *
     * @return array Array of meta_key => label pairs
     */
    private function get_meta_field_columns_for_directory( $directory_id ) {
        // If no directory ID, check legacy settings
        if ( empty( $directory_id ) ) {
            return $this->get_legacy_meta_field_columns();
        }

        // Get directory settings from database
        $directory_post = get_post( $directory_id );
        if ( ! $directory_post || $directory_post->post_type !== 'wpuf_user_listing' ) {
            // Fallback to legacy if new directory not found
            return $this->get_legacy_meta_field_columns();
        }

        // Parse stored settings
        $stored_settings = [];
        if ( ! empty( $directory_post->post_content ) ) {
            $stored_settings = json_decode( $directory_post->post_content, true ) ?: [];
        }

        // Get about tab fields that are marked for table display
        $about_fields = $stored_settings['about_tabs_content'] ?? [];
        $meta_columns = [];

        foreach ( $about_fields as $field ) {
            // Check if this is a meta field and is marked to show in listing
            if ( $field['type'] === 'meta_field' && ! empty( $field['show_in_listing'] ) && ! empty( $field['meta_key'] ) ) {
                // Use label if provided, otherwise use meta_key
                $label = ! empty( $field['label'] ) ? $field['label'] : $field['meta_key'];
                $meta_columns[ $field['meta_key'] ] = $label;
            }
        }

        return $meta_columns;
    }

    /**
     * Get legacy meta field columns from wpuf_userlisting option
     *
     * @since 4.2.0
     *
     * @return array Array of meta_key => label pairs
     */
    private function get_legacy_meta_field_columns() {
        $legacy_settings = get_option( 'wpuf_userlisting', [] );

        if ( empty( $legacy_settings['fields'] ) ) {
            return [];
        }

        $meta_columns = [];

        foreach ( $legacy_settings['fields'] as $field ) {
            // Check if this is a meta field marked for table display
            if ( $field['type'] === 'meta' && isset( $field['in_table'] ) && $field['in_table'] === 'yes' && ! empty( $field['meta'] ) ) {
                $label = ! empty( $field['label'] ) ? $field['label'] : $field['meta'];
                $meta_columns[ $field['meta'] ] = $label;
            }
        }

        return $meta_columns;
    }

    /**
     * Extract user IDs from excluded_users array
     * Handles both formats: array of objects [{id: 1, name: "admin"}] or array of IDs [1, 2, 3]
     *
     * @since 4.2.0
     *
     * @param array $excluded_users Array of user objects or user IDs
     *
     * @return array Array of user IDs
     */
    private function extract_user_ids_from_excluded_users( $excluded_users ) {
        if ( ! is_array( $excluded_users ) || empty( $excluded_users ) ) {
            return [];
        }

        $user_ids = [];

        foreach ( $excluded_users as $user ) {
            if ( is_array( $user ) && isset( $user['id'] ) ) {
                // User object format: {id: 1, name: "admin"}
                $user_ids[] = intval( $user['id'] );
            } elseif ( is_object( $user ) && isset( $user->id ) ) {
                // User object format: object with id property
                $user_ids[] = intval( $user->id );
            } elseif ( is_numeric( $user ) ) {
                // Already a user ID
                $user_ids[] = intval( $user );
            }
        }

        return array_filter( array_unique( $user_ids ) ); // Remove duplicates and zeros
    }

    /**
     * Render complete table structure for API responses (search/sort functionality)
     *
     * @since 4.2.0
     *
     * @param array $users Array of WP_User objects.
     * @param array $args Arguments for rendering.
     *
     * @return string HTML content for complete table structure.
     */
    private function render_table_rows_for_api( $users, $args = [] ) {
        // Reuse the render_table_layout method from Blocks class to ensure consistency
        if ( class_exists( '\WPUF\UserDirectory\Blocks' ) ) {
            $blocks_instance = new \WPUF\UserDirectory\Blocks();

            // Convert numeric avatar size to string size for compatibility
            $avatar_size_string = $this->convert_numeric_avatar_size_to_string( $args['avatar_size'] ?? 'medium' );

            // Convert API args to block attributes format
            $attributes = [
                'show_avatar' => isset( $args['show_avatar'] ) ? $args['show_avatar'] : true,
                'avatar_size' => $avatar_size_string,
                'avatar_shape' => isset( $args['avatar_shape'] ) ? $args['avatar_shape'] : 'circle',
                'avatar_fallback_type' => isset( $args['avatar_fallback_type'] ) ? $args['avatar_fallback_type'] : 'initial',
                'profile_base' => isset( $args['profile_base'] ) ? $args['profile_base'] : 'username',
                'base_url' => isset( $args['base_url'] ) ? $args['base_url'] : '',
                'search' => isset( $args['search'] ) ? $args['search'] : '',
            ];

            // Try to get the actual table_columns and avatar settings from the block context if available
            if ( ! empty( $args['block_id'] ) && ! empty( $args['page_id'] ) ) {
                $block_attributes = $this->get_block_attributes_from_context( $args['block_id'], $args['page_id'] );
                if ( ! empty( $block_attributes['table_columns'] ) ) {
                    $attributes['table_columns'] = $block_attributes['table_columns'];
                }
                // Get avatar settings from block context
                if ( isset( $block_attributes['show_avatar'] ) ) {
                    $attributes['show_avatar'] = $block_attributes['show_avatar'];
                }
                if ( isset( $block_attributes['avatar_size'] ) ) {
                    $attributes['avatar_size'] = $this->convert_numeric_avatar_size_to_string( $block_attributes['avatar_size'] );
                }
                if ( isset( $block_attributes['avatar_shape'] ) ) {
                    $attributes['avatar_shape'] = $block_attributes['avatar_shape'];
                }
                if ( isset( $block_attributes['avatar_fallback_type'] ) ) {
                    $attributes['avatar_fallback_type'] = $block_attributes['avatar_fallback_type'];
                }
            }

            // Fallback to default if no block context found
            if ( empty( $attributes['table_columns'] ) ) {
                $attributes['table_columns'] = ['username', 'email', 'display_name'];
            }


            // Use the complete table layout method instead of just rows
            return $blocks_instance->render_table_layout( $users, $attributes, 'api' );
        }

        // Fallback to original implementation if Blocks class is not available

        $html = '';

        // Get table settings from args
        $show_avatar = isset( $args['show_avatar'] ) ? $args['show_avatar'] : true;
        $avatar_size = isset( $args['avatar_size'] ) ? $args['avatar_size'] : 'medium';
        $avatar_shape = isset( $args['avatar_shape'] ) ? $args['avatar_shape'] : 'circle';
        $avatar_fallback_type = isset( $args['avatar_fallback_type'] ) ? $args['avatar_fallback_type'] : 'gravatar';
        $table_columns = isset( $args['table_columns'] ) ? $args['table_columns'] : ['username', 'email', 'display_name'];
        $profile_base = isset( $args['profile_base'] ) ? $args['profile_base'] : 'username';

        // Ensure base_url is always available for profile button generation
        $base_url = isset( $args['base_url'] ) ? $args['base_url'] : '';

        // Ensure table_columns is an array
        if ( ! is_array( $table_columns ) ) {
            $table_columns = ['username', 'email', 'display_name'];
        }

        // Start table with proper data attributes for JavaScript updates
        $html .= '<div class="wpuf-table-container wpuf-overflow-x-auto" data-table-layout="true">';
        $html .= '<table class="wpuf-user-table wpuf-w-full wpuf-border-collapse wpuf-border wpuf-border-gray-300" data-table-users="' . count( $users ) . '">';

        // Table header
        $html .= '<thead>';
        $html .= '<tr class="wpuf-bg-gray-50">';

        // Avatar column header (if enabled)
        if ( $show_avatar ) {
            $html .= '<th class="wpuf-px-4 wpuf-py-3 wpuf-text-left wpuf-text-xs wpuf-font-medium wpuf-text-gray-500 wpuf-uppercase wpuf-tracking-wider wpuf-border-b wpuf-border-gray-300">' . __( 'Avatar', 'wpuf-pro' ) . '</th>';
        }

        // Dynamic column headers
        foreach ( $table_columns as $column ) {
            $column_label = $this->get_table_column_label( $column );
            $html .= '<th class="wpuf-px-4 wpuf-py-3 wpuf-text-left wpuf-text-xs wpuf-font-medium wpuf-text-gray-500 wpuf-uppercase wpuf-tracking-wider wpuf-border-b wpuf-border-gray-300">' . esc_html( $column_label ) . '</th>';
        }

        // Actions column header
        $html .= '<th class="wpuf-px-4 wpuf-py-3 wpuf-text-left wpuf-text-xs wpuf-font-medium wpuf-text-gray-500 wpuf-uppercase wpuf-tracking-wider wpuf-border-b wpuf-border-gray-300"></th>';

        $html .= '</tr>';
        $html .= '</thead>';

        // Table body
        $html .= '<tbody class="wpuf-bg-white wpuf-divide-y wpuf-divide-gray-300 wpuf-ud-tbody">';

        // Render table rows
        foreach ( $users as $user ) {
            $html .= '<tr class="wpuf-hover:wpuf-bg-gray-50">';

            // Avatar column (if enabled)
            if ( $show_avatar ) {
                $html .= '<td class="wpuf-px-4 wpuf-py-3 wpuf-whitespace-nowrap wpuf-border-b wpuf-border-gray-300">';
                $html .= $this->render_user_avatar_for_api( $user, $avatar_size, $avatar_shape, $avatar_fallback_type );
                $html .= '</td>';
            }

            // Dynamic columns
            foreach ( $table_columns as $column ) {
                $html .= '<td class="wpuf-px-4 wpuf-py-3 wpuf-whitespace-nowrap wpuf-border-b wpuf-border-gray-300">';
                $html .= $this->render_table_cell_for_api( $user, $column );
                $html .= '</td>';
            }

            // View Profile button column
            $html .= '<td class="wpuf-px-4 wpuf-py-3 wpuf-whitespace-nowrap wpuf-border-b wpuf-border-gray-300">';
            // Ensure base_url is explicitly passed to the profile button method
            $profile_args = array_merge( $args, ['base_url' => $base_url] );
            $html .= $this->render_profile_button_for_api( $user, $profile_base, $profile_args );
            $html .= '</td>';

            $html .= '</tr>';
        }

        $html .= '</tbody>';
        $html .= '</table>';
        $html .= '</div>';

        return $html;
    }

    /**
     * Get table column label for display
     *
     * @since 4.2.0
     *
     * @param string $column Column name.
     *
     * @return string Column label.
     */
    private function get_table_column_label( $column ) {
        $labels = [
            'username' => __( 'Username', 'wpuf-pro' ),
            'first_name' => __( 'First Name', 'wpuf-pro' ),
            'last_name' => __( 'Last Name', 'wpuf-pro' ),
            'nickname' => __( 'Nickname', 'wpuf-pro' ),
            'display_name' => __( 'Display Name', 'wpuf-pro' ),
            'email' => __( 'Email', 'wpuf-pro' ),
            'website' => __( 'Website', 'wpuf-pro' ),
            'bio' => __( 'Bio', 'wpuf-pro' ),
            'registered_date' => __( 'Registration Date', 'wpuf-pro' ),
            'posts_count' => __( 'Posts Count', 'wpuf-pro' ),
            'comments_count' => __( 'Comments Count', 'wpuf-pro' ),
            'location' => __( 'Location', 'wpuf-pro' )
        ];

        // If we have a predefined label, use it
        if ( isset( $labels[ $column ] ) ) {
            return $labels[ $column ];
        }

        // For meta keys (like delivery_location, user_phone_no), format them to be readable
        // Convert underscores to spaces and capitalize first letter of each word
        if ( strpos( $column, '_' ) !== false ) {
            return ucwords( str_replace( '_', ' ', $column ) );
        }

        // For single words, just capitalize the first letter
        return ucfirst( $column );
    }

    /**
     * Render user avatar for API table rows
     *
     * @since 4.2.0
     *
     * @param \WP_User $user User object.
     * @param string $size Avatar size.
     * @param string $shape Avatar shape.
     * @param string $fallback_type Fallback type.
     *
     * @return string Avatar HTML.
     */
    private function render_user_avatar_for_api( $user, $size = 'medium', $shape = 'circle', $fallback_type = 'gravatar' ) {
        $size_classes = [
            'small' => 'wpuf-w-8 wpuf-h-8',
            'medium' => 'wpuf-w-24 wpuf-h-24',
            'large' => 'wpuf-w-36 wpuf-h-36',
            'xlarge' => 'wpuf-w-72 wpuf-h-72',
        ];

        $shape_classes = [
            'circle' => 'wpuf-rounded-full',
            'squared' => 'wpuf-rounded-none',
            'rounded' => 'wpuf-rounded-lg',
        ];

        $size_class = isset( $size_classes[ $size ] ) ? $size_classes[ $size ] : $size_classes['medium'];
        $shape_class = isset( $shape_classes[ $shape ] ) ? $shape_classes[ $shape ] : $shape_classes['circle'];

        // Use the proper avatar handling infrastructure that checks wpuf_profile_photo first
        $display_size = wpuf_ud_get_avatar_display_size( $size, 150 ); // Default custom size
        $avatar_data = wpuf_ud_get_block_avatar_data( $user, $display_size, $fallback_type );
        $avatar_url = $avatar_data['url'];
        $avatar_alt = $avatar_data['alt'];

        if ( $avatar_url ) {
            return '<img decoding="async" src="' . esc_url( $avatar_url ) . '" alt="' . esc_attr( $avatar_alt ) . '" class="' . esc_attr( $size_class . ' ' . $shape_class ) . ' wpuf-object-cover">';
        } else {
            // Fallback to initials
            $initials = wpuf_ud_get_user_initials( $user );
            $bg_color = 'wpuf-bg-gray-300';

            return '<div class="' . esc_attr( $size_class . ' ' . $shape_class . ' ' . $bg_color ) . ' wpuf-flex wpuf-items-center wpuf-justify-center wpuf-text-sm wpuf-font-medium wpuf-text-gray-700">' . esc_html( $initials ) . '</div>';
        }
    }

    /**
     * Render table cell content for API table rows
     *
     * @since 4.2.0
     *
     * @param \WP_User $user User object.
     * @param string $column Column key.
     *
     * @return string Cell content.
     */
    private function render_table_cell_for_api( $user, $column ) {
        // Reuse the render_table_cell method from Blocks class to ensure consistency
        if ( class_exists( '\WPUF\UserDirectory\Blocks' ) ) {
            $blocks_instance = new \WPUF\UserDirectory\Blocks();
            return $blocks_instance->render_table_cell_public( $user, $column );
        }

        // Fallback to original implementation if Blocks class is not available
        switch ( $column ) {
            case 'username':
                return '<span class="wpuf-text-sm wpuf-font-medium wpuf-text-gray-900">' . esc_html( $user->user_login ) . '</span>';

            case 'first_name':
                return '<span class="wpuf-text-sm wpuf-text-gray-900">' . esc_html( get_user_meta( $user->ID, 'first_name', true ) ) . '</span>';

            case 'last_name':
                return '<span class="wpuf-text-sm wpuf-text-gray-900">' . esc_html( get_user_meta( $user->ID, 'last_name', true ) ) . '</span>';

            case 'nickname':
                return '<span class="wpuf-text-sm wpuf-text-gray-900">' . esc_html( get_user_meta( $user->ID, 'nickname', true ) ) . '</span>';

            case 'display_name':
                return '<span class="wpuf-text-sm wpuf-font-medium wpuf-text-gray-900">' . esc_html( $user->display_name ) . '</span>';

            case 'email':
                return '<span class="wpuf-text-sm wpuf-text-gray-900">' . esc_html( $user->user_email ) . '</span>';

            case 'website':
                $website = $user->user_url;
                if ( $website ) {
                    return '<a href="' . esc_url( $website ) . '" class="wpuf-text-sm wpuf-text-blue-600 hover:wpuf-text-blue-800" target="_blank" rel="noopener noreferrer">' . esc_html( $website ) . '</a>';
                }
                return '<span class="wpuf-text-sm wpuf-text-gray-400">-</span>';

            default:
                // Handle custom meta fields - now using the same logic as Blocks class
                $meta_value = get_user_meta( $user->ID, $column, true );
                if ( $meta_value ) {
                    return '<span class="wpuf-text-sm wpuf-text-gray-900">' . esc_html( $meta_value ) . '</span>';
                }

                return '<span class="wpuf-text-sm wpuf-text-gray-400">-</span>';
        }
    }

    /**
     * Render profile button for API table rows
     *
     * @since 4.2.0
     *
     * @param \WP_User $user User object.
     * @param string $profile_base Profile base.
     * @param array $args Additional arguments including base_url.
     *
     * @return string Profile button HTML.
     */
    private function render_profile_button_for_api( $user, $profile_base = 'username', $args = [] ) {
        // ULTRA-SIMPLIFIED APPROACH: If we have base_url, just append username
        if ( ! empty( $args['base_url'] ) ) {
            $base_url = $args['base_url'];

            // Remove trailing slash from base_url if present
            $base_url = rtrim( $base_url, '/' );

            // Remove query parameters if present
            if ( strpos( $base_url, '?' ) !== false ) {
                $base_url = strtok( $base_url, '?' );
            }

            // Simply append username to the full page URL
            $profile_url = $base_url . '/' . $user->user_login;

        } else {
            // FALLBACK: Use the Blocks class pretty URL generation method
            if ( class_exists( '\WPUF\UserDirectory\Blocks' ) ) {
                $blocks_instance = new \WPUF\UserDirectory\Blocks();
                $profile_url = $blocks_instance->generate_pretty_profile_url( $user, $args['base_url'] ?? null );
            } else {
                // Final fallback to simple pretty URL generation
                $current_url = get_permalink();

                if ( ! $current_url ) {
                    $current_url = home_url( $_SERVER['REQUEST_URI'] );
                    $current_url = remove_query_arg( array_keys( $_GET ), $current_url );
                }

                // Clean up pagination from the current URL
                $current_url = preg_replace( '#/page/\d+/?$#', '', $current_url );


                // Remove any existing profile parameters
                $current_url = remove_query_arg( [ 'id', 'user', 'user_id', 'username' ], $current_url );

                // Parse the URL to get the path
                $parsed_url = parse_url( $current_url );
                $path = $parsed_url['path'] ?? '/';


                // Remove any existing username from the path
                $path_segments = explode( '/', trim( $path, '/' ) );
                if ( ! empty( $path_segments ) ) {
                    $last_segment = end( $path_segments );
                    if ( preg_match( '/[0-9]/', $last_segment ) && preg_match( '/^[a-zA-Z0-9_-]+$/', $last_segment ) ) {
                        array_pop( $path_segments );
                        $path = '/' . implode( '/', $path_segments );
                        if ( ! empty( $path_segments ) ) {
                            $path .= '/';
                        }
                    }
                }

                // Ensure path ends with a slash (but not if it's just root)
                if ( $path !== '/' && ! str_ends_with( $path, '/' ) ) {
                    $path .= '/';
                }

                // Build the pretty URL by appending username to the directory block URL
                $pretty_url = $path . $user->user_login;


                // Rebuild the full URL
                $scheme = $parsed_url['scheme'] ?? 'http';
                $host = $parsed_url['host'] ?? '';
                $port = ! empty( $parsed_url['port'] ) ? ':' . $parsed_url['port'] : '';
                $query = ! empty( $parsed_url['query'] ) ? '?' . $parsed_url['query'] : '';

                $profile_url = $scheme . '://' . $host . $port . $pretty_url . $query;
            }
        }


        return '<a href="' . esc_url( $profile_url ) . '" class="wpuf-inline-flex wpuf-items-center wpuf-px-3 wpuf-py-1.5 wpuf-border wpuf-border-transparent wpuf-text-xs wpuf-font-medium wpuf-rounded-md wpuf-text-white wpuf-bg-emerald-600 hover:wpuf-bg-emerald-700 focus:wpuf-outline-none focus:wpuf-ring-2 focus:wpuf-ring-offset-2 focus:wpuf-ring-emerald-500">' . esc_html__( 'View Profile', 'wpuf-pro' ) . '</a>';
    }

    /**
     * Get block attributes from block context
     *
     * @since 4.2.0
     *
     * @param string $block_id Block instance ID.
     * @param string $page_id Page ID containing the block.
     *
     * @return array Block attributes.
     */
    private function get_block_attributes_from_context( $block_id, $page_id ) {
        $attributes = [];

        // Get the post containing the block
        $post = get_post( $page_id );
        if ( ! $post ) {
            return $attributes;
        }

        // Parse blocks from post content
        $blocks = parse_blocks( $post->post_content );



        // Find the wpuf-ud/directory block (we can't match by generated ID, so find by block name)
        foreach ( $blocks as $block ) {
            if ( isset( $block['blockName'] ) && $block['blockName'] === 'wpuf-ud/directory' ) {
                // Found the directory block, extract its attributes
                $attributes = $block['attrs'] ?? [];


                break;
            }
        }



        return $attributes;
    }

    /**
     * Render complete block container for search/sort updates
     * Uses the same block rendering system as the frontend
     *
     * @since 4.2.0
     *
     * @param array $users Array of WP_User objects
     * @param array $args Arguments for rendering
     * @return string Complete HTML content for .wpuf-user-list-container
     */
    private function render_block_container($users, $args = []) {
        // Ensure args is an array
        if (!is_array($args)) {
            $args = [];
        }

        $directory_layout = $args['directory_layout'] ?? 'layout-1';
        $total = count($users);

        // Handle empty results
        if ($total === 0) {
            return $this->render_empty_block_container($args);
        }

        // Get the Blocks class instance
        $blocks_instance = new \WPUF\UserDirectory\Blocks();

        // Get template block from the page content (same as existing API)
        $page_id = $args['page_id'] ?? '';
        $block_id = $args['block_id'] ?? '';
        $template_block = $this->get_template_block_from_page($page_id, $block_id);

        // Prepare attributes array for block rendering (same as existing API)
        $attributes = [
            'roles'              => $args['roles'] ?? 'all',
            'max_item_per_page'  => $args['max_item_per_page'] ?? 10,
            'orderby'            => $args['orderby'] ?? 'display_name',
            'order'              => $args['order'] ?? 'asc',
            'directory_layout'   => $directory_layout,
            'enable_search'      => $args['enable_search'] ?? true,
            'search_placeholder' => $args['search_placeholder'] ?? __('Search Users', 'wpuf-pro'),
            'profile_base'       => $args['profile_base'] ?? 'user',
            'usersPerRow'        => $args['users_per_row'] ?? 3, // Add users per row for grid layouts
            'base_url'           => $args['base_url'] ?? '',
            'block_id'           => $args['block_id'] ?? '',
            'page_id'            => $args['page_id'] ?? '',
        ];

        // Handle missing template block with graceful fallback
        if (!$template_block) {
            $template_block = [
                'blockName' => 'wpuf-ud/directory-item',
                'innerBlocks' => [],
                'attrs' => []
            ];
        }

        // Start building the container HTML
        $html = '<div class="wpuf-user-list-container">';

        // Handle different layouts
        if ($directory_layout === 'table') {
            // Table layout
            $html .= '<div class="wpuf-table-layout-container">';
            $html .= '<div class="wpuf-table-wrapper wpuf-ud-list wpuf-ud-list-table" data-table-wrapper="true">';
            $html .= $this->render_table_rows_for_api($users, $attributes);
            $html .= '</div>';
            $html .= '</div>';
        } else {
            // Grid layouts - use the same structure as frontend rendering
            $usersPerRow = $attributes['usersPerRow'] ?? 3;
            $grid_classes = 'wp-block-post-template is-layout-grid columns-' . $usersPerRow . ' wpuf-ud-list wpuf-ud-list-' . $directory_layout . ' wpuf-flow-root';
            $html .= '<ul class="' . esc_attr($grid_classes) . '" role="list">';

            // Use the same block rendering method as frontend
            $html .= $blocks_instance->render_user_list_items($users, $template_block, $attributes, 'ajax');
            $html .= '</ul>';
        }

        $html .= '</div>'; // Close user list container

        return $html;
    }

    /**
     * Render empty block container for no results
     *
     * @since 4.2.0
     *
     * @param array $args Arguments for rendering
     * @return string Complete HTML content for empty container
     */
    private function render_empty_block_container($args = []) {
        // Ensure args is an array
        if (!is_array($args)) {
            $args = [];
        }

        $search_term = !empty($args['search']) ? sanitize_text_field($args['search']) : '';
        $no_users_message = !empty($search_term)
            ? __('No users found matching your search criteria.', 'wpuf-pro')
            : __('No users found.', 'wpuf-pro');

        $html = '<div class="wpuf-user-list-container">';
        $html .= '<div class="wpuf-no-users-container !wpuf-flex !wpuf-items-center !wpuf-justify-center !wpuf-min-h-[400px]">';
        $html .= '<div class="wpuf-no-users-found !wpuf-flex !wpuf-flex-col !wpuf-items-center !wpuf-justify-center !wpuf-text-center">';
        $html .= '<h3 class="!wpuf-text-base !wpuf-font-semibold !wpuf-text-gray-900 !wpuf-mb-2">' . esc_html($no_users_message) . '</h3>';
        $html .= '<p class="!wpuf-text-sm !wpuf-text-gray-500">' . esc_html(__('Try adjusting your search or filter to find what you\'re looking for.', 'wpuf-pro')) . '</p>';
        $html .= '</div>';
        $html .= '</div>';
        $html .= '</div>';

        return $html;
    }

    /**
     * Convert numeric avatar size to string size for compatibility
     *
     * @since 4.2.0
     *
     * @param mixed $avatar_size Avatar size (numeric or string)
     * @return string String avatar size
     */
    private function convert_numeric_avatar_size_to_string( $avatar_size ) {
        // If it's already a string, return as-is
        if ( is_string( $avatar_size ) ) {
            return $avatar_size;
        }

        // Convert numeric size to string size
        $numeric_size = intval( $avatar_size );

        if ( $numeric_size <= 32 ) {
            return 'small';
        } elseif ( $numeric_size <= 96 ) {
            return 'medium';
        } elseif ( $numeric_size <= 150 ) {
            return 'large';
        } else {
            return 'xlarge';
        }
    }
}
