<?php
/**
 * ZERTI — per-tab content components and AJAX partial loading.
 */

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

/**
 * Whether $slug is a registered in-app nav / component slug.
 */
function zerti_is_valid_component_slug(string $slug): bool
{
    return in_array($slug, zerti_admin_route_tab_slugs(), true);
}

/**
 * Inventory sidebar sub-views (slug = query ?zerti_inv= / POST inventory_sub: categories, attributes, warehouse, import-export).
 *
 * @return list<string>
 */
function zerti_inventory_sub_slugs(): array
{
    return zerti_inventory_component_sub_slugs();
}

function zerti_is_valid_inventory_sub(string $sub): bool
{
    return in_array($sub, zerti_inventory_sub_slugs(), true);
}

/**
 * Active inventory sub from AJAX POST or admin URL query.
 */
function zerti_get_inventory_sub(): string
{
    if (wp_doing_ajax() && isset($_POST['inventory_sub'])) { // phpcs:ignore WordPress.Security.NonceVerification.Missing -- nonce verified in zerti_ajax_load_component().
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- nonce verified in zerti_ajax_load_component().
        $v = sanitize_key(wp_unslash((string) $_POST['inventory_sub']));
        return zerti_is_valid_inventory_sub($v) ? $v : zerti_routes()['inventory']['default'];
    }
    if (isset($_GET['zerti_inv'])) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only admin navigation; sanitized below.
        // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only admin navigation; sanitized below.
        $v = sanitize_key(wp_unslash((string) $_GET['zerti_inv']));
        return zerti_is_valid_inventory_sub($v) ? $v : zerti_routes()['inventory']['default'];
    }

    return zerti_routes()['inventory']['default'];
}

/**
 * Path to one inventory sub-component template (full root markup per file).
 */
function zerti_inventory_sub_component_path(string $sub): string
{
    return plugin_dir_path(ZERTI_PLUGIN_FILE) . 'includes/components/inventory-' . $sub . '.php';
}

/**
 * Echo one inventory sub as a standalone component (used by inventory.php, SSR, and AJAX partial).
 */
function zerti_render_inventory_sub_component(?string $sub = null): void
{
    $sub = $sub ?? zerti_get_inventory_sub();
    if (!zerti_is_valid_inventory_sub($sub)) {
        $sub = zerti_routes()['inventory']['default'];
    }
    if ($sub === 'warehouse' && function_exists('zerti_inventory_should_show_product_editor') && zerti_inventory_should_show_product_editor()) {
        zerti_render_inventory_product_edit();

        return;
    }
    $path = zerti_inventory_sub_component_path($sub);
    if (is_readable($path)) {
        include $path;

        return;
    }
    ?>
    <div class="zerti-component zerti-component-inventory zerti-inventory-sub-slot" data-zerti-inv-active="<?php echo esc_attr($sub); ?>">
        <p class="alert alert-warning mb-0" role="alert"><?php esc_html_e('Template for this subsection is not available.', 'zerti'); ?></p>
    </div>
    <?php
}

/**
 * Financial sidebar sub-views (?zerti_fin= / POST financial_sub).
 *
 * @return list<string>
 */
function zerti_financial_sub_slugs(): array
{
    return array_column(zerti_financial_leaf_subs(), 'slug');
}

function zerti_is_valid_financial_sub(string $sub): bool
{
    return in_array($sub, zerti_financial_sub_slugs(), true);
}

function zerti_get_financial_sub(): string
{
    if (wp_doing_ajax() && isset($_POST['financial_sub'])) { // phpcs:ignore WordPress.Security.NonceVerification.Missing -- nonce verified in zerti_ajax_load_component().
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- nonce verified in zerti_ajax_load_component().
        $v = sanitize_key(wp_unslash((string) $_POST['financial_sub']));
        return zerti_is_valid_financial_sub($v) ? $v : zerti_routes()['financial']['default'];
    }
    if (isset($_GET['zerti_fin'])) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only admin navigation; sanitized below.
        // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only admin navigation; sanitized below.
        $v = sanitize_key(wp_unslash((string) $_GET['zerti_fin']));
        return zerti_is_valid_financial_sub($v) ? $v : zerti_routes()['financial']['default'];
    }

    return zerti_routes()['financial']['default'];
}

function zerti_financial_sub_component_path(string $sub): string
{
    $default = plugin_dir_path(ZERTI_PLUGIN_FILE) . 'includes/components/financial-' . $sub . '.php';

    return (string) apply_filters('zerti_financial_sub_component_path', $default, $sub);
}

/**
 * Path to shop order detail partial (AJAX / in-tab view).
 */
function zerti_shop_order_detail_component_path(): string
{
    return plugin_dir_path(ZERTI_PLUGIN_FILE) . 'includes/components/shop-order-detail.php';
}

/**
 * Path to shop order invoice partial (AJAX / in-tab view).
 */
function zerti_shop_order_invoice_component_path(): string
{
    return plugin_dir_path(ZERTI_PLUGIN_FILE) . 'includes/components/shop-order-invoice.php';
}

/**
 * Path to shop order create partial (AJAX / in-tab view).
 */
function zerti_shop_order_create_component_path(): string
{
    $default = plugin_dir_path(ZERTI_PLUGIN_FILE) . 'includes/components/shop-order-create.php';

    return (string) apply_filters('zerti_shop_order_create_component_path', $default);
}

/**
 * Whether the current user may view this order in ZERTI (WooCommerce capability).
 */
