<?php
/**
 * Dish mode helpers and frontend rendering.
 *
 * @package Maca_Menu_List
 */

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

/**
 * Valid content modes.
 *
 * @return array<int, string>
 */
function maca_menulist_get_content_modes() {
    return array('files', 'dishes');
}

/**
 * Current menu content mode from settings.
 *
 * @return string
 */
function maca_menulist_get_content_mode() {
    $mode = get_option('maca_menulist_content_mode', 'files');

    if (!in_array($mode, maca_menulist_get_content_modes(), true)) {
        return 'files';
    }

    return $mode;
}

/**
 * Whether dish registration mode is selected in settings.
 *
 * @return bool
 */
function maca_menulist_is_dishes_mode_selected() {
    return maca_menulist_get_content_mode() === 'dishes';
}

/**
 * Whether the plugin is in dish registration mode with an active Pro license.
 *
 * @return bool
 */
function maca_menulist_is_dishes_mode() {
    if (!maca_menulist_is_dishes_mode_selected()) {
        return false;
    }

    return function_exists('maca_menulist_pro_has_feature') && maca_menulist_pro_has_feature('dishes');
}

/**
 * Whether public pages should keep file menus visible during dish migration.
 *
 * @return bool
 */
function maca_menulist_keep_files_public_during_dish_migration() {
    return get_option('maca_menulist_public_keep_files_during_migration', '0') === '1';
}

/**
 * Effective public content mode (frontend/QR) while keeping admin in dish mode.
 *
 * @return string files|dishes
 */
function maca_menulist_get_public_content_mode() {
    if (!maca_menulist_is_dishes_mode()) {
        return 'files';
    }

    return maca_menulist_keep_files_public_during_dish_migration() ? 'files' : 'dishes';
}

/**
 * Whether public pages should render dish mode right now.
 *
 * @return bool
 */
function maca_menulist_is_public_dishes_mode() {
    return maca_menulist_get_public_content_mode() === 'dishes';
}

/**
 * Price currency label from settings.
 *
 * @return string
 */
function maca_menulist_get_price_currency() {
    $currency = get_option('maca_menulist_price_currency', 'kr');

    return is_string($currency) && $currency !== '' ? $currency : 'kr';
}

/**
 * Format a dish price with currency.
 *
 * @param float|string|null $price Price value.
 * @return string
 */
function maca_menulist_format_price($price) {
    if ($price === null || $price === '') {
        return '';
    }

    $amount = (float) $price;
    $decimals = (floor($amount) === $amount) ? 0 : 2;
    $formatted = number_format($amount, $decimals, ',', ' ');

    return trim($formatted . ' ' . maca_menulist_get_price_currency());
}

/**
 * Group active dishes by category in sort order.
 *
 * @param array<string, mixed> $args {
 *     @type bool $qr_schedule     When true, full schedule (date, weekday, time). When false, website date range only.
 *     @type bool $takeaway_menu   When true, QR schedule + takeaway_active filter applies.
 *     @type int|null $timestamp   Optional preview timestamp (site timezone).
 * }
 * @return array<int, array{category: object, dishes: array<int, object>}>
 */
function maca_menulist_get_grouped_dishes($args = array()) {
    $args = wp_parse_args(
        is_array($args) ? $args : array(),
        array(
            'qr_schedule' => false,
            'takeaway_menu' => false,
            'timestamp' => null,
        )
    );

    if ($args['takeaway_menu']) {
        $args['qr_schedule'] = true;
    }

    $timestamp = $args['timestamp'] !== null ? (int) $args['timestamp'] : null;

    $categories = maca_menulist_db_get_dish_categories(true);
    $grouped = array();
    $all_dish_ids = array();

    foreach ($categories as $category) {
        if (!maca_menulist_dish_category_is_active($category)) {
            continue;
        }

        if ($args['qr_schedule']) {
            if (!maca_menulist_dish_category_is_visible_on_qr($category, $timestamp)) {
                continue;
            }
        } elseif (!maca_menulist_dish_category_is_visible_on_website($category, $timestamp)) {
            continue;
        }

        $dishes = maca_menulist_db_get_dishes_by_category($category->id, true);
        $visible_dishes = array();

        foreach ($dishes as $dish) {
            if ($args['takeaway_menu']) {
                if (!function_exists('maca_menulist_dish_is_available_for_takeaway') || !maca_menulist_dish_is_available_for_takeaway($dish)) {
                    continue;
                }
            } elseif ($args['qr_schedule']) {
                if (!maca_menulist_dish_is_visible_on_qr($dish, $timestamp)) {
                    continue;
                }
            } elseif (!maca_menulist_dish_is_visible_on_website($dish)) {
                continue;
            }

            $visible_dishes[] = $dish;
        }

        if (empty($visible_dishes)) {
            continue;
        }

        foreach ($visible_dishes as $dish) {
            $all_dish_ids[] = (int) $dish->id;
        }

        $grouped[] = array(
            'category' => $category,
            'dishes' => $visible_dishes,
        );
    }

    if (!empty($all_dish_ids)) {
        $allergens_by_dish = maca_menulist_db_get_allergens_for_dish_ids($all_dish_ids);

        foreach ($grouped as $group_index => $group) {
            foreach ($group['dishes'] as $dish_index => $dish) {
                $dish_id = (int) $dish->id;
                $grouped[$group_index]['dishes'][$dish_index]->allergens = isset($allergens_by_dish[$dish_id])
                    ? $allergens_by_dish[$dish_id]
                    : array();
            }
        }
    }

    if (!empty($all_dish_ids) && maca_menulist_dish_guest_ratings_enabled()) {
        $voter_key = maca_menulist_get_dish_rating_voter_key();
        $ratings_by_dish = maca_menulist_db_get_dish_rating_data_for_dish_ids($all_dish_ids, $voter_key);

        foreach ($grouped as $group_index => $group) {
            foreach ($group['dishes'] as $dish_index => $dish) {
                $dish_id = (int) $dish->id;
                $rating_data = isset($ratings_by_dish[$dish_id]) ? $ratings_by_dish[$dish_id] : array(
                    'average' => 0.0,
                    'count' => 0,
                    'user_rating' => 0,
                );
                $grouped[$group_index]['dishes'][$dish_index]->rating_average = (float) $rating_data['average'];
                $grouped[$group_index]['dishes'][$dish_index]->rating_count = (int) $rating_data['count'];
                $grouped[$group_index]['dishes'][$dish_index]->user_rating = (int) $rating_data['user_rating'];
            }
        }
    }

    return $grouped;
}

