<?php
/**
 * Guest table order helpers and staff view configuration.
 *
 * @package Maca_Menu_List
 */

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

/**
 * Available delivery modes for guest table orders.
 *
 * @return array<int, string>
 */
function maca_menulist_get_table_order_delivery_modes() {
    return array('guest_screen', 'waiter', 'kitchen', 'both');
}

/**
 * Current table order delivery mode.
 *
 * @return string
 */
function maca_menulist_get_table_order_delivery_mode() {
    if (!maca_menulist_table_order_enabled()) {
        return 'guest_screen';
    }

    $mode = get_option('maca_menulist_table_order_delivery_mode', 'guest_screen');

    if (!in_array($mode, maca_menulist_get_table_order_delivery_modes(), true)) {
        return 'guest_screen';
    }

    return $mode;
}

/**
 * Valid order status keys in workflow order.
 *
 * @return array<int, string>
 */
function maca_menulist_get_table_order_statuses() {
    return array('new', 'accepted', 'preparing', 'ready', 'served', 'paid', 'closed');
}

/**
 * Statuses still visible on staff boards for a given view.
 *
 * @param string $view Staff view: waiter or kitchen.
 * @return array<int, string>
 */
function maca_menulist_get_active_table_order_statuses($view = 'waiter') {
    $view = sanitize_key($view);

    if ($view === 'kitchen') {
        // Ready tickets drop off KDS once status moves past ready (e.g. waiter marks served).
        return array('new', 'accepted', 'preparing', 'ready');
    }

    return array('new', 'accepted', 'preparing', 'ready', 'served', 'paid', 'handled');
}

/**
 * Whether an order should appear on the kitchen (KDS) board.
 *
 * When the waiter screen is active, ready tickets are removed once the waiter marks
 * the order as served (status moves past ready). Without a waiter screen, kitchen
 * staff clear ready tickets themselves.
 *
 * @param object $order Order row.
 * @return bool
 */
function maca_menulist_order_visible_on_kitchen_board($order) {
    if (
        function_exists('maca_menulist_order_visible_on_staff_boards')
        && !maca_menulist_order_visible_on_staff_boards($order, 'kitchen')
    ) {
        return false;
    }

    $status = maca_menulist_normalize_table_order_status((string) $order->status);

    return in_array($status, maca_menulist_get_active_table_order_statuses('kitchen'), true);
}

/**
 * Normalize legacy status values.
 *
 * @param string $status Raw status.
 * @return string
 */
function maca_menulist_normalize_table_order_status($status) {
    $status = sanitize_key($status);

    if ($status === 'handled') {
        return 'served';
    }

    return $status;
}

/**
 * Whether a status key is valid.
 *
 * @param string $status Status key.
 * @return bool
 */
function maca_menulist_is_valid_table_order_status($status) {
    $status = maca_menulist_normalize_table_order_status($status);

    return in_array($status, maca_menulist_get_table_order_statuses(), true);
}

/**
 * Whether the order is archived and hidden from staff boards.
 *
 * @param string $status Status key.
 * @return bool
 */
function maca_menulist_is_table_order_closed($status) {
    return maca_menulist_normalize_table_order_status($status) === 'closed';
}

/**
 * Whether the guest food journey is complete.
 *
 * @param string $status Status key.
 * @return bool
 */
function maca_menulist_is_table_order_guest_complete($status) {
    $status = maca_menulist_normalize_table_order_status($status);

    return in_array($status, array('served', 'paid', 'closed'), true);
}

/**
 * Whether the order is finished for guests/staff lists.
 *
 * @param string $status Status key.
 * @return bool
 * @deprecated Use maca_menulist_is_table_order_closed() or maca_menulist_is_table_order_guest_complete().
 */
function maca_menulist_is_table_order_complete($status) {
    return maca_menulist_is_table_order_closed($status);
}

/**
 * Statuses considered completed/archived for order history cleanup.
 *
 * @return array<int, string>
 */
function maca_menulist_get_completed_table_order_statuses() {
    return array('served', 'paid', 'closed', 'handled');
}

/**
 * Human-readable status label.
 *
 * @param string $status Status key.
 * @return string
 */
function maca_menulist_get_table_order_status_label($status) {
    $status = maca_menulist_normalize_table_order_status($status);
    $labels = array(
        'new' => __('Received', 'maca-restu-pro'),
        'accepted' => __('Accepted', 'maca-restu-pro'),
        'preparing' => __('Being prepared', 'maca-restu-pro'),
        'ready' => __('Ready', 'maca-restu-pro'),
        'served' => __('Served', 'maca-restu-pro'),
        'paid' => __('Paid', 'maca-restu-pro'),
        'closed' => __('Guest left', 'maca-restu-pro'),
    );

    return isset($labels[$status]) ? $labels[$status] : $labels['new'];
}

/**
 * Guest-facing status steps for progress UI.
 *
 * @return array<int, array<string, string>>
 */
function maca_menulist_get_guest_order_status_steps() {
    return array(
        array(
            'key' => 'new',
            'label' => __('Order received', 'maca-restu-pro'),
        ),
        array(
            'key' => 'accepted',
            'label' => __('Accepted by kitchen', 'maca-restu-pro'),
        ),
        array(
            'key' => 'preparing',
            'label' => __('Being prepared', 'maca-restu-pro'),
        ),
        array(
            'key' => 'ready',
            'label' => __('Food is ready', 'maca-restu-pro'),
        ),
        array(
            'key' => 'served',
            'label' => __('Enjoy your meal!', 'maca-restu-pro'),
        ),
    );
}

/**
 * Order statuses that allow guest add-on lines (depends on service mode).
 *
 * @param object|null $order Optional order row (service_mode).
 * @return array<int, string>
 */
function maca_menulist_table_order_guest_append_statuses($order = null) {
    $statuses = array('new');

    if ($order && function_exists('maca_menulist_get_order_service_mode')) {
        $mode = maca_menulist_get_order_service_mode($order);
        $early_kitchen = array('new', 'accepted', 'preparing', 'ready');

        if ($mode === 'takeaway') {
            return $early_kitchen;
        }

        if (
            $mode === 'dine_in'
            && function_exists('maca_menulist_get_order_payment_balance')
        ) {
            $balance = maca_menulist_get_order_payment_balance($order);
            if ($balance['paid'] > 0.009) {
                return array('new', 'accepted', 'preparing');
            }
        }
    }

    return $statuses;
}

/**
 * Whether more items can be added to an existing open order.
 *
 * @param string      $status Status key.
 * @param object|null $order  Optional order row for service-mode rules.
 * @return bool
 */
function maca_menulist_table_order_allows_append($status, $order = null) {
    $status = maca_menulist_normalize_table_order_status($status);

    return in_array($status, maca_menulist_table_order_guest_append_statuses($order), true);
}

/**
 * Kitchen actions available for the current status.
 *
 * @param string $status Current status.
 * @return array<int, array<string, mixed>>
 */
function maca_menulist_get_kitchen_order_actions($status) {
    $status = maca_menulist_normalize_table_order_status($status);

    switch ($status) {
        case 'new':
            return array(
                array(
                    'status' => 'accepted',
                    'label' => __('Accept', 'maca-restu-pro'),
                ),
            );
        case 'accepted':
            return array(
                array(
                    'status' => 'preparing',
                    'label' => __('Start preparing', 'maca-restu-pro'),
                ),
            );
        case 'preparing':
            return array(
                array(
                    'status' => 'preparing',
                    'label' => __('Ready in…', 'maca-restu-pro'),
                    'needs_eta' => true,
                    'update_eta_only' => true,
                ),
                array(
                    'status' => 'ready',
                    'label' => __('Food is ready', 'maca-restu-pro'),
                ),
            );
        case 'ready':
            if (!maca_menulist_table_order_waiter_page_enabled()) {
                return array(
                    array(
                        'status' => 'served',
                        'label' => __('Picked up', 'maca-restu-pro'),
                    ),
                );
            }

            return array();
        default:
            return array();
    }
}

/**
 * Kitchen actions for a specific order (blocked until takeaway is paid).
 *
 * @param object $order Order row.
 * @return array<int, array<string, mixed>>
 */
function maca_menulist_get_kitchen_order_actions_for_order($order) {
    if (
        function_exists('maca_menulist_order_blocks_kitchen_until_prepayment')
        && maca_menulist_order_blocks_kitchen_until_prepayment($order)
    ) {
        return array();
    }

    return maca_menulist_get_kitchen_order_actions((string) $order->status);
}

/**
 * Validate and normalize a kitchen status change (incl. ETA-only updates while preparing).
 *
 * @param string   $current_status Current order status.
 * @param string   $target_status  Requested status from staff UI.
 * @param int|null $eta_minutes    Optional ETA minutes from staff UI.
 * @return array{allowed: bool, status: string, eta_minutes: int|null, eta_only?: bool, message?: string}
 */
function maca_menulist_validate_kitchen_status_change($current_status, $target_status, $eta_minutes = null) {
    $current_status = maca_menulist_normalize_table_order_status($current_status);
    $target_status = maca_menulist_normalize_table_order_status(sanitize_key($target_status));
    $eta_minutes = $eta_minutes !== null ? (int) $eta_minutes : null;

    // Legacy/cached kitchen UI may POST ready+eta while the order is still preparing.
    if (
        $current_status === 'preparing'
        && $target_status === 'ready'
        && $eta_minutes !== null
        && $eta_minutes > 0
    ) {
        return array(
            'allowed' => true,
            'status' => 'preparing',
            'eta_minutes' => $eta_minutes,
            'eta_only' => true,
        );
    }

    foreach (maca_menulist_get_kitchen_order_actions($current_status) as $action) {
        if (!empty($action['update_eta_only'])) {
            if (
                $current_status === 'preparing'
                && $target_status === 'preparing'
                && $eta_minutes !== null
                && $eta_minutes >= 0
            ) {
                return array(
                    'allowed' => true,
                    'status' => 'preparing',
                    'eta_minutes' => $eta_minutes,
                    'eta_only' => true,
                );
            }
            continue;
        }

        if ((string) $action['status'] !== $target_status) {
            continue;
        }

        if (!empty($action['needs_eta']) && ($eta_minutes === null || $eta_minutes < 0)) {
            return array(
                'allowed' => false,
                'status' => $target_status,
                'eta_minutes' => null,
                'message' => __('Enter how many minutes until ready.', 'maca-restu-pro'),
            );
        }

        return array(
            'allowed' => true,
            'status' => $target_status,
            'eta_minutes' => $target_status === 'ready' ? null : $eta_minutes,
        );
    }

    return array(
        'allowed' => false,
        'status' => $target_status,
        'eta_minutes' => null,
    );
}