function zerti_user_can_view_shop_order(int $order_id): bool
{
    return $order_id > 0 && current_user_can('edit_shop_order', $order_id);
}

/**
 * Whether the current user may load the shop orders grid.
 */
function zerti_user_can_view_shop_orders_grid(): bool
{
    return current_user_can('edit_shop_orders') || current_user_can('manage_woocommerce');
}

/**
 * Convert one WooCommerce order to the AG Grid row payload.
 *
 * @return array<string, mixed>
 */
function zerti_shop_order_grid_row(WC_Order $order): array
{
    $order_id_raw = (int) $order->get_id();
    $order_id_label = zerti_to_persian_digits((string) $order_id_raw);
    $created = $order->get_date_created();
    $ts = ($created && is_a($created, 'WC_DateTime')) ? (int) $created->getTimestamp() : 0;
    $date_label = $ts > 0 ? zerti_format_shop_order_list_date($ts) : '—';
    $date_filter = ($created && $ts > 0 && function_exists('zerti_format_date'))
        ? zerti_format_date($created)
        : $date_label;

    $customer_name = trim((string) $order->get_formatted_billing_full_name());
    if ($customer_name === '') {
        $customer_name = trim((string) $order->get_billing_email());
    }
    if ($customer_name === '') {
        $customer_name = __('Untitled', 'zerti');
    }
    $customer_search_name = trim(implode(' ', array_filter([
        trim((string) $order->get_billing_first_name()),
        trim((string) $order->get_billing_last_name()),
        $customer_name,
    ])));

    $gateway_title = trim((string) $order->get_payment_method_title());
    $gateway_id = trim((string) $order->get_payment_method());
    if ($gateway_title === '' && $gateway_id !== '' && function_exists('WC')) {
        $wc = WC();
        if ($wc && $wc->payment_gateways()) {
            $registered = $wc->payment_gateways()->payment_gateways();
            if (is_array($registered) && isset($registered[$gateway_id]) && is_object($registered[$gateway_id])) {
                $gw_obj = $registered[$gateway_id];
                if (method_exists($gw_obj, 'get_title')) {
                    $gateway_title = trim((string) $gw_obj->get_title());
                }
            }
        }
    }
    if ($gateway_title === '' && $gateway_id !== '') {
        $gateway_title = $gateway_id;
    }
    $provider_label = zerti_wc_payment_gateway_display_label($gateway_id, $gateway_title);

    $status_slug = $order->get_status();
    $status_label = function_exists('zerti_wc_order_status_label_fa')
        ? zerti_wc_order_status_label_fa((string) $status_slug)
        : (string) $status_slug;

    $status_badge_class = 'bg-secondary';
    switch ($status_slug) {
        case 'completed':
        case 'processing':
            $status_badge_class = 'bg-success';
            break;
        case 'pending':
        case 'on-hold':
        case 'draft':
        case 'checkout-draft':
            $status_badge_class = 'bg-warning text-dark';
            break;
        case 'cancelled':
        case 'failed':
            $status_badge_class = 'bg-danger';
            break;
        case 'refunded':
            $status_badge_class = 'bg-info text-dark';
            break;
        default:
            $status_badge_class = 'bg-secondary';
            break;
    }

    $greg_line = ($ts > 0 && function_exists('wp_date'))
        ? wp_date('Y-m-d H:i', $ts, zerti_get_timezone())
        : '';
    $cust_display = zerti_to_persian_digits($customer_name);
    $total_amt = (float) $order->get_total();
    $total_html = function_exists('zerti_wc_price_html_persian')
        ? zerti_wc_price_html_persian($total_amt)
        : esc_html((string) $total_amt);
    $total_plain = trim(wp_strip_all_tags($total_html));
    $total_plain_p = zerti_to_persian_digits($total_plain);
    $status_disp = function_exists('zerti_localize_digits')
        ? zerti_localize_digits($status_label)
        : zerti_to_persian_digits($status_label);
    $status_dir = function_exists('zerti_get_text_direction') ? zerti_get_text_direction() : 'rtl';
    $search_text = trim(implode(' ', [
        (string) $order_id_raw,
        $order_id_label,
        zerti_to_latin_digits($order_id_label),
        (string) $ts,
        $greg_line,
        zerti_to_latin_digits($greg_line),
        $date_label,
        zerti_to_latin_digits($date_label),
        $date_filter,
        zerti_to_latin_digits($date_filter),
        $customer_name,
        $cust_display,
        zerti_to_latin_digits($cust_display),
        (string) $total_amt,
        $total_plain,
        $total_plain_p,
        zerti_to_latin_digits($total_plain_p),
        $gateway_id,
        $gateway_title,
        $provider_label,
        zerti_to_latin_digits($provider_label),
        $status_slug,
        $status_label,
        $status_disp,
        zerti_to_latin_digits($status_disp),
    ]));

    return [
        'idRaw' => $order_id_raw,
        'id' => $order_id_label,
        'date' => $date_label,
        'dateFilter' => $date_filter,
        'dateSort' => $ts,
        'customer' => $cust_display,
        'customerSearch' => $customer_search_name,
        'totalHtml' => $total_html,
        'totalText' => $total_plain_p,
        'totalSort' => $total_amt,
        'gateway' => $provider_label,
        'statusHtml' => sprintf(
            '<span class="badge %s" dir="%s">%s</span>',
            esc_attr($status_badge_class),
            esc_attr($status_dir),
            esc_html($status_disp)
        ),
        'statusText' => $status_disp,
        'statusSort' => (string) $status_slug,
        'searchText' => $search_text,
        'downloadHtml' => function_exists('zerti_shop_order_grid_pdf_download_html')
            ? zerti_shop_order_grid_pdf_download_html($order_id_raw, $status_slug)
            : '',
    ];
}