/**
 * Whether dish category filter buttons are enabled on the menu page.
 *
 * @return bool
 */
function maca_menulist_show_dish_category_filter() {
    if (!maca_menulist_is_dishes_mode()) {
        return false;
    }

    return get_option('maca_menulist_dish_category_filter', '1') === '1';
}

/**
 * Online menu category navigation mode: pill buttons or compact dropdown.
 *
 * @return string buttons|dropdown
 */
function maca_menulist_get_dish_category_nav_mode() {
    $mode = get_option('maca_menulist_dish_category_nav', '');

    if ($mode === '') {
        $mode = get_option('maca_menulist_qr_category_nav', 'buttons');
    }

    return in_array($mode, array('buttons', 'dropdown'), true) ? $mode : 'buttons';
}

/**
 * @deprecated 1.11.2 Use maca_menulist_get_dish_category_nav_mode().
 * @return string buttons|dropdown
 */
function maca_menulist_get_qr_category_nav_mode() {
    return maca_menulist_get_dish_category_nav_mode();
}

/**
 * Whether campaigns hide regular categories while active.
 *
 * @return bool
 */
function maca_menulist_campaign_exclusive_mode_enabled() {
    if (!maca_menulist_is_dishes_mode()) {
        return false;
    }

    return get_option('maca_menulist_campaign_exclusive_mode', '1') === '1';
}

/**
 * Whether dish thumbnail images open in a larger view when clicked.
 *
 * @return bool
 */
function maca_menulist_dish_images_clickable() {
    if (!maca_menulist_is_dishes_mode()) {
        return false;
    }

    return get_option('maca_menulist_dish_clickable_images', '0') === '1';
}

/**
 * Whether guests can rate dishes with 1–5 stars.
 *
 * @return bool
 */
function maca_menulist_dish_guest_ratings_enabled() {
    if (!maca_menulist_is_dishes_mode()) {
        return false;
    }

    return get_option('maca_menulist_dish_guest_ratings', '0') === '1';
}

/**
 * Whether guests can build a table order list for the waiter.
 *
 * @return bool
 */
function maca_menulist_table_order_enabled() {
    if (!maca_menulist_is_dishes_mode()) {
        return false;
    }

    return get_option('maca_menulist_table_order', '0') === '1';
}

/**
 * Whether guests can add a note per ordered item.
 *
 * @return bool
 */
function maca_menulist_table_order_line_notes_enabled() {
    return maca_menulist_table_order_enabled()
        && get_option('maca_menulist_table_order_line_notes', '1') === '1';
}

/**
 * Whether guests can add a general order note.
 *
 * @return bool
 */
function maca_menulist_table_order_order_note_enabled() {
    return maca_menulist_table_order_enabled()
        && get_option('maca_menulist_table_order_order_note', '1') === '1';
}

/**
 * Whether prices are shown in the guest order summary.
 *
 * @return bool
 */
function maca_menulist_table_order_show_prices() {
    return maca_menulist_table_order_enabled()
        && get_option('maca_menulist_table_order_show_prices', '1') === '1';
}

/**
 * Read the guest voter cookie without creating a new key.
 *
 * @return string
 */
function maca_menulist_get_dish_rating_voter_key() {
    $cookie_name = 'maca_menulist_dish_voter';

    if (empty($_COOKIE[$cookie_name])) {
        return '';
    }

    $raw = sanitize_text_field(wp_unslash($_COOKIE[$cookie_name]));

    if (preg_match('/^[a-f0-9]{32}$/', $raw)) {
        return $raw;
    }

    return '';
}

/**
 * Ensure a guest voter cookie exists and return its key.
 *
 * @return string
 */
function maca_menulist_ensure_dish_rating_voter_key() {
    $existing = maca_menulist_get_dish_rating_voter_key();
    if ($existing !== '') {
        return $existing;
    }

    $cookie_name = 'maca_menulist_dish_voter';
    $key = bin2hex(random_bytes(16));

    if (!headers_sent()) {
        setcookie($cookie_name, $key, time() + YEAR_IN_SECONDS, COOKIEPATH, COOKIE_DOMAIN, is_ssl(), true);
    }

    $_COOKIE[$cookie_name] = $key;

    return $key;
}

