<?php
/**
 * ZERTI — shared grid query handlers (AJAX + REST).
 */

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

/**
 * Normalize shop orders grid input from POST or REST.
 *
 * @param array<string, mixed> $input
 * @return array{start:int,limit:int,search:string,filters:array<string,string>,orderby:string,order:string}
 */
function zerti_shop_orders_grid_input_from_array(array $input): array
{
    $start = isset($input['start']) ? max(0, absint((string) $input['start'])) : 0;
    $limit = isset($input['limit']) ? absint((string) $input['limit']) : 50;
    $limit = min(100, max(1, $limit));

    $search = isset($input['search']) ? sanitize_text_field((string) $input['search']) : '';
    if (zerti_shop_orders_grid_search_length(zerti_shop_orders_grid_search_normalize($search)) < 3) {
        $search = '';
    }

    $filter_model_raw = isset($input['filter_model']) ? (string) $input['filter_model'] : '';
    if ($filter_model_raw === '' && isset($input['filterModel'])) {
        $filter_model_raw = is_string($input['filterModel'])
            ? (string) $input['filterModel']
            : wp_json_encode($input['filterModel']);
    }
    $filters = zerti_shop_orders_grid_filters_from_json($filter_model_raw);

    $order = 'DESC';
    $orderby = 'date';
    $sort_model_raw = isset($input['sort_model']) ? (string) $input['sort_model'] : '';
    if ($sort_model_raw === '' && isset($input['sortModel'])) {
        $sort_model_raw = is_string($input['sortModel'])
            ? (string) $input['sortModel']
            : wp_json_encode($input['sortModel']);
    }
    if ($sort_model_raw !== '') {
        $sort_model = json_decode($sort_model_raw, true);
        if (is_array($sort_model) && isset($sort_model[0]) && is_array($sort_model[0])) {
            $sort_col = isset($sort_model[0]['colId']) ? sanitize_key((string) $sort_model[0]['colId']) : '';
            $sort_dir = isset($sort_model[0]['sort']) ? strtolower(sanitize_key((string) $sort_model[0]['sort'])) : '';
            $order = $sort_dir === 'asc' ? 'ASC' : 'DESC';
            if ($sort_col === 'c0') {
                $orderby = 'ID';
            } elseif ($sort_col === 'c3') {
                $orderby = 'total';
            }
        }
    }

    return [
        'start' => $start,
        'limit' => $limit,
        'search' => $search,
        'filters' => $filters,
        'orderby' => $orderby,
        'order' => $order,
    ];
}

/**
 * Map grid dropdown filters to wc_get_orders() query args (same rules as the orders list).
 *
 * @param array<string, string> $filters
 * @return array{query_args: array<string, mixed>, php_filters: array<string, string>}
 */
function zerti_shop_orders_grid_build_wc_query_args(array $filters, string $orderby, string $order): array
{
    $query_args = [
        'orderby' => $orderby,
        'order' => $order,
    ];

    $php_filters = $filters;
    if (isset($php_filters['statusText'])) {
        $status_slug = zerti_shop_orders_grid_status_slug_from_label($php_filters['statusText']);
        if ($status_slug !== '') {
            $query_args['status'] = $status_slug;
            unset($php_filters['statusText']);
        }
    }
    if (isset($php_filters['dateFilter'])) {
        $date_range = zerti_shop_orders_grid_date_created_range($php_filters['dateFilter']);
        if ($date_range !== '') {
            $query_args['date_created'] = $date_range;
            unset($php_filters['dateFilter']);
        }
    }
    if (isset($php_filters['gateway'])) {
        $payment_method = zerti_shop_orders_grid_payment_method_from_label($php_filters['gateway']);
        if ($payment_method !== '') {
            $query_args['payment_method'] = $payment_method;
        }
    }

    $query_args = zerti_shop_orders_grid_normalize_query_args($query_args);

    return [
        'query_args' => $query_args,
        'php_filters' => $php_filters,
    ];
}

/**
 * Order statuses that must never appear in orders/payments lists.
 *
 * @return list<string>
 */
function zerti_shop_orders_grid_excluded_statuses(): array
{
    return ['trash', 'auto-draft'];
}

/**
 * Default WooCommerce order statuses for list queries (matches wc_get_orders() without a status filter).
 *
 * @return list<string> Slugs without wc- prefix.
 */