/**
 * Normalize AG Grid dropdown filters from colId => model to row field => value.
 *
 * @return array<string, string>
 */
function zerti_shop_orders_grid_filters_from_json(string $filter_model_raw): array
{
    if ($filter_model_raw === '') {
        return [];
    }

    $filter_model = json_decode($filter_model_raw, true);
    if (!is_array($filter_model)) {
        return [];
    }

    $field_map = [
        'c1' => 'dateFilter',
        'c4' => 'gateway',
        'c5' => 'statusText',
    ];
    $filters = [];
    foreach ($field_map as $col_id => $field) {
        if (!isset($filter_model[$col_id]) || !is_array($filter_model[$col_id])) {
            continue;
        }
        $value = isset($filter_model[$col_id]['value']) ? sanitize_text_field((string) $filter_model[$col_id]['value']) : '';
        if ($value !== '') {
            $filters[$field] = $value;
        }
    }

    return $filters;
}

/**
 * Normalize customer search text for Persian/Arabic name matching.
 */
function zerti_shop_orders_grid_search_normalize(string $value): string
{
    $value = zerti_to_latin_digits($value);
    $value = str_replace(
        ["\xE2\x80\x8C", 'ي', 'ى', 'ك', 'ۀ', 'ة'],
        ['', 'ی', 'ی', 'ک', 'ه', 'ه'],
        $value
    );
    $value = preg_replace('/\s+/u', ' ', trim($value));
    if (!is_string($value)) {
        return '';
    }

    return function_exists('mb_strtolower') ? mb_strtolower($value, 'UTF-8') : strtolower($value);
}

/**
 * UTF-8 length of a normalized customer search term.
 */
function zerti_shop_orders_grid_search_length(string $value): int
{
    return function_exists('mb_strlen') ? mb_strlen($value, 'UTF-8') : strlen($value);
}

/**
 * Test a formatted grid row against dropdown filters and the toolbar search.
 *
 * @param array<string, mixed> $row
 * @param array<string, string> $filters
 */
function zerti_shop_order_grid_row_matches(array $row, array $filters, string $search): bool
{
    foreach ($filters as $field => $expected) {
        $actual = isset($row[$field]) ? (string) $row[$field] : '';
        if ($actual !== $expected) {
            return false;
        }
    }

    if ($search === '') {
        return true;
    }

    $needle = zerti_shop_orders_grid_search_normalize($search);
    if ($needle === '' || zerti_shop_orders_grid_search_length($needle) < 3) {
        return true;
    }
    $haystack = zerti_shop_orders_grid_search_normalize(trim(implode(' ', [
        isset($row['customerSearch']) ? (string) $row['customerSearch'] : '',
        isset($row['customer']) ? (string) $row['customer'] : '',
    ])));

    return function_exists('mb_stripos')
        ? mb_stripos($haystack, $needle, 0, 'UTF-8') !== false
        : stripos($haystack, $needle) !== false;
}

/**
 * Convert a formatted Jalali filter value to a WooCommerce date_created range.
 */
function zerti_shop_orders_grid_date_created_range(string $value): string
{
    return zerti_admin_date_display_to_created_range($value);
}

/**
 * Resolve a Persian status label from the dropdown back to a WooCommerce status slug.
 */
function zerti_shop_orders_grid_status_slug_from_label(string $value): string
{
    $needle = zerti_to_latin_digits(trim($value));
    $statuses = function_exists('wc_get_order_statuses') ? wc_get_order_statuses() : [];
    foreach ($statuses as $key => $wc_label) {
        $key = (string) $key;
        $slug = strpos($key, 'wc-') === 0 ? substr($key, 3) : $key;
        $labels = [
            (string) $slug,
            (string) $wc_label,
            function_exists('zerti_wc_order_status_label_fa') ? zerti_wc_order_status_label_fa($slug) : (string) $slug,
        ];
        foreach ($labels as $label) {
            if (zerti_to_latin_digits(trim($label)) === $needle) {
                return $slug;
            }
        }
    }

    return '';
}

/**
 * Resolve a payment method dropdown label back to a gateway ID when possible.
 */
function zerti_shop_orders_grid_payment_method_from_label(string $value): string
{
    $needle = zerti_to_latin_digits(trim($value));
    if ($needle === '' || !function_exists('WC')) {
        return '';
    }

    $wc = WC();
    if (!$wc || !$wc->payment_gateways()) {
        return '';
    }

    $gateways = $wc->payment_gateways()->payment_gateways();
    if (!is_array($gateways)) {
        return '';
    }

    foreach ($gateways as $gateway_id => $gateway) {
        if (!is_object($gateway)) {
            continue;
        }
        $title = '';
        if (method_exists($gateway, 'get_title')) {
            $title = trim((string) $gateway->get_title());
        } elseif (isset($gateway->title)) {
            $title = trim((string) $gateway->title);
        }
        $labels = array_unique(array_filter([
            (string) $gateway_id,
            $title,
            zerti_wc_payment_gateway_label_fa((string) $gateway_id, $title),
            zerti_wc_payment_gateway_display_label((string) $gateway_id, $title),
        ], static function ($label) {
            return is_string($label) && trim($label) !== '' && $label !== '—';
        }));
        foreach ($labels as $label) {
            if (zerti_to_latin_digits(trim($label)) === $needle) {
                return (string) $gateway_id;
            }
        }
    }

    return '';
}