/**
 * Config array for dish rating JavaScript.
 *
 * @return array<string, mixed>
 */
function maca_menulist_get_dish_rating_script_config() {
    return maca_menulist_get_dish_menu_script_config();
}

/**
 * JavaScript config for dish menu (ratings, click tracking).
 *
 * @return array<string, mixed>
 */
function maca_menulist_get_dish_menu_script_config() {
    maca_menulist_load_textdomain();

    $config = array(
        'ajaxUrl' => admin_url('admin-ajax.php'),
        'nonce' => wp_create_nonce('maca_dish_rating_nonce'),
        'trackNonce' => wp_create_nonce('maca_dish_track_nonce'),
        'ratingsEnabled' => maca_menulist_dish_guest_ratings_enabled(),
        'trackingEnabled' => maca_menulist_is_dishes_mode(),
        'i18n' => array(
            'rateDish' => maca_menulist_guest_ui_string('Rate this dish'),
            'ratingSaved' => maca_menulist_guest_ui_string('Thank you for your rating!'),
            'ratingError' => maca_menulist_guest_ui_string('Could not save your rating. Please try again.'),
            'ratingCountSingular' => maca_menulist_guest_ui_string('(%d rating)'),
            'ratingCountPlural' => maca_menulist_guest_ui_string('(%d ratings)'),
        ),
    );

    return $config;
}

/**
 * Render guest star rating UI for a dish.
 *
 * @param object $dish Dish row object.
 */
function maca_menulist_render_dish_rating($dish) {
    if (!maca_menulist_dish_guest_ratings_enabled()) {
        return;
    }

    $dish_id = (int) $dish->id;
    $average = isset($dish->rating_average) ? (float) $dish->rating_average : 0.0;
    $count = isset($dish->rating_count) ? (int) $dish->rating_count : 0;
    $user_rating = isset($dish->user_rating) ? (int) $dish->user_rating : 0;

    echo '<div class="maca-dish-rating" data-dish-id="' . esc_attr($dish_id) . '" data-user-rating="' . esc_attr($user_rating) . '">';
    echo '<span class="maca-dish-rating-label">' . esc_html(maca_menulist_guest_ui_string('Rate:')) . '</span>';
    echo '<span class="maca-dish-rating-stars" role="group" aria-label="' . esc_attr(maca_menulist_guest_ui_string('Rate this dish')) . '">';

    for ($star = 1; $star <= 5; $star++) {
        $is_active = $user_rating >= $star;
        echo '<button type="button" class="maca-dish-rating-star' . ($is_active ? ' is-active' : '') . '" data-value="' . esc_attr($star) . '" aria-label="' . esc_attr(
            sprintf(maca_menulist_guest_ui_string('%d stars'), $star)
        ) . '" aria-pressed="' . ($is_active ? 'true' : 'false') . '">★</button>';
    }

    echo '</span>';
    echo '<span class="maca-dish-rating-meta' . ($count === 0 ? ' maca-dish-rating-meta--empty' : '') . '">';

    if ($count > 0) {
        echo '<span class="maca-dish-rating-average">' . esc_html(number_format($average, 1, ',', ' ')) . '</span>';
        echo '<span class="maca-dish-rating-count">' . esc_html(
            sprintf(
                maca_menulist_guest_ui_string_plural('(%d rating)', '(%d ratings)', $count),
                $count
            )
        ) . '</span>';
    }

    echo '</span>';
    echo '</div>';
}

/**
 * Render dish category and campaign filter controls.
 *
 * @param array<int, array{category: object, dishes: array<int, object>}>   $grouped           Grouped dishes.
 * @param array<int, array{campaign: object, dishes: array<int, object>}> $grouped_campaigns Grouped campaigns.
 * @param string                                                            $context           Menu context: qr, inline, standalone, takeaway.
 * @param array<string, mixed>                                              $options {
 *     @type bool $preview Admin schedule preview (interactive filters).
 * }
 */