function zerti_shop_orders_grid_default_status_slugs(): array
{
    $excluded = array_flip(zerti_shop_orders_grid_excluded_statuses());
    $slugs = [];

    if (function_exists('wc_get_order_statuses')) {
        foreach (array_keys(wc_get_order_statuses()) as $status_key) {
            $status_key = (string) $status_key;
            $slug = strpos($status_key, 'wc-') === 0 ? substr($status_key, 3) : $status_key;
            if ($slug !== '' && !isset($excluded[$slug])) {
                $slugs[$slug] = true;
            }
        }
    }

    if ($slugs === []) {
        foreach (['pending', 'processing', 'on-hold', 'completed', 'cancelled', 'refunded', 'failed', 'draft', 'checkout-draft'] as $fallback) {
            if (!isset($excluded[$fallback])) {
                $slugs[$fallback] = true;
            }
        }
    }

    return array_keys($slugs);
}

/**
 * Apply default list statuses when the grid query does not already filter by status.
 *
 * @param array<string, mixed> $query_args
 * @return array<string, mixed>
 */
function zerti_shop_orders_grid_normalize_query_args(array $query_args): array
{
    if (empty($query_args['status'])) {
        $query_args['status'] = zerti_shop_orders_grid_default_status_slugs();
    }

    return $query_args;
}

/**
 * Status variants for SQL IN (...) (HPOS + legacy), excluding trash.
 *
 * @param array<string, mixed> $query_args
 * @return list<string>
 */
function zerti_shop_orders_grid_statuses_for_sql(array $query_args): array
{
    if (!function_exists('zerti_wc_home_expand_status_for_query')) {
        require_once dirname(__DIR__) . '/zerti-wc-home-stats.php';
    }

    $raw = !empty($query_args['status'])
        ? (is_array($query_args['status']) ? $query_args['status'] : [$query_args['status']])
        : zerti_shop_orders_grid_default_status_slugs();

    $excluded = array_flip(zerti_shop_orders_grid_excluded_statuses());
    $expanded = [];
    foreach ($raw as $status) {
        $status = (string) $status;
        $slug = strpos($status, 'wc-') === 0 ? substr($status, 3) : $status;
        if ($slug === '' || isset($excluded[$slug])) {
            continue;
        }
        foreach (zerti_wc_home_expand_status_for_query($slug) as $variant) {
            $variant_slug = strpos($variant, 'wc-') === 0 ? substr($variant, 3) : $variant;
            if ($variant_slug !== '' && !isset($excluded[$variant_slug])) {
                $expanded[$variant] = true;
            }
        }
    }

    return array_keys($expanded);
}

/**
 * Whether a loaded order should appear in orders/payments grids.
 */
function zerti_shop_order_grid_include_in_list(WC_Order $order): bool
{
    return !in_array($order->get_status(), zerti_shop_orders_grid_excluded_statuses(), true);
}


/**
 * Fetch one page of shop orders for AG Grid.
 *
 * @param array<string, mixed> $input See zerti_shop_orders_grid_input_from_array().
 * @return array{rows:list<array<string,mixed>>,total:int}|WP_Error
 */