/**
 * AJAX: return one database-backed page of shop orders for AG Grid.
 */
function zerti_ajax_shop_orders_grid(): void
{
    check_ajax_referer('zerti_shop_orders_grid', 'nonce');

    $post = zerti_unslash_post_array();
    // phpcs:ignore WordPress.Security.NonceVerification.Missing -- nonce verified above.
    $input = zerti_shop_orders_grid_input_from_array($post);

    $result = zerti_fetch_shop_orders_grid($input);
    if (is_wp_error($result)) {
        $status = (int) ($result->get_error_data()['status'] ?? 400);
        wp_send_json_error(['message' => $result->get_error_code()], $status > 0 ? $status : 400);
    }

    wp_send_json_success($result);
}

/**
 * Clear per-request caches for sidebar nav badge counts (after order/review mutations).
 */
function zerti_invalidate_sidebar_badge_counts(): void
{
    if (function_exists('zerti_shop_processing_orders_count')) {
        zerti_shop_processing_orders_count(true);
    }
    if (function_exists('zerti_product_pending_reviews_count')) {
        zerti_product_pending_reviews_count(true);
    }
    if (function_exists('zerti_product_pending_comments_count')) {
        zerti_product_pending_comments_count(true);
    }
    if (function_exists('zerti_wc_home_invalidate_dashboard_stats_cache')) {
        zerti_wc_home_invalidate_dashboard_stats_cache();
    }
}

/**
 * AJAX: home dashboard stats (cards + revenue chart).
 */
function zerti_ajax_home_dashboard_stats(): void
{
    check_ajax_referer('zerti_component', 'nonce');

    if (!zerti_user_can_access_tab('home')) {
        wp_send_json_error(['message' => 'forbidden'], 403);
    }

    if (!function_exists('zerti_wc_home_dashboard_stats_client_payload')) {
        wp_send_json_error(['message' => 'unavailable'], 500);
    }

    // phpcs:ignore WordPress.Security.NonceVerification.Missing -- nonce verified above.
    $refresh = !empty($_POST['refresh']);
    // phpcs:ignore WordPress.Security.NonceVerification.Missing -- nonce verified above.
    $section = isset($_POST['section']) ? sanitize_key(wp_unslash((string) $_POST['section'])) : '';

    if ($section !== '') {
        wp_send_json_success(
            zerti_wc_home_dashboard_stats_section_payload($section, 30, $refresh)
        );
    }

    wp_send_json_success(
        zerti_wc_home_dashboard_stats_client_payload(30, $refresh)
    );
}

/**
 * AJAX: processing shop orders count (sidebar badge).
 */
function zerti_ajax_shop_processing_orders_count(): void
{
    check_ajax_referer('zerti_component', 'nonce');

    if (!zerti_user_can_view_shop_orders_grid()) {
        wp_send_json_error(['message' => 'forbidden'], 403);
    }

    wp_send_json_success([
        'count' => zerti_shop_processing_orders_count(true),
    ]);
}

/**
 * AJAX: pending product reviews count for sidebar badges.
 */
function zerti_ajax_product_pending_reviews_count(): void
{
    check_ajax_referer('zerti_component', 'nonce');

    if (
        !function_exists('zerti_user_can_manage_product_reviews')
        || !zerti_user_can_manage_product_reviews()
    ) {
        wp_send_json_error(['message' => 'forbidden'], 403);
    }

    wp_send_json_success([
        'count' => zerti_product_pending_reviews_count(true),
    ]);
}

/**
 * AJAX: pending product comments count for sidebar badges.
 */
function zerti_ajax_product_pending_comments_count(): void
{
    check_ajax_referer('zerti_component', 'nonce');

    if (
        !function_exists('zerti_user_can_manage_product_reviews')
        || !zerti_user_can_manage_product_reviews()
    ) {
        wp_send_json_error(['message' => 'forbidden'], 403);
    }

    wp_send_json_success([
        'count' => zerti_product_pending_comments_count(true),
    ]);
}

/**
 * AJAX: combined pending reviews + comments counts for Customer affairs badges.
 */
function zerti_ajax_product_pending_feedback_counts(): void
{
    check_ajax_referer('zerti_component', 'nonce');

    if (
        !function_exists('zerti_user_can_manage_product_reviews')
        || !zerti_user_can_manage_product_reviews()
    ) {
        wp_send_json_error(['message' => 'forbidden'], 403);
    }

    $reviews = zerti_product_pending_reviews_count(true);
    $comments = zerti_product_pending_comments_count(true);

    wp_send_json_success([
        'reviews' => $reviews,
        'comments' => $comments,
        'total' => $reviews + $comments,
    ]);
}


/**
 * Whether the current user may load the customers grid.
 */
function zerti_user_can_view_customers_grid(): bool
{
    return current_user_can('list_users') || current_user_can('manage_woocommerce');
}

/**
 * Whether the current user may view this customer's detail page.
 */
function zerti_user_can_view_customer_detail(int $user_id): bool
{
    if ($user_id <= 0) {
        return false;
    }
    if (current_user_can('list_users') || current_user_can('manage_woocommerce')) {
        return true;
    }
    return (int) get_current_user_id() === $user_id;
}