function maca_menulist_render_dish_category_filters($grouped, $grouped_campaigns = array(), $context = 'inline', $options = array()) {
    if (!maca_menulist_show_dish_category_filter()) {
        return;
    }

    if (empty($grouped) && empty($grouped_campaigns)) {
        return;
    }

    $options = wp_parse_args(
        is_array($options) ? $options : array(),
        array(
            'preview' => false,
        )
    );

    $use_dropdown = maca_menulist_get_dish_category_nav_mode() === 'dropdown';

    if ($use_dropdown) {
        $select_id = wp_unique_id('maca-dish-category-select-');
        $placeholder = esc_html__('Select menu category', 'maca-restu-pro');

        echo '<div class="maca-dish-filters maca-dish-filters--dropdown">';
        echo '<select id="' . esc_attr($select_id) . '" class="maca-dish-category-select" aria-label="' . esc_attr($placeholder) . '">';
        if (!empty($options['preview'])) {
            echo '<option value="all" selected>' . esc_html__('All', 'maca-restu-pro') . '</option>';
        } else {
            echo '<option value="" disabled hidden>' . esc_html($placeholder) . '</option>';
            echo '<option value="all" selected>' . esc_html__('All', 'maca-restu-pro') . '</option>';
        }

        foreach ($grouped_campaigns as $group) {
            $campaign = $group['campaign'];
            echo '<option value="campaign:' . esc_attr($campaign->id) . '">';
            echo esc_html(maca_menulist_get_campaign_display_name($campaign));
            echo '</option>';
        }

        foreach ($grouped as $group) {
            $category = $group['category'];
            echo '<option value="category:' . esc_attr($category->id) . '">';
            echo esc_html(maca_menulist_get_dish_category_display_name($category));
            echo '</option>';
        }

        echo '</select>';
        echo '</div>';
        return;
    }

    echo '<div class="maca-dish-filters maca-filter-bar" role="tablist" aria-label="' . esc_attr__('Filter dishes by category', 'maca-restu-pro') . '">';
    echo '<button type="button" class="maca-dish-filter active" data-category-id="all" role="tab" aria-selected="true">';
    echo esc_html__('All', 'maca-restu-pro');
    echo '</button>';

    foreach ($grouped_campaigns as $group) {
        $campaign = $group['campaign'];
        echo '<button type="button" class="maca-dish-filter" data-campaign-id="' . esc_attr($campaign->id) . '" role="tab" aria-selected="false">';
        echo esc_html(maca_menulist_get_campaign_display_name($campaign));
        echo '</button>';
    }

    foreach ($grouped as $group) {
        $category = $group['category'];
        echo '<button type="button" class="maca-dish-filter" data-category-id="' . esc_attr($category->id) . '" role="tab" aria-selected="false">';
        echo esc_html(maca_menulist_get_dish_category_display_name($category));
        echo '</button>';
    }

    echo '</div>';
}

/**
 * Render a dish category heading with decorative ornament markup.
 *
 * @param string $label         Display name.
 * @param string $tag             Heading tag (h3 or h4).
 * @param string $extra_classes Additional CSS classes on the heading.
 * @return void
 */
function maca_menulist_render_dish_category_heading($label, $tag = 'h3', $extra_classes = '') {
    $tag = in_array($tag, array('h3', 'h4'), true) ? $tag : 'h3';
    $mod = ('h4' === $tag) ? ' maca-dish-category-heading--sub' : '';
    $classes = trim('maca-dish-category-title ' . $extra_classes);

    echo '<div class="' . esc_attr('maca-dish-category-heading' . $mod) . '">';
    echo '<div class="maca-dish-category-ornament" aria-hidden="true">';
    echo '<span class="maca-dish-category-ornament__arm maca-dish-category-ornament__arm--left"></span>';
    echo '<span class="maca-dish-category-ornament__mark"></span>';
    echo '<span class="maca-dish-category-ornament__arm maca-dish-category-ornament__arm--right"></span>';
    echo '</div>';
    echo '<' . tag_escape($tag) . ' class="' . esc_attr($classes) . '">' . esc_html($label) . '</' . tag_escape($tag) . '>';
    echo '</div>';
}

/**
 * Render a campaign section with dishes.
 *
 * @param object               $campaign            Campaign row.
 * @param array<int, object>   $dishes              Dishes in this campaign.
 * @param bool                 $allow_order_actions Whether add-to-order controls are visible.
 */
function maca_menulist_render_dish_campaign_section($campaign, $dishes, $allow_order_actions = true) {
    $categories = maca_menulist_db_get_dish_categories();
    $categories_by_id = array();
    $dishes_by_category = array();

    foreach ($categories as $category) {
        $category_id = (int) $category->id;
        $categories_by_id[$category_id] = $category;
        $dishes_by_category[$category_id] = array();
    }

    foreach ($dishes as $dish) {
        $category_id = (int) $dish->category_id;
        if (!isset($dishes_by_category[$category_id])) {
            $dishes_by_category[$category_id] = array();
        }
        $dishes_by_category[$category_id][] = $dish;
    }

    echo '<section class="maca-dish-category maca-dish-campaign" data-dish-campaign-id="' . esc_attr($campaign->id) . '">';
    maca_menulist_render_dish_category_heading(
        maca_menulist_get_campaign_display_name($campaign),
        'h3',
        'maca-dish-campaign-title'
    );

    foreach ($dishes_by_category as $category_id => $category_dishes) {
        if (empty($category_dishes)) {
            continue;
        }

        $category = isset($categories_by_id[$category_id]) ? $categories_by_id[$category_id] : null;
        if ($category && !maca_menulist_dish_category_is_active($category)) {
            continue;
        }

        if ($category) {
            maca_menulist_render_dish_category_heading(
                maca_menulist_get_dish_category_display_name($category),
                'h4',
                'maca-dish-category-subtitle'
            );
        }

        echo '<ul class="maca-dish-list">';
        foreach ($category_dishes as $dish) {
            maca_menulist_render_dish_item($dish, $allow_order_actions);
        }
        echo '</ul>';
    }

    echo '</section>';
}

/**
 * Build category groups from active campaign dishes.
 *
 * @param array<int, array{campaign: object, dishes: array<int, object>}> $grouped_campaigns Active campaigns.
 * @return array<int, array{category: object, dishes: array<int, object>}>
 */