function zerti_fetch_shop_orders_grid(array $input)
{
    if (!zerti_user_can_view_shop_orders_grid()) {
        return new WP_Error('zerti_forbidden', 'forbidden', ['status' => 403]);
    }
    if (!function_exists('wc_get_orders')) {
        return new WP_Error('zerti_wc_missing', 'woocommerce_missing', ['status' => 400]);
    }

    $parsed = zerti_shop_orders_grid_input_from_array($input);
    $start = $parsed['start'];
    $limit = $parsed['limit'];
    $search = $parsed['search'];
    $filters = $parsed['filters'];
    $orderby = $parsed['orderby'];
    $order = $parsed['order'];

    $built = zerti_shop_orders_grid_build_wc_query_args($filters, $orderby, $order);
    $query_args = array_merge($built['query_args'], [
        'limit' => $limit,
        'offset' => $start,
        'paginate' => false,
    ]);
    $php_filters = $built['php_filters'];

    $needs_scan = $php_filters !== [] || $search !== '';

    if ($needs_scan) {
        $rows = [];
        $total = 0;
        $scan_page = 1;
        $scan_limit = 200;
        $target_end = $start + $limit;
        $scan_total_pages = 1;
        $scan_args = $query_args;
        $scan_args['limit'] = $scan_limit;
        $scan_args['paginate'] = true;

        do {
            $scan_args['paged'] = $scan_page;
            $scan_result = wc_get_orders($scan_args);
            $scan_orders = is_object($scan_result) && isset($scan_result->orders) && is_array($scan_result->orders)
                ? $scan_result->orders
                : [];
            $scan_total_pages = is_object($scan_result) && isset($scan_result->max_num_pages)
                ? max(1, (int) $scan_result->max_num_pages)
                : $scan_total_pages;

            foreach ($scan_orders as $order_obj) {
                if (!is_a($order_obj, 'WC_Order') || !zerti_shop_order_grid_include_in_list($order_obj)) {
                    continue;
                }
                $row = zerti_shop_order_grid_row($order_obj);
                if (!zerti_shop_order_grid_row_matches($row, $php_filters, $search)) {
                    continue;
                }
                if ($total >= $start && $total < $target_end) {
                    $rows[] = $row;
                }
                $total++;
            }
            $scan_page++;
        } while ($scan_page <= $scan_total_pages);

        return [
            'rows' => $rows,
            'total' => $total,
        ];
    }

    $count_args = $query_args;
    unset($count_args['limit'], $count_args['offset'], $count_args['paginate'], $count_args['paged']);
    $total = zerti_wc_shop_orders_grid_count_from_db($count_args);

    $fetch_args = $query_args;
    $fetch_args['limit'] = $limit;
    $fetch_args['offset'] = $start;
    $fetch_args['paginate'] = false;
    unset($fetch_args['paged']);

    $result = wc_get_orders($fetch_args);
    $orders = is_array($result) ? $result : [];

    $rows = [];
    foreach ($orders as $order_obj) {
        if (is_a($order_obj, 'WC_Order') && zerti_shop_order_grid_include_in_list($order_obj)) {
            $rows[] = zerti_shop_order_grid_row($order_obj);
        }
    }

    return [
        'rows' => $rows,
        'total' => $total,
    ];
}

/**
 * Build HPOS / legacy WHERE clauses for shop-order grid SQL (count + export).
 *
 * @param array<string, mixed> $query_args wc_get_orders()-style args (no limit/offset).
 * @return array{hpos: bool, clauses: list<string>, params: list<mixed>}
 */
function zerti_wc_shop_orders_grid_build_db_where_clauses(array $query_args): array
{
    if (!function_exists('zerti_wc_home_uses_hpos')) {
        require_once dirname(__DIR__) . '/zerti-wc-home-stats.php';
    }

    $hpos = zerti_wc_home_uses_hpos();
    $clauses = [];
    $params = [];

    $statuses_sql = zerti_shop_orders_grid_statuses_for_sql($query_args);

    if ($hpos) {
        $clauses[] = "o.type = 'shop_order'";

        if ($statuses_sql !== []) {
            $clauses[] = 'o.status IN (' . implode(', ', array_fill(0, count($statuses_sql), '%s')) . ')';
            $params = array_merge($params, $statuses_sql);
        }

        if (!empty($query_args['payment_method'])) {
            $clauses[] = 'o.payment_method = %s';
            $params[] = (string) $query_args['payment_method'];
        }

        if (!empty($query_args['date_created']) && is_string($query_args['date_created'])) {
            $parts = explode('...', $query_args['date_created'], 2);
            if (count($parts) === 2) {
                $start_gmt = zerti_wc_shop_orders_site_datetime_to_gmt(trim($parts[0]));
                $end_gmt = zerti_wc_shop_orders_site_datetime_to_gmt(trim($parts[1]));
                if ($start_gmt !== '' && $end_gmt !== '') {
                    $clauses[] = 'o.date_created_gmt >= %s AND o.date_created_gmt <= %s';
                    $params[] = $start_gmt;
                    $params[] = $end_gmt;
                }
            }
        }
    } else {
        $clauses[] = "o.post_type = 'shop_order'";

        if ($statuses_sql !== []) {
            $clauses[] = 'o.post_status IN (' . implode(', ', array_fill(0, count($statuses_sql), '%s')) . ')';
            $params = array_merge($params, $statuses_sql);
        }

        global $wpdb;

        if (!empty($query_args['payment_method'])) {
            $clauses[] = "EXISTS (
                SELECT 1 FROM {$wpdb->postmeta} pm
                WHERE pm.post_id = o.ID
                    AND pm.meta_key = '_payment_method'
                    AND pm.meta_value = %s
            )";
            $params[] = (string) $query_args['payment_method'];
        }

        if (!empty($query_args['date_created']) && is_string($query_args['date_created'])) {
            $parts = explode('...', $query_args['date_created'], 2);
            if (count($parts) === 2) {
                $start_gmt = zerti_wc_shop_orders_site_datetime_to_gmt(trim($parts[0]));
                $end_gmt = zerti_wc_shop_orders_site_datetime_to_gmt(trim($parts[1]));
                if ($start_gmt !== '' && $end_gmt !== '') {
                    $clauses[] = 'o.post_date_gmt >= %s AND o.post_date_gmt <= %s';
                    $params[] = $start_gmt;
                    $params[] = $end_gmt;
                }
            }
        }
    }

    return [
        'hpos' => $hpos,
        'clauses' => $clauses,
        'params' => $params,
    ];
}