/**
 * Waiter actions available for the current status.
 *
 * @param string $status Current status.
 * @return array<int, array<string, mixed>>
 */
function maca_menulist_get_waiter_order_actions($status) {
    $status = maca_menulist_normalize_table_order_status($status);
    $mode = maca_menulist_get_table_order_delivery_mode();
    $kitchen_active = in_array($mode, array('kitchen', 'both'), true);

    switch ($status) {
        case 'new':
            if (!$kitchen_active) {
                return array(
                    array(
                        'status' => 'accepted',
                        'label' => __('Take order', 'maca-restu-pro'),
                    ),
                );
            }
            return array();
        case 'accepted':
            if (!$kitchen_active) {
                return array(
                    array(
                        'status' => 'served',
                        'label' => __('Delivered to table', 'maca-restu-pro'),
                    ),
                );
            }
            return array();
        case 'preparing':
            return array();
        case 'ready':
            return array(
                array(
                    'status' => 'served',
                    'label' => in_array(maca_menulist_get_table_order_delivery_mode(), array('kitchen', 'both'), true)
                        ? __('Mark as served', 'maca-restu-pro')
                        : __('Deliver to table', 'maca-restu-pro'),
                ),
            );
        case 'served':
            return array(
                array(
                    'status' => 'paid',
                    'label' => __('Mark paid', 'maca-restu-pro'),
                ),
            );
        case 'paid':
            return array(
                array(
                    'status' => 'closed',
                    'label' => __('Guest left', 'maca-restu-pro'),
                ),
            );
        default:
            return array();
    }
}

/**
 * Waiter actions for an order row (hides Mark paid when guest already paid online).
 *
 * @param object $order Order row.
 * @return array<int, array<string, mixed>>
 */
function maca_menulist_get_waiter_order_actions_for_order($order) {
    if (
        function_exists('maca_menulist_order_requires_takeaway_prepayment')
        && maca_menulist_order_requires_takeaway_prepayment($order)
        && !maca_menulist_order_is_payment_complete($order)
    ) {
        if (maca_menulist_payments_allow_manual()) {
            return array(
                array(
                    'status' => 'paid',
                    'label' => __('Register payment', 'maca-restu-pro'),
                ),
            );
        }

        return array();
    }

    $status = maca_menulist_normalize_table_order_status((string) $order->status);
    $actions = maca_menulist_get_waiter_order_actions($status);
    $payment_status = isset($order->payment_status) ? sanitize_key((string) $order->payment_status) : 'none';

    if ($status === 'served' && !maca_menulist_order_is_payment_complete($order)) {
        if (maca_menulist_order_bill_split_is_active($order)) {
            return array(
                array(
                    'status' => 'paid',
                    'label' => __('Register one share paid', 'maca-restu-pro'),
                    'bill_split_share' => true,
                ),
            );
        }

        $paid_so_far = round(max(0, (float) ($order->payment_amount ?? 0)), 2);

        if ($paid_so_far > 0.01) {
            return array(
                array(
                    'status' => 'paid',
                    'label' => __('Register payment', 'maca-restu-pro'),
                    'partial_payment' => true,
                ),
            );
        }

        return $actions;
    }

    if (!maca_menulist_order_is_payment_complete($order)) {
        return $actions;
    }

    $actions = array_values(
        array_filter(
            $actions,
            static function ($action) {
                return ($action['status'] ?? '') !== 'paid';
            }
        )
    );

    if ($status === 'served' && empty($actions)) {
        return array(
            array(
                'status' => 'closed',
                'label' => __('Guest left', 'maca-restu-pro'),
            ),
        );
    }

    return $actions;
}

/**
 * Whether guests can track order status after submit.
 *
 * @return bool
 */
function maca_menulist_table_order_guest_status_enabled() {
    if (!maca_menulist_table_order_enabled() || !maca_menulist_table_order_uses_server()) {
        return false;
    }

    $option = get_option('maca_menulist_table_order_guest_status', '');

    if ($option === '') {
        $mode = maca_menulist_get_table_order_delivery_mode();

        return in_array($mode, array('kitchen', 'both'), true);
    }

    return $option === '1';
}

/**
 * Whether orders are sent to the server for staff pages.
 *
 * @return bool
 */
function maca_menulist_table_order_uses_server() {
    $mode = maca_menulist_get_table_order_delivery_mode();

    return in_array($mode, array('waiter', 'kitchen', 'both'), true);
}

/**
 * Why waiter/kitchen pages are unavailable (empty string = available).
 *
 * @return string
 */
function maca_menulist_get_staff_order_pages_block_reason() {
    if (!maca_menulist_is_dishes_mode_selected()) {
        return __('Select “Online menu in the plugin (Pro)” as menu content type and save settings.', 'maca-restu-pro');
    }

    if (!maca_menulist_is_dishes_mode()) {
        return __('Online menu requires an active Pro license. Check Subscription in the admin menu.', 'maca-restu-pro');
    }

    if (get_option('maca_menulist_table_order', '0') !== '1') {
        return __('Enable guest table ordering on the Order tab and save settings.', 'maca-restu-pro');
    }

    if (!maca_menulist_table_order_uses_server()) {
        return __('Set delivery mode to Waiter, Kitchen, or Both on the Order tab and save settings.', 'maca-restu-pro');
    }

    return '';
}

/**
 * Whether staff order pages (waiter/kitchen) can be used.
 *
 * @return bool
 */
function maca_menulist_staff_order_pages_active() {
    return maca_menulist_get_staff_order_pages_block_reason() === '';
}

/**
 * Whether the waiter staff page is enabled.
 *
 * @return bool
 */
function maca_menulist_table_order_waiter_page_enabled() {
    if (!maca_menulist_staff_order_pages_active()) {
        return false;
    }

    $mode = maca_menulist_get_table_order_delivery_mode();

    return $mode === 'waiter' || $mode === 'both';
}

/**
 * Whether the kitchen staff page is enabled.
 *
 * @return bool
 */
function maca_menulist_table_order_kitchen_page_enabled() {
    if (!maca_menulist_staff_order_pages_active()) {
        return false;
    }

    $mode = maca_menulist_get_table_order_delivery_mode();

    return $mode === 'kitchen' || $mode === 'both';
}

/**
 * Staff PIN for order pages.
 *
 * @return string
 */
function maca_menulist_get_table_order_staff_pin($bypass_cache = false) {
    if ($bypass_cache) {
        wp_cache_delete('maca_menulist_table_order_staff_pin', 'options');
    }

    $pin = get_option('maca_menulist_table_order_staff_pin', '');

    return is_string($pin) ? $pin : '';
}

/**
 * Prefix for plugin-native HMAC staff PIN hashes.
 */
function maca_menulist_staff_pin_hmac_prefix() {
    return 'maca.pin.v1.';
}

/**
 * Whether the stored staff PIN uses the plugin HMAC format.
 *
 * @param string $value Stored option value.
 * @return bool
 */
function maca_menulist_table_order_staff_pin_is_hmac($value) {
    $prefix = maca_menulist_staff_pin_hmac_prefix();

    return is_string($value) && strpos((string) $value, $prefix) === 0 && strlen($value) > strlen($prefix);
}

/**
 * Whether the stored staff PIN uses a password hash format.
 *
 * @param string $value Stored option value.
 * @return bool
 */
function maca_menulist_table_order_staff_pin_is_hashed($value) {
    if (maca_menulist_table_order_staff_pin_is_hmac($value)) {
        return false;
    }

    $value = (string) $value;

    if ($value === '' || strlen($value) < 20 || $value[0] !== '$') {
        return false;
    }

    // Supports legacy phpass ($P$...), bcrypt/argon ($2y$... / $argon2...),
    // and newer WordPress wrapper hashes (e.g. $wp$2y$...).
    return preg_match('/^\$(wp\$)?(P[\$B]|P\$|2y|2b|2a|argon2)/i', $value) === 1
        || strpos($value, '$wp$') === 0;
}

/**
 * Revision token that changes whenever the staff PIN is updated.
 *
 * @return string
 */
function maca_menulist_get_table_order_staff_pin_revision() {
    return (string) get_option('maca_menulist_table_order_staff_pin_revision', '0');
}

/**
 * Bump staff PIN revision and clear cached option values.
 *
 * @return void
 */
function maca_menulist_bump_table_order_staff_pin_revision() {
    update_option('maca_menulist_table_order_staff_pin_revision', (string) time(), false);
    wp_cache_delete('maca_menulist_table_order_staff_pin_revision', 'options');
    wp_cache_delete('maca_menulist_table_order_staff_pin', 'options');
}

/**
 * Clear staff PIN lockouts for every device/browser.
 *
 * @return void
 */
function maca_menulist_clear_all_staff_pin_lockouts() {
    global $wpdb;

    if (!isset($wpdb->options)) {
        return;
    }

    $like_transient = $wpdb->esc_like('_transient_maca_staff_pin_lock_') . '%';
    $like_timeout = $wpdb->esc_like('_transient_timeout_maca_staff_pin_lock_') . '%';

    // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
    $wpdb->query(
        $wpdb->prepare(
            "DELETE FROM {$wpdb->options} WHERE option_name LIKE %s OR option_name LIKE %s",
            $like_transient,
            $like_timeout
        )
    );
}