function maca_menulist_build_grouped_dishes_from_campaigns($grouped_campaigns) {
    if (empty($grouped_campaigns)) {
        return array();
    }

    $categories = maca_menulist_db_get_dish_categories(true);
    $categories_by_id = array();
    foreach ($categories as $category) {
        if (!maca_menulist_dish_category_is_active($category)) {
            continue;
        }
        $categories_by_id[(int) $category->id] = $category;
    }

    $dishes_by_category = array();
    $seen_dish_ids = array();

    foreach ($grouped_campaigns as $group) {
        if (empty($group['dishes'])) {
            continue;
        }

        foreach ($group['dishes'] as $dish) {
            $dish_id = (int) $dish->id;
            if (isset($seen_dish_ids[$dish_id])) {
                continue;
            }

            $seen_dish_ids[$dish_id] = true;
            $category_id = (int) $dish->category_id;
            if (!isset($dishes_by_category[$category_id])) {
                $dishes_by_category[$category_id] = array();
            }
            $dishes_by_category[$category_id][] = $dish;
        }
    }

    if (empty($dishes_by_category)) {
        return array();
    }

    $grouped = array();
    foreach ($categories as $category) {
        $category_id = (int) $category->id;
        if (empty($dishes_by_category[$category_id])) {
            continue;
        }

        $grouped[] = array(
            'category' => $category,
            'dishes' => $dishes_by_category[$category_id],
        );
    }

    foreach ($dishes_by_category as $category_id => $dishes) {
        if (isset($categories_by_id[$category_id])) {
            continue;
        }

        $grouped[] = array(
            'category' => (object) array(
                'id' => $category_id,
                'name' => __('Other', 'maca-restu-pro'),
                'name_en' => __('Other', 'maca-restu-pro'),
            ),
            'dishes' => $dishes,
        );
    }

    return $grouped;
}

/**
 * Capture dish menu HTML for admin schedule preview.
 *
 * @param string   $context   Display context: inline, qr, standalone.
 * @param int|null $timestamp Optional preview timestamp (site timezone).
 * @return string
 */
function maca_menulist_capture_dish_menu_html($context = 'inline', $timestamp = null) {
    if ($timestamp === null) {
        $timestamp = maca_menulist_wp_mysql_to_timestamp(maca_menulist_wp_now_mysql());
        if ($timestamp === false) {
            $timestamp = time();
        }
    } else {
        $timestamp = (int) $timestamp;
    }
    $eval_mysql = maca_menulist_get_schedule_eval_mysql($timestamp);

    maca_menulist_push_schedule_eval_mysql($eval_mysql);
    ob_start();
    maca_menulist_render_dish_menu(
        $context,
        false,
        array(
            'timestamp' => $timestamp,
            'preview' => true,
        )
    );
    maca_menulist_pop_schedule_eval_mysql();

    return (string) ob_get_clean();
}

/**
 * Render the full dish menu for frontend display.
 *
 * @param string               $context         Display context: inline, qr, standalone.
 * @param bool                 $render_order_ui Whether to output the order cart UI in place.
 * @param array<string, mixed> $options {
 *     @type int|null $timestamp Optional preview timestamp (site timezone).
 *     @type bool     $preview   When true, skip interactive ordering UI.
 * }
 */