/**
 * Path to customer detail partial (AJAX / in-tab view).
 */
function zerti_customer_detail_component_path(): string
{
    return plugin_dir_path(ZERTI_PLUGIN_FILE) . 'includes/components/customer-detail.php';
}

/**
 * Build a customers export / grid row from a batched SQL user row.
 *
 * @param array<string, mixed> $sql_row
 * @return array<string, mixed>
 */

/**
 * Convert one WordPress / WooCommerce customer user to the AG Grid row payload.
 *
 * @return array<string, mixed>
 */
function zerti_customer_grid_row(WP_User $user): array
{
    $user_id_raw = (int) $user->ID;
    $user_id_label = zerti_to_persian_digits((string) $user_id_raw);

    $first_name = trim((string) get_user_meta($user_id_raw, 'billing_first_name', true));
    $last_name = trim((string) get_user_meta($user_id_raw, 'billing_last_name', true));
    $display_name = trim((string) $user->display_name);
    if ($display_name === '') {
        $display_name = trim($first_name . ' ' . $last_name);
    }
    if ($display_name === '') {
        $display_name = trim((string) $user->user_login);
    }
    if ($display_name === '') {
        $display_name = __('Untitled', 'zerti');
    }
    $name_display = zerti_to_persian_digits($display_name);

    $email = trim((string) $user->user_email);
    $email_display = $email !== '' ? $email : '—';

    $phone = trim((string) get_user_meta($user_id_raw, 'billing_phone', true));
    if ($phone === '') {
        $phone = trim((string) get_user_meta($user_id_raw, 'shipping_phone', true));
    }
    $phone_display = $phone !== '' ? zerti_to_persian_digits($phone) : '—';

    $username = trim((string) $user->user_login);
    $username_display = $username !== '' ? $username : '—';

    $registered_ts = strtotime((string) $user->user_registered);
    if ($registered_ts === false) {
        $registered_ts = 0;
    }
    $registered_label = $registered_ts > 0 ? zerti_format_datetime($registered_ts) : '—';
    $greg_registered = ($registered_ts > 0 && function_exists('wp_date'))
        ? wp_date('Y-m-d H:i', $registered_ts, zerti_get_timezone())
        : '';

    $search_text = trim(implode(' ', array_filter([
        (string) $user_id_raw,
        $user_id_label,
        zerti_to_latin_digits($user_id_label),
        $display_name,
        $name_display,
        zerti_to_latin_digits($name_display),
        $first_name,
        $last_name,
        $email,
        zerti_to_latin_digits($email),
        $phone,
        zerti_to_latin_digits($phone_display),
        $username,
        zerti_to_latin_digits($username),
        $greg_registered,
        zerti_to_latin_digits($greg_registered),
        $registered_label,
        zerti_to_latin_digits($registered_label),
        (string) $registered_ts,
    ])));

    return [
        'idRaw' => $user_id_raw,
        'id' => $user_id_label,
        'name' => $name_display,
        'email' => $email_display,
        'phone' => $phone_display,
        'username' => $username_display,
        'registered' => $registered_label,
        'registeredSort' => $registered_ts,
        'searchText' => $search_text,
    ];
}

/**
 * Test a formatted customer grid row against the toolbar search.
 *
 * @param array<string, mixed> $row
 */
function zerti_customer_grid_row_matches(array $row, string $search): bool
{
    if ($search === '') {
        return true;
    }

    $needle = zerti_shop_orders_grid_search_normalize($search);
    if ($needle === '' || zerti_shop_orders_grid_search_length($needle) < 3) {
        return true;
    }

    $haystack = zerti_shop_orders_grid_search_normalize(isset($row['searchText']) ? (string) $row['searchText'] : '');

    return function_exists('mb_stripos')
        ? mb_stripos($haystack, $needle, 0, 'UTF-8') !== false
        : stripos($haystack, $needle) !== false;
}

/**
 * AJAX: return one page of WooCommerce customers for AG Grid.
 */
function zerti_ajax_customers_grid(): void
{
    check_ajax_referer('zerti_customers_grid', 'nonce');

    $post = zerti_unslash_post_array();
    // phpcs:ignore WordPress.Security.NonceVerification.Missing -- nonce verified above.
    $input = zerti_customers_grid_input_from_array($post);

    $result = zerti_fetch_customers_grid($input);
    if (is_wp_error($result)) {
        $status = (int) ($result->get_error_data()['status'] ?? 400);
        wp_send_json_error(['message' => $result->get_error_code()], $status > 0 ? $status : 400);
    }

    wp_send_json_success($result);
}



/**
 * Echo customer detail for one user ID (used by AJAX partial).
 */