/**
 * Normalize a submitted staff PIN from POST.
 *
 * @return string
 */
function maca_menulist_get_staff_pin_from_request() {
    // phpcs:ignore WordPress.Security.NonceVerification.Missing -- PIN-protected staff API.
    foreach (array('staff_pin', 'pin') as $key) {
        if (!isset($_POST[$key])) {
            continue;
        }

        $pin = wp_unslash($_POST[$key]);
        if (is_string($pin)) {
            $pin = trim($pin);
            if ($pin !== '') {
                return $pin;
            }
        }
    }

    return '';
}

/**
 * Read staff AJAX action from POST (avoid front-end "action" parameter conflicts).
 *
 * @return string
 */
function maca_menulist_get_staff_action_from_request() {
    // phpcs:ignore WordPress.Security.NonceVerification.Missing -- PIN-protected staff API.
    if (isset($_POST['maca_staff_action'])) {
        return sanitize_key(wp_unslash($_POST['maca_staff_action']));
    }

    if (isset($_POST['action'])) {
        return sanitize_key(wp_unslash($_POST['action']));
    }

    return '';
}

/**
 * Staff board API action names.
 *
 * @return string[]
 */
function maca_menulist_get_staff_api_actions() {
    return array(
        'maca_verify_staff_pin',
        'maca_get_staff_table_summary',
        'maca_get_staff_orders',
        'maca_handle_staff_order',
        'maca_update_staff_order_status',
        'maca_update_staff_order_table',
    );
}

/**
 * Mark the current request as a staff board API call (skips guest gettext filters).
 *
 * @return void
 */
function maca_menulist_mark_staff_api_request() {
    if (!defined('MACA_MENULIST_STAFF_API_REQUEST')) {
        define('MACA_MENULIST_STAFF_API_REQUEST', true);
    }
}

/**
 * Whether the current HTTP request is a staff board API call.
 *
 * @return bool
 */
function maca_menulist_is_staff_api_request() {
    if (defined('MACA_MENULIST_STAFF_API_REQUEST') && MACA_MENULIST_STAFF_API_REQUEST) {
        return true;
    }

    $action = maca_menulist_get_staff_action_from_request();
    if ($action === '' && isset($_REQUEST['action'])) {
        $action = sanitize_key((string) wp_unslash($_REQUEST['action']));
    }

    if ($action === '' || !in_array($action, maca_menulist_get_staff_api_actions(), true)) {
        return false;
    }

    if (defined('DOING_AJAX') && DOING_AJAX) {
        return true;
    }

    if (function_exists('maca_menulist_is_staff_orders_request') && maca_menulist_is_staff_orders_request()) {
        return true;
    }

    if (function_exists('maca_menulist_bootstrap_staff_request_query_vars')
        && maca_menulist_bootstrap_staff_request_query_vars()) {
        return true;
    }

    return false;
}

/**
 * Verify a submitted PIN against a stored hash or legacy plain value.
 *
 * @param string $submitted Plain PIN from staff UI.
 * @param string $expected  Stored option value.
 * @return bool
 */
function maca_menulist_verify_pin_against_stored($submitted, $expected) {
    $submitted = trim((string) $submitted);
    $expected = (string) $expected;

    if ($submitted === '' || $expected === '') {
        return false;
    }

    if (maca_menulist_table_order_staff_pin_is_hmac($expected)) {
        return hash_equals($expected, maca_menulist_hash_table_order_staff_pin($submitted));
    }

    if (maca_menulist_table_order_staff_pin_is_hashed($expected)) {
        if (wp_check_password($submitted, $expected)) {
            return true;
        }

        // WordPress 6.8+ wrapped bcrypt on hosts where core check misses the prefix.
        if (strpos($expected, '$wp$') === 0) {
            $inner = substr($expected, 4);
            if ($inner !== '' && password_verify($submitted, $inner)) {
                return true;
            }
        }

        return false;
    }

    return hash_equals($expected, $submitted);
}

/**
 * Hash a staff PIN for secure storage.
 *
 * @param string $pin Plain text PIN.
 * @return string
 */
function maca_menulist_hash_table_order_staff_pin($pin) {
    $pin = (string) $pin;
    if ($pin === '') {
        return '';
    }

    return maca_menulist_staff_pin_hmac_prefix() . hash_hmac('sha256', $pin, wp_salt('maca_menulist_staff_pin'));
}

/**
 * Build lockout key for failed staff PIN attempts.
 *
 * @return string
 */
function maca_menulist_staff_pin_lock_key() {
    $ip = isset($_SERVER['REMOTE_ADDR']) ? sanitize_text_field(wp_unslash($_SERVER['REMOTE_ADDR'])) : 'unknown';
    $ua = isset($_SERVER['HTTP_USER_AGENT']) ? sanitize_text_field(wp_unslash($_SERVER['HTTP_USER_AGENT'])) : 'unknown';
    $fingerprint = strtolower(trim($ip . '|' . $ua));

    return 'maca_staff_pin_lock_' . md5($fingerprint);
}

/**
 * Get failed-attempt state for staff PIN lockout.
 *
 * @return array{count:int,locked_until:int}
 */
function maca_menulist_get_staff_pin_lock_state() {
    $state = get_transient(maca_menulist_staff_pin_lock_key());
    if (!is_array($state)) {
        return array('count' => 0, 'locked_until' => 0);
    }

    return array(
        'count' => isset($state['count']) ? (int) $state['count'] : 0,
        'locked_until' => isset($state['locked_until']) ? (int) $state['locked_until'] : 0,
    );
}

/**
 * Register a failed staff PIN attempt and potentially lock further attempts.
 *
 * @return void
 */
function maca_menulist_register_staff_pin_failed_attempt() {
    $state = maca_menulist_get_staff_pin_lock_state();
    $count = (int) $state['count'] + 1;
    $lock_after = 10;
    $lock_seconds = 10 * MINUTE_IN_SECONDS;
    $locked_until = 0;

    if ($count >= $lock_after) {
        $locked_until = time() + $lock_seconds;
    }

    set_transient(
        maca_menulist_staff_pin_lock_key(),
        array(
            'count' => $count,
            'locked_until' => $locked_until,
        ),
        $lock_seconds
    );
}

/**
 * PIN-attempt feedback for staff pages.
 *
 * @return array{is_locked:bool,attempts_used:int,attempts_left:int,max_attempts:int,locked_seconds_remaining:int}
 */
function maca_menulist_get_staff_pin_attempt_feedback() {
    $state = maca_menulist_get_staff_pin_lock_state();
    $max_attempts = 10;
    $attempts_used = max(0, (int) $state['count']);
    $attempts_left = max(0, $max_attempts - $attempts_used);
    $locked_seconds_remaining = max(0, (int) $state['locked_until'] - time());

    return array(
        'is_locked' => $locked_seconds_remaining > 0,
        'attempts_used' => $attempts_used,
        'attempts_left' => $attempts_left,
        'max_attempts' => $max_attempts,
        'locked_seconds_remaining' => $locked_seconds_remaining,
    );
}

/**
 * Clear failed-attempt state after successful PIN verification.
 *
 * @return void
 */
function maca_menulist_clear_staff_pin_failed_attempts() {
    delete_transient(maca_menulist_staff_pin_lock_key());
}

/**
 * Whether staff PIN verification is currently locked due to failures.
 *
 * @return bool
 */
function maca_menulist_is_staff_pin_locked() {
    $state = maca_menulist_get_staff_pin_lock_state();

    return (int) $state['locked_until'] > time();
}

/**
 * Verify staff PIN.
 *
 * @param string $pin Submitted PIN.
 * @return bool
 */
function maca_menulist_verify_table_order_staff_pin($pin) {
    if (maca_menulist_is_staff_pin_locked()) {
        return false;
    }

    $submitted = trim((string) $pin);
    if ($submitted === '') {
        return false;
    }

    $expected = maca_menulist_get_table_order_staff_pin(true);

    if ($expected === '') {
        return false;
    }

    $valid = maca_menulist_verify_pin_against_stored($submitted, $expected);
    if ($valid && !maca_menulist_table_order_staff_pin_is_hmac($expected)) {
        update_option('maca_menulist_table_order_staff_pin', maca_menulist_hash_table_order_staff_pin($submitted), false);
        maca_menulist_bump_table_order_staff_pin_revision();
        wp_cache_delete('maca_menulist_table_order_staff_pin', 'options');
    }

    if ($valid) {
        maca_menulist_clear_staff_pin_failed_attempts();
        return true;
    }

    maca_menulist_register_staff_pin_failed_attempt();

    return false;
}

/**
 * Client fingerprint for staff session binding (IP only).
 *
 * @return string
 */
function maca_menulist_staff_request_client_fingerprint() {
    $ip = '';

    if (!empty($_SERVER['REMOTE_ADDR'])) {
        $ip = (string) wp_unslash($_SERVER['REMOTE_ADDR']);
    }

    return md5(strtolower(trim($ip)));
}

/**
 * Transient key for a staff session token.
 *
 * @param string $token Session token.
 * @return string
 */
function maca_menulist_staff_session_storage_key($token) {
    $token = strtolower(preg_replace('/[^a-f0-9]/', '', (string) $token));

    if (strlen($token) !== 64) {
        return '';
    }

    return 'maca_staff_sess_' . $token;
}

/**
 * Read staff session token from POST.
 *
 * @return string
 */
function maca_menulist_get_staff_session_from_request() {
    // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Session-bound staff API.
    if (!isset($_POST['staff_session'])) {
        return '';
    }

    return sanitize_text_field(wp_unslash($_POST['staff_session']));
}

/**
 * Create a short-lived staff session after PIN verification.
 *
 * @param string $view Staff view.
 * @return string Session token.
 */