/**
 * Fresh order count from DB for grid filters (HPOS or legacy posts).
 *
 * @param array<string, mixed> $query_args wc_get_orders()-style args (no limit/offset).
 */
function zerti_wc_shop_orders_grid_count_from_db(array $query_args): int
{
    global $wpdb;

    $built = zerti_wc_shop_orders_grid_build_db_where_clauses($query_args);
    $where = implode(' AND ', $built['clauses']);
    $params = $built['params'];

    if ($built['hpos']) {
        $table = "{$wpdb->prefix}wc_orders";
        $sql = "SELECT COUNT(*) FROM {$table} o WHERE {$where}";
    } else {
        $sql = "SELECT COUNT(*) FROM {$wpdb->posts} o WHERE {$where}";
    }

    // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- dynamic WHERE built above.
    $prepared = $params !== [] ? $wpdb->prepare($sql, $params) : $sql;
    // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter -- $prepared from $wpdb->prepare() above.
    $count = $wpdb->get_var($prepared);

    return is_numeric($count) ? (int) $count : 0;
}


/**
 * Convert site-local datetime string to GMT for order table filters.
 */
function zerti_wc_shop_orders_site_datetime_to_gmt(string $site_dt): string
{
    if ($site_dt === '') {
        return '';
    }

    try {
        return (new DateTimeImmutable($site_dt, zerti_get_timezone()))
            ->setTimezone(new DateTimeZone('UTC'))
            ->format('Y-m-d H:i:s');
    } catch (Exception $e) {
        return '';
    }
}

/**
 * @param array<string, mixed> $input
 * @return array{start:int,limit:int,search:string,orderby:string,order:string,meta_key:string}
 */
function zerti_customers_grid_input_from_array(array $input): array
{
    $start = isset($input['start']) ? max(0, absint((string) $input['start'])) : 0;
    $limit = isset($input['limit']) ? absint((string) $input['limit']) : 50;
    $limit = min(100, max(1, $limit));

    $search = isset($input['search']) ? sanitize_text_field((string) $input['search']) : '';
    if (zerti_shop_orders_grid_search_length(zerti_shop_orders_grid_search_normalize($search)) < 3) {
        $search = '';
    }

    $order = 'DESC';
    $orderby = 'registered';
    $meta_key = '';
    $sort_model_raw = isset($input['sort_model']) ? (string) $input['sort_model'] : '';
    if ($sort_model_raw === '' && isset($input['sortModel'])) {
        $sort_model_raw = is_string($input['sortModel'])
            ? (string) $input['sortModel']
            : wp_json_encode($input['sortModel']);
    }
    if ($sort_model_raw !== '') {
        $sort_model = json_decode($sort_model_raw, true);
        if (is_array($sort_model) && isset($sort_model[0]) && is_array($sort_model[0])) {
            $sort_col = isset($sort_model[0]['colId']) ? sanitize_key((string) $sort_model[0]['colId']) : '';
            $sort_dir = isset($sort_model[0]['sort']) ? strtolower(sanitize_key((string) $sort_model[0]['sort'])) : '';
            $order = $sort_dir === 'asc' ? 'ASC' : 'DESC';
            switch ($sort_col) {
                case 'c0':
                    $orderby = 'ID';
                    break;
                case 'c1':
                    $orderby = 'display_name';
                    break;
                case 'c2':
                    $orderby = 'user_email';
                    break;
                case 'c3':
                    $orderby = 'meta_value';
                    $meta_key = 'billing_phone';
                    break;
                case 'c4':
                    $orderby = 'login';
                    break;
                case 'c5':
                    $orderby = 'registered';
                    break;
                default:
                    break;
            }
        }
    }

    return [
        'start' => $start,
        'limit' => $limit,
        'search' => $search,
        'orderby' => $orderby,
        'order' => $order,
        // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key -- Admin grid sort column mapping.
        'meta_key' => $meta_key,
    ];
}