function zerti_render_customer_detail(int $user_id): void
{
    if (!class_exists('WooCommerce')) {
        ?>
        <div class="zerti-component zerti-component-customers zerti-customer-detail" data-zerti-customer-id="0">
            <p class="alert alert-warning mb-0" role="alert">
                <?php esc_html_e('WooCommerce is not available.', 'zerti'); ?>
            </p>
        </div>
        <?php

        return;
    }
    $user = $user_id > 0 ? get_user_by('id', $user_id) : null;
    if (!($user instanceof WP_User)) {
        ?>
        <div class="zerti-component zerti-component-customers zerti-customer-detail" data-zerti-customer-id="0">
            <p class="alert alert-warning mb-0" role="alert">
                <?php esc_html_e('Customer not found.', 'zerti'); ?>
            </p>
        </div>
        <?php

        return;
    }
    if (!zerti_user_can_view_customer_detail($user_id)) {
        ?>
        <div class="zerti-component zerti-component-customers zerti-customer-detail" data-zerti-customer-id="0">
            <p class="alert alert-warning mb-0" role="alert">
                <?php esc_html_e('You do not have permission to view this customer.', 'zerti'); ?>
            </p>
        </div>
        <?php

        return;
    }
    $path = zerti_customer_detail_component_path();
    if (!is_readable($path)) {
        ?>
        <div class="zerti-component zerti-component-customers zerti-customer-detail" data-zerti-customer-id="0">
            <p class="alert alert-warning mb-0" role="alert"><?php esc_html_e('Customer details template is not available.', 'zerti'); ?></p>
        </div>
        <?php

        return;
    }
    /** @var WP_User $user */
    include $path;
}

/**
 * Echo shop purchase detail for one order ID (used by AJAX partial).
 */
function zerti_render_shop_order_detail(int $order_id): void
{
    if (!function_exists('wc_get_order')) {
        ?>
        <div class="zerti-component zerti-component-shop zerti-shop-order-detail" data-zerti-order-id="0">
            <p class="alert alert-warning mb-0" role="alert">
                <?php esc_html_e('WooCommerce is not available.', 'zerti'); ?>
            </p>
        </div>
        <?php

        return;
    }
    $order = wc_get_order($order_id);
    if (!is_a($order, 'WC_Order')) {
        ?>
        <div class="zerti-component zerti-component-shop zerti-shop-order-detail" data-zerti-order-id="0">
            <p class="alert alert-warning mb-0" role="alert">
                <?php esc_html_e('Order not found.', 'zerti'); ?>
            </p>
        </div>
        <?php

        return;
    }
    if (!zerti_user_can_view_shop_order($order_id)) {
        ?>
        <div class="zerti-component zerti-component-shop zerti-shop-order-detail" data-zerti-order-id="0">
            <p class="alert alert-warning mb-0" role="alert">
                <?php esc_html_e('You do not have permission to view this order.', 'zerti'); ?>
            </p>
        </div>
        <?php

        return;
    }
    $path = zerti_shop_order_detail_component_path();
    if (!is_readable($path)) {
        ?>
        <div class="zerti-component zerti-component-shop zerti-shop-order-detail" data-zerti-order-id="0">
            <p class="alert alert-warning mb-0" role="alert"><?php esc_html_e('Order details template is not available.', 'zerti'); ?></p>
        </div>
        <?php

        return;
    }
    /** @var WC_Order $order */
    include $path;
}

/**
 * Echo shop create-order form (used by AJAX partial and order=0 route).
 */
function zerti_render_shop_order_create(): void
{
    if (!class_exists('WooCommerce')) {
        ?>
        <div class="zerti-component zerti-component-shop zerti-shop-order-detail zerti-shop-order-create" data-zerti-order-id="0" data-zerti-shop-create="1">
            <p class="alert alert-warning mb-0" role="alert">
                <?php esc_html_e('WooCommerce is not available.', 'zerti'); ?>
            </p>
        </div>
        <?php

        return;
    }

    if (!function_exists('zerti_user_can_create_shop_order') || !zerti_user_can_create_shop_order()) {
        ?>
        <div class="zerti-component zerti-component-shop zerti-shop-order-detail zerti-shop-order-create" data-zerti-order-id="0" data-zerti-shop-create="1">
            <p class="alert alert-warning mb-0" role="alert">
                <?php esc_html_e('You do not have permission to create orders.', 'zerti'); ?>
            </p>
        </div>
        <?php

        return;
    }

    $path = zerti_shop_order_create_component_path();
    if (!is_readable($path)) {
        ?>
        <div class="zerti-component zerti-component-shop zerti-shop-order-detail zerti-shop-order-create" data-zerti-order-id="0" data-zerti-shop-create="1">
            <p class="alert alert-warning mb-0" role="alert"><?php esc_html_e('Create order template is not available.', 'zerti'); ?></p>
        </div>
        <?php

        return;
    }

    include $path;
}

/**
 * Echo shop invoice view for one order ID (used by AJAX partial).
 */
function zerti_render_shop_order_invoice(int $order_id): void
{
    if (!function_exists('wc_get_order')) {
        ?>
        <div class="zerti-component zerti-component-shop zerti-shop-order-detail zerti-shop-order-invoice" data-zerti-order-id="0">
            <p class="alert alert-warning mb-0" role="alert">
                <?php esc_html_e('WooCommerce is not available.', 'zerti'); ?>
            </p>
        </div>
        <?php

        return;
    }
    $order = wc_get_order($order_id);
    if (!is_a($order, 'WC_Order')) {
        ?>
        <div class="zerti-component zerti-component-shop zerti-shop-order-detail zerti-shop-order-invoice" data-zerti-order-id="0">
            <p class="alert alert-warning mb-0" role="alert">
                <?php esc_html_e('Order not found.', 'zerti'); ?>
            </p>
        </div>
        <?php

        return;
    }
    if (!zerti_user_can_view_shop_order($order_id)) {
        ?>
        <div class="zerti-component zerti-component-shop zerti-shop-order-detail zerti-shop-order-invoice" data-zerti-order-id="0">
            <p class="alert alert-warning mb-0" role="alert">
                <?php esc_html_e('You do not have permission to view this order.', 'zerti'); ?>
            </p>
        </div>
        <?php

        return;
    }
    $path = zerti_shop_order_invoice_component_path();
    if (!is_readable($path)) {
        ?>
        <div class="zerti-component zerti-component-shop zerti-shop-order-detail zerti-shop-order-invoice" data-zerti-order-id="0">
            <p class="alert alert-warning mb-0" role="alert"><?php esc_html_e('Order invoice template is not available.', 'zerti'); ?></p>
        </div>
        <?php

        return;
    }
    /** @var WC_Order $order */
    include $path;
}