function maca_menulist_create_staff_session($view = 'waiter') {
    try {
        $token = bin2hex(random_bytes(32));
    } catch (Throwable $e) {
        $token = wp_hash(wp_generate_password(32, true, true) . microtime(true));
        $token = strtolower(preg_replace('/[^a-f0-9]/', '', $token));
        if (strlen($token) < 64) {
            $token = str_pad($token, 64, '0');
        }
        $token = substr($token, 0, 64);
    }

    $view = sanitize_key($view) === 'kitchen' ? 'kitchen' : 'waiter';
    $key = maca_menulist_staff_session_storage_key($token);

    if ($key === '') {
        return '';
    }

    set_transient(
        $key,
        array(
            'view'          => $view,
            'pin_revision'  => maca_menulist_get_table_order_staff_pin_revision(),
            'client'        => maca_menulist_staff_request_client_fingerprint(),
        ),
        45 * MINUTE_IN_SECONDS
    );

    return $token;
}

/**
 * Whether a staff session token is valid for the current client.
 *
 * @param string $token Session token.
 * @return bool
 */
function maca_menulist_validate_staff_session($token) {
    $key = maca_menulist_staff_session_storage_key($token);

    if ($key === '') {
        return false;
    }

    $data = get_transient($key);

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

    if (($data['pin_revision'] ?? '') !== maca_menulist_get_table_order_staff_pin_revision()) {
        return false;
    }

    return ($data['client'] ?? '') === maca_menulist_staff_request_client_fingerprint();
}

/**
 * Validate staff PIN from the current request.
 *
 * @return array{success:bool,pin?:string,session?:string,status?:int,data?:array<string,mixed>}
 */
function maca_menulist_validate_staff_pin_from_request() {
    $session = maca_menulist_get_staff_session_from_request();

    if ($session !== '' && maca_menulist_validate_staff_session($session)) {
        return array(
            'success' => true,
            'session' => $session,
        );
    }

    $pin = maca_menulist_get_staff_pin_from_request();

    if ($pin === '') {
        return array(
            'success' => false,
            'status'  => 400,
            'data'    => array(
                'message'     => __('PIN was not received. Reload the page and try again.', 'maca-restu-pro'),
                'missing_pin' => true,
            ),
        );
    }

    if (!maca_menulist_verify_table_order_staff_pin($pin)) {
        $pin_feedback = maca_menulist_get_staff_pin_attempt_feedback();
        $message = maca_menulist_is_staff_pin_locked()
            ? __('Too many failed PIN attempts. Please wait a few minutes and try again.', 'maca-restu-pro')
            : __('Incorrect PIN.', 'maca-restu-pro');

        return array(
            'success' => false,
            'status'  => 403,
            'data'    => array_merge(
                array('message' => $message),
                $pin_feedback
            ),
        );
    }

    return array(
        'success' => true,
        'pin'     => $pin,
    );
}

/**
 * Read staff PIN from the request or send a JSON error response.
 *
 * @return string Verified PIN (never returns when invalid).
 */
function maca_menulist_assert_staff_pin_or_json_error() {
    $result = maca_menulist_validate_staff_pin_from_request();
    if (empty($result['success'])) {
        wp_send_json_error($result['data'], $result['status'] ?? 403);
    }

    return (string) $result['pin'];
}

/**
 * Waiter staff page slug.
 *
 * @return string
 */
function maca_menulist_get_waiter_order_slug() {
    $slug = get_option('maca_menulist_waiter_order_slug', 'meny-servitris');
    $slug = sanitize_title($slug);

    if ($slug === '') {
        $slug = 'meny-servitris';
    }

    return $slug;
}

/**
 * Kitchen staff page slug.
 *
 * @return string
 */
function maca_menulist_get_kitchen_order_slug() {
    $slug = get_option('maca_menulist_kitchen_order_slug', 'meny-kok');
    $slug = sanitize_title($slug);

    if ($slug === '') {
        $slug = 'meny-kok';
    }

    return $slug;
}

/**
 * Public URL for the waiter order page.
 *
 * @return string
 */
function maca_menulist_get_waiter_order_url() {
    return home_url('/' . maca_menulist_get_waiter_order_slug() . '/');
}

/**
 * Public staff order page URL for a view (also used for same-origin AJAX).
 *
 * @param string $view Staff view: waiter or kitchen.
 * @return string
 */
function maca_menulist_get_staff_orders_page_url($view = 'waiter') {
    return sanitize_key($view) === 'kitchen'
        ? maca_menulist_get_kitchen_order_url()
        : maca_menulist_get_waiter_order_url();
}

/**
 * Reliable POST target for staff board API calls (rewrite-free).
 *
 * @param string $view Staff view: waiter or kitchen.
 * @return string
 */
function maca_menulist_get_staff_orders_post_url($view = 'waiter') {
    return maca_menulist_get_staff_orders_page_url($view);
}

/**
 * REST endpoint URL for staff board API calls.
 *
 * @return string
 */
function maca_menulist_get_staff_rest_url() {
    return rest_url('maca-menulist/v1/staff');
}

/**
 * Public URL for the kitchen order page.
 *
 * @return string
 */
function maca_menulist_get_kitchen_order_url() {
    return home_url('/' . maca_menulist_get_kitchen_order_slug() . '/');
}

/**
 * Minimal waiter poll payload for a single table (low memory on shared hosting).
 *
 * @param object $order Order row with lines attached.
 * @param string $view  Staff view.
 * @return array<string, mixed>
 */
function maca_menulist_format_table_order_staff_poll($order, $view = 'waiter') {
    $view = sanitize_key($view);
    $lines = array();

    foreach ($order->lines ?? array() as $line) {
        $lines[] = array(
            'qty'   => (int) $line->qty,
            'title' => (string) $line->title,
            'note'  => isset($line->line_note) ? (string) $line->line_note : '',
        );
    }

    $status = maca_menulist_normalize_table_order_status((string) $order->status);
    $service_mode = function_exists('maca_menulist_get_order_service_mode')
        ? maca_menulist_get_order_service_mode($order)
        : 'dine_in';

    $payload = array(
        'id'           => (int) $order->id,
        'table_number' => $order->table_number,
        'service_mode' => $service_mode,
        'pickup_name'  => isset($order->pickup_name) ? (string) $order->pickup_name : '',
        'pickup_phone' => isset($order->pickup_phone) ? (string) $order->pickup_phone : '',
        'order_note'   => isset($order->order_note) ? (string) $order->order_note : '',
        'status'       => $status,
        'status_label' => maca_menulist_get_table_order_status_label($status),
        'created_at'   => $order->created_at,
        'time_label'   => maca_menulist_format_order_time($order->created_at),
        'lines'        => $lines,
        'can_change_table' => false,
    );

    if ($view === 'waiter') {
        $payload['waiter_actions'] = maca_menulist_get_waiter_order_actions_for_order($order);
    }

    $payment_status = isset($order->payment_status) ? sanitize_key((string) $order->payment_status) : 'none';
    if (
        ($payment_status !== 'none' || !empty($order->payment_method))
        && function_exists('maca_menulist_build_staff_payment_state')
    ) {
        try {
            $payload['payment'] = maca_menulist_build_staff_payment_state($order, true);
        } catch (Throwable $payment_error) {
            unset($payload['payment']);
        }
    }

    if ($view === 'waiter' && maca_menulist_table_order_show_prices()) {
        $total = 0.0;
        foreach ($order->lines ?? array() as $line) {
            $qty = (int) $line->qty;
            $price = $line->price !== null && $line->price !== '' ? (float) $line->price : null;
            if ($price !== null) {
                $total += $price * $qty;
            }
        }
        if ($total > 0) {
            $payload['total'] = $total;
            $payload['total_label'] = maca_menulist_format_price($total);
        }
    }

    return $payload;
}

/**
 * Format order timestamp for display.
 *
 * @param string $datetime MySQL datetime.
 * @return string
 */
function maca_menulist_format_order_time($datetime) {
    return maca_menulist_format_wp_datetime($datetime, get_option('time_format'), '');
}

/**
 * Date used for guest order table choices (today in site timezone).
 *
 * @return string Y-m-d
 */
function maca_menulist_guest_order_table_date() {
    return wp_date('Y-m-d');
}

/**
 * Active physical table numbers for guest orders (from table reservations).
 *
 * When a splittable table is split for the given date (salongsöversikt),
 * returns split labels (e.g. 8a, 8b) instead of the base number.
 *
 * @param string $date Optional date Y-m-d; defaults to today.
 * @return array<int, string>
 */
function maca_menulist_get_guest_order_table_numbers($date = '') {
    if (!function_exists('maca_menulist_db_get_booking_tables')) {
        return array();
    }

    if (function_exists('maca_menulist_db_ensure_table_booking_tables')) {
        maca_menulist_db_ensure_table_booking_tables();
    }

    $date = trim((string) $date);
    if ($date === '') {
        $date = maca_menulist_guest_order_table_date();
    }

    $numbers = array();

    foreach (maca_menulist_db_get_booking_tables(true) as $table) {
        $number = trim((string) $table->table_number);

        if ($number === '') {
            continue;
        }

        $table_id = isset($table->id) ? absint($table->id) : 0;
        $split_today = $table_id > 0
            && function_exists('maca_menulist_is_table_split_for_date')
            && maca_menulist_is_table_split_for_date($table_id, $date);

        if ($split_today && function_exists('maca_menulist_get_table_split_slot_letters')) {
            foreach (maca_menulist_get_table_split_slot_letters($table, $date) as $slot) {
                $label = function_exists('maca_menulist_format_split_table_number')
                    ? maca_menulist_format_split_table_number($number, $slot)
                    : $number . $slot;

                if ($label !== '') {
                    $numbers[ $label ] = $label;
                }
            }
            continue;
        }

        $numbers[ $number ] = $number;
    }

    $list = array_values($numbers);

    usort($list, 'strnatcasecmp');

    return $list;
}

/**
 * Normalize QR/menu prefill when the table is split today.
 *
 * Bare base numbers (?bord=8) are not injected when only 8a/8b are valid.
 *
 * @param string $prefill_table Raw value from URL or argument.
 * @return string Resolved prefill, or empty to force guest choice.
 */