/**
 * @param array<string, mixed> $input
 * @return array{rows:list<array<string,mixed>>,total:int}|WP_Error
 */
function zerti_fetch_customers_grid(array $input)
{
    if (!zerti_user_can_view_customers_grid()) {
        return new WP_Error('zerti_forbidden', 'forbidden', ['status' => 403]);
    }
    if (!class_exists('WooCommerce')) {
        return new WP_Error('zerti_wc_missing', 'woocommerce_missing', ['status' => 400]);
    }

    $parsed = zerti_customers_grid_input_from_array($input);
    $start = $parsed['start'];
    $limit = $parsed['limit'];
    $search = $parsed['search'];
    $orderby = $parsed['orderby'];
    $order = $parsed['order'];
    $meta_key = $parsed['meta_key'];

    $query_args = [
        'role' => 'customer',
        'number' => $limit,
        'offset' => $start,
        'orderby' => $orderby,
        'order' => $order,
        'fields' => 'all',
    ];
    if ($meta_key !== '') {
        // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key -- Admin customer grid sort; paginated.
        $query_args['meta_key'] = $meta_key;
    }

    $needs_scan = $search !== '';

    if ($needs_scan) {
        $rows = [];
        $total = 0;
        $scan_page = 1;
        $scan_limit = 200;
        $target_end = $start + $limit;
        $scan_args = [
            'role' => 'customer',
            'number' => $scan_limit,
            'offset' => 0,
            'orderby' => 'ID',
            'order' => 'ASC',
            'fields' => 'all',
        ];

        while (true) {
            $scan_args['offset'] = ($scan_page - 1) * $scan_limit;
            $scan_users = get_users($scan_args);
            if ($scan_users === []) {
                break;
            }

            foreach ($scan_users as $user_obj) {
                if (!($user_obj instanceof WP_User)) {
                    continue;
                }
                $row = zerti_customer_grid_row($user_obj);
                if (!zerti_customer_grid_row_matches($row, $search)) {
                    continue;
                }
                if ($total >= $start && $total < $target_end) {
                    $rows[] = $row;
                }
                $total++;
            }

            if (count($scan_users) < $scan_limit) {
                break;
            }
            $scan_page++;
        }

        return [
            'rows' => $rows,
            'total' => $total,
        ];
    }

    $user_query = new WP_User_Query($query_args);
    $users = $user_query->get_results();
    $total = (int) $user_query->get_total();

    $rows = [];
    foreach ($users as $user_obj) {
        if ($user_obj instanceof WP_User) {
            $rows[] = zerti_customer_grid_row($user_obj);
        }
    }

    return [
        'rows' => $rows,
        'total' => $total,
    ];
}

/**
 * Allowed URL / AJAX scope presets for the payments grid.
 *
 * @return list<string>
 */
function zerti_payments_scope_slugs(): array
{
    return ['today_success', 'period_success'];
}

function zerti_is_valid_payments_scope(string $scope): bool
{
    return in_array($scope, zerti_payments_scope_slugs(), true);
}

/**
 * Site-timezone date_created range for «today» (matches home dashboard stats).
 */
function zerti_payments_scope_today_date_created_range(): string
{
    $tz = zerti_get_timezone();
    $now = new DateTimeImmutable('now', $tz);
    $start = $now->setTime(0, 0, 0);
    $end = $now->setTime(23, 59, 59);

    return $start->format('Y-m-d H:i:s') . '...' . $end->format('Y-m-d H:i:s');
}