function maca_menulist_render_dish_menu($context = 'inline', $render_order_ui = true, $options = array()) {
    maca_menulist_load_textdomain();

    $options = wp_parse_args(
        is_array($options) ? $options : array(),
        array(
            'timestamp' => null,
            'preview' => false,
        )
    );

    if (!empty($options['preview'])) {
        $render_order_ui = false;
    }

    $is_takeaway = ($context === 'takeaway');
    $schedule_args = array(
        'qr_schedule' => ($context === 'qr' || $is_takeaway),
        'takeaway_menu' => $is_takeaway,
        'timestamp' => $options['timestamp'],
    );
    $allow_order_actions = false;
    if ($context === 'qr' && function_exists('maca_menulist_dine_in_ordering_enabled')) {
        $allow_order_actions = maca_menulist_dine_in_ordering_enabled();
    } elseif ($is_takeaway && function_exists('maca_menulist_takeaway_ordering_enabled')) {
        $allow_order_actions = maca_menulist_takeaway_ordering_enabled();
    }

    if (
        $is_takeaway
        && $allow_order_actions
        && function_exists('maca_menulist_takeaway_accepting_orders_now')
        && !maca_menulist_takeaway_accepting_orders_now()
    ) {
        $allow_order_actions = false;
    }

    $grouped_campaigns = maca_menulist_get_grouped_campaigns($schedule_args);
    $grouped = maca_menulist_get_grouped_dishes($schedule_args);

    $exclusive_campaign_mode = maca_menulist_campaign_exclusive_mode_enabled() && !empty($grouped_campaigns);

    // Exclusive campaign mode: if any campaign section is active right now,
    // only campaign-marked dishes are shown, grouped by normal dish category.
    if ($exclusive_campaign_mode) {
        $grouped = maca_menulist_build_grouped_dishes_from_campaigns($grouped_campaigns);
    }

    if (empty($grouped) && empty($grouped_campaigns)) {
        $empty_message = ($context === 'qr' || $is_takeaway)
            ? __('No dishes are available on the menu right now.', 'maca-restu-pro')
            : __('No dishes available.', 'maca-restu-pro');
        echo '<p class="maca-dishes-empty">' . esc_html($empty_message) . '</p>';
        return;
    }

    $container_class = 'maca-dish-menu';
    if ($context === 'qr') {
        $container_class .= ' maca-dish-menu-qr';
    } elseif ($is_takeaway) {
        $container_class .= ' maca-dish-menu-takeaway';
    } elseif ($context === 'standalone') {
        $container_class .= ' maca-dish-menu-standalone';
    }

    echo '<div class="' . esc_attr(maca_menulist_get_dish_menu_theme_shell_class()) . '">';
    echo '<div class="' . esc_attr($container_class) . '">';

    if (
        !empty($options['preview'])
        && $exclusive_campaign_mode
        && !empty($grouped_campaigns)
    ) {
        echo '<p class="maca-menu-preview-exclusive-notice description">';
        echo esc_html__(
            'Exclusive campaign mode is active at this time. Only campaign dishes are shown below.',
            'maca-restu-pro'
        );
        echo '</p>';
    }

    maca_menulist_render_menu_language_switcher(
        array(
            'context' => ($context === 'qr' || $is_takeaway) ? 'qr' : 'inline',
        )
    );

    if (
        $is_takeaway
        && function_exists('maca_menulist_takeaway_accepting_orders_now')
        && !maca_menulist_takeaway_accepting_orders_now()
        && function_exists('maca_menulist_render_takeaway_hours_notice')
    ) {
        maca_menulist_render_takeaway_hours_notice();
    }

    $campaign_filters = $exclusive_campaign_mode ? array() : $grouped_campaigns;
    maca_menulist_render_dish_category_filters(
        $grouped,
        $campaign_filters,
        $context,
        array(
            'preview' => !empty($options['preview']),
        )
    );

    if (!$exclusive_campaign_mode) {
        foreach ($grouped_campaigns as $group) {
            maca_menulist_render_dish_campaign_section($group['campaign'], $group['dishes'], $allow_order_actions);
        }
    }

    foreach ($grouped as $group) {
        $category = $group['category'];
        $dishes = $group['dishes'];

        echo '<section class="maca-dish-category" data-dish-category-id="' . esc_attr($category->id) . '">';
        maca_menulist_render_dish_category_heading(maca_menulist_get_dish_category_display_name($category));
        echo '<ul class="maca-dish-list">';

        foreach ($dishes as $dish) {
            maca_menulist_render_dish_item($dish, $allow_order_actions);
        }

        echo '</ul>';
        echo '</section>';
    }

    echo '</div>';

    maca_menulist_render_dish_menu_footer('screen');

    echo '</div>';

    if (maca_menulist_dish_images_clickable()) {
        maca_menulist_render_dish_image_lightbox();
    }

    if ($render_order_ui && $allow_order_actions) {
        if ($is_takeaway && function_exists('maca_menulist_set_guest_order_channel')) {
            maca_menulist_set_guest_order_channel('takeaway');
        }
        maca_menulist_render_table_order_ui();
    }
}

/**
 * Output a single lightbox container for enlarged dish images.
 */
function maca_menulist_render_dish_image_lightbox() {
    static $rendered = false;

    if ($rendered) {
        return;
    }

    $rendered = true;

    echo '<div class="maca-dish-lightbox" id="maca-dish-lightbox" hidden aria-hidden="true">';
    echo '<div class="maca-dish-lightbox-overlay" data-maca-lightbox-close="1"></div>';
    echo '<div class="maca-dish-lightbox-dialog" role="dialog" aria-modal="true" aria-label="' . esc_attr__('Dish image', 'maca-restu-pro') . '">';
    echo '<button type="button" class="maca-dish-lightbox-close" data-maca-lightbox-close="1" aria-label="' . esc_attr__('Close image', 'maca-restu-pro') . '">&times;</button>';
    echo '<img class="maca-dish-lightbox-image" src="" alt="">';
    echo '</div>';
    echo '</div>';
}

/**
 * Render a single dish row.
 *
 * @param object $dish                Dish row object.
 * @param bool   $allow_order_actions Whether add-to-order controls are visible.
 */
