<?php

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

class AIEO_DMM_Google_Reviews extends AIEO_DMM_Module_Base {

    public function get_id()       { return 'google_reviews'; }
    public function get_label()    { return __('Locations & Reviews', 'ai-eshop-optimizer'); }
    public function get_priority() { return 65; }
    public function get_group()    { return 'custom_post_types'; } 

    public function __construct() {
        $this->load_services();
    }

    
    private function load_services() {
        $base = AIEO_DIR . 'includes/dmm/google-reviews/';
        foreach (array(
            'class-aieo-google-reviews-settings.php',
            'class-aieo-google-reviews-locations.php',
            'class-aieo-google-reviews-cache.php',
            'class-aieo-google-reviews-place-lookup.php',
            'class-aieo-google-reviews-fetcher.php',
            'class-aieo-google-reviews-cron.php',
            'class-aieo-google-reviews-helpers.php',
            'class-aieo-google-reviews-block.php',
            'class-aieo-store-info-block.php',
            'class-aieo-store-locator-block.php',
            'class-aieo-opening-hours-block.php',
            'class-aieo-business-info-block.php',
            'class-aieo-store-hero-block.php',
            'class-aieo-store-map-block.php',
            'class-aieo-store-cpt.php',
        ) as $f) {
            $path = $base . $f;
            if (file_exists($path)) { require_once $path; }
        }
        
        
        
        if (class_exists('AIEO_Google_Reviews_Cron')) {
            AIEO_Google_Reviews_Cron::init();
        }
        
        
        
        if (class_exists('AIEO_Store_CPT')) {
            AIEO_Store_CPT::register();
        }
        
        
        
        
        
        
        
        
        if ( ! defined( 'AIEO_RUNTIME_OWNS_AJAX' ) ) {
            if (class_exists('AIEO_Google_Reviews_Block')) {
                AIEO_Google_Reviews_Block::register();
            }
            if (class_exists('AIEO_Store_Info_Block')) {
                AIEO_Store_Info_Block::register();
            }
            if (class_exists('AIEO_Store_Locator_Block')) {
                AIEO_Store_Locator_Block::register();
            }
            if (class_exists('AIEO_Opening_Hours_Block')) {
                AIEO_Opening_Hours_Block::register();
            }
            if (class_exists('AIEO_Business_Info_Block')) {
                AIEO_Business_Info_Block::register();
            }
            if (class_exists('AIEO_Store_Hero_Block')) {
                AIEO_Store_Hero_Block::register();
            }
            if (class_exists('AIEO_Store_Map_Block')) {
                AIEO_Store_Map_Block::register();
            }
        }
    }

    public function register_hooks() {
        add_action('wp_ajax_aieo_grv_save_settings',   array($this, 'ajax_save_settings'));
        add_action('wp_ajax_aieo_grv_save_location',   array($this, 'ajax_save_location'));
        add_action('wp_ajax_aieo_grv_delete_location', array($this, 'ajax_delete_location'));
        add_action('wp_ajax_aieo_grv_lookup_place',    array($this, 'ajax_lookup_place'));
        add_action('wp_ajax_aieo_grv_fetch_now',       array($this, 'ajax_fetch_now'));
        add_action('wp_ajax_aieo_grv_fetch_all',       array($this, 'ajax_fetch_all'));
        add_action('wp_ajax_aieo_grv_reorder_location', array($this, 'ajax_reorder_location'));
    }

    public function render_subtab() {
        $section = isset($_GET['grv_section']) ? sanitize_key((string) $_GET['grv_section']) : 'locations';
        if (!in_array($section, array('locations', 'history', 'settings'), true)) { $section = 'locations'; }
        $base_url = admin_url('admin.php?page=' . AIEO_DMM::MENU_SLUG . '&dmm_tab=google_reviews');

        echo '<div class="aieo-dmm-section">';
        echo '<h2>' . esc_html__('Google Reviews', 'ai-eshop-optimizer') . '</h2>';
        echo '<p class="description">' . esc_html__('Pull Google reviews per location into a local table, surface them via a block pattern, and keep a permanent history (Google\'s Places API only ever returns the latest 5 — we diff each fetch and append unseen reviews so nothing is lost over time).', 'ai-eshop-optimizer') . '</p>';

        echo '<div class="nav-tab-wrapper" style="margin-bottom:14px;">';
        foreach (array(
            'locations' => __('Locations', 'ai-eshop-optimizer'),
            'history'   => __('Review History', 'ai-eshop-optimizer'),
            'settings'  => __('Settings', 'ai-eshop-optimizer'),
        ) as $slug => $label) {
            $active = $section === $slug ? ' nav-tab-active' : '';
            echo '<a href="' . esc_url($base_url . '&grv_section=' . $slug) . '" class="nav-tab' . $active . '">' . esc_html($label) . '</a>'; 
        }
        echo '</div>';

        if ($section === 'locations') { $this->render_locations(); }
        if ($section === 'history')   { $this->render_history(); }
        if ($section === 'settings')  { $this->render_settings(); }

        echo '</div>';
    }