function maca_menulist_guest_order_resolve_prefill_table($prefill_table) {
    $prefill_table = trim((string) $prefill_table);

    if ($prefill_table === '') {
        return '';
    }

    $active = maca_menulist_get_guest_order_table_numbers();

    if (in_array($prefill_table, $active, true)) {
        return $prefill_table;
    }

    if (!function_exists('maca_menulist_db_get_booking_tables')) {
        return $prefill_table;
    }

    $date = maca_menulist_guest_order_table_date();

    foreach (maca_menulist_db_get_booking_tables(true) as $table) {
        $base = trim((string) $table->table_number);

        if ($base === '' || strcasecmp($prefill_table, $base) !== 0) {
            continue;
        }

        $table_id = isset($table->id) ? absint($table->id) : 0;

        if (
            $table_id > 0
            && function_exists('maca_menulist_is_table_split_for_date')
            && maca_menulist_is_table_split_for_date($table_id, $date)
        ) {
            return '';
        }

        break;
    }

    return $prefill_table;
}

/**
 * Table number from QR/menu URL (?bord= or ?table=).
 *
 * @return string
 */
function maca_menulist_get_guest_order_table_from_request() {
    // phpcs:disable WordPress.Security.NonceVerification.Recommended -- Public menu deep link.
    if (isset($_GET['bord'])) {
        return sanitize_text_field(wp_unslash((string) $_GET['bord']));
    }

    if (isset($_GET['table'])) {
        return sanitize_text_field(wp_unslash((string) $_GET['table']));
    }
    // phpcs:enable WordPress.Security.NonceVerification.Recommended

    return '';
}

/**
 * Table choices for guest dropdown (active tables + optional QR prefill).
 *
 * @param string $prefill_table Table from URL when not in active list.
 * @return array<int, string>
 */
function maca_menulist_build_guest_order_table_choices($prefill_table = '') {
    $choices = maca_menulist_get_guest_order_table_numbers();
    $prefill_table = maca_menulist_guest_order_resolve_prefill_table($prefill_table);

    if ($prefill_table !== '' && !in_array($prefill_table, $choices, true)) {
        array_unshift($choices, $prefill_table);
    }

    return $choices;
}

/**
 * Whether a submitted table number is allowed for guest orders.
 *
 * @param string $table_number   Submitted table number.
 * @param string $prefill_table  Optional QR prefill from the menu page.
 * @return bool
 */
function maca_menulist_is_allowed_guest_order_table_number($table_number, $prefill_table = '') {
    $table_number = trim((string) $table_number);

    if ($table_number === '') {
        return false;
    }

    $active = maca_menulist_get_guest_order_table_numbers();

    if (empty($active)) {
        return true;
    }

    $allowed = maca_menulist_build_guest_order_table_choices($prefill_table);

    return in_array($table_number, $allowed, true);
}

/**
 * Whether staff may change table number on an open dine-in order.
 *
 * @param object $order Order row.
 * @return bool
 */
function maca_menulist_staff_can_change_order_table($order) {
    if (!$order) {
        return false;
    }

    $service_mode = function_exists('maca_menulist_get_order_service_mode')
        ? maca_menulist_get_order_service_mode($order)
        : 'dine_in';

    if ($service_mode === 'takeaway') {
        return false;
    }

    $status = maca_menulist_normalize_table_order_status((string) $order->status);

    if (maca_menulist_is_table_order_closed($status)) {
        return false;
    }

    if (
        function_exists('maca_menulist_order_is_payment_complete')
        && maca_menulist_order_is_payment_complete($order)
    ) {
        return false;
    }

    return true;
}

/**
 * Render table selector for guest order panel.
 *
 * @return void
 */
function maca_menulist_render_guest_order_table_select() {
    $prefill_raw = maca_menulist_get_guest_order_table_from_request();
    $prefill = maca_menulist_guest_order_resolve_prefill_table($prefill_raw);
    $choices = maca_menulist_build_guest_order_table_choices($prefill_raw);

    echo '<label class="maca-table-order-field" for="maca-table-order-table-select">';
    echo '<span>' . esc_html(maca_menulist_guest_ui_string('Table')) . '</span>';
    echo '<select id="maca-table-order-table-select" class="maca-table-order-table-select">';

    echo '<option value="">' . esc_html(maca_menulist_guest_ui_string('Choose table')) . '</option>';

    if (empty($choices)) {
        echo '<option value="" disabled>' . esc_html(maca_menulist_guest_ui_string('No tables available')) . '</option>';
    } else {
        foreach ($choices as $number) {
            printf(
                '<option value="%s" %s>%s</option>',
                esc_attr($number),
                selected($prefill, $number, false),
                esc_html(
                    sprintf(
                        /* translators: %s: table number */
                        maca_menulist_guest_ui_string('Table %s'),
                        $number
                    )
                )
            );
        }
    }

    echo '</select>';
    echo '</label>';
}

/**
 * Guest table order script config.
 *
 * @param string $context website|qr|takeaway
 * @return array<string, mixed>
 */