function maca_menulist_render_dish_item($dish, $allow_order_actions = true) {
    $dish_title = maca_menulist_get_dish_display_title($dish);
    $dish_description = maca_menulist_get_dish_display_description($dish);
    $image_url = !empty($dish->image_url) ? esc_url($dish->image_url) : '';
    $price_channel = function_exists('maca_menulist_resolve_guest_order_channel')
        ? maca_menulist_resolve_guest_order_channel()
        : 'dine_in';
    $price = function_exists('maca_menulist_get_dish_price_label')
        ? maca_menulist_get_dish_price_label($dish, $price_channel)
        : maca_menulist_format_price($dish->price);

    echo '<li class="maca-dish-item" data-dish-id="' . esc_attr((int) $dish->id) . '">';

    if ($image_url !== '') {
        $image_class = 'maca-dish-image';
        if (maca_menulist_dish_images_clickable()) {
            $image_class .= ' maca-dish-image--clickable';
        }

        echo '<div class="' . esc_attr($image_class) . '">';

        if (maca_menulist_dish_images_clickable()) {
            echo '<button type="button" class="maca-dish-image-button" data-dish-id="' . esc_attr((int) $dish->id) . '" data-full-image-url="' . esc_url($dish->image_url) . '" aria-label="' . esc_attr(
                /* translators: %s: dish title */
                sprintf(__('View larger image of %s', 'maca-restu-pro'), $dish_title)
            ) . '">';
        }

        echo '<img src="' . esc_url($dish->image_url) . '" alt="' . esc_attr($dish_title) . '">';

        if (maca_menulist_dish_images_clickable()) {
            echo '</button>';
        }

        echo '</div>';
    }

    echo '<div class="maca-dish-details">';
    echo '<div class="maca-dish-header">';
    echo '<h4 class="maca-dish-title">' . esc_html($dish_title) . '</h4>';

    if ($price !== '') {
        echo '<div class="maca-dish-price">' . esc_html($price) . '</div>';
    }

    echo '</div>';

    if ($dish_description !== '') {
        echo '<p class="maca-dish-description">' . esc_html($dish_description) . '</p>';
    }

    $allergens = isset($dish->allergens) ? $dish->allergens : array();
    if (empty($allergens) && !empty($dish->id)) {
        $allergens = maca_menulist_db_get_allergens_for_dish($dish->id);
    }

    if (!empty($allergens)) {
        $allergen_names = array();
        foreach ($allergens as $allergen) {
            if (!empty($allergen->name) || !empty($allergen->name_en)) {
                $label = maca_menulist_get_allergen_display_name($allergen);
                if ($label !== '') {
                    $allergen_names[] = $label;
                }
            }
        }

        if (!empty($allergen_names)) {
            echo '<p class="maca-dish-allergens">';
            echo '<strong class="maca-dish-allergens-label">' . esc_html__('Allergens:', 'maca-restu-pro') . '</strong> ';
            echo '<span class="maca-dish-allergens-list">' . esc_html(implode(', ', $allergen_names)) . '</span>';
            echo '</p>';
        }
    }

    maca_menulist_render_dish_rating($dish);

    if ($allow_order_actions && maca_menulist_table_order_enabled()) {
        maca_menulist_render_dish_order_actions($dish);
    }

    echo '</div>';

    echo '</li>';
}

/**
 * Render add-to-order controls for a dish row.
 *
 * @param object $dish Dish row object.
 */
function maca_menulist_render_dish_order_actions($dish) {
    $dish_id = (int) $dish->id;
    $dish_title = maca_menulist_get_dish_display_title($dish);
    $price_channel = function_exists('maca_menulist_resolve_guest_order_channel')
        ? maca_menulist_resolve_guest_order_channel()
        : 'dine_in';
    $price_raw = function_exists('maca_menulist_get_dish_price_raw')
        ? maca_menulist_get_dish_price_raw($dish, $price_channel)
        : (($dish->price !== null && $dish->price !== '') ? (float) $dish->price : null);
    $price_label = $price_raw !== null ? maca_menulist_format_price($price_raw) : '';

    echo '<div class="maca-dish-order-actions" data-dish-id="' . esc_attr($dish_id) . '" data-dish-title="' . esc_attr($dish_title) . '"';

    if ($price_raw !== null) {
        echo ' data-dish-price="' . esc_attr($price_raw) . '" data-dish-price-label="' . esc_attr($price_label) . '"';
    }

    echo '>';
    echo '<button type="button" class="maca-dish-add-to-order">' . esc_html(maca_menulist_guest_ui_string('Add')) . '</button>';
    echo '</div>';
}

/**
 * Output guest table order UI (cart bar, review panel, waiter screen).
 */