    private function render_locations() {
        $locations = AIEO_Google_Reviews_Locations::list_all();
        $settings  = AIEO_Google_Reviews_Settings::get_all();
        $nonce     = wp_create_nonce('aieo_grv_locations');
        $ajax      = admin_url('admin-ajax.php');
        $has_global_key = trim((string) $settings['global_api_key']) !== '';
        $enabled_count  = 0;
        foreach ($locations as $l) { if (!empty($l['enabled'])) { $enabled_count++; } }

        
        echo '<div style="margin-bottom:10px;display:flex;align-items:center;gap:8px;flex-wrap:wrap;">';
        echo '<button type="button" class="button button-primary" id="aieo-grv-fetch-all"' . ($enabled_count > 0 ? '' : ' disabled') . '>';
        echo esc_html(sprintf(
            
            __('Fetch all enabled (%d)', 'ai-eshop-optimizer'),
            $enabled_count
        ));
        echo '</button>';
        echo '<span style="color:#646970;font-size:12px;">' . esc_html__('Hits Google once per enabled location and writes any new reviews to the local table.', 'ai-eshop-optimizer') . '</span>';
        echo '</div>';

        
        echo '<table class="wp-list-table widefat fixed striped">';
        echo '<thead><tr>';
        echo '<th style="width:104px;">' . esc_html__('Order', 'ai-eshop-optimizer') . '</th>';
        echo '<th>' . esc_html__('Location', 'ai-eshop-optimizer') . '</th>';
        echo '<th>' . esc_html__('place_id', 'ai-eshop-optimizer') . '</th>';
        echo '<th>' . esc_html__('Lang', 'ai-eshop-optimizer') . '</th>';
        echo '<th>' . esc_html__('Rating', 'ai-eshop-optimizer') . '</th>';
        echo '<th>' . esc_html__('Reviews', 'ai-eshop-optimizer') . '</th>';
        echo '<th>' . esc_html__('Last fetch', 'ai-eshop-optimizer') . '</th>';
        echo '<th>' . esc_html__('Enabled', 'ai-eshop-optimizer') . '</th>';
        echo '<th></th>';
        echo '</tr></thead><tbody id="aieo-grv-loc-rows">';
        if (empty($locations)) {
            echo '<tr><td colspan="9"><em>' . esc_html__('No locations yet. Add one below.', 'ai-eshop-optimizer') . '</em></td></tr>';
        } else {
            foreach ($locations as $l) {
                $place_short = strlen((string) $l['place_id']) > 28
                    ? substr($l['place_id'], 0, 24) . '…'
                    : (string) $l['place_id'];
                $is_chij = strpos((string) $l['place_id'], 'ChIJ') === 0;
                $rating  = $l['baseline_rating'] !== null ? number_format_i18n((float) $l['baseline_rating'], 1) : '—';
                $count   = $l['baseline_review_count'] !== null ? (int) $l['baseline_review_count'] : '—';
                $last    = !empty($l['last_fetched_at']) ? esc_html($l['last_fetched_at']) . ' UTC' : '—';
                $status  = !empty($l['last_fetch_status']) ? (string) $l['last_fetch_status'] : '';
                if ($status !== '' && $status !== 'OK') {
                    $last .= '<br><small style="color:#b91c1c;">' . esc_html($status) . '</small>';
                }
                echo '<tr data-grv-row="' . esc_attr((int) $l['id']) . '">';
                echo '<td style="white-space:nowrap;">';
                echo '<button type="button" class="button button-small" data-aieo-grv-move="up" data-id="' . esc_attr((int) $l['id']) . '" title="' . esc_attr__('Move up', 'ai-eshop-optimizer') . '" aria-label="' . esc_attr__('Move up', 'ai-eshop-optimizer') . '">&#9650;</button> ';
                echo '<button type="button" class="button button-small" data-aieo-grv-move="down" data-id="' . esc_attr((int) $l['id']) . '" title="' . esc_attr__('Move down', 'ai-eshop-optimizer') . '" aria-label="' . esc_attr__('Move down', 'ai-eshop-optimizer') . '">&#9660;</button> ';
                echo '<span data-grv-num style="display:inline-block;min-width:1.6em;text-align:center;color:#646970;">' . esc_html((string) (int) ($l['sort_order'] ?? 0)) . '</span>';
                echo '</td>';
                echo '<td><strong>' . esc_html((string) $l['label']) . '</strong><br><code style="font-size:11px;">' . esc_html((string) $l['slug']) . '</code></td>';
                echo '<td title="' . esc_attr((string) $l['place_id']) . '"><code style="font-size:11px;">' . esc_html($place_short) . '</code>';
                if (!$is_chij) {
                    echo '<br><small style="color:#92400e;">' . esc_html__('Not a ChIJ id — resolve before fetching', 'ai-eshop-optimizer') . '</small>';
                }
                echo '</td>';
                echo '<td>' . esc_html((string) $l['language']) . '</td>';
                echo '<td>' . esc_html((string) $rating) . '</td>';
                echo '<td>' . esc_html((string) $count) . '</td>';
                echo '<td>' . wp_kses_post($last) . '</td>';
                echo '<td>' . ($l['enabled'] ? '✓' : '—') . '</td>';
                echo '<td style="white-space:nowrap;">';
                echo '<button type="button" class="button" data-aieo-grv-edit="' . esc_attr((int) $l['id']) . '">' . esc_html__('Edit', 'ai-eshop-optimizer') . '</button> ';
                echo '<button type="button" class="button button-primary" data-aieo-grv-fetch="' . esc_attr((int) $l['id']) . '">' . esc_html__('Fetch now', 'ai-eshop-optimizer') . '</button> ';
                echo '<button type="button" class="button button-link-delete" data-aieo-grv-delete="' . esc_attr((int) $l['id']) . '">' . esc_html__('Delete', 'ai-eshop-optimizer') . '</button>';
                echo '</td>';
                echo '</tr>';
            }
        }
        echo '</tbody></table>';

        
        echo '<div id="aieo-grv-action-status" style="margin-top:8px;font-size:13px;"></div>';

        
        wp_enqueue_media(); 
        echo '<div class="aieo-dmm-card" id="aieo-grv-loc-form-card" style="margin-top:18px;">';
        echo '<h3 id="aieo-grv-form-title">' . esc_html__('Add a location', 'ai-eshop-optimizer') . '</h3>';

        echo '<form id="aieo-grv-location-form" class="aieo-dmm-form" onsubmit="return false;">';
        echo '<input type="hidden" name="id" value="">';
        echo '<table class="form-table">';
        echo '<tr><th>' . esc_html__('Slug', 'ai-eshop-optimizer') . '</th><td><input type="text" name="slug" required class="regular-text" placeholder="' . esc_attr__('e.g. main-store, downtown, branch-2', 'ai-eshop-optimizer') . '" pattern="[a-z0-9_-]+">';
        echo '<p class="description">' . esc_html__('URL-safe identifier, used internally + in block-pattern attributes. Letters, numbers, dash and underscore only.', 'ai-eshop-optimizer') . '</p></td></tr>';
        echo '<tr><th>' . esc_html__('Display name', 'ai-eshop-optimizer') . '</th><td><input type="text" name="label" required class="regular-text" placeholder="' . esc_attr__('Customer-facing label for this location', 'ai-eshop-optimizer') . '"></td></tr>';

        echo '<tr><th>' . esc_html__('place_id', 'ai-eshop-optimizer') . '</th><td>';
        echo '<input type="text" name="place_id" required class="large-text" placeholder="ChIJ…" style="font-family:monospace;">';
        echo '<p class="description">' . esc_html__('Paste the ChIJ identifier — or use the lookup helper below.', 'ai-eshop-optimizer') . '</p>';
        echo '</td></tr>';

        echo '<tr><th>' . esc_html__('Lookup place_id', 'ai-eshop-optimizer') . '</th><td>';
        echo '<input type="text" id="aieo-grv-lookup-query" class="large-text" placeholder="' . esc_attr__('Business name, address, or Google Maps / g.page URL', 'ai-eshop-optimizer') . '">';
        echo ' <button type="button" class="button" id="aieo-grv-lookup-btn"' . ($has_global_key ? '' : ' disabled') . '>' . esc_html__('Resolve', 'ai-eshop-optimizer') . '</button>';
        echo '<p class="description">';
        if ($has_global_key) {
            echo esc_html__('Hits Google\'s findplacefromtext (~$0.017 per call). On success, the place_id field above is auto-filled.', 'ai-eshop-optimizer');
        } else {
            echo '<span style="color:#92400e;">' . esc_html__('Disabled — set the global API key under Settings first.', 'ai-eshop-optimizer') . '</span>';
        }
        echo '</p>';
        echo '<div id="aieo-grv-lookup-status" style="font-size:13px;"></div>';
        echo '</td></tr>';

        echo '<tr><th>' . esc_html__('Per-location API key', 'ai-eshop-optimizer') . '</th><td>';
        echo '<input type="text" name="api_key" class="regular-text" autocomplete="off" placeholder="' . esc_attr__('Leave empty to use the global key', 'ai-eshop-optimizer') . '">';
        echo '<p class="description">' . esc_html__('Optional. Useful when you want this location\'s fetches billed to a different Google Cloud project.', 'ai-eshop-optimizer') . '</p>';
        echo '</td></tr>';

        echo '<tr><th>' . esc_html__('"Rate us" URL', 'ai-eshop-optimizer') . '</th><td>';
        echo '<input type="text" name="review_url" class="large-text" placeholder="https://g.page/r/…/review" style="font-family:monospace;">';
        echo '<p class="description">' . esc_html__('Optional. Paste the g.page/r/.../review shortlink Google issued for in-store QR codes. If empty, we auto-construct https://search.google.com/local/writereview?placeid=… from the place_id.', 'ai-eshop-optimizer') . '</p>';
        echo '</td></tr>';

        echo '<tr><th>' . esc_html__('Language', 'ai-eshop-optimizer') . '</th><td>';
        echo '<input type="text" name="language" class="small-text" maxlength="10" value="' . esc_attr($settings['language']) . '">';
        echo '</td></tr>';
        echo '<tr><th>' . esc_html__('Enabled', 'ai-eshop-optimizer') . '</th><td><label><input type="checkbox" name="enabled" value="1" checked> ' . esc_html__('Include in scheduled fetches', 'ai-eshop-optimizer') . '</label></td></tr>';

        echo '<tr><th>' . esc_html__('Sort order', 'ai-eshop-optimizer') . '</th><td>';
        echo '<input type="number" name="sort_order" class="small-text" value="0" min="0" step="1">';
        echo '<p class="description">' . esc_html__('Lower numbers appear first on the store locator (and in the reviews block / schema). Default 0.', 'ai-eshop-optimizer') . '</p></td></tr>';

        
        
        
        $pickup_names = $this->pickup_location_names();
        echo '<tr><th>' . esc_html__('Associated pickup location', 'ai-eshop-optimizer') . '</th><td>';
        echo '<select name="pickup_name"><option value="">' . esc_html__('— none —', 'ai-eshop-optimizer') . '</option>';
        foreach ($pickup_names as $pn) {
            echo '<option value="' . esc_attr($pn) . '">' . esc_html($pn) . '</option>';
        }
        echo '</select>';
        if (empty($pickup_names)) {
            echo ' <span style="color:#92400e;font-size:12px;">' . esc_html__('No pickup locations defined yet — add them in the Local Pickup tab first.', 'ai-eshop-optimizer') . '</span>';
        }
        echo '<p class="description">' . esc_html__('Link this Google location to a store pickup. The pickup then shows a "View on Google Maps" link at checkout and in the order email. Leave empty if this location has no pickup.', 'ai-eshop-optimizer') . '</p>';
        echo '</td></tr>';

        
        echo '<tr><th>' . esc_html__('Headquarters', 'ai-eshop-optimizer') . '</th><td><label><input type="checkbox" name="is_hq" value="1"> ' . esc_html__('This is the HQ / primary location — its address backs the Organization schema (only one location can be HQ).', 'ai-eshop-optimizer') . '</label></td></tr>';

        
        
        echo '<tr><th>' . esc_html__('Address &amp; contact', 'ai-eshop-optimizer') . '</th><td>';
        echo '<p class="description" style="margin-top:0;">' . esc_html__('Auto-filled from Google when you fetch this location — edit to override. Click "Fetch now" on a saved location to populate these.', 'ai-eshop-optimizer') . '</p>';
        echo '<input type="text" name="street" class="regular-text" placeholder="' . esc_attr__('Street address', 'ai-eshop-optimizer') . '" style="margin-bottom:4px;"><br>';
        echo '<input type="text" name="locality" placeholder="' . esc_attr__('City', 'ai-eshop-optimizer') . '"> ';
        echo '<input type="text" name="region" placeholder="' . esc_attr__('Region', 'ai-eshop-optimizer') . '"> ';
        echo '<input type="text" name="postcode" size="10" placeholder="' . esc_attr__('Postcode', 'ai-eshop-optimizer') . '"> ';
        echo '<input type="text" name="country" size="3" maxlength="2" placeholder="GR"><br>';
        echo '<input type="text" name="phone" placeholder="' . esc_attr__('Phone (+30…)', 'ai-eshop-optimizer') . '" style="margin-top:4px;"> ';
        echo '<input type="text" name="lat" size="12" placeholder="lat"> ';
        echo '<input type="text" name="lng" size="12" placeholder="lng">';
        echo '<div id="aieo-grv-hours-readout" style="margin-top:6px;font-size:12px;color:#646970;"></div>';
        echo '</td></tr>';

        echo '<tr><th>' . esc_html__('Schema extras', 'ai-eshop-optimizer') . '</th><td>';
        echo '<p class="description" style="margin-top:0;">' . esc_html__('Operator-set — Google\'s API does not return these.', 'ai-eshop-optimizer') . '</p>';
        echo '<label style="display:block;margin:4px 0;">' . esc_html__('Price range', 'ai-eshop-optimizer') . ' ';
        echo '<select name="price_range">';
        echo '<option value="">' . esc_html__('— not set —', 'ai-eshop-optimizer') . '</option>';
        foreach ( array(
            '€'    => esc_html__('€ — Budget', 'ai-eshop-optimizer'),
            '€€'   => esc_html__('€€ — Moderate', 'ai-eshop-optimizer'),
            '€€€'  => esc_html__('€€€ — Pricey', 'ai-eshop-optimizer'),
            '€€€€' => esc_html__('€€€€ — High-end', 'ai-eshop-optimizer'),
        ) as $pr_val => $pr_lbl ) {
            echo '<option value="' . esc_attr( $pr_val ) . '">' . $pr_lbl . '</option>';
        }
        echo '</select> <span class="description" style="font-weight:400;">' . esc_html__('Google-recognised price tier (schema.org priceRange).', 'ai-eshop-optimizer') . '</span></label>';
        echo '<label style="display:block;margin:4px 0;">' . esc_html__('Google Business Profile / Maps URL (sameAs)', 'ai-eshop-optimizer') . ' <input type="url" name="gbp_url" class="large-text" placeholder="' . esc_attr__('Leave blank to auto-derive from place_id', 'ai-eshop-optimizer') . '" style="font-family:monospace;"></label>';
        echo '<label style="display:block;margin:4px 0;">' . esc_html__('Storefront image', 'ai-eshop-optimizer') . ' ';
        echo '<input type="url" name="image_url" id="aieo-grv-image-url" class="large-text" placeholder="https://…" style="font-family:monospace;max-width:55%;"> ';
        echo '<button type="button" class="button" id="aieo-grv-image-pick">' . esc_html__('Select / upload image', 'ai-eshop-optimizer') . '</button>';
        echo '<span id="aieo-grv-image-preview" style="display:block;margin-top:6px;"></span></label>';
        echo '</td></tr>';

        
        
        echo '<tr><th>' . esc_html__('Shop-page hero', 'ai-eshop-optimizer') . '</th><td>';
        echo '<p class="description" style="margin-top:0;">' . esc_html__('Header for this location\'s store page (the aieo/store-hero block / "Shop page" pattern). Listing title plays top, heading left, description right — over the storefront image above.', 'ai-eshop-optimizer') . '</p>';
        echo '<label style="display:block;margin:4px 0;">' . esc_html__('Listing title (top)', 'ai-eshop-optimizer') . '<br><input type="text" name="listing_title" class="regular-text" maxlength="190" placeholder="' . esc_attr__('e.g. Our store in Glyfada', 'ai-eshop-optimizer') . '"></label>';
        echo '<label style="display:block;margin:4px 0;">' . esc_html__('Heading (left)', 'ai-eshop-optimizer') . '<br><input type="text" name="hero_heading" class="regular-text" maxlength="190" placeholder="' . esc_attr__('Leave blank to use the location label', 'ai-eshop-optimizer') . '"></label>';
        echo '<label style="display:block;margin:4px 0;">' . esc_html__('Description (right)', 'ai-eshop-optimizer') . '<br><textarea name="hero_description" class="large-text" rows="3" placeholder="' . esc_attr__('A short welcome / what makes this store special.', 'ai-eshop-optimizer') . '"></textarea></label>';
        echo '</td></tr>';
        echo '</table>';

        echo '<p>';
        echo '<button type="button" class="button button-primary" id="aieo-grv-save-loc">' . esc_html__('Save location', 'ai-eshop-optimizer') . '</button> ';
        echo '<button type="button" class="button" id="aieo-grv-cancel-loc">' . esc_html__('Reset form', 'ai-eshop-optimizer') . '</button>';
        echo ' <span id="aieo-grv-save-status" style="margin-left:10px;color:#646970;font-size:13px;"></span>';
        echo '</p>';
        echo '</form>';
        echo '</div>';

        
        
        $locations_json = wp_json_encode(array_map(function($l) {
            return array(
                'id'         => (int) $l['id'],
                'slug'       => (string) $l['slug'],
                'label'      => (string) $l['label'],
                'place_id'   => (string) $l['place_id'],
                'api_key'    => '',
                'language'   => (string) $l['language'],
                'enabled'    => (int) $l['enabled'],
                'review_url' => isset($l['review_url']) ? (string) $l['review_url'] : '',
                'pickup_name'=> $this->pickup_for_review_slug((string) $l['slug']),
                'is_hq'      => (int) ($l['is_hq'] ?? 0),
                'price_range'=> (string) ($l['price_range'] ?? ''),
                'gbp_url'    => (string) ($l['gbp_url'] ?? ''),
                'image_url'  => (string) ($l['image_url'] ?? ''),
                'street'     => (string) ($l['street'] ?? ''),
                'locality'   => (string) ($l['locality'] ?? ''),
                'region'     => (string) ($l['region'] ?? ''),
                'postcode'   => (string) ($l['postcode'] ?? ''),
                'country'    => (string) ($l['country'] ?? ''),
                'phone'      => (string) ($l['phone'] ?? ''),
                'lat'        => (isset($l['lat']) && $l['lat'] !== null) ? (string) $l['lat'] : '',
                'lng'        => (isset($l['lng']) && $l['lng'] !== null) ? (string) $l['lng'] : '',
                'hours_summary' => $this->hours_summary($l['hours_json'] ?? ''),
                'sort_order' => (int) ($l['sort_order'] ?? 0),
                'listing_title'    => (string) ($l['listing_title'] ?? ''),
                'hero_heading'     => (string) ($l['hero_heading'] ?? ''),
                'hero_description' => (string) ($l['hero_description'] ?? ''),
            );
        }, $locations));
        
        echo '<script>(function(){
            var locs   = ' . $locations_json . ';
            var nonce  = "' . esc_js($nonce) . '";
            var ajax   = "' . esc_js($ajax) . '";
            var form   = document.getElementById("aieo-grv-location-form");
            var save   = document.getElementById("aieo-grv-save-loc");
            var cancel = document.getElementById("aieo-grv-cancel-loc");
            var status = document.getElementById("aieo-grv-save-status");
            var title  = document.getElementById("aieo-grv-form-title");
            var actSt  = document.getElementById("aieo-grv-action-status");

            // Inline reorder — the Order column up/down arrows on the locations list.
            // Persists via the reorder endpoint, then moves the row + renumbers in place.
            (function(){
                var tb = document.getElementById("aieo-grv-loc-rows");
                if (!tb) { return; }
                function renumber(){
                    var rows = tb.querySelectorAll("tr[data-grv-row]");
                    for (var i=0;i<rows.length;i++){ var n=rows[i].querySelector("[data-grv-num]"); if(n){ n.textContent=(i+1); } }
                }
                tb.addEventListener("click", function(e){
                    var btn = e.target && e.target.closest ? e.target.closest("[data-aieo-grv-move]") : null;
                    if (!btn) { return; }
                    var dir = btn.getAttribute("data-aieo-grv-move");
                    var row = btn.closest("tr[data-grv-row]");
                    if (!row) { return; }
                    var fd = new FormData();
                    fd.append("action", "aieo_grv_reorder_location");
                    fd.append("nonce", nonce);
                    fd.append("id", btn.getAttribute("data-id"));
                    fd.append("dir", dir);
                    btn.disabled = true;
                    fetch(ajax, { method:"POST", body:fd, credentials:"same-origin" })
                        .then(function(r){ return r.json(); })
                        .then(function(j){
                            btn.disabled = false;
                            if (!j || !j.success) { return; }
                            if (dir === "up") { var p=row.previousElementSibling; if(p && p.hasAttribute("data-grv-row")){ tb.insertBefore(row, p); } }
                            else { var nx=row.nextElementSibling; if(nx && nx.hasAttribute("data-grv-row")){ tb.insertBefore(nx, row); } }
                            renumber();
                        })
                        .catch(function(){ btn.disabled = false; });
                });
            })();

            function fillForm(l) {
                form.querySelector("[name=id]").value       = l ? l.id : "";
                form.querySelector("[name=slug]").value     = l ? l.slug : "";
                form.querySelector("[name=label]").value    = l ? l.label : "";
                form.querySelector("[name=place_id]").value = l ? l.place_id : "";
                form.querySelector("[name=api_key]").value  = "";
                form.querySelector("[name=language]").value = l ? l.language : "' . esc_js($settings['language']) . '";
                form.querySelector("[name=review_url]").value = l ? (l.review_url || "") : "";
                form.querySelector("[name=pickup_name]").value = l ? (l.pickup_name || "") : "";
                form.querySelector("[name=enabled]").checked = l ? !!l.enabled : true;
                form.querySelector("[name=is_hq]").checked = l ? !!l.is_hq : false;
                form.querySelector("[name=price_range]").value = l ? (l.price_range || "") : "";
                form.querySelector("[name=gbp_url]").value = l ? (l.gbp_url || "") : "";
                form.querySelector("[name=image_url]").value = l ? (l.image_url || "") : "";
                (function(){ var _p=document.getElementById("aieo-grv-image-preview"), _u=l?(l.image_url||""):""; if(_p){ _p.innerHTML = _u ? ("<img src=\'"+_u+"\' style=\'max-height:70px;border:1px solid #e2e8f0;border-radius:4px;\'>") : ""; } })();
                ["street","locality","region","postcode","country","phone","lat","lng"].forEach(function(f){
                    var el = form.querySelector("[name="+f+"]"); if (el) { el.value = l ? (l[f] || "") : ""; }
                });
                (function(){ var so=form.querySelector("[name=sort_order]"); if(so){ so.value = l ? (l.sort_order || 0) : 0; } })();
                ["listing_title","hero_heading","hero_description"].forEach(function(f){
                    var el = form.querySelector("[name="+f+"]"); if (el) { el.value = l ? (l[f] || "") : ""; }
                });
                var hr = document.getElementById("aieo-grv-hours-readout"); if (hr) { hr.textContent = l ? (l.hours_summary || "") : ""; }
                title.textContent = l ? "' . esc_js(__('Edit location', 'ai-eshop-optimizer')) . '" : "' . esc_js(__('Add a location', 'ai-eshop-optimizer')) . '";
            }
            cancel.addEventListener("click", function(){ fillForm(null); });

            // Storefront image — media-library picker (fills the image_url field + preview).
            (function(){
                var pick=document.getElementById("aieo-grv-image-pick"), fld=document.getElementById("aieo-grv-image-url"), prev=document.getElementById("aieo-grv-image-preview"), frame;
                if(!pick){ return; }
                pick.addEventListener("click", function(e){
                    e.preventDefault();
                    if(!window.wp||!wp.media){ return; }
                    if(frame){ frame.open(); return; }
                    frame = wp.media({ title:"' . esc_js(__('Storefront image', 'ai-eshop-optimizer')) . '", multiple:false, library:{ type:"image" } });
                    frame.on("select", function(){
                        var a=frame.state().get("selection").first().toJSON();
                        if(fld){ fld.value=a.url; }
                        if(prev){ prev.innerHTML="<img src=\'"+a.url+"\' style=\'max-height:70px;border:1px solid #e2e8f0;border-radius:4px;\'>"; }
                    });
                    frame.open();
                });
            })();

            document.querySelectorAll("[data-aieo-grv-edit]").forEach(function(b){
                b.addEventListener("click", function(){
                    var id = parseInt(b.getAttribute("data-aieo-grv-edit"), 10);
                    var l  = locs.filter(function(x){ return x.id === id; })[0];
                    if (l) { fillForm(l); document.getElementById("aieo-grv-loc-form-card").scrollIntoView({behavior:"smooth"}); }
                });
            });
            document.querySelectorAll("[data-aieo-grv-delete]").forEach(function(b){
                b.addEventListener("click", function(){
                    if (!confirm("' . esc_js(__('Delete this location? Its review history stays in the table.', 'ai-eshop-optimizer')) . '")) return;
                    var fd = new FormData();
                    fd.set("action","aieo_grv_delete_location"); fd.set("nonce",nonce);
                    fd.set("id", b.getAttribute("data-aieo-grv-delete"));
                    fetch(ajax,{method:"POST",credentials:"same-origin",body:fd}).then(function(){ location.reload(); });
                });
            });

            save.addEventListener("click", function(){
                var fd = new FormData(form);
                fd.set("action","aieo_grv_save_location"); fd.set("nonce",nonce);
                if (!fd.has("enabled")) fd.set("enabled","");
                status.style.color = "#646970";
                status.textContent = "' . esc_js(__('Saving…', 'ai-eshop-optimizer')) . '";
                fetch(ajax,{method:"POST",credentials:"same-origin",body:fd})
                    .then(function(r){return r.json();})
                    .then(function(j){
                        if (j && j.success) { status.style.color = "#15803d"; status.textContent = "' . esc_js(__('Saved.', 'ai-eshop-optimizer')) . '"; setTimeout(function(){ location.reload(); }, 600); }
                        else { status.style.color = "#b91c1c"; status.textContent = (j && j.data && j.data.message) || "' . esc_js(__('Save failed.', 'ai-eshop-optimizer')) . '"; }
                    });
            });

            var lookupBtn = document.getElementById("aieo-grv-lookup-btn");
            var lookupQ   = document.getElementById("aieo-grv-lookup-query");
            var lookupSt  = document.getElementById("aieo-grv-lookup-status");
            if (lookupBtn) {
                lookupBtn.addEventListener("click", function(){
                    var q = (lookupQ.value || "").trim();
                    if (!q) { lookupSt.style.color = "#b91c1c"; lookupSt.textContent = "' . esc_js(__('Enter a name or URL.', 'ai-eshop-optimizer')) . '"; return; }
                    lookupSt.style.color = "#646970";
                    lookupSt.textContent = "' . esc_js(__('Looking up…', 'ai-eshop-optimizer')) . '";
                    var fd = new FormData();
                    fd.set("action","aieo_grv_lookup_place"); fd.set("nonce",nonce); fd.set("query", q);
                    fd.set("language", form.querySelector("[name=language]").value || "el");
                    fetch(ajax,{method:"POST",credentials:"same-origin",body:fd})
                        .then(function(r){return r.json();})
                        .then(function(j){
                            if (j && j.success && j.data && j.data.place_id) {
                                form.querySelector("[name=place_id]").value = j.data.place_id;
                                if (!form.querySelector("[name=label]").value && j.data.name) {
                                    form.querySelector("[name=label]").value = j.data.name;
                                }
                                lookupSt.style.color = "#15803d";
                                lookupSt.innerHTML = "' . esc_js(__('Resolved:', 'ai-eshop-optimizer')) . ' <strong>" + (j.data.name || "") + "</strong>" + (j.data.address ? " — <small>" + j.data.address + "</small>" : "");
                            } else {
                                lookupSt.style.color = "#b91c1c";
                                lookupSt.textContent = (j && j.data && j.data.message) || "' . esc_js(__('Lookup failed.', 'ai-eshop-optimizer')) . '";
                            }
                        });
                });
            }

            var fetchAll = document.getElementById("aieo-grv-fetch-all");
            if (fetchAll) {
                fetchAll.addEventListener("click", function(){
                    if (!confirm("' . esc_js(__('Fetch from Google for every enabled location? This consumes one paid API call per location.', 'ai-eshop-optimizer')) . '")) return;
                    fetchAll.disabled = true;
                    actSt.style.color = "#646970";
                    actSt.textContent = "' . esc_js(__('Fetching all enabled locations…', 'ai-eshop-optimizer')) . '";
                    var fd = new FormData();
                    fd.set("action","aieo_grv_fetch_all"); fd.set("nonce",nonce);
                    fetch(ajax,{method:"POST",credentials:"same-origin",body:fd})
                        .then(function(r){return r.json();}).then(function(j){
                            fetchAll.disabled = false;
                            if (j && j.success) {
                                var d = j.data || {};
                                actSt.style.color = "#15803d";
                                actSt.innerHTML = "<strong>" + (d.locations|0) + "</strong> ' . esc_js(__('locations processed', 'ai-eshop-optimizer')) . ' · " +
                                    "<strong>" + (d.fetched|0) + "</strong> ' . esc_js(__('reviews fetched', 'ai-eshop-optimizer')) . ' · " +
                                    "<strong style=\"color:#15803d\">" + (d.inserted|0) + "</strong> ' . esc_js(__('new', 'ai-eshop-optimizer')) . ' · " +
                                    "<strong style=\"color:#b91c1c\">" + (d.errors|0) + "</strong> ' . esc_js(__('errors', 'ai-eshop-optimizer')) . '";
                                if (d.error_messages && d.error_messages.length) {
                                    actSt.innerHTML += "<br><small>" + d.error_messages.slice(0,5).join("<br>") + "</small>";
                                }
                                setTimeout(function(){ location.reload(); }, 2500);
                            } else {
                                actSt.style.color = "#b91c1c";
                                actSt.textContent = (j && j.data && j.data.message) || "' . esc_js(__('Fetch all failed.', 'ai-eshop-optimizer')) . '";
                            }
                        });
                });
            }

            document.querySelectorAll("[data-aieo-grv-fetch]").forEach(function(b){
                b.addEventListener("click", function(){
                    actSt.style.color = "#646970";
                    actSt.textContent = "' . esc_js(__('Fetching from Google…', 'ai-eshop-optimizer')) . '";
                    b.disabled = true;
                    var fd = new FormData();
                    fd.set("action","aieo_grv_fetch_now"); fd.set("nonce",nonce);
                    fd.set("id", b.getAttribute("data-aieo-grv-fetch"));
                    fetch(ajax,{method:"POST",credentials:"same-origin",body:fd})
                        .then(function(r){return r.json();}).then(function(j){
                            b.disabled = false;
                            if (j && j.success) {
                                var d = j.data || {};
                                actSt.style.color = "#15803d";
                                actSt.innerHTML = "<strong>" + (d.label || "") + "</strong>: " +
                                    (d.fetched|0) + " ' . esc_js(__('reviews fetched', 'ai-eshop-optimizer')) . ', ' .
                                    '<strong>" + (d.inserted|0) + "</strong> ' . esc_js(__('new', 'ai-eshop-optimizer')) . ' " +
                                    (d.rating ? "(" + d.rating + "★ × " + (d.review_count || 0) + ")" : "");
                                setTimeout(function(){ location.reload(); }, 1500);
                            } else {
                                actSt.style.color = "#b91c1c";
                                actSt.textContent = (j && j.data && j.data.message) || "' . esc_js(__('Fetch failed.', 'ai-eshop-optimizer')) . '";
                            }
                        });
                });
            });
        })();</script>';
        
    }

    private function render_history() {
        global $wpdb;
        $tbl_rev = $wpdb->prefix . 'aieo_google_reviews';
        $tbl_loc = $wpdb->prefix . 'aieo_google_locations';

        
        $f_location  = isset($_GET['grv_location']) ? (int) sanitize_text_field( wp_unslash( $_GET['grv_location'] ) ) : 0;
        $f_rating    = isset($_GET['grv_rating'])   ? (int) sanitize_text_field( wp_unslash( $_GET['grv_rating'] ) )   : 0;
        $f_date_from = isset($_GET['grv_from'])     ? sanitize_text_field((string) wp_unslash( $_GET['grv_from'] )) : '';
        $f_date_to   = isset($_GET['grv_to'])       ? sanitize_text_field((string) wp_unslash( $_GET['grv_to'] ))   : '';
        $f_match     = isset($_GET['grv_match'])    ? sanitize_key((string) wp_unslash( $_GET['grv_match'] )) : '';
        $f_visible   = isset($_GET['grv_visible'])  ? sanitize_key((string) $_GET['grv_visible']) : '';
        $f_search    = isset($_GET['grv_q'])        ? sanitize_text_field((string) wp_unslash( $_GET['grv_q'] )) : '';

        
        $orderby_allowed = array('id', 'location', 'rating', 'review_time', 'author_name', 'seen_at');
        $orderby = isset($_GET['orderby']) && in_array(sanitize_text_field( wp_unslash( $_GET['orderby'] ) ), $orderby_allowed, true) ? sanitize_text_field( wp_unslash( $_GET['orderby'] ) ) : 'review_time';
        $order   = isset($_GET['order']) && strtoupper(sanitize_text_field( wp_unslash( $_GET['order'] ) )) === 'ASC' ? 'ASC' : 'DESC';
        $per     = 50;
        $page    = max(1, isset($_GET['paged']) ? (int) $_GET['paged'] : 1);
        $offset  = ($page - 1) * $per;

        
        $where  = array('1=1');
        $params = array();
        if ($f_location > 0) { $where[] = 'r.location_id = %d'; $params[] = $f_location; }
        if ($f_rating > 0)   { $where[] = 'r.rating = %d';      $params[] = $f_rating; }
        if ($f_date_from !== '') {
            $ts = strtotime($f_date_from . ' 00:00:00');
            if ($ts > 0) { $where[] = 'r.review_time >= %d'; $params[] = $ts; }
        }
        if ($f_date_to !== '') {
            $ts = strtotime($f_date_to . ' 23:59:59');
            if ($ts > 0) { $where[] = 'r.review_time <= %d'; $params[] = $ts; }
        }
        if ($f_match === 'matched')   { $where[] = 'r.matched_user_id IS NOT NULL'; }
        if ($f_match === 'unmatched') { $where[] = 'r.matched_user_id IS NULL'; }
        if ($f_visible === 'visible')   { $where[] = 'r.is_visible = 1'; }
        if ($f_visible === 'hidden')    { $where[] = 'r.is_visible = 0'; }
        if ($f_search !== '') {
            $like = '%' . $wpdb->esc_like($f_search) . '%';
            $where[] = '(r.author_name LIKE %s OR r.review_text LIKE %s)';
            $params[] = $like; $params[] = $like;
        }

        $where_sql = implode(' AND ', $where);

        
        $order_col = 'r.review_time';
        if ($orderby === 'id')           { $order_col = 'r.id'; }
        elseif ($orderby === 'location') { $order_col = 'l.label'; }
        elseif ($orderby === 'rating')   { $order_col = 'r.rating'; }
        elseif ($orderby === 'author_name') { $order_col = 'r.author_name'; }
        elseif ($orderby === 'seen_at')  { $order_col = 'r.seen_at'; }

        
        
        $count_sql = "SELECT COUNT(*) FROM {$tbl_rev} r LEFT JOIN {$tbl_loc} l ON l.id = r.location_id WHERE {$where_sql}";
        $total = (int) (empty($params)
            ? $wpdb->get_var($count_sql)
            : $wpdb->get_var($wpdb->prepare($count_sql, $params)));

        $list_sql = "SELECT r.*, l.slug AS location_slug, l.label AS location_label
                     FROM {$tbl_rev} r LEFT JOIN {$tbl_loc} l ON l.id = r.location_id
                     WHERE {$where_sql}
                     ORDER BY {$order_col} {$order}, r.id DESC
                     LIMIT %d OFFSET %d";
        $list_params = array_merge($params, array($per, $offset));
        $rows = $wpdb->get_results($wpdb->prepare($list_sql, $list_params), ARRAY_A);
        

        
        $base_url = admin_url('admin.php?page=' . AIEO_DMM::MENU_SLUG . '&dmm_tab=google_reviews&grv_section=history');
        $locations = AIEO_Google_Reviews_Locations::list_all();

        echo '<form method="get" action="' . esc_url(admin_url('admin.php')) . '" style="margin-bottom:10px;display:flex;flex-wrap:wrap;gap:8px;align-items:end;">';
        echo '<input type="hidden" name="page" value="' . esc_attr(AIEO_DMM::MENU_SLUG) . '">';
        echo '<input type="hidden" name="dmm_tab" value="google_reviews">';
        echo '<input type="hidden" name="grv_section" value="history">';

        echo '<label><span style="display:block;font-size:11px;color:#646970;">' . esc_html__('Location', 'ai-eshop-optimizer') . '</span><select name="grv_location"><option value="0">' . esc_html__('Any', 'ai-eshop-optimizer') . '</option>';
        foreach ($locations as $l) {
            echo '<option value="' . (int) $l['id'] . '"' . selected($f_location, (int) $l['id'], false) . '>' . esc_html($l['label']) . '</option>';
        }
        echo '</select></label>';

        echo '<label><span style="display:block;font-size:11px;color:#646970;">' . esc_html__('Rating', 'ai-eshop-optimizer') . '</span><select name="grv_rating">';
        echo '<option value="0">' . esc_html__('Any', 'ai-eshop-optimizer') . '</option>';
        for ($i = 5; $i >= 1; $i--) {
            echo '<option value="' . $i . '"' . selected($f_rating, $i, false) . '>' . str_repeat('★', $i) . '</option>'; 
        } 
        echo '</select></label>';

        echo '<label><span style="display:block;font-size:11px;color:#646970;">' . esc_html__('From', 'ai-eshop-optimizer') . '</span><input type="date" name="grv_from" value="' . esc_attr($f_date_from) . '"></label>';
        echo '<label><span style="display:block;font-size:11px;color:#646970;">' . esc_html__('To', 'ai-eshop-optimizer') . '</span><input type="date" name="grv_to" value="' . esc_attr($f_date_to) . '"></label>';

        echo '<label><span style="display:block;font-size:11px;color:#646970;">' . esc_html__('Customer match', 'ai-eshop-optimizer') . '</span><select name="grv_match">';
        echo '<option value=""'         . selected($f_match, '', false)         . '>' . esc_html__('Any', 'ai-eshop-optimizer')          . '</option>';
        echo '<option value="matched"'  . selected($f_match, 'matched', false)  . '>' . esc_html__('Matched only', 'ai-eshop-optimizer') . '</option>';
        echo '<option value="unmatched"'. selected($f_match, 'unmatched', false). '>' . esc_html__('Unmatched only', 'ai-eshop-optimizer'). '</option>';
        echo '</select></label>';

        echo '<label><span style="display:block;font-size:11px;color:#646970;">' . esc_html__('Visibility', 'ai-eshop-optimizer') . '</span><select name="grv_visible">';
        echo '<option value=""'        . selected($f_visible, '', false)        . '>' . esc_html__('All', 'ai-eshop-optimizer')         . '</option>';
        echo '<option value="visible"' . selected($f_visible, 'visible', false) . '>' . esc_html__('Visible only', 'ai-eshop-optimizer'). '</option>';
        echo '<option value="hidden"'  . selected($f_visible, 'hidden', false)  . '>' . esc_html__('Hidden only', 'ai-eshop-optimizer') . '</option>';
        echo '</select></label>';

        echo '<label><span style="display:block;font-size:11px;color:#646970;">' . esc_html__('Search author/text', 'ai-eshop-optimizer') . '</span><input type="search" name="grv_q" value="' . esc_attr($f_search) . '" placeholder="' . esc_attr__('name or word…', 'ai-eshop-optimizer') . '"></label>';

        echo '<button type="submit" class="button button-primary">' . esc_html__('Filter', 'ai-eshop-optimizer') . '</button>';
        echo '<a href="' . esc_url($base_url) . '" class="button">' . esc_html__('Reset', 'ai-eshop-optimizer') . '</a>';
        echo '</form>';

        echo '<p class="description">' . esc_html(sprintf(
            
            __('Showing %1$d–%2$d of %3$d', 'ai-eshop-optimizer'),
            $total === 0 ? 0 : $offset + 1,
            min($offset + $per, $total),
            $total
        )) . '</p>';

        
        $sort_link = function ($col, $label) use ($base_url, $orderby, $order, $f_location, $f_rating, $f_date_from, $f_date_to, $f_match, $f_visible, $f_search) {
            $next = ($orderby === $col && $order === 'ASC') ? 'DESC' : 'ASC';
            $arrow = $orderby === $col ? ($order === 'ASC' ? ' ▲' : ' ▼') : '';
            $url = add_query_arg(array(
                'orderby'      => $col,
                'order'        => $next,
                'grv_location' => $f_location ?: false,
                'grv_rating'   => $f_rating   ?: false,
                'grv_from'     => $f_date_from ?: false,
                'grv_to'       => $f_date_to   ?: false,
                'grv_match'    => $f_match    ?: false,
                'grv_visible'  => $f_visible  ?: false,
                'grv_q'        => $f_search   ?: false,
            ), $base_url);
            return '<a href="' . esc_url($url) . '">' . esc_html($label) . $arrow . '</a>';
        };

        
        echo '<table class="wp-list-table widefat fixed striped">';
        echo '<thead><tr>';
        echo '<th style="width:60px;">' . $sort_link('id', 'ID') . '</th>'; 
        echo '<th style="width:140px;">' . $sort_link('location', __('Location', 'ai-eshop-optimizer')) . '</th>'; 
        echo '<th style="width:80px;">' . $sort_link('rating', __('Rating', 'ai-eshop-optimizer')) . '</th>'; 
        echo '<th style="width:160px;">' . $sort_link('author_name', __('Author', 'ai-eshop-optimizer')) . '</th>'; 
        echo '<th style="width:120px;">' . $sort_link('review_time', __('Review date', 'ai-eshop-optimizer')) . '</th>'; 
        echo '<th>' . esc_html__('Text', 'ai-eshop-optimizer') . '</th>'; 
        echo '<th style="width:160px;">' . esc_html__('Customer match', 'ai-eshop-optimizer') . '</th>';
        echo '<th style="width:60px;">' . esc_html__('Vis', 'ai-eshop-optimizer') . '</th>';
        echo '<th style="width:120px;">' . $sort_link('seen_at', __('Seen at', 'ai-eshop-optimizer')) . '</th>'; 
        echo '</tr></thead><tbody>'; 

        if (empty($rows)) {
            echo '<tr><td colspan="9"><em>' . esc_html__('No reviews match the filters.', 'ai-eshop-optimizer') . '</em></td></tr>';
        } else {
            foreach ($rows as $r) {
                $review_date = $r['review_time'] ? date_i18n('Y-m-d', (int) $r['review_time']) : '—';
                $seen_at_fmt = !empty($r['seen_at']) ? esc_html((string) $r['seen_at']) . ' UTC' : '—';
                $author_html = !empty($r['author_url'])
                    ? '<a href="' . esc_url($r['author_url']) . '" target="_blank" rel="noopener noreferrer">' . esc_html($r['author_name']) . '</a>'
                    : esc_html((string) $r['author_name']);

                $match_html = '—';
                if (!empty($r['matched_user_id'])) {
                    $u = get_user_by('id', (int) $r['matched_user_id']);
                    $disp = $u ? trim((string) $u->display_name) : ('user#' . (int) $r['matched_user_id']);
                    $email = $u ? $u->user_email : '';
                    $edit  = admin_url('user-edit.php?user_id=' . (int) $r['matched_user_id']);
                    $method = (string) ($r['match_method'] ?? '');
                    $score  = isset($r['match_score']) ? number_format_i18n((float) $r['match_score'], 2) : '';
                    $match_html  = '<a href="' . esc_url($edit) . '"><strong>' . esc_html($disp) . '</strong></a>';
                    if ($email !== '') { $match_html .= '<br><small style="color:#6b7280;">' . esc_html($email) . '</small>'; }
                    $match_html .= '<br><small style="color:' . ($method === 'manual' ? '#0f766e' : '#475569') . ';">' . esc_html($method);
                    if ($score !== '') { $match_html .= ' (' . esc_html($score) . ')'; }
                    $match_html .= '</small>';
                }

                $text = (string) ($r['review_text'] ?? '');
                $text_short = mb_strlen($text) > 200 ? mb_substr($text, 0, 200) . '…' : $text;
                $stars = str_repeat('★', (int) $r['rating']) . str_repeat('☆', 5 - (int) $r['rating']);

                echo '<tr>';
                echo '<td>' . (int) $r['id'] . '</td>';
                echo '<td><strong>' . esc_html((string) $r['location_label']) . '</strong><br><code style="font-size:11px;">' . esc_html((string) $r['location_slug']) . '</code></td>';
                echo '<td style="color:' . ((int) $r['rating'] >= 4 ? '#15803d' : ((int) $r['rating'] === 1 ? '#b91c1c' : '#92400e')) . ';font-weight:600;">' . esc_html($stars) . ' <small>' . (int) $r['rating'] . '</small></td>';
                echo '<td>' . $author_html; 
                if (!empty($r['profile_photo_url'])) { 
                    echo '<br><small><img src="' . esc_url($r['profile_photo_url']) . '" alt="" style="width:24px;height:24px;border-radius:50%;vertical-align:middle;" loading="lazy" referrerpolicy="no-referrer"> ';
                    if (!empty($r['language'])) { echo '<span style="color:#6b7280;">' . esc_html((string) $r['language']) . '</span>'; }
                    echo '</small>';
                }
                echo '</td>';
                echo '<td>' . esc_html($review_date) . '<br><small style="color:#6b7280;">' . esc_html((string) ($r['relative_time'] ?? '')) . '</small></td>';
                echo '<td><div style="max-height:80px;overflow:auto;white-space:pre-line;">' . esc_html($text_short) . '</div></td>';
                echo '<td>' . $match_html . '</td>'; 
                echo '<td style="text-align:center;">' . ((int) $r['is_visible'] ? '✓' : '—') . '</td>'; 
                echo '<td>' . $seen_at_fmt . '</td>'; 
                echo '</tr>'; 
            }
        }
        echo '</tbody></table>';

        
        $total_pages = (int) ceil($total / $per);
        if ($total_pages > 1) {
            $pagi_args = array(
                'grv_location' => $f_location ?: false,
                'grv_rating'   => $f_rating   ?: false,
                'grv_from'     => $f_date_from ?: false,
                'grv_to'       => $f_date_to   ?: false,
                'grv_match'    => $f_match    ?: false,
                'grv_visible'  => $f_visible  ?: false,
                'grv_q'        => $f_search   ?: false,
                'orderby'      => $orderby,
                'order'        => $order,
            );
            echo '<div class="tablenav"><div class="tablenav-pages" style="margin-top:8px;">';
            echo '<span class="displaying-num">' . esc_html(sprintf(
                
                _n('%d item', '%d items', $total, 'ai-eshop-optimizer'),
                $total
            )) . '</span> ';
            for ($p = 1; $p <= min(20, $total_pages); $p++) {
                $url = add_query_arg(array_merge($pagi_args, array('paged' => $p)), $base_url);
                if ($p === $page) {
                    echo '<span class="page-numbers current" style="margin:0 4px;padding:4px 8px;background:#2271b1;color:#fff;border-radius:3px;">' . $p . '</span>'; 
                } else { 
                    echo '<a class="page-numbers" href="' . esc_url($url) . '" style="margin:0 4px;padding:4px 8px;border:1px solid #c3c4c7;border-radius:3px;">' . $p . '</a>'; 
                } 
            }
            echo '</div></div>';
        }
    }

    private function render_settings() {
        $s = AIEO_Google_Reviews_Settings::get_all();
        $nonce = wp_create_nonce('aieo_grv_settings');

        echo '<div class="aieo-dmm-card">';
        echo '<form id="aieo-grv-settings-form" class="aieo-dmm-form">';
        echo '<table class="form-table">';

        echo '<tr><th>' . esc_html__('Global API key', 'ai-eshop-optimizer') . '</th><td>';
        echo '<input type="text" name="global_api_key" value="' . esc_attr($s['global_api_key']) . '" class="large-text" autocomplete="off" placeholder="AIza...">';
        echo '<p class="description">' . esc_html__('Google Places API key used as fallback when a location row has no key of its own. Per-location keys (set in phase 2) override this so you can shard quota across multiple Google Cloud projects.', 'ai-eshop-optimizer') . '</p>';
        echo '</td></tr>';

        echo '<tr><th>' . esc_html__('Default language', 'ai-eshop-optimizer') . '</th><td>';
        echo '<input type="text" name="language" value="' . esc_attr($s['language']) . '" class="small-text" maxlength="10">';
        echo '<p class="description">' . esc_html__('ISO code (e.g. el, en). Per-location override available in phase 2.', 'ai-eshop-optimizer') . '</p>';
        echo '</td></tr>';

        echo '<tr><th>' . esc_html__('Refresh schedule', 'ai-eshop-optimizer') . '</th><td>';
        echo '<select name="schedule">';
        foreach (array(
            'twice_daily' => __('Twice daily',   'ai-eshop-optimizer'),
            'daily'       => __('Once a day',    'ai-eshop-optimizer'),
            'weekly'      => __('Once a week',   'ai-eshop-optimizer'),
            'monthly'     => __('Once a month',  'ai-eshop-optimizer'),
        ) as $val => $label) {
            echo '<option value="' . esc_attr($val) . '" ' . selected($s['schedule'], $val, false) . '>' . esc_html($label) . '</option>';
        }
        echo '</select>';
        echo '<p class="description">' . esc_html__('How often the cron pulls fresh reviews from Google. Each fetch costs one Places Details API call per location.', 'ai-eshop-optimizer') . '</p>';
        echo '</td></tr>';

        echo '<tr><th>' . esc_html__('Run time', 'ai-eshop-optimizer') . '</th><td>';
        echo '<input type="time" name="run_time" value="' . esc_attr($s['run_time']) . '">';
        $tz = function_exists('wp_timezone_string') ? wp_timezone_string() : (string) get_option('timezone_string');
        echo '<p class="description">' . esc_html(sprintf( __('Time of day (in site timezone: %s) when the cron runs. For "twice daily" the second run happens 12 hours later.', 'ai-eshop-optimizer'), $tz ?: 'UTC')) . '</p>';
        echo '</td></tr>';

        echo '<tr><th>' . esc_html__('Rate limit / hour', 'ai-eshop-optimizer') . '</th><td>';
        echo '<input type="number" name="rate_limit_per_hour" value="' . esc_attr($s['rate_limit_per_hour']) . '" min="1" max="10000" class="small-text">';
        echo '<p class="description">' . esc_html__('Soft cap on Google API calls per hour, shared across locations. Prevents runaway billing if cron schedules misfire.', 'ai-eshop-optimizer') . '</p>';
        echo '</td></tr>';

        echo '<tr><th>' . esc_html__('Rate limit / day', 'ai-eshop-optimizer') . '</th><td>';
        echo '<input type="number" name="rate_limit_per_day" value="' . esc_attr($s['rate_limit_per_day']) . '" min="1" max="100000" class="small-text">';
        echo '</td></tr>';

        echo '<tr><th>' . esc_html__('Object-cache block render', 'ai-eshop-optimizer') . '</th><td>';
        echo '<label><input type="checkbox" name="block_cache_enabled" value="1" ' . checked(!empty($s['block_cache_enabled']), true, false) . '> ' . esc_html__('Cache rendered review blocks via wp_cache (Redis/APCu when present).', 'ai-eshop-optimizer') . '</label>';
        echo '<p class="description">' . esc_html__('Cache key is bound to a per-location version counter that the cron bumps after each successful fetch — so cached HTML is invalidated the moment new data lands, without iterating cache entries.', 'ai-eshop-optimizer') . '</p>';
        echo '</td></tr>';

        echo '<tr><th>' . esc_html__('Customer-match threshold', 'ai-eshop-optimizer') . '</th><td>';
        echo '<input type="number" name="match_customer_min_score" value="' . esc_attr($s['match_customer_min_score']) . '" min="0" max="1" step="0.05" class="small-text">';
        echo '<p class="description">' . esc_html__('Fuzzy similarity threshold (0.00–1.00) for auto-linking a Google reviewer name to a WC customer. Manual links from the Review History tab override auto-matches.', 'ai-eshop-optimizer') . '</p>';
        echo '</td></tr>';

        echo '</table>';
        echo '<p>';
        echo '<button type="button" class="button button-primary" id="aieo-grv-save-settings">' . esc_html__('Save settings', 'ai-eshop-optimizer') . '</button>';
        echo ' <span id="aieo-grv-status" style="margin-left:10px;color:#646970;font-size:13px;"></span>';
        echo '</p>';
        echo '</form>';
        echo '</div>';

        $ajax = admin_url('admin-ajax.php');
        echo '<script>(function(){
            var btn = document.getElementById("aieo-grv-save-settings");
            var form = document.getElementById("aieo-grv-settings-form");
            var status = document.getElementById("aieo-grv-status");
            btn.addEventListener("click", function(){
                var fd = new FormData(form);
                fd.set("action", "aieo_grv_save_settings");
                fd.set("nonce", "' . esc_js($nonce) . '");
                if (!fd.has("block_cache_enabled")) { fd.set("block_cache_enabled", ""); }
                status.style.color = "#646970";
                status.textContent = "' . esc_js(__('Saving…', 'ai-eshop-optimizer')) . '";
                fetch("' . esc_js($ajax) . '", {method:"POST", credentials:"same-origin", body:fd})
                    .then(function(r){return r.json();})
                    .then(function(j){
                        if (j && j.success) {
                            status.style.color = "#15803d";
                            status.textContent = "' . esc_js(__('Saved.', 'ai-eshop-optimizer')) . '";
                        } else {
                            status.style.color = "#b91c1c";
                            status.textContent = (j && j.data && j.data.message) || "' . esc_js(__('Save failed.', 'ai-eshop-optimizer')) . '";
                        }
                    })
                    .catch(function(){ status.style.color = "#b91c1c"; status.textContent = "' . esc_js(__('Network error.', 'ai-eshop-optimizer')) . '"; });
            });
        })();</script>';

        
        
        
        echo '<div class="aieo-dmm-card" style="margin-top:18px;background:#f8fafc;border-left:3px solid #2271b1;">';
        echo '<h3 style="margin-top:0;">' . esc_html__('How to set up a location', 'ai-eshop-optimizer') . '</h3>';
        echo '<p>' . esc_html__('Each location needs two distinct things from Google. Do not confuse them — they look nothing alike:', 'ai-eshop-optimizer') . '</p>';

        echo '<ol style="margin-left:18px;line-height:1.7;">';
        echo '<li><strong>' . esc_html__('A Google Places API key', 'ai-eshop-optimizer') . '</strong> ';
        echo esc_html__('(format:', 'ai-eshop-optimizer') . ' <code>AIzaSy…</code>' . esc_html__(') — set once above. Optionally override per-location to shard quota across multiple Google Cloud projects.', 'ai-eshop-optimizer') . '<br>';
        echo '<small>' . esc_html__('Get one:', 'ai-eshop-optimizer') . ' ';
        echo '<a href="https://console.cloud.google.com/apis/credentials" target="_blank" rel="noopener">console.cloud.google.com → APIs &amp; Services → Credentials</a>. ';
        echo esc_html__('Then enable the "Places API" library on the same project. Restrict the key by HTTP referrer or by your server\'s IP.', 'ai-eshop-optimizer');
        echo '</small></li>';

        echo '<li><strong>' . esc_html__('A place_id', 'ai-eshop-optimizer') . '</strong> ';
        echo esc_html__('(format:', 'ai-eshop-optimizer') . ' <code>ChIJ…</code>' . esc_html__(') — one per location. This is Google\'s identifier for the physical place.', 'ai-eshop-optimizer') . '<br>';
        echo '<small>' . esc_html__('Quickest way to get one: open the', 'ai-eshop-optimizer') . ' ';
        echo '<a href="https://developers.google.com/maps/documentation/places/web-service/place-id" target="_blank" rel="noopener">' . esc_html__('Place ID Finder', 'ai-eshop-optimizer') . '</a>, ';
        echo esc_html__('search your business name, click the pin, copy the', 'ai-eshop-optimizer') . ' <code>ChIJ…</code> ' . esc_html__('value. Or, on the Locations tab, paste a business name or Google Maps URL into the lookup helper and we\'ll resolve the place_id via the API.', 'ai-eshop-optimizer');
        echo '</small></li>';
        echo '</ol>';

        echo '<p style="margin-top:14px;"><strong>' . esc_html__('Important: about the "5 reviews max" cap', 'ai-eshop-optimizer') . '</strong><br>';
        echo esc_html__('Google\'s Places Details API returns at most 5 reviews per call — there is no pagination, no way to get the full archive in one shot. We diff each fetch against the local table and INSERT only unseen reviews, so over weeks of polling, our archive grows past Google\'s 5-row cap. The first fetch of any location will always be 5; that is a Google constraint, not ours.', 'ai-eshop-optimizer') . '</p>';

        echo '<p style="margin-top:14px;"><strong>' . esc_html__('Note about review shortlinks (g.page/r/…):', 'ai-eshop-optimizer') . '</strong> ';
        echo esc_html__('Those are public review-request URLs. They contain an internal Google "FID" that is NOT a place_id and cannot be pasted into the place_id field directly. Use the Place ID Finder or the Lookup helper on the Locations tab.', 'ai-eshop-optimizer') . '</p>';

        
        
        
        echo '<hr style="margin:18px 0;border:none;border-top:1px solid #d1d5db;">';
        echo '<h3 style="margin-top:0;">' . esc_html__('How to display the reviews on the storefront', 'ai-eshop-optimizer') . '</h3>';

        echo '<p>' . esc_html__('Three rendering surfaces are available — all share the same render code, the same cache, and the same CSS, so output is identical across themes:', 'ai-eshop-optimizer') . '</p>';

        echo '<h4 style="margin:14px 0 6px;">' . esc_html__('1. FSE / block themes (RoosterX, Twenty Twenty-Five, etc.)', 'ai-eshop-optimizer') . '</h4>';
        echo '<p style="margin:0 0 6px;">' . esc_html__('Open the page or template in the Site Editor → Block Inserter → search', 'ai-eshop-optimizer') . ' <strong>"AIEO Google Reviews"</strong>. ';
        echo esc_html__('Inserts the', 'ai-eshop-optimizer') . ' <code>aieo/google-reviews</code> ' . esc_html__('block. Pick Single / Mixed / Cards Grid mode + per-location slug in the InspectorControls sidebar.', 'ai-eshop-optimizer') . '</p>';
        echo '<p style="margin:0;color:#646970;font-size:12px;">' . esc_html__('Bonus: the block is also exposed by the runtime plugin from version 5.7, so it renders correctly on storefront pages even when the admin plugin isn\'t in the request\'s plugin whitelist (runtime-only contexts).', 'ai-eshop-optimizer') . '</p>';

        echo '<h4 style="margin:14px 0 6px;">' . esc_html__('2. Classic themes / page builders / sidebars / widget areas', 'ai-eshop-optimizer') . '</h4>';
        echo '<p style="margin:0 0 8px;">' . esc_html__('Use one of these shortcodes anywhere shortcodes are accepted (post content, widgets, Elementor / Bricks / Divi text widgets, theme template overrides via', 'ai-eshop-optimizer') . ' <code>do_shortcode()</code>' . esc_html__('):', 'ai-eshop-optimizer') . '</p>';

        echo '<ul style="margin:6px 0 6px 18px;line-height:1.7;font-family:Menlo,Consolas,monospace;font-size:13px;">';
        echo '<li><code>[aieo_google_reviews]</code> <span style="color:#646970;font-family:sans-serif;font-size:12px;">— ' . esc_html__('all locations, mixed feed (default mode)', 'ai-eshop-optimizer') . '</span></li>';
        echo '<li><code>[aieo_google_reviews location="downtown"]</code> <span style="color:#646970;font-family:sans-serif;font-size:12px;">— ' . esc_html__('a specific location\'s reviews (use the slug from the Locations table)', 'ai-eshop-optimizer') . '</span></li>';
        echo '<li><code>[aieo_google_locations_grid]</code> <span style="color:#646970;font-family:sans-serif;font-size:12px;">— ' . esc_html__('per-location cards grid (one card per location with rating + stats)', 'ai-eshop-optimizer') . '</span></li>';
        echo '</ul>';

        echo '<p style="margin:6px 0 4px;"><strong>' . esc_html__('Shortcode attributes', 'ai-eshop-optimizer') . '</strong> ' . esc_html__('(all optional, all mirror the block\'s InspectorControls):', 'ai-eshop-optimizer') . '</p>';
        echo '<ul style="margin:0 0 6px 18px;line-height:1.6;font-size:12px;">';
        echo '<li><code>location</code> — ' . esc_html__('location slug (single-mode only)', 'ai-eshop-optimizer') . '</li>';
        echo '<li><code>mode</code> — <code>single</code> | <code>all_mixed</code> | <code>cards_grid</code> ' . esc_html__('(auto-defaults from whether location is set)', 'ai-eshop-optimizer') . '</li>';
        echo '<li><code>min_rating</code> — 1 to 5, ' . esc_html__('default 5 (only show 5-star reviews)', 'ai-eshop-optimizer') . '</li>';
        echo '<li><code>max_count</code> — 1 to 50, ' . esc_html__('default 5', 'ai-eshop-optimizer') . '</li>';
        echo '<li><code>layout</code> — <code>grid</code> | <code>list</code> | <code>carousel</code></li>';
        echo '<li><code>presentation</code> — <code>static</code> | <code>slider</code> | <code>rotator</code></li>';
        echo '<li><code>autoplay_sec</code> — 2 to 60, ' . esc_html__('default 6 (slider / rotator only)', 'ai-eshop-optimizer') . '</li>';
        echo '<li><code>show_overall</code> — <code>yes</code> | <code>no</code>, ' . esc_html__('default yes (show overall rating + count)', 'ai-eshop-optimizer') . '</li>';
        echo '<li><code>show_rate_us</code> — <code>yes</code> | <code>no</code>, ' . esc_html__('default yes ("Leave a review" button)', 'ai-eshop-optimizer') . '</li>';
        echo '<li><code>show_maps_button</code> — <code>yes</code> | <code>no</code>, ' . esc_html__('default no (open in Google Maps button)', 'ai-eshop-optimizer') . '</li>';
        echo '<li><code>heading</code> — ' . esc_html__('optional custom heading text', 'ai-eshop-optimizer') . '</li>';
        echo '</ul>';

        echo '<p style="margin:8px 0 4px;"><strong>' . esc_html__('Example — full carousel of 4+ star reviews from one location', 'ai-eshop-optimizer') . ':</strong></p>';
        echo '<pre style="margin:0;padding:8px 10px;background:#1f2937;color:#e5e7eb;border-radius:4px;font-size:12px;overflow-x:auto;">[aieo_google_reviews location="downtown" min_rating="4" max_count="12" layout="carousel" presentation="slider" autoplay_sec="8" heading="What our customers say"]</pre>';

        echo '<h4 style="margin:14px 0 6px;">' . esc_html__('3. PHP templates (theme child / mu-plugin)', 'ai-eshop-optimizer') . '</h4>';
        echo '<p style="margin:0;">' . esc_html__('Echo the shortcode via', 'ai-eshop-optimizer') . ' <code>do_shortcode()</code> ' . esc_html__('from any PHP template:', 'ai-eshop-optimizer') . '</p>';
        echo '<pre style="margin:6px 0 0;padding:8px 10px;background:#1f2937;color:#e5e7eb;border-radius:4px;font-size:12px;overflow-x:auto;">echo do_shortcode(\'[aieo_google_reviews location="downtown"]\');</pre>';

        echo '</div>';
    }

    public function ajax_save_settings() {
        check_ajax_referer('aieo_grv_settings', 'nonce');
        if (!$this->can_manage()) {
            wp_send_json_error(array('message' => __('Permission denied.', 'ai-eshop-optimizer')));
        }
        $patch = array();
        foreach (array_keys(AIEO_Google_Reviews_Settings::defaults()) as $k) {
            if (array_key_exists($k, $_POST)) {
                $patch[$k] = wp_unslash($_POST[$k]);
            }
        }
        
        
        AIEO_Google_Reviews_Settings::update($patch);
        
        if (class_exists('AIEO_Google_Reviews_Cron')) {
            AIEO_Google_Reviews_Cron::reschedule();
        }
        wp_send_json_success();
    }

    
    private function hours_summary($json) {
        $json = (string) $json;
        if ('' === $json) { return ''; }
        $periods = json_decode($json, true);
        if (!is_array($periods) || empty($periods)) { return ''; }
        return sprintf(
            
            _n('Opening hours: %d entry auto-filled from Google.', 'Opening hours: %d entries auto-filled from Google.', count($periods), 'ai-eshop-optimizer'),
            count($periods)
        );
    }

    public function ajax_save_location() {
        check_ajax_referer('aieo_grv_locations', 'nonce');
        if (!$this->can_manage()) {
            wp_send_json_error(array('message' => __('Permission denied.', 'ai-eshop-optimizer')));
        }
        $data = array(
            'id'         => isset($_POST['id'])         ? (int) $_POST['id']                                           : 0,
            'slug'       => isset($_POST['slug'])       ? sanitize_key(wp_unslash((string) $_POST['slug']))             : '',
            'label'      => isset($_POST['label'])      ? sanitize_text_field(wp_unslash((string) $_POST['label']))     : '',
            'place_id'   => isset($_POST['place_id'])   ? sanitize_text_field(wp_unslash((string) $_POST['place_id']))  : '',
            'api_key'    => isset($_POST['api_key'])    ? sanitize_text_field(wp_unslash((string) $_POST['api_key']))   : '',
            'language'   => isset($_POST['language'])   ? sanitize_text_field(wp_unslash((string) $_POST['language']))  : 'el',
            'review_url' => isset($_POST['review_url']) ? esc_url_raw(wp_unslash((string) $_POST['review_url']))         : '',
            'enabled'    => !empty($_POST['enabled']),
            
            'is_hq'       => !empty($_POST['is_hq']) ? 1 : 0,
            'price_range' => isset($_POST['price_range']) ? sanitize_text_field(wp_unslash((string) $_POST['price_range'])) : '',
            'gbp_url'     => isset($_POST['gbp_url'])     ? esc_url_raw(wp_unslash((string) $_POST['gbp_url']))               : '',
            'image_url'   => isset($_POST['image_url'])   ? esc_url_raw(wp_unslash((string) $_POST['image_url']))             : '',
            'street'      => isset($_POST['street'])   ? sanitize_text_field(wp_unslash((string) $_POST['street']))   : '',
            'locality'    => isset($_POST['locality']) ? sanitize_text_field(wp_unslash((string) $_POST['locality'])) : '',
            'region'      => isset($_POST['region'])   ? sanitize_text_field(wp_unslash((string) $_POST['region']))   : '',
            'postcode'    => isset($_POST['postcode']) ? sanitize_text_field(wp_unslash((string) $_POST['postcode'])) : '',
            'country'     => isset($_POST['country'])  ? sanitize_text_field(wp_unslash((string) $_POST['country']))  : '',
            'phone'       => isset($_POST['phone'])    ? sanitize_text_field(wp_unslash((string) $_POST['phone']))    : '',
            'lat'         => isset($_POST['lat'])      ? wp_unslash((string) $_POST['lat'])                           : '',
            'lng'         => isset($_POST['lng'])      ? wp_unslash((string) $_POST['lng'])                           : '',
            'sort_order'  => isset($_POST['sort_order']) ? (int) wp_unslash((string) $_POST['sort_order'])            : 0,
            
            'listing_title'    => isset($_POST['listing_title']) ? sanitize_text_field(wp_unslash((string) $_POST['listing_title'])) : '',
            'hero_heading'     => isset($_POST['hero_heading'])  ? sanitize_text_field(wp_unslash((string) $_POST['hero_heading']))  : '',
            'hero_description' => isset($_POST['hero_description']) ? wp_unslash((string) $_POST['hero_description']) : '', 
        );
        $result = AIEO_Google_Reviews_Locations::save($data);
        if (is_wp_error($result)) {
            wp_send_json_error(array('message' => $result->get_error_message()));
        }

        
        
        
        
        
        $pickup_name = isset($_POST['pickup_name']) ? sanitize_text_field(wp_unslash((string) $_POST['pickup_name'])) : '';
        $review_row  = AIEO_Google_Reviews_Locations::get((int) $result);
        $this->set_pickup_association((string) $data['slug'], is_array($review_row) ? $review_row : array(), $pickup_name);

        wp_send_json_success(array('id' => (int) $result));
    }

    
    
    

    
    private function pickup_location_names() {
        $raw   = get_option('pickup_location_pickup_locations', array());
        $names = array();
        if (is_array($raw)) {
            foreach ($raw as $loc) {
                if (is_array($loc) && !empty($loc['name'])) { $names[] = (string) $loc['name']; }
            }
        }
        return $names;
    }

    
    private function pickup_for_review_slug($review_slug) {
        if ($review_slug === '') { return ''; }
        $raw = get_option('pickup_location_pickup_locations', array());
        if (is_array($raw)) {
            foreach ($raw as $loc) {
                if (is_array($loc) && isset($loc['review_slug']) && (string) $loc['review_slug'] === (string) $review_slug && !empty($loc['name'])) {
                    return (string) $loc['name'];
                }
            }
        }
        return '';
    }

    
    private function set_pickup_association($review_slug, $review_row, $pickup_name) {
        $raw = get_option('pickup_location_pickup_locations', array());
        if (!is_array($raw)) { return; }

        $maps_url = '';
        if ($review_slug !== '' && !empty($review_row) && class_exists('AIEO_Google_Reviews_Helpers')) {
            $maps_url = (string) AIEO_Google_Reviews_Helpers::maps_url($review_row);
        }

        $changed = false;
        foreach ($raw as $i => $loc) {
            if (!is_array($loc)) { continue; }
            $name = isset($loc['name']) ? (string) $loc['name'] : '';
            $cur  = isset($loc['review_slug']) ? (string) $loc['review_slug'] : '';
            if ($pickup_name !== '' && $name === $pickup_name) {
                $raw[$i]['review_slug'] = $review_slug;
                $raw[$i]['maps_url']    = $maps_url;
                $changed = true;
            } elseif ($review_slug !== '' && $cur === $review_slug) {
                
                $raw[$i]['review_slug'] = '';
                $raw[$i]['maps_url']    = '';
                $changed = true;
            }
        }
        if ($changed) { update_option('pickup_location_pickup_locations', $raw); }
    }

    public function ajax_delete_location() {
        check_ajax_referer('aieo_grv_locations', 'nonce');
        if (!$this->can_manage()) {
            wp_send_json_error(array('message' => __('Permission denied.', 'ai-eshop-optimizer')));
        }
        $id = isset($_POST['id']) ? (int) $_POST['id'] : 0;
        if ($id > 0) { AIEO_Google_Reviews_Locations::delete($id); }
        wp_send_json_success();
    }

    public function ajax_lookup_place() {
        check_ajax_referer('aieo_grv_locations', 'nonce');
        if (!$this->can_manage()) {
            wp_send_json_error(array('message' => __('Permission denied.', 'ai-eshop-optimizer')));
        }
        $query    = isset($_POST['query'])    ? sanitize_text_field(wp_unslash((string) $_POST['query']))    : '';
        $language = isset($_POST['language']) ? sanitize_text_field(wp_unslash((string) $_POST['language'])) : 'el';
        $api_key  = (string) AIEO_Google_Reviews_Settings::get('global_api_key');

        if ($api_key === '') {
            wp_send_json_error(array('message' => __('Set the global API key under Settings first.', 'ai-eshop-optimizer')));
        }
        $r = AIEO_Google_Reviews_Place_Lookup::resolve($query, $api_key, $language);
        if (!empty($r['error'])) {
            wp_send_json_error(array('message' => (string) $r['error']));
        }
        wp_send_json_success($r);
    }

    public function ajax_fetch_now() {
        check_ajax_referer('aieo_grv_locations', 'nonce');
        if (!$this->can_manage()) {
            wp_send_json_error(array('message' => __('Permission denied.', 'ai-eshop-optimizer')));
        }
        $id = isset($_POST['id']) ? (int) $_POST['id'] : 0;
        $location = AIEO_Google_Reviews_Locations::get($id);
        if (!$location) {
            wp_send_json_error(array('message' => __('Location not found.', 'ai-eshop-optimizer')));
        }
        @set_time_limit(60);
        $result = AIEO_Google_Reviews_Fetcher::fetch_one($location); 
        if (empty($result['ok'])) {
            wp_send_json_error(array('message' => sprintf(
                
                __('%1$s — %2$s', 'ai-eshop-optimizer'),
                (string) $result['status'],
                (string) ($result['error'] ?? '')
            )));
        }
        $result['label'] = (string) $location['label'];
        wp_send_json_success($result);
    }

    public function ajax_fetch_all() {
        check_ajax_referer('aieo_grv_locations', 'nonce');
        if (!$this->can_manage()) {
            wp_send_json_error(array('message' => __('Permission denied.', 'ai-eshop-optimizer')));
        }
        @set_time_limit(300);
 
        
        
        $totals = AIEO_Google_Reviews_Fetcher::fetch_all_enabled(false);
        wp_send_json_success($totals);
    }

    
    public function ajax_reorder_location() {
        check_ajax_referer('aieo_grv_locations', 'nonce');
        if (!$this->can_manage()) {
            wp_send_json_error(array('message' => __('Permission denied.', 'ai-eshop-optimizer')));
        }
        global $wpdb;
        $id  = isset($_POST['id']) ? (int) $_POST['id'] : 0;
        $dir = (isset($_POST['dir']) && 'down' === $_POST['dir']) ? 'down' : 'up';

        
        $ids = array_map(static function ($l) { return (int) $l['id']; }, AIEO_Google_Reviews_Locations::list_all());
        $pos = array_search($id, $ids, true);
        if (false === $pos) {
            wp_send_json_error(array('message' => __('Location not found.', 'ai-eshop-optimizer')));
        }
        $swap = ('up' === $dir) ? $pos - 1 : $pos + 1;
        if ($swap < 0 || $swap >= count($ids)) {
            wp_send_json_success(array('order' => $ids, 'noop' => true)); 
        }
        $tmp = $ids[$pos]; $ids[$pos] = $ids[$swap]; $ids[$swap] = $tmp;

        
        $table = AIEO_Google_Reviews_Locations::table();
        foreach ($ids as $i => $rid) {
            $wpdb->update($table, array('sort_order' => $i + 1), array('id' => (int) $rid));
        }
        wp_send_json_success(array('order' => $ids));
    }

    private function can_manage() {
        return current_user_can(defined('AIEO_CAPABILITY') ? AIEO_CAPABILITY : 'manage_options');
    }
}