function maca_menulist_get_table_order_script_config($context = 'website') {
    maca_menulist_load_textdomain();

    $delivery_mode = maca_menulist_get_table_order_delivery_mode();
    $uses_server = maca_menulist_table_order_uses_server();
    $url_table_param = maca_menulist_get_guest_order_table_from_request();
    $is_takeaway = ($context === 'takeaway');
    $requires_table = !$is_takeaway && !empty(maca_menulist_get_guest_order_table_numbers());
    $ordering_open = !$is_takeaway
        || !function_exists('maca_menulist_takeaway_accepting_orders_now')
        || maca_menulist_takeaway_accepting_orders_now();

    return array(
        'enabled' => true,
        'orderingOpen' => $ordering_open,
        'outsideHoursNotice' => $is_takeaway && !$ordering_open && function_exists('maca_menulist_get_takeaway_outside_hours_notice')
            ? maca_menulist_get_takeaway_outside_hours_notice()
            : '',
        'deliveryMode' => $delivery_mode,
        'usesServer' => $uses_server,
        'guestStatus' => maca_menulist_table_order_guest_status_enabled(),
        'pollInterval' => 15000,
        'pollIntervalActive' => 5000,
        'pollIntervalPreparing' => 3000,
        'ajaxUrl' => admin_url('admin-ajax.php'),
        'nonce' => wp_create_nonce('maca_table_order_nonce'),
        'lineNotes' => maca_menulist_table_order_line_notes_enabled(),
        'orderNote' => maca_menulist_table_order_order_note_enabled(),
        'showPrices' => maca_menulist_table_order_show_prices(),
        'orderChannel' => $is_takeaway ? 'takeaway' : 'dine_in',
        'requiresTable' => $requires_table,
        'applyQrSchedule' => ($context === 'qr' || $context === 'takeaway'),
        'storageKey' => 'maca_menulist_table_order',
        'guestOrderStorageKey' => 'maca_menulist_guest_order',
        'urlTableParam' => $url_table_param,
        'urlTable' => maca_menulist_guest_order_resolve_prefill_table($url_table_param),
        'activeTables' => maca_menulist_get_guest_order_table_numbers(),
        'currency' => maca_menulist_get_price_currency(),
        'statusSteps' => maca_menulist_get_guest_order_status_steps(),
        'payments' => maca_menulist_get_guest_payment_script_config($context),
        'guestQrImageApi' => maca_menulist_table_order_guest_status_enabled()
            ? 'https://api.qrserver.com/v1/create-qr-code/?size=220x220&data='
            : '',
        'i18n' => array(
            'add' => maca_menulist_guest_ui_string('Add'),
            'added' => maca_menulist_guest_ui_string('Added'),
            'yourOrder' => maca_menulist_guest_ui_string('Your order'),
            'item' => maca_menulist_guest_ui_string('item'),
            'items' => maca_menulist_guest_ui_string('items'),
            'viewOrder' => maca_menulist_guest_ui_string('View order'),
            'showWaiter' => maca_menulist_guest_ui_string('Show waiter'),
            'sendOrder' => maca_menulist_guest_ui_string('Send order'),
            'close' => maca_menulist_guest_ui_string('Close'),
            'backToMenu' => maca_menulist_guest_ui_string('Back to menu'),
            'emptyOrder' => maca_menulist_guest_ui_string('Your order is empty.'),
            'orderForWaiter' => maca_menulist_guest_ui_string('Order for waiter'),
            'orderSent' => maca_menulist_guest_ui_string('Order sent! The staff has been notified.'),
            'orderSendError' => maca_menulist_guest_ui_string('Could not send the order. Please try again or show the screen to staff.'),
            'connectionError' => maca_menulist_guest_ui_string('Could not reach the server. Wait a minute, reload the page, and try again.'),
            'table' => maca_menulist_guest_ui_string('Table'),
            'tableNumber' => maca_menulist_guest_ui_string('Table number'),
            'tablePlaceholder' => maca_menulist_guest_ui_string('e.g. 12'),
            'chooseTable' => maca_menulist_guest_ui_string('Choose table'),
            'tableLabel' => maca_menulist_guest_ui_string('Table %s'),
            'noTablesAvailable' => maca_menulist_guest_ui_string('No tables available'),
            'tableRequired' => maca_menulist_guest_ui_string('Please choose your table.'),
            'takeaway' => maca_menulist_guest_ui_string('Takeaway'),
            'nameForPickup' => maca_menulist_guest_ui_string('Name for pickup'),
            'phone' => maca_menulist_guest_ui_string('Phone'),
            'pickupNameRequired' => maca_menulist_guest_ui_string('Please enter your name for pickup.'),
            'invalidTable' => maca_menulist_guest_ui_string('Invalid table number.'),
            'tableLocked' => maca_menulist_guest_ui_string('Table cannot be changed after you have placed an order.'),
            'note' => maca_menulist_guest_ui_string('Note'),
            'orderNote' => maca_menulist_guest_ui_string('Order note'),
            'orderNotePlaceholder' => maca_menulist_guest_ui_string('e.g. allergies, birthday...'),
            'lineNotePlaceholder' => maca_menulist_guest_ui_string('Special request...'),
            'remove' => maca_menulist_guest_ui_string('Remove'),
            'clearOrder' => maca_menulist_guest_ui_string('Clear order'),
            'total' => maca_menulist_guest_ui_string('Total'),
            'yourOrderStatus' => maca_menulist_guest_ui_string('Your order'),
            'orderStatusHint' => maca_menulist_guest_ui_string('We will update this page as your order progresses.'),
            'readyIn' => maca_menulist_guest_ui_string('Ready in about %d minutes'),
            'readyNow' => maca_menulist_guest_ui_string('Your order is ready!'),
            'orderComplete' => maca_menulist_guest_ui_string('Your order has been served. Enjoy!'),
            'etaPrompt' => maca_menulist_guest_ui_string('Ready in how many minutes?'),
            'orderMore' => maca_menulist_guest_ui_string('Order more'),
            'viewMyOrders' => maca_menulist_guest_ui_string('My orders'),
            'addOnOrder' => maca_menulist_guest_ui_string('Add-on order'),
            'sendAddOrder' => maca_menulist_guest_ui_string('Send add-on order'),
            'addOnSent' => maca_menulist_guest_ui_string('Add-on order sent! The staff has been notified.'),
            'itemsAdded' => maca_menulist_guest_ui_string('Items added to your order!'),
            'payOnline' => maca_menulist_guest_ui_string('Pay online'),
            'payWaiter' => maca_menulist_guest_ui_string('Pay with server'),
            'paymentHow' => maca_menulist_guest_ui_string('How would you like to pay?'),
            'paymentWaitServed' => maca_menulist_guest_ui_string('You can pay when your order has been served.'),
            'paymentManualChosen' => maca_menulist_guest_ui_string('You will pay your server. They will confirm when payment is received.'),
            'paymentPaid' => maca_menulist_guest_ui_string('Paid — thank you!'),
            'allPaid' => maca_menulist_guest_ui_string('All paid'),
            'payYourShare' => maca_menulist_guest_ui_string('Pay your share'),
            'billSplitTitle' => maca_menulist_guest_ui_string('Split the bill'),
            'billSplitShow' => maca_menulist_guest_ui_string('Split the bill'),
            'billSplitHide' => maca_menulist_guest_ui_string('Close'),
            'billSplitHint' => maca_menulist_guest_ui_string('Choose equal split or a fixed amount per person. Each payment covers one share until the bill is paid.'),
            'billSplitModeEqual' => maca_menulist_guest_ui_string('Split equally'),
            'billSplitModeFixed' => maca_menulist_guest_ui_string('Fixed amount per person'),
            'billSplitPeople' => maca_menulist_guest_ui_string('Number of people'),
            'billSplitFixedAmount' => maca_menulist_guest_ui_string('Amount per person'),
            'billSplitApply' => maca_menulist_guest_ui_string('Confirm split'),
            'billSplitClear' => maca_menulist_guest_ui_string('Pay full bill instead'),
            'billSplitActive' => maca_menulist_guest_ui_string('Split among %d people'),
            'billSplitActiveFixed' => maca_menulist_guest_ui_string('Each person pays %s'),
            'billSplitYourShare' => maca_menulist_guest_ui_string('Next payment: %s'),
            'billSplitProgress' => maca_menulist_guest_ui_string('%1$d of %2$d shares paid'),
            'billSplitProgressFixed' => maca_menulist_guest_ui_string('%1$d payments · %2$s left'),
            'billSplitRemaining' => maca_menulist_guest_ui_string('%s left on the bill'),
            'billSplitError' => maca_menulist_guest_ui_string('Could not update bill split. Please try again.'),
            'paymentPending' => maca_menulist_guest_ui_string('Complete your online payment'),
            'paymentError' => maca_menulist_guest_ui_string('Could not start payment. Please try again or pay with your server.'),
            'paymentChooseError' => maca_menulist_guest_ui_string('Could not save your choice. Please try again.'),
            'tipTitle' => maca_menulist_guest_ui_string('Add a tip?'),
            'tipSelectPlaceholder' => maca_menulist_guest_ui_string('Choose tip'),
            'tipCustom' => maca_menulist_guest_ui_string('Custom amount'),
            'tipCustomPlaceholder' => maca_menulist_guest_ui_string('Amount'),
            'tipApply' => maca_menulist_guest_ui_string('Save tip'),
            'tipNoTip' => maca_menulist_guest_ui_string('No tip'),
            'subtotal' => maca_menulist_guest_ui_string('Subtotal'),
            'tip' => maca_menulist_guest_ui_string('Tip'),
            'grandTotal' => maca_menulist_guest_ui_string('Total to pay'),
            'tipSaveError' => maca_menulist_guest_ui_string('Could not save tip. Please try again.'),
            'tipRequired' => maca_menulist_guest_ui_string('Please choose a tip option before paying.'),
            'confirmationEmailToggle' => maca_menulist_guest_ui_string('Email order confirmation'),
            'confirmationEmailPlaceholder' => maca_menulist_guest_ui_string('Email address'),
            'confirmationEmailInvalid' => maca_menulist_guest_ui_string('Enter a valid email address for the confirmation.'),
            'combinedPaymentTitle' => maca_menulist_guest_ui_string('Pay all orders together'),
            'combinedPaymentCount' => maca_menulist_guest_ui_string('%d unpaid orders'),
            'combinedPaymentIncluded' => maca_menulist_guest_ui_string('Included in combined payment'),
            'payAllOnline' => maca_menulist_guest_ui_string('Pay all online'),
            'payAllWaiter' => maca_menulist_guest_ui_string('Pay all with server'),
            'paymentTransactions' => maca_menulist_guest_ui_string('Payments'),
            'paymentNumber' => maca_menulist_guest_ui_string('Payment %d'),
            'shareOrderQr' => maca_menulist_guest_ui_string('Show QR for another phone'),
            'shareOrderQrTitle' => maca_menulist_guest_ui_string('Scan to open order'),
            'shareOrderQrHint' => maca_menulist_guest_ui_string('Another person can scan this QR code on their phone to view the order and pay their share.'),
        ),
    );
}

/**
 * Staff board polling interval in milliseconds.
 *
 * @param string $view waiter|kitchen.
 * @return int
 */
function maca_menulist_get_staff_orders_poll_interval_ms($view = 'waiter') {
    $view = sanitize_key($view);

    if ($view === 'kitchen') {
        return 10000;
    }

    return 30000;
}

/**
 * Waiter table-overview polling interval in milliseconds.
 *
 * @return int
 */
function maca_menulist_get_staff_table_summary_poll_interval_ms() {
    return 30000;
}

/**
 * Waiter table drill-down polling interval in milliseconds.
 *
 * @return int
 */
function maca_menulist_get_staff_table_detail_poll_interval_ms() {
    return 45000;
}

/**
 * Staff order page script config.
 *
 * @param string $view Staff view: waiter or kitchen.
 * @return array<string, mixed>
 */