/**
 * Site-timezone date_created range for current month through today (matches home period sales).
 */
function zerti_payments_scope_period_date_created_range(): string
{
    $tz = zerti_get_timezone();
    $now = new DateTimeImmutable('now', $tz);
    $start = $now->modify('first day of this month')->setTime(0, 0, 0);
    $end = $now->setTime(23, 59, 59);

    return $start->format('Y-m-d H:i:s') . '...' . $end->format('Y-m-d H:i:s');
}

/**
 * Apply a payments list scope preset to wc_get_orders() args.
 *
 * @param array<string, mixed> $query_args
 * @return array<string, mixed>
 */
function zerti_payments_scope_apply_to_query_args(array $query_args, string $scope): array
{
    if (!zerti_is_valid_payments_scope($scope)) {
        return $query_args;
    }

    if ($scope === 'today_success') {
        if (!function_exists('zerti_wc_home_get_paid_statuses_for_query')) {
            require_once dirname(__DIR__) . '/zerti-wc-home-stats.php';
        }
        $query_args['status'] = zerti_wc_home_get_paid_statuses_for_query();
        $query_args['date_created'] = zerti_payments_scope_today_date_created_range();
    }

    if ($scope === 'period_success') {
        if (!function_exists('zerti_wc_home_get_paid_statuses_for_query')) {
            require_once dirname(__DIR__) . '/zerti-wc-home-stats.php';
        }
        $query_args['status'] = zerti_wc_home_get_paid_statuses_for_query();
        $query_args['date_created'] = zerti_payments_scope_period_date_created_range();
    }

    return $query_args;
}

/**
 * Normalize payments grid input from POST or REST.
 *
 * @param array<string, mixed> $input
 * @return array{start:int,limit:int,search:string,filters:array<string,string>,orderby:string,order:string,scope:string}
 */
function zerti_payments_grid_input_from_array(array $input): array
{
    $start = isset($input['start']) ? max(0, absint((string) $input['start'])) : 0;
    $limit = isset($input['limit']) ? absint((string) $input['limit']) : 50;
    $limit = min(100, max(1, $limit));

    $search = isset($input['search']) ? sanitize_text_field((string) $input['search']) : '';
    if (zerti_shop_orders_grid_search_length(zerti_shop_orders_grid_search_normalize($search)) < 3) {
        $search = '';
    }

    $filter_model_raw = isset($input['filter_model']) ? (string) $input['filter_model'] : '';
    if ($filter_model_raw === '' && isset($input['filterModel'])) {
        $filter_model_raw = is_string($input['filterModel'])
            ? (string) $input['filterModel']
            : wp_json_encode($input['filterModel']);
    }
    $filters = zerti_payments_grid_filters_from_json($filter_model_raw);

    $scope = isset($input['payments_scope']) ? sanitize_key((string) $input['payments_scope']) : '';
    if (!zerti_is_valid_payments_scope($scope)) {
        $scope = '';
    }

    $order = 'DESC';
    $orderby = 'date_paid';
    $sort_model_raw = isset($input['sort_model']) ? (string) $input['sort_model'] : '';
    if ($sort_model_raw === '' && isset($input['sortModel'])) {
        $sort_model_raw = is_string($input['sortModel'])
            ? (string) $input['sortModel']
            : wp_json_encode($input['sortModel']);
    }
    if ($sort_model_raw !== '') {
        $sort_model = json_decode($sort_model_raw, true);
        if (is_array($sort_model) && isset($sort_model[0]) && is_array($sort_model[0])) {
            $sort_col = isset($sort_model[0]['colId']) ? sanitize_key((string) $sort_model[0]['colId']) : '';
            $sort_dir = isset($sort_model[0]['sort']) ? strtolower(sanitize_key((string) $sort_model[0]['sort'])) : '';
            $order = $sort_dir === 'asc' ? 'ASC' : 'DESC';
            switch ($sort_col) {
                case 'c0':
                    $orderby = 'ID';
                    break;
                case 'c1':
                    $orderby = 'date_paid';
                    break;
                case 'c3':
                    $orderby = 'total';
                    break;
                default:
                    break;
            }
        }
    }

    return [
        'start' => $start,
        'limit' => $limit,
        'search' => $search,
        'filters' => $filters,
        'orderby' => $orderby,
        'order' => $order,
        'scope' => $scope,
    ];
}