/**
 * Whether the current user may load the financial payments grid.
 */
function zerti_user_can_view_payments_grid(): bool
{
    return zerti_user_can_access_tab('financial')
        && (current_user_can('view_woocommerce_reports') || current_user_can('edit_shop_orders') || current_user_can('manage_woocommerce'));
}

/**
 * Whether the current user may load the financial sales report grid.
 */
function zerti_user_can_view_sales_report_grid(): bool
{
    return zerti_user_can_view_payments_grid();
}

/**
 * AJAX: return one page of sold line items for the sales report grid.
 */
function zerti_ajax_sales_report_grid(): void
{
    check_ajax_referer('zerti_sales_report_grid', 'nonce');

    $post = zerti_unslash_post_array();
    // phpcs:ignore WordPress.Security.NonceVerification.Missing -- nonce verified above.
    $input = zerti_sales_report_grid_input_from_array($post);

    $result = zerti_fetch_sales_report_grid($input);
    if (is_wp_error($result)) {
        $status = (int) ($result->get_error_data()['status'] ?? 400);
        wp_send_json_error(['message' => $result->get_error_code()], $status > 0 ? $status : 400);
    }

    wp_send_json_success($result);
}

/**
 * Convert one WooCommerce order to the financial payments AG Grid row payload.
 *
 * @return array<string, mixed>
 */
function zerti_payment_grid_row(WC_Order $order): array
{
    $order_id_raw = (int) $order->get_id();
    $order_id_label = zerti_to_persian_digits((string) $order_id_raw);

    $paid_dt = $order->get_date_paid();
    $paid_ts = ($paid_dt && is_a($paid_dt, 'WC_DateTime')) ? (int) $paid_dt->getTimestamp() : 0;
    if ($paid_ts <= 0) {
        $created = $order->get_date_created();
        $paid_ts = ($created && is_a($created, 'WC_DateTime')) ? (int) $created->getTimestamp() : 0;
    }
    $paid_label = $paid_ts > 0 ? zerti_format_datetime($paid_ts) : '—';
    $greg_paid = ($paid_ts > 0 && function_exists('wp_date'))
        ? wp_date('Y-m-d H:i', $paid_ts, zerti_get_timezone())
        : '';

    $customer_name = trim((string) $order->get_formatted_billing_full_name());
    if ($customer_name === '') {
        $customer_name = trim((string) $order->get_billing_email());
    }
    if ($customer_name === '') {
        $customer_name = __('Untitled', 'zerti');
    }
    $cust_display = zerti_to_persian_digits($customer_name);

    $gateway_title = trim((string) $order->get_payment_method_title());
    $gateway_id = trim((string) $order->get_payment_method());
    if ($gateway_title === '' && $gateway_id !== '' && function_exists('WC')) {
        $wc = WC();
        if ($wc && $wc->payment_gateways()) {
            $registered = $wc->payment_gateways()->payment_gateways();
            if (is_array($registered) && isset($registered[$gateway_id]) && is_object($registered[$gateway_id])) {
                $gw_obj = $registered[$gateway_id];
                if (method_exists($gw_obj, 'get_title')) {
                    $gateway_title = trim((string) $gw_obj->get_title());
                }
            }
        }
    }
    if ($gateway_title === '' && $gateway_id !== '') {
        $gateway_title = $gateway_id;
    }
    $provider_label = zerti_wc_payment_gateway_display_label($gateway_id, $gateway_title);

    $tx_id = trim((string) $order->get_transaction_id());
    $tx_display = $tx_id !== '' ? zerti_to_persian_digits($tx_id) : '—';

    $status_slug = $order->get_status();
    $status_label = function_exists('zerti_wc_order_status_label_fa')
        ? zerti_wc_order_status_label_fa((string) $status_slug)
        : (string) $status_slug;

    $status_badge_class = 'bg-secondary';
    switch ($status_slug) {
        case 'completed':
        case 'processing':
            $status_badge_class = 'bg-success';
            break;
        case 'pending':
        case 'on-hold':
        case 'draft':
        case 'checkout-draft':
            $status_badge_class = 'bg-warning text-dark';
            break;
        case 'cancelled':
        case 'failed':
            $status_badge_class = 'bg-danger';
            break;
        case 'refunded':
            $status_badge_class = 'bg-info text-dark';
            break;
        default:
            break;
    }

    $total_amt = (float) $order->get_total();
    $total_html = function_exists('zerti_wc_price_html_persian')
        ? zerti_wc_price_html_persian($total_amt)
        : esc_html((string) $total_amt);
    $total_plain = trim(wp_strip_all_tags($total_html));
    $total_plain_p = zerti_to_persian_digits($total_plain);
    $status_disp = function_exists('zerti_localize_digits')
        ? zerti_localize_digits($status_label)
        : zerti_to_persian_digits($status_label);
    $status_dir = function_exists('zerti_get_text_direction') ? zerti_get_text_direction() : 'rtl';

    $search_text = trim(implode(' ', [
        (string) $order_id_raw,
        $order_id_label,
        zerti_to_latin_digits($order_id_label),
        $customer_name,
        $cust_display,
        zerti_to_latin_digits($cust_display),
        trim((string) $order->get_billing_email()),
        trim((string) $order->get_billing_phone()),
        $gateway_id,
        $gateway_title,
        $provider_label,
        zerti_to_latin_digits($provider_label),
        $tx_id,
        $tx_display,
        zerti_to_latin_digits($tx_display),
        $greg_paid,
        zerti_to_latin_digits($greg_paid),
        $paid_label,
        zerti_to_latin_digits($paid_label),
        (string) $paid_ts,
        (string) $total_amt,
        $total_plain,
        $total_plain_p,
        zerti_to_latin_digits($total_plain_p),
        $status_slug,
        $status_label,
        $status_disp,
        zerti_to_latin_digits($status_disp),
    ]));

    return [
        'idRaw' => $order_id_raw,
        'id' => $order_id_label,
        'paidDate' => $paid_label,
        'paidDateSort' => $paid_ts,
        'customer' => $cust_display,
        'totalHtml' => $total_html,
        'totalText' => $total_plain_p,
        'totalSort' => $total_amt,
        'gateway' => $provider_label,
        'transactionId' => $tx_display,
        'statusHtml' => sprintf(
            '<span class="badge %s" dir="%s">%s</span>',
            esc_attr($status_badge_class),
            esc_attr($status_dir),
            esc_html($status_disp)
        ),
        'statusText' => $status_disp,
        'statusSort' => (string) $status_slug,
        'searchText' => $search_text,
    ];
}