function maca_menulist_get_staff_order_script_config($view) {
    $view = sanitize_key($view);

    $pin_feedback = maca_menulist_get_staff_pin_attempt_feedback();

    return array(
        'view' => $view,
        'ajaxUrl' => admin_url('admin-ajax.php'),
        'adminAjaxUrl' => admin_url('admin-ajax.php'),
        'postUrl' => maca_menulist_get_staff_orders_post_url($view),
        'restUrl' => maca_menulist_get_staff_rest_url(),
        'preferSamePageApi' => true,
        'tableFirstMode' => $view === 'waiter',
        'tableSummaryPollInterval' => maca_menulist_get_staff_table_summary_poll_interval_ms(),
        'tableDetailPollInterval' => maca_menulist_get_staff_table_detail_poll_interval_ms(),
        'pinRevision' => maca_menulist_get_table_order_staff_pin_revision(),
        'pinLocked' => !empty($pin_feedback['is_locked']),
        'pollInterval' => maca_menulist_get_staff_orders_poll_interval_ms($view),
        'showPrices' => $view === 'waiter' && maca_menulist_table_order_show_prices(),
        'paymentAlerts' => maca_menulist_payments_guest_flow_enabled(),
        'currency' => maca_menulist_get_price_currency(),
        'staffFilterEnabled' => $view === 'waiter' && maca_menulist_has_configured_waiters(),
        'waiters' => $view === 'waiter' ? maca_menulist_get_waiters_for_staff_ui() : array(),
        'activeTables' => maca_menulist_build_guest_order_table_choices(),
        'i18n' => array(
            'enterPin' => __('Enter staff PIN', 'maca-restu-pro'),
            'pinPlaceholder' => __('PIN', 'maca-restu-pro'),
            'unlock' => __('Show orders', 'maca-restu-pro'),
            'invalidPin' => __('Incorrect PIN.', 'maca-restu-pro'),
            'connectionError' => __('Could not reach the server. Wait a minute, reload the page, and try again.', 'maca-restu-pro'),
            'rateLimited' => __('Too many requests. Wait a minute and try again.', 'maca-restu-pro'),
            'loadingOrders' => __('Loading orders…', 'maca-restu-pro'),
            /* translators: 1: remaining attempts, 2: max attempts */
            'pinAttemptsLeft' => __('Attempts left: %1$d of %2$d', 'maca-restu-pro'),
            /* translators: %d: minutes until PIN unlock */
            'pinLockedFor' => __('PIN locked. Try again in %d min.', 'maca-restu-pro'),
            'noOrders' => __('No active orders right now.', 'maca-restu-pro'),
            'table' => __('Table', 'maca-restu-pro'),
            'takeaway' => __('Takeaway', 'maca-restu-pro'),
            'orderNote' => __('Order note', 'maca-restu-pro'),
            'markHandled' => __('Mark as served', 'maca-restu-pro'),
            'waiterTitle' => __('Waiter orders', 'maca-restu-pro'),
            'kitchenTitle' => __('Kitchen orders', 'maca-restu-pro'),
            'allTables' => __('All tables', 'maca-restu-pro'),
            'waiterFilter' => __('Show orders for', 'maca-restu-pro'),
            'total' => __('Total', 'maca-restu-pro'),
            'unitPrice' => __('Unit price', 'maca-restu-pro'),
            'lineTotal' => __('Sum', 'maca-restu-pro'),
            'newOrder' => __('Active order', 'maca-restu-pro'),
            'status' => __('Status', 'maca-restu-pro'),
            'etaPrompt' => __('Ready in how many minutes?', 'maca-restu-pro'),
            'invalidEta' => __('Enter a valid number of minutes.', 'maca-restu-pro'),
            'orderUpdateError' => __('Could not update order.', 'maca-restu-pro'),
            'tip' => __('Tip', 'maca-restu-pro'),
            'subtotal' => __('Subtotal', 'maca-restu-pro'),
            'amountPaid' => __('Amount paid', 'maca-restu-pro'),
            'amountRemaining' => __('Left to pay', 'maca-restu-pro'),
            'paidAt' => __('Paid at', 'maca-restu-pro'),
            'paymentId' => __('Payment ID', 'maca-restu-pro'),
            'paymentTransactions' => __('Betalningar', 'maca-restu-pro'),
            /* translators: %d: payment sequence number */
            'paymentNumber' => __('Payment %d', 'maca-restu-pro'),
            'paymentReceived' => __('Payment received', 'maca-restu-pro'),
            'howPaid' => __('How paid', 'maca-restu-pro'),
            'provider' => __('Provider', 'maca-restu-pro'),
            'kdsNewOrders' => __('New orders', 'maca-restu-pro'),
            'kdsInProgress' => __('In progress', 'maca-restu-pro'),
            'kdsReady' => __('Ready', 'maca-restu-pro'),
            'kdsKitchenTime' => __('Kitchen time', 'maca-restu-pro'),
            'kdsAvgTime' => __('Average time', 'maca-restu-pro'),
            'kdsOrdersToday' => __('Orders today', 'maca-restu-pro'),
            /* translators: %d: minutes */
            'kdsMinutes' => __('%d min', 'maca-restu-pro'),
            'kdsReadyBadge' => __('Food is ready', 'maca-restu-pro'),
            'kdsPaid' => __('Paid', 'maca-restu-pro'),
            'guestRecoveryQr' => __('Guest QR', 'maca-restu-pro'),
            'guestRecoveryTitle' => __('Scan to open order', 'maca-restu-pro'),
            'guestRecoveryHint' => __('Show only to the guest at the table.', 'maca-restu-pro'),
            'close' => __('Close', 'maca-restu-pro'),
            'changeTableNumber' => __('Change table number', 'maca-restu-pro'),
            'tableNumberUpdateError' => __('Could not update table number.', 'maca-restu-pro'),
            'tablesOverview' => __('Choose a table', 'maca-restu-pro'),
            'tablesOverviewHint' => __('Tap a table to view and manage its orders.', 'maca-restu-pro'),
            'backToTables' => __('All tables', 'maca-restu-pro'),
            'tableOrders' => __('Orders', 'maca-restu-pro'),
            'tableReady' => __('Food ready', 'maca-restu-pro'),
        ),
        'guestQrImageApi' => 'https://api.qrserver.com/v1/create-qr-code/?size=220x220&data=',
        'guestRecoveryEnabled' => maca_menulist_table_order_guest_status_enabled(),
    );
}

/**
 * Public menu URL where a guest can reopen their order (QR scan target).
 *
 * @param object $order Order row.
 * @return string Empty when recovery is unavailable.
 */
function maca_menulist_get_guest_order_recovery_url($order) {
    if (!$order || empty($order->guest_token) || !maca_menulist_table_order_guest_status_enabled()) {
        return '';
    }

    $status = maca_menulist_normalize_table_order_status((string) $order->status);
    if (maca_menulist_is_table_order_closed($status)) {
        return '';
    }

    $guest_token = sanitize_text_field((string) $order->guest_token);
    if ($guest_token === '') {
        return '';
    }

    $service_mode = function_exists('maca_menulist_get_order_service_mode')
        ? maca_menulist_get_order_service_mode($order)
        : 'dine_in';

    if ($service_mode === 'takeaway') {
        if (!function_exists('maca_menulist_get_takeaway_url')) {
            require_once MACA_MENULIST_PLUGIN_DIR . 'includes/takeaway.php';
        }
        $base = maca_menulist_get_takeaway_url();
    } else {
        if (!function_exists('maca_menulist_get_qr_url')) {
            require_once MACA_MENULIST_PLUGIN_DIR . 'includes/rewrites.php';
        }
        $table_number = isset($order->table_number) ? (string) $order->table_number : '';
        $base = maca_menulist_get_qr_url(0, $table_number);
    }

    return add_query_arg('maca_order', $guest_token, $base);
}

/**
 * Public URL where another guest device can restore the full order session (QR scan).
 *
 * @param array<int, object|string> $orders Order rows or guest tokens (sorted by id internally).
 * @return string Empty when sharing is unavailable.
 */
function maca_menulist_get_guest_session_share_url($orders) {
    if (empty($orders) || !maca_menulist_table_order_guest_status_enabled()) {
        return '';
    }

    $normalized = array();

    foreach ($orders as $order) {
        if (is_string($order)) {
            $token = sanitize_text_field($order);
            if ($token !== '') {
                $normalized[] = array(
                    'id' => 0,
                    'token' => $token,
                    'order' => null,
                );
            }
            continue;
        }

        if (!$order || empty($order->guest_token)) {
            continue;
        }

        $status = maca_menulist_normalize_table_order_status((string) $order->status);
        if (maca_menulist_is_table_order_closed($status)) {
            continue;
        }

        $normalized[] = array(
            'id' => (int) $order->id,
            'token' => sanitize_text_field((string) $order->guest_token),
            'order' => $order,
        );
    }

    if (empty($normalized)) {
        return '';
    }

    usort(
        $normalized,
        static function ($a, $b) {
            if ($a['id'] !== $b['id']) {
                return $a['id'] <=> $b['id'];
            }

            return strcmp($a['token'], $b['token']);
        }
    );

    $reference = null;
    foreach ($normalized as $entry) {
        if ($entry['order']) {
            $reference = $entry['order'];
            break;
        }
    }

    if (!$reference) {
        $reference = maca_menulist_get_guest_order_for_token($normalized[0]['token']);
    }

    if (!$reference) {
        return '';
    }

    $base = maca_menulist_get_guest_order_recovery_url($reference);
    if ($base === '') {
        return '';
    }

    $base = remove_query_arg(array('maca_order', 'maca_orders', 'maca_payment'), $base);
    $tokens = array_values(
        array_unique(
            array_map(
                static function ($entry) {
                    return $entry['token'];
                },
                $normalized
            )
        )
    );

    if (count($tokens) === 1) {
        return add_query_arg('maca_order', $tokens[0], $base);
    }

    return add_query_arg('maca_orders', implode(',', $tokens), $base);
}

/**
 * Modal markup for guest "share QR" flow (another phone at the table).
 *
 * @return string
 */
function maca_menulist_render_guest_share_qr_modal() {
    if (!maca_menulist_table_order_guest_status_enabled()) {
        return '';
    }

    ob_start();
    ?>
    <div id="maca-guest-share-qr-modal" class="maca-guest-share-qr-modal" hidden>
        <button type="button" class="maca-guest-share-qr-modal-backdrop" tabindex="-1" aria-hidden="true"></button>
        <div class="maca-guest-share-qr-modal-panel" role="dialog" aria-modal="true" aria-labelledby="maca-guest-share-qr-title">
            <h3 id="maca-guest-share-qr-title"><?php echo esc_html(maca_menulist_guest_ui_string('Scan to open order')); ?></h3>
            <p class="maca-guest-share-qr-hint"><?php echo esc_html(maca_menulist_guest_ui_string('Another person can scan this QR code on their phone to view the order and pay their share.')); ?></p>
            <img class="maca-guest-share-qr-image" src="" alt="" width="220" height="220" decoding="async">
            <button type="button" class="maca-guest-share-qr-close"><?php echo esc_html(maca_menulist_guest_ui_string('Close')); ?></button>
        </div>
    </div>
    <?php
    return (string) ob_get_clean();
}

/**
 * Modal markup for staff "Guest QR" recovery flow.
 *
 * @return string
 */
function maca_menulist_render_staff_guest_qr_modal() {
    if (!maca_menulist_table_order_guest_status_enabled()) {
        return '';
    }

    ob_start();
    ?>
    <div id="maca-staff-guest-qr-modal" class="maca-staff-guest-qr-modal" hidden>
        <button type="button" class="maca-staff-guest-qr-modal-backdrop" tabindex="-1" aria-hidden="true"></button>
        <div class="maca-staff-guest-qr-modal-panel" role="dialog" aria-modal="true" aria-labelledby="maca-staff-guest-qr-title">
            <h2 id="maca-staff-guest-qr-title"><?php esc_html_e('Scan to open order', 'maca-restu-pro'); ?></h2>
            <p class="maca-staff-guest-qr-hint"><?php esc_html_e('Show this QR code to the guest so they can follow their order on their phone.', 'maca-restu-pro'); ?></p>
            <img class="maca-staff-guest-qr-image" src="" alt="" width="220" height="220" decoding="async">
            <button type="button" class="maca-staff-guest-qr-close"><?php esc_html_e('Close', 'maca-restu-pro'); ?></button>
        </div>
    </div>
    <?php
    return (string) ob_get_clean();
}

/**
 * Format a table order for JSON responses.
 *
 * @param object $order Order row with lines attached.
 * @param string $view  Staff view: waiter or kitchen.
 * @param bool   $lite  Omit heavy payment details for staff polling.
 * @return array<string, mixed>
 */