/**
 * Fetch one page of WooCommerce order payments for AG Grid.
 *
 * @param array<string, mixed> $input See zerti_payments_grid_input_from_array().
 * @return array{rows:list<array<string,mixed>>,total:int}|WP_Error
 */
function zerti_fetch_payments_grid(array $input)
{
    if (!zerti_user_can_view_payments_grid()) {
        return new WP_Error('zerti_forbidden', 'forbidden', ['status' => 403]);
    }
    if (!function_exists('wc_get_orders')) {
        return new WP_Error('zerti_wc_missing', 'woocommerce_missing', ['status' => 400]);
    }

    $parsed = zerti_payments_grid_input_from_array($input);
    $start = $parsed['start'];
    $limit = $parsed['limit'];
    $search = $parsed['search'];
    $filters = $parsed['filters'];
    $orderby = $parsed['orderby'];
    $order = $parsed['order'];
    $scope = $parsed['scope'];

    $query_args = [
        'limit' => $limit,
        'offset' => $start,
        'paginate' => false,
        'orderby' => $orderby,
        'order' => $order,
        'type' => 'shop_order',
    ];

    $php_filters = $filters;
    if (isset($php_filters['statusText'])) {
        $status_slug = zerti_shop_orders_grid_status_slug_from_label($php_filters['statusText']);
        if ($status_slug !== '') {
            $query_args['status'] = $status_slug;
            unset($php_filters['statusText']);
        }
    }
    if (isset($php_filters['gateway'])) {
        $payment_method = zerti_shop_orders_grid_payment_method_from_label($php_filters['gateway']);
        if ($payment_method !== '') {
            $query_args['payment_method'] = $payment_method;
            unset($php_filters['gateway']);
        }
    }

    if ($scope !== '') {
        $query_args = zerti_payments_scope_apply_to_query_args($query_args, $scope);
    }

    $query_args = zerti_shop_orders_grid_normalize_query_args($query_args);

    $needs_scan = $php_filters !== [] || $search !== '';

    if ($needs_scan) {
        $rows = [];
        $total = 0;
        $scan_page = 1;
        $scan_limit = 200;
        $target_end = $start + $limit;
        $scan_total_pages = 1;
        $scan_args = $query_args;
        $scan_args['limit'] = $scan_limit;
        $scan_args['paginate'] = true;
        unset($scan_args['offset']);

        do {
            $scan_args['paged'] = $scan_page;
            $scan_result = wc_get_orders($scan_args);
            $scan_orders = is_object($scan_result) && isset($scan_result->orders) && is_array($scan_result->orders)
                ? $scan_result->orders
                : [];
            $scan_total_pages = is_object($scan_result) && isset($scan_result->max_num_pages)
                ? max(1, (int) $scan_result->max_num_pages)
                : $scan_total_pages;

            foreach ($scan_orders as $order_obj) {
                if (!is_a($order_obj, 'WC_Order') || !zerti_shop_order_grid_include_in_list($order_obj)) {
                    continue;
                }
                $row = zerti_payment_grid_row($order_obj);
                if (!zerti_payment_grid_row_matches($row, $php_filters, $search)) {
                    continue;
                }
                if ($total >= $start && $total < $target_end) {
                    $rows[] = $row;
                }
                $total++;
            }
            $scan_page++;
        } while ($scan_page <= $scan_total_pages);

        return [
            'rows' => $rows,
            'total' => $total,
        ];
    }

    $count_args = $query_args;
    unset($count_args['limit'], $count_args['offset'], $count_args['paginate'], $count_args['paged']);
    $total = zerti_wc_shop_orders_grid_count_from_db($count_args);

    $fetch_args = $query_args;
    $fetch_args['limit'] = $limit;
    $fetch_args['offset'] = $start;
    $fetch_args['paginate'] = false;
    unset($fetch_args['paged']);

    $result = wc_get_orders($fetch_args);
    $orders = is_array($result) ? $result : [];

    $rows = [];
    foreach ($orders as $order_obj) {
        if (is_a($order_obj, 'WC_Order') && zerti_shop_order_grid_include_in_list($order_obj)) {
            $rows[] = zerti_payment_grid_row($order_obj);
        }
    }

    return [
        'rows' => $rows,
        'total' => $total,
    ];
}