function maca_menulist_render_table_order_ui() {
    static $rendered = false;

    if ($rendered || !maca_menulist_table_order_enabled()) {
        return;
    }

    maca_menulist_load_textdomain();

    $rendered = true;

    echo '<div id="maca-table-order-root" class="maca-table-order-root" hidden aria-hidden="true">';

    echo '<div id="maca-table-order-bar" class="maca-table-order-bar" hidden>';
    echo '<button type="button" class="maca-table-order-bar-button" id="maca-table-order-open">';
    echo '<span class="maca-table-order-bar-count" id="maca-table-order-bar-count">0</span>';
    echo '<span class="maca-table-order-bar-label" id="maca-table-order-bar-label">' . esc_html(maca_menulist_guest_ui_string('View order')) . '</span>';
    echo '</button>';
    echo '</div>';

    echo '<div id="maca-table-order-panel" class="maca-table-order-panel" hidden aria-hidden="true" role="dialog" aria-modal="true" aria-labelledby="maca-table-order-panel-title">';
    echo '<div class="maca-table-order-panel-header">';
    echo '<h2 id="maca-table-order-panel-title">' . esc_html(maca_menulist_guest_ui_string('Your order')) . '</h2>';
    echo '<button type="button" class="maca-table-order-close" data-maca-order-close="panel" aria-label="' . esc_attr(maca_menulist_guest_ui_string('Close')) . '">&times;</button>';
    echo '</div>';
    echo '<div class="maca-table-order-panel-body">';

    if (
        !function_exists('maca_menulist_is_takeaway_order_channel')
        || !maca_menulist_is_takeaway_order_channel()
    ) {
        if (function_exists('maca_menulist_render_guest_order_table_select')) {
            maca_menulist_render_guest_order_table_select();
        }
    } else {
        maca_menulist_render_takeaway_pickup_fields();
    }

    echo '<div id="maca-table-order-lines" class="maca-table-order-lines"></div>';
    echo '<p id="maca-table-order-empty" class="maca-table-order-empty">' . esc_html(maca_menulist_guest_ui_string('Your order is empty.')) . '</p>';

    echo '<label class="maca-table-order-field maca-table-order-field--order-note" for="maca-table-order-note-input">';
    echo '<span>' . esc_html(maca_menulist_guest_ui_string('Order note')) . '</span>';
    echo '<textarea id="maca-table-order-note-input" class="maca-table-order-note-input" rows="2" placeholder="' . esc_attr(maca_menulist_guest_ui_string('e.g. allergies, birthday...')) . '"></textarea>';
    echo '</label>';

    echo '<div id="maca-table-order-total-wrap" class="maca-table-order-total-wrap" hidden>';
    echo '<span>' . esc_html(maca_menulist_guest_ui_string('Total')) . '</span>';
    echo '<strong id="maca-table-order-total"></strong>';
    echo '</div>';
    echo '</div>';

    echo '<div class="maca-table-order-panel-footer">';
    echo '<button type="button" class="maca-table-order-clear" id="maca-table-order-clear">' . esc_html(maca_menulist_guest_ui_string('Clear order')) . '</button>';
    echo '<button type="button" class="maca-table-order-show-waiter" id="maca-table-order-show-waiter">' . esc_html(maca_menulist_guest_ui_string('Show waiter')) . '</button>';
    echo '</div>';
    echo '</div>';

    echo '<div id="maca-table-order-waiter" class="maca-table-order-waiter" hidden aria-hidden="true" role="dialog" aria-modal="true" aria-labelledby="maca-table-order-waiter-title">';
    echo '<div class="maca-table-order-waiter-inner">';
    echo '<div class="maca-table-order-waiter-header">';
    echo '<h2 id="maca-table-order-waiter-title">' . esc_html(maca_menulist_guest_ui_string('Order for waiter')) . '</h2>';
    echo '<p id="maca-table-order-waiter-table" class="maca-table-order-waiter-table" hidden></p>';
    echo '</div>';
    echo '<div id="maca-table-order-waiter-content" class="maca-table-order-waiter-content"></div>';
    echo '<div class="maca-table-order-waiter-actions">';
    echo '<button type="button" class="maca-table-order-more" id="maca-table-order-more">' . esc_html(maca_menulist_guest_ui_string('Order more')) . '</button>';
    echo '<button type="button" class="maca-table-order-back" id="maca-table-order-back">' . esc_html(maca_menulist_guest_ui_string('Back to menu')) . '</button>';
    echo '</div>';
    echo '</div>';
    echo '</div>';

    echo '<div id="maca-table-order-overlay" class="maca-table-order-overlay" hidden data-maca-order-close="panel"></div>';

    echo '<div id="maca-guest-order-status" class="maca-guest-order-status" hidden aria-hidden="true" role="dialog" aria-modal="true" aria-labelledby="maca-guest-order-status-title">';
    echo '<button type="button" class="maca-guest-order-status-dismiss" id="maca-guest-order-status-close" aria-label="' . esc_attr(maca_menulist_guest_ui_string('Close')) . '">&times;</button>';
    echo '<div class="maca-guest-order-status-inner">';
    echo '<div class="maca-guest-order-status-header">';
    echo '<h2 id="maca-guest-order-status-title">' . esc_html(maca_menulist_guest_ui_string('Your order')) . '</h2>';
    echo '<p id="maca-guest-order-status-table" class="maca-guest-order-status-table" hidden></p>';
    echo '<p id="maca-guest-order-status-all-paid" class="maca-guest-order-status-all-paid" hidden></p>';
    echo '<p class="maca-guest-order-status-hint" id="maca-guest-order-status-hint">' . esc_html(maca_menulist_guest_ui_string('We will update this page as your order progresses.')) . '</p>';
    echo '</div>';
    echo '<ol id="maca-guest-order-status-steps" class="maca-guest-order-status-steps"></ol>';
    echo '<p id="maca-guest-order-status-message" class="maca-guest-order-status-message"></p>';
    echo '<p id="maca-guest-order-status-eta" class="maca-guest-order-status-eta" hidden></p>';
    echo '<ul id="maca-guest-order-status-lines" class="maca-guest-order-status-lines"></ul>';
    echo '<div id="maca-guest-order-status-orders" class="maca-guest-order-status-orders"></div>';
    echo '<div id="maca-guest-order-status-combined" class="maca-guest-order-status-combined" hidden></div>';
    echo '<div class="maca-guest-order-status-actions">';
    echo '<button type="button" class="maca-guest-order-share-qr" id="maca-guest-order-share-qr" hidden>' . esc_html(maca_menulist_guest_ui_string('Show QR for another phone')) . '</button>';
    echo '<button type="button" class="maca-guest-order-more" id="maca-guest-order-more">' . esc_html(maca_menulist_guest_ui_string('Order more')) . '</button>';
    echo '</div>';
    echo '</div>';
    echo '</div>';
    echo maca_menulist_render_guest_share_qr_modal(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped

    echo '<div id="maca-guest-order-session-bar" class="maca-guest-order-session-bar" hidden>';
    echo '<button type="button" class="maca-guest-order-session-button" id="maca-guest-order-view-status">';
    echo '<span id="maca-guest-order-session-label">' . esc_html(maca_menulist_guest_ui_string('My orders')) . '</span>';
    echo '</button>';
    echo '</div>';

    echo '</div>';
}