/**
 * Persian labels for registered WooCommerce payment gateways (payments grid filter).
 *
 * @return list<string>
 */
function zerti_payments_grid_gateway_labels(): array
{
    if (!function_exists('WC')) {
        return [];
    }
    $wc = WC();
    if (!$wc || !$wc->payment_gateways()) {
        return [];
    }

    $gateways = $wc->payment_gateways()->payment_gateways();
    if (!is_array($gateways)) {
        return [];
    }

    $labels = [];
    foreach ($gateways as $gateway_id => $gateway) {
        if (!is_object($gateway)) {
            continue;
        }
        $title = '';
        if (method_exists($gateway, 'get_title')) {
            $title = trim((string) $gateway->get_title());
        } elseif (isset($gateway->title)) {
            $title = trim((string) $gateway->title);
        }
        $label = zerti_wc_payment_gateway_display_label((string) $gateway_id, $title);
        if ($label !== '' && $label !== '—') {
            $labels[] = $label;
        }
    }

    return array_values(array_unique($labels));
}

/**
 * Normalize payments grid dropdown filters (gateway c4, status c6).
 *
 * @return array<string, string>
 */
function zerti_payments_grid_filters_from_json(string $filter_model_raw): array
{
    if ($filter_model_raw === '') {
        return [];
    }

    $filter_model = json_decode($filter_model_raw, true);
    if (!is_array($filter_model)) {
        return [];
    }

    $field_map = [
        'c4' => 'gateway',
        'c6' => 'statusText',
    ];
    $filters = [];
    foreach ($field_map as $col_id => $field) {
        if (!isset($filter_model[$col_id]) || !is_array($filter_model[$col_id])) {
            continue;
        }
        $value = isset($filter_model[$col_id]['value']) ? sanitize_text_field((string) $filter_model[$col_id]['value']) : '';
        if ($value !== '') {
            $filters[$field] = $value;
        }
    }

    return $filters;
}

/**
 * Test a formatted payments grid row against dropdown filters and the toolbar search.
 *
 * @param array<string, mixed> $row
 * @param array<string, string> $filters
 */
function zerti_payment_grid_row_matches(array $row, array $filters, string $search): bool
{
    return zerti_shop_order_grid_row_matches($row, $filters, $search);
}

/**
 * AJAX: return one page of WooCommerce payments for AG Grid.
 */
function zerti_ajax_payments_grid(): void
{
    check_ajax_referer('zerti_payments_grid', 'nonce');

    $post = zerti_unslash_post_array();
    // phpcs:ignore WordPress.Security.NonceVerification.Missing -- nonce verified above.
    $input = zerti_payments_grid_input_from_array($post);

    $result = zerti_fetch_payments_grid($input);
    if (is_wp_error($result)) {
        $status = (int) ($result->get_error_data()['status'] ?? 400);
        wp_send_json_error(['message' => $result->get_error_code()], $status > 0 ? $status : 400);
    }

    wp_send_json_success($result);
}

function zerti_render_financial_sub_component(?string $sub = null): void
{
    $sub = $sub ?? zerti_get_financial_sub();
    if (!zerti_is_valid_financial_sub($sub)) {
        $sub = 'payments';
    }

    $path = zerti_financial_sub_component_path($sub);
    $render = static function () use ($path, $sub): void {
        if (is_readable($path)) {
            include $path;

            return;
        }
        ?>
        <div class="zerti-component zerti-component-financial zerti-financial-sub-slot" data-zerti-fin-active="<?php echo esc_attr($sub); ?>">
            <p class="alert alert-warning mb-0" role="alert"><?php esc_html_e('Template for this subsection is not available.', 'zerti'); ?></p>
        </div>
        <?php
    };

    $render();
}