function maca_menulist_format_table_order_for_json($order, $view = 'waiter', $lite = false) {
    $view = sanitize_key($view);
    $show_prices = $view === 'waiter' && maca_menulist_table_order_show_prices();
    $lines = array();
    $total = 0.0;

    foreach ($order->lines ?? array() as $line) {
        $qty = (int) $line->qty;
        $price = $line->price !== null && $line->price !== '' ? (float) $line->price : null;
        $line_data = array(
            'qty' => $qty,
            'title' => $line->title,
            'note' => $line->line_note,
        );

        if ($show_prices && $price !== null) {
            $line_total = $price * $qty;
            $line_data['price'] = $price;
            $line_data['unit_price_label'] = maca_menulist_format_price($price);
            $line_data['line_total'] = $line_total;
            $line_data['line_total_label'] = maca_menulist_format_price($line_total);
            $line_data['price_label'] = $line_data['unit_price_label'];
            $total += $line_total;
        }

        $lines[] = $line_data;
    }

    $status = maca_menulist_normalize_table_order_status($order->status);

    if (
        $view === 'waiter'
        && $status === 'paid'
        && function_exists('maca_menulist_order_is_payment_complete')
        && !maca_menulist_order_is_payment_complete($order)
    ) {
        $status = 'served';
    }

    $eta_minutes = isset($order->eta_minutes) ? (int) $order->eta_minutes : 0;

    $service_mode = function_exists('maca_menulist_get_order_service_mode')
        ? maca_menulist_get_order_service_mode($order)
        : 'dine_in';

    $payload = array(
        'id' => (int) $order->id,
        'table_number' => $order->table_number,
        'service_mode' => $service_mode,
        'pickup_name' => isset($order->pickup_name) ? (string) $order->pickup_name : '',
        'pickup_phone' => isset($order->pickup_phone) ? (string) $order->pickup_phone : '',
        'order_note' => $order->order_note,
        'status' => $status,
        'status_label' => maca_menulist_get_table_order_status_label($status),
        'created_at' => $order->created_at,
        'time_label' => maca_menulist_format_order_time($order->created_at),
        'lines' => $lines,
    );

    if ($eta_minutes > 0) {
        $payload['eta_minutes'] = $eta_minutes;
    }

    if (
        $view === 'kitchen'
        && $status === 'preparing'
        && $eta_minutes > 0
        && !empty($order->status_updated_at)
    ) {
        $payload['status_updated_at'] = $order->status_updated_at;
    }

    if ($view === 'kitchen') {
        $payload['kitchen_actions'] = maca_menulist_get_kitchen_order_actions_for_order($order);
    }

    if ($view === 'waiter') {
        $payload['waiter_actions'] = maca_menulist_get_waiter_order_actions_for_order($order);
    }

    if ($show_prices && $total > 0) {
        $payload['total'] = $total;
        $payload['total_label'] = maca_menulist_format_price($total);
    }

    if (maca_menulist_staff_shows_payment_info($view)) {
        try {
            $stored_payment = sanitize_key((string) ($order->payment_status ?? 'none'));
            $payment_method = isset($order->payment_method) ? sanitize_key((string) $order->payment_method) : '';
            if ($lite && $stored_payment === 'none' && $payment_method === '') {
                $payload['payment'] = array(
                    'payment_status' => 'none',
                    'payment_method' => null,
                    'payment_is_complete' => false,
                    'badge' => '',
                    'payment_summary_label' => '',
                    'show_receipt' => false,
                );
            } else {
                $payload['payment'] = maca_menulist_build_staff_payment_state($order, $lite);
            }
        } catch (Throwable $payment_error) {
            if (function_exists('maca_menulist_debug_log')) {
                maca_menulist_debug_log(
                    'staff payment state error order ' . (int) $order->id . ': ' . $payment_error->getMessage()
                );
            }
        }
    }

    try {
        if (!$lite) {
            $recovery_url = maca_menulist_get_guest_order_recovery_url($order);
            if ($recovery_url !== '') {
                $payload['guest_recovery'] = array(
                    'url' => $recovery_url,
                );
            }
        }
    } catch (Throwable $recovery_error) {
        if (function_exists('maca_menulist_debug_log')) {
            maca_menulist_debug_log(
                'staff guest recovery error order ' . (int) $order->id . ': ' . $recovery_error->getMessage()
            );
        }
    }

    if (!$lite) {
        $payload['can_change_table'] = maca_menulist_staff_can_change_order_table($order);
    } else {
        $payload['can_change_table'] = false;
    }

    return $payload;
}

/**
 * Format guest order status payload.
 *
 * @param object $order Order row with lines attached.
 * @return array<string, mixed>
 */
function maca_menulist_format_guest_order_status_for_json($order) {
    $status = maca_menulist_normalize_table_order_status($order->status);
    $eta_minutes = isset($order->eta_minutes) ? (int) $order->eta_minutes : 0;
    $steps = maca_menulist_get_guest_order_status_steps();
    $status_index = 0;

    foreach ($steps as $index => $step) {
        if ($step['key'] === $status) {
            $status_index = $index;
            break;
        }
    }

    if (maca_menulist_is_table_order_guest_complete($status)) {
        $status_index = count($steps) - 1;
    }

    $payload = array(
        'order_id' => (int) $order->id,
        'guest_token' => isset($order->guest_token) ? (string) $order->guest_token : '',
        'table_number' => $order->table_number,
        'status' => $status,
        'status_label' => maca_menulist_get_table_order_status_label($status),
        'status_index' => $status_index,
        'complete' => maca_menulist_is_table_order_guest_complete($status),
        'closed' => maca_menulist_is_table_order_closed($status),
        'appendable' => maca_menulist_table_order_allows_append($status, $order),
        'steps' => $steps,
        'lines' => maca_menulist_format_table_order_for_json($order, 'kitchen')['lines'],
    );

    if (isset($order->eta_minutes) && $order->eta_minutes !== null && $order->eta_minutes !== '') {
        $payload['eta_minutes'] = max(0, (int) $order->eta_minutes);
    }

    $payload['payment'] = maca_menulist_build_guest_payment_state($order);

    return $payload;
}

if (!defined('MACA_MENULIST_ORDERS_PURGE_CRON_HOOK')) {
    define('MACA_MENULIST_ORDERS_PURGE_CRON_HOOK', 'maca_menulist_orders_purge_cron');
}

/**
 * Whether automatic cleanup of old completed orders is enabled.
 *
 * @return bool
 */
function maca_menulist_orders_purge_enabled() {
    return get_option('maca_menulist_orders_purge_enabled', '0') === '1';
}

/**
 * Retention in days for automatic order cleanup.
 *
 * @return int
 */
function maca_menulist_orders_purge_get_days() {
    $days = (int) get_option('maca_menulist_orders_purge_days', 90);

    return max(1, $days);
}

/**
 * Daily run time for order cleanup (HH:MM, site timezone).
 *
 * @return string
 */
function maca_menulist_orders_purge_get_daily_time() {
    $time = get_option('maca_menulist_orders_purge_daily_time', '04:00');
    if (!is_string($time) || !preg_match('/^\d{2}:\d{2}$/', $time)) {
        return '04:00';
    }

    return $time;
}

/**
 * Cron callback — delete completed orders older than the configured retention.
 */
function maca_menulist_orders_purge_run_cron() {
    if (!maca_menulist_orders_purge_enabled() || !maca_menulist_table_order_enabled()) {
        return;
    }

    $days = maca_menulist_orders_purge_get_days();
    $deleted = maca_menulist_db_delete_old_completed_table_orders($days);

    update_option('maca_menulist_orders_purge_last_run', maca_menulist_wp_now_mysql());
    update_option('maca_menulist_orders_purge_last_count', (int) $deleted);
}

/**
 * Next daily purge timestamp in site timezone.
 *
 * @return int
 */
function maca_menulist_orders_purge_next_run_timestamp() {
    $time = maca_menulist_orders_purge_get_daily_time();
    $timezone = wp_timezone();
    $now = new DateTime('now', $timezone);
    $parts = explode(':', $time);
    $scheduled = clone $now;
    $scheduled->setTime((int) $parts[0], (int) $parts[1], 0);

    if ($scheduled <= $now) {
        $scheduled->modify('+1 day');
    }

    return $scheduled->getTimestamp();
}

/**
 * Schedule daily order cleanup cron.
 */
function maca_menulist_orders_purge_schedule_cron() {
    maca_menulist_orders_purge_unschedule_cron();

    if (!maca_menulist_orders_purge_enabled() || !maca_menulist_table_order_enabled()) {
        return;
    }

    wp_schedule_event(
        maca_menulist_orders_purge_next_run_timestamp(),
        'daily',
        MACA_MENULIST_ORDERS_PURGE_CRON_HOOK
    );
}

/**
 * Clear scheduled order cleanup cron.
 */
function maca_menulist_orders_purge_unschedule_cron() {
    wp_clear_scheduled_hook(MACA_MENULIST_ORDERS_PURGE_CRON_HOOK);
}

/**
 * Ensure order cleanup cron is scheduled when enabled.
 */
function maca_menulist_orders_purge_maybe_schedule_cron() {
    if (!maca_menulist_orders_purge_enabled() || !maca_menulist_table_order_enabled()) {
        maca_menulist_orders_purge_unschedule_cron();
        return;
    }

    if (!wp_next_scheduled(MACA_MENULIST_ORDERS_PURGE_CRON_HOOK)) {
        maca_menulist_orders_purge_schedule_cron();
    }
}

/**
 * Save automatic order cleanup settings from admin.
 *
 * @param bool   $enabled Enable automatic cleanup.
 * @param int    $days    Retention days.
 * @param string $time    Daily time HH:MM.
 */
function maca_menulist_orders_purge_save_settings($enabled, $days, $time) {
    update_option('maca_menulist_orders_purge_enabled', $enabled ? '1' : '0');
    update_option('maca_menulist_orders_purge_days', max(1, (int) $days));

    $time = is_string($time) ? trim($time) : '';
    if (preg_match('/^(\d{2}:\d{2})/', $time, $matches)) {
        update_option('maca_menulist_orders_purge_daily_time', $matches[1]);
    }

    maca_menulist_orders_purge_schedule_cron();
}
