<?php
if (!defined('ABSPATH')) { exit; }

class PTMS_Core {
    private static $instance = null;
    private static $live_sync_pending = array();

    public static function instance() {
        if (self::$instance === null) { self::$instance = new self(); }
        return self::$instance;
    }

    private function __construct() {
        add_action('save_post_product', array(__CLASS__, 'clear_product_analysis_cache'));
        add_action('deleted_post', array(__CLASS__, 'clear_product_analysis_cache'));
        add_action('woocommerce_product_set_stock', array(__CLASS__, 'clear_product_analysis_cache'));
        add_action('woocommerce_variation_set_stock', array(__CLASS__, 'clear_product_analysis_cache'));
        add_action('rest_api_init', array(__CLASS__, 'register_rest_routes'));
        add_action('ptms_sync_batch_event', array(__CLASS__, 'process_sync_batch'), 10, 1);
        add_action('wp_ajax_ptms_async_worker', array(__CLASS__, 'handle_async_worker'));
        add_action('wp_ajax_nopriv_ptms_async_worker', array(__CLASS__, 'handle_async_worker'));
        add_action('wp_ajax_ptms_live_direct_worker', array(__CLASS__, 'handle_live_direct_worker'));
        add_action('wp_ajax_nopriv_ptms_live_direct_worker', array(__CLASS__, 'handle_live_direct_worker'));
        add_action('wp_ajax_ptms_live_direct_delete', array(__CLASS__, 'handle_live_direct_delete'));
        add_action('wp_ajax_nopriv_ptms_live_direct_delete', array(__CLASS__, 'handle_live_direct_delete'));
        add_action('admin_init', array(__CLASS__, 'worker_watchdog'));
        add_action('wp_ajax_ptms_sync_status', array(__CLASS__, 'worker_watchdog'), 1);
        add_action('save_post_product', array(__CLASS__, 'schedule_product_sync'), 20, 3);
        add_action('woocommerce_new_product', array(__CLASS__, 'schedule_wc_product_sync'), 30, 1);
        add_action('woocommerce_update_product', array(__CLASS__, 'schedule_wc_product_sync'), 30, 1);
        add_action('woocommerce_product_object_updated_props', array(__CLASS__, 'schedule_updated_props_sync'), 30, 2);
        add_action('woocommerce_product_set_stock', array(__CLASS__, 'schedule_stock_sync'), 20, 1);
        add_action('woocommerce_variation_set_stock', array(__CLASS__, 'schedule_variation_parent_sync'), 20, 1);
        add_action('shutdown', array(__CLASS__, 'flush_live_sync_queue'), 20);
        add_action('before_delete_post', array(__CLASS__, 'capture_deleted_product'), 10, 2);
        add_action('wp_trash_post', array(__CLASS__, 'capture_trashed_product'), 10, 1);
        add_action('ptms_start_scheduled_sync', array(__CLASS__, 'run_scheduled_sync'), 10, 1);
        add_action('ptms_live_sync_wakeup', array(__CLASS__, 'run_live_sync_wakeup'));
        add_action('ptms_live_retry_event', array(__CLASS__, 'run_live_retry_queue'));
        add_action('ptms_delete_retry_event', array(__CLASS__, 'run_delete_retry_queue'));
        add_action('ptms_refresh_merchant_snapshot_event', array(__CLASS__, 'refresh_merchant_snapshot_after_sync'));
        add_action('ptms_catalog_renewal_event', array(__CLASS__, 'run_catalog_renewal'));
        add_action('ptms_verify_connection_event', array(__CLASS__, 'scheduled_connection_verification'));
        add_action('admin_init', array(__CLASS__, 'maybe_verify_connection'), 5);
        self::run_migrations();
        self::ensure_catalog_renewal_schedule();
    }

    public static function clear_product_analysis_cache() {
        delete_transient('ptms_product_analysis_dataset_v106');
        delete_transient('ptms_product_analysis_dataset_v107');
    }

    public static function activate() {
        $stored_version = (string) get_option('ptms_version', '');
        if (!get_option('ptms_installed_at')) { update_option('ptms_installed_at', time(), false); }
        if (!get_option('ptms_license_status')) { update_option('ptms_license_status', 'inactive', false); }
        if (!get_option('ptms_wizard_status')) { update_option('ptms_wizard_status', array(), false); }
        update_option('ptms_domain_fingerprint', self::domain_fingerprint(), false);
        if (!get_option('ptms_install_id')) { update_option('ptms_install_id', bin2hex(random_bytes(16)), false); }
        if (get_option('ptms_auto_sync_enabled', null) === null) { update_option('ptms_auto_sync_enabled', false, false); }
        if (get_option('ptms_sync_status', null) === null) { update_option('ptms_sync_status', 'ready', false); }
        if (get_option('ptms_sync_failed_queue', null) === null) { update_option('ptms_sync_failed_queue', array(), false); }
        if (get_option('ptms_sync_logs', null) === null) { update_option('ptms_sync_logs', array(), false); }
        if (get_option('ptms_delete_queue', null) === null) { update_option('ptms_delete_queue', array(), false); }
        if (get_option('ptms_sync_schedule', null) === null) { update_option('ptms_sync_schedule', array(), false); }
        if (get_option('ptms_api_usage', null) === null) { update_option('ptms_api_usage', array(), false); }
        if (get_option('ptms_merchant_issues_cache', null) === null) { update_option('ptms_merchant_issues_cache', array(), false); }
        if (get_option('ptms_live_direct_threshold', null) === null) { update_option('ptms_live_direct_threshold', 10, false); }
        if (get_option('ptms_live_retry_queue', null) === null) { update_option('ptms_live_retry_queue', array(), false); }
        if (get_option('ptms_delete_failed_queue', null) === null) { update_option('ptms_delete_failed_queue', array(), false); }
        if (get_option('ptms_connection_state', null) === null) {
            update_option('ptms_connection_state', get_option('ptms_google_connected', false) ? 'unverified' : 'not_connected', false);
        }
        if (get_option('ptms_history_stats', null) === null) {
            update_option('ptms_history_stats', array(
                'processed' => (int) get_option('ptms_sync_done', 0) + (int) get_option('ptms_sync_failed', 0),
                'successful' => (int) get_option('ptms_sync_done', 0),
                'failed' => (int) get_option('ptms_sync_failed', 0),
                'live_successful' => 0,
                'live_failed' => 0,
                'updated_at' => time(),
            ), false);
        }
        self::run_migrations($stored_version);
        self::ensure_catalog_renewal_schedule();
    }

    public static function deactivate() {
        self::unschedule_workers();
        foreach (array(
            'ptms_start_scheduled_sync',
            'ptms_live_sync_wakeup',
            'ptms_live_retry_event',
            'ptms_delete_retry_event',
            'ptms_refresh_merchant_snapshot_event',
            'ptms_catalog_renewal_event',
            'ptms_verify_connection_event',
        ) as $hook) {
            wp_clear_scheduled_hook($hook);
        }
        delete_transient('ptms_sync_worker_lock');
        delete_transient('ptms_live_direct_lock');
    }

    public static function domain() {
        $host = wp_parse_url(home_url('/'), PHP_URL_HOST);
        if (!$host) {
            $url = preg_replace('#^[a-z][a-z0-9+.-]*://#i', '', trim((string) home_url('/')));
            $host = explode('/', (string) $url)[0];
        }
        $host = strtolower(trim((string) $host));
        $host = preg_replace('/:\d+$/', '', $host);
        $host = preg_replace('/^www\./i', '', (string) $host);
        return rtrim((string) $host, '.');
    }

    public static function domain_fingerprint() {
        $salt = defined('AUTH_SALT') ? AUTH_SALT : wp_salt('auth');
        return hash('sha256', self::domain() . '|' . $salt . '|pw-merchant-api-system');
    }

    public static function demo_days_total() { return 30; }

    public static function demo_days_left() {
        $installed = (int) get_option('ptms_installed_at', time());
        $elapsed = (int) floor(max(0, time() - $installed) / DAY_IN_SECONDS);
        return max(0, self::demo_days_total() - $elapsed);
    }

    public static function license_status() {
        return class_exists('PTMS_License') ? PTMS_License::status() : 'expired';
    }



    public static function register_rest_routes() {
        register_rest_route('ptms/v1', '/connect', array(
            array('methods' => 'POST', 'callback' => array(__CLASS__, 'rest_connect_challenge'), 'permission_callback' => '__return_true'),
            array('methods' => 'GET', 'callback' => array(__CLASS__, 'rest_connect_callback'), 'permission_callback' => '__return_true'),
        ));
    }

    public static function callback_url() {
        return rest_url('ptms/v1/connect');
    }

    public static function rest_connect_challenge(WP_REST_Request $request) {
        $data = $request->get_json_params();
        $install_id = (string) get_option('ptms_install_id', '');
        $fingerprint = self::domain_fingerprint();
        $valid = is_array($data)
            && isset($data['challenge'], $data['install_id'], $data['domain_fingerprint'], $data['product'], $data['expires_at'])
            && hash_equals($install_id, (string) $data['install_id'])
            && hash_equals($fingerprint, (string) $data['domain_fingerprint'])
            && (string) $data['product'] === 'pt-merchant'
            && (int) $data['expires_at'] >= time();
        if (!$valid) return new WP_REST_Response(array('ok' => false, 'error' => 'challenge_invalid'), 403);
        return new WP_REST_Response(array(
            'ok' => true,
            'challenge' => (string) $data['challenge'],
            'install_id' => $install_id,
            'domain_fingerprint' => $fingerprint,
            'product' => 'pt-merchant',
        ), 200);
    }

    public static function rest_connect_callback(WP_REST_Request $request) {
        $result = sanitize_key((string) $request->get_param('pw_connect'));
        $connection_id = preg_replace('/[^A-Za-z0-9_-]/', '', (string) $request->get_param('connection_id'));
        $state = preg_replace('/[^A-Za-z0-9_-]/', '', (string) $request->get_param('state'));
        $expected = (string) get_transient('ptms_oauth_state_' . $state);
        if ($result !== 'success' || $connection_id === '' || $state === '' || $expected === '' || !hash_equals($state, $expected)) {
            update_option('ptms_connect_error', __('Google link return could not be verified. Please restart the connection.', 'pw-merchant-api-system'), false);
            return new WP_REST_Response(null, 302, array('Location' => admin_url('admin.php?page=pw-merchant-api-system-google&ptms_connect=error')));
        }
        delete_transient('ptms_oauth_state_' . $state);
        $response = self::fetch_connection_result($connection_id);
        if (is_wp_error($response)) {
            update_option('ptms_connect_error', $response->get_error_message(), false);
            return new WP_REST_Response(null, 302, array('Location' => admin_url('admin.php?page=pw-merchant-api-system-google&ptms_connect=error')));
        }
        $connection = isset($response['connection']) && is_array($response['connection']) ? $response['connection'] : array();
        $new_connection_id = sanitize_text_field((string)($connection['connection_id'] ?? ''));
        $new_email = sanitize_email((string)($connection['google_account_email'] ?? ''));
        $new_accounts = is_array($connection['merchant_accounts'] ?? null) ? $connection['merchant_accounts'] : array();
        update_option('ptms_pending_connection_id', $new_connection_id, false);
        update_option('ptms_pending_google_email', $new_email, false);
        update_option('ptms_pending_merchant_accounts', $new_accounts, false);
        if ((bool) get_option('ptms_connection_reconnect_required', false)) {
            $merchant_id = preg_replace('/\D+/', '', (string) get_option('ptms_merchant_id', ''));
            if ($merchant_id !== '' && self::merchant_account_is_available($merchant_id, $new_accounts)) {
                self::accept_verified_connection($new_connection_id, $new_email, $new_accounts, $merchant_id);
                delete_option('ptms_connect_error');
                return new WP_REST_Response(null, 302, array('Location' => admin_url('admin.php?page=pw-merchant-api-system-google&ptms_connect=reconnected')));
            }
        }
        delete_option('ptms_connect_error');
        return new WP_REST_Response(null, 302, array('Location' => admin_url('admin.php?page=pw-merchant-api-system-google&ptms_connect=choose')));
    }

    public static function ensure_install() {
        $install_id = (string) get_option('ptms_install_id', '');
        if ($install_id === '') {
            $install_id = bin2hex(random_bytes(16));
            update_option('ptms_install_id', $install_id, false);
        }
        $secret = (string) get_option('ptms_install_secret', '');
        if ($secret !== '') return array('ok' => true, 'install_id' => $install_id, 'install_secret' => $secret);

        $payload = array(
            'install_id' => $install_id,
            'site_url' => home_url('/'),
            'callback_url' => self::callback_url(),
            'domain_fingerprint' => self::domain_fingerprint(),
            'plugin_version' => PTMS_VERSION,
            'product' => 'pt-merchant',
        );
        $response = wp_remote_post(trailingslashit(PTMS_CONNECT_SERVICE_URL) . 'api/register.php', array(
            'timeout' => 20,
            'headers' => array('Content-Type' => 'application/json', 'Accept' => 'application/json'),
            'body' => wp_json_encode($payload),
            'sslverify' => true,
        ));
        if (is_wp_error($response)) return $response;
        $body = json_decode((string) wp_remote_retrieve_body($response), true);
        if (wp_remote_retrieve_response_code($response) !== 200 || !is_array($body) || empty($body['ok']) || empty($body['install_secret'])) {
            $message = is_array($body) && !empty($body['message']) ? (string)$body['message'] : __('PW Connect site registration could not be completed.', 'pw-merchant-api-system');
            return new WP_Error('ptms_register_failed', $message);
        }
        update_option('ptms_install_secret', sanitize_text_field((string)$body['install_secret']), false);
        return array('ok' => true, 'install_id' => $install_id, 'install_secret' => (string)$body['install_secret']);
    }

    private static function is_missing_connect_site_record($remote_error, $message) {
        $remote_error = sanitize_key((string) $remote_error);
        if (in_array($remote_error, array(
            'site_not_found',
            'site_connection_not_found',
            'site_registration_not_found',
            'install_not_found',
            'installation_not_registered',
        ), true)) {
            return true;
        }

        $message = strtolower(remove_accents(trim((string) $message)));
        return strpos($message, 'site baglanti kaydi') !== false
            || strpos($message, 'site connection record') !== false
            || strpos($message, 'site registration record') !== false;
    }

    private static function signed_payload(array $data, $secret) {
        ksort($data);
        $canonical = http_build_query($data, '', '&', PHP_QUERY_RFC3986);
        $data['sig'] = hash_hmac('sha256', $canonical, $secret);
        return $data;
    }

    /**
     * Lisans isteklerini siteye ortak bir Trust anahtarı dağıtmadan PW Connect'e iletir.
     * PW Connect isteği kurulumun benzersiz anahtarıyla doğrular ve Trust çağrısını
     * yalnız sunucuda saklanan servis kimliğiyle gerçekleştirir.
     */
    public static function trust_request($endpoint, array $payload) {
        $encoded = wp_json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
        if (!is_string($encoded)) {
            return new WP_Error('ptms_trust_payload', __('The license request could not be prepared.', 'pw-merchant-api-system'));
        }

        for ($attempt = 0; $attempt < 2; $attempt++) {
            $install = self::ensure_install();
            if (is_wp_error($install)) return $install;

            $data = array(
                'install_id' => (string) $install['install_id'],
                'operation' => sanitize_file_name((string) $endpoint),
                'payload' => base64_encode($encoded),
                'ts' => time(),
                'nonce' => bin2hex(random_bytes(16)),
            );
            $data = self::signed_payload($data, (string) $install['install_secret']);

            $response = wp_remote_post(trailingslashit(PTMS_CONNECT_SERVICE_URL) . 'api/license.php', array(
                'timeout' => 30,
                'redirection' => 0,
                'sslverify' => true,
                'headers' => array('Content-Type' => 'application/json', 'Accept' => 'application/json'),
                'body' => wp_json_encode($data),
            ));
            if (is_wp_error($response)) return $response;

            $code = (int) wp_remote_retrieve_response_code($response);
            $body = json_decode((string) wp_remote_retrieve_body($response), true);
            if (!is_array($body)) {
                return new WP_Error('ptms_connect_license_response', __('Invalid response received from license security service.', 'pw-merchant-api-system'));
            }
            if ($code >= 200 && $code < 300 && !empty($body['ok'])) {
                return $body;
            }

            $remote_error = sanitize_key((string) ($body['error'] ?? 'license_failed'));
            $message = !empty($body['message'])
                ? sanitize_text_field((string) $body['message'])
                : __('The licensing process could not be completed.', 'pw-merchant-api-system');

            if ($attempt === 0 && self::is_missing_connect_site_record($remote_error, $message)) {
                // Veritabanı taşınmasında aynı install_id korunur; yalnız kayıp
                // Connect sırrı yeniden güvenli kayıt akışıyla üretilir.
                delete_option('ptms_install_secret');
                continue;
            }

            return new WP_Error('ptms_connect_' . $remote_error, $message, array(
                'remote_error' => $remote_error,
                'http_status' => $code,
            ));
        }

        return new WP_Error('ptms_connect_license_failed', __('The licensing process could not be completed.', 'pw-merchant-api-system'));
    }

    public static function connect_start_url() {
        // Faz 3B.1: Google OAuth başlamadan önce gerçek google.connect scope kapısı.
        // Yetki alınamazsa mevcut Google bağlantısı ve Merchant ayarları değiştirilmez.
        if (class_exists('PTMS_License_Client_V2')) {
            $scope_gate = PTMS_License_Client_V2::require_scope('google.connect');
            if (is_wp_error($scope_gate)) { return $scope_gate; }
        }

        $install = self::ensure_install();
        if (is_wp_error($install)) return $install;
        $state = rtrim(strtr(base64_encode(random_bytes(24)), '+/', '-_'), '=');
        set_transient('ptms_oauth_state_' . $state, $state, 15 * MINUTE_IN_SECONDS);
        $data = array(
            'install_id' => (string)$install['install_id'],
            'plugin_state' => $state,
            'ts' => time(),
            'nonce' => bin2hex(random_bytes(16)),
        );
        $data = self::signed_payload($data, (string)$install['install_secret']);
        return add_query_arg($data, trailingslashit(PTMS_CONNECT_SERVICE_URL) . 'start.php');
    }

    public static function fetch_connection_result($connection_id) {
        $install = self::ensure_install();
        if (is_wp_error($install)) return $install;
        $data = array(
            'install_id' => (string)$install['install_id'],
            'connection_id' => (string)$connection_id,
            'ts' => time(),
            'nonce' => bin2hex(random_bytes(16)),
        );
        $data = self::signed_payload($data, (string)$install['install_secret']);
        $response = wp_remote_post(trailingslashit(PTMS_CONNECT_SERVICE_URL) . 'api/connection-result.php', array(
            'timeout' => 20,
            'headers' => array('Content-Type' => 'application/json', 'Accept' => 'application/json'),
            'body' => wp_json_encode($data),
            'sslverify' => true,
        ));
        if (is_wp_error($response)) return $response;
        $body = json_decode((string) wp_remote_retrieve_body($response), true);
        if (wp_remote_retrieve_response_code($response) !== 200 || !is_array($body) || empty($body['ok'])) {
            return new WP_Error('ptms_result_failed', is_array($body) && !empty($body['message']) ? (string)$body['message'] : __('No Google link results were obtained.', 'pw-merchant-api-system'));
        }
        return $body;
    }

    public static function refresh_connection_status($force = true) {
        if (!$force) {
            $cached = get_transient('ptms_verified_connection_status');
            if (is_array($cached) && !empty($cached['ok'])) return $cached;
        }

        for ($attempt = 0; $attempt < 2; $attempt++) {
            $install = self::ensure_install();
            if (is_wp_error($install)) return $install;

            $data = array(
                'install_id' => (string)$install['install_id'],
                'verify_only' => 1,
                'ts' => time(),
                'nonce' => bin2hex(random_bytes(16)),
            );
            $data = self::signed_payload($data, (string)$install['install_secret']);
            $response = wp_remote_post(trailingslashit(PTMS_CONNECT_SERVICE_URL) . 'api/status.php', array(
                'timeout' => 35,
                'headers' => array('Content-Type' => 'application/json', 'Accept' => 'application/json'),
                'body' => wp_json_encode($data),
                'sslverify' => true,
            ));
            if (is_wp_error($response)) return $response;

            $http_status = (int) wp_remote_retrieve_response_code($response);
            $body = json_decode((string)wp_remote_retrieve_body($response), true);
            if ($http_status !== 200 || !is_array($body) || empty($body['ok'])) {
                $remote_error = sanitize_key((string)($body['error'] ?? 'status_failed'));
                $message = is_array($body) && !empty($body['message'])
                    ? sanitize_text_field((string)$body['message'])
                    : __('Failed to retrieve PW Connect connection status.', 'pw-merchant-api-system');
                if ($attempt === 0 && self::is_missing_connect_site_record($remote_error, $message)) {
                    delete_option('ptms_install_secret');
                    delete_transient('ptms_verified_connection_status');
                    continue;
                }
                $error = new WP_Error('ptms_connect_' . $remote_error, $message, array(
                    'remote_error' => $remote_error,
                    'http_status' => $http_status,
                ));
                if (self::is_connection_reconnect_error($error)) {
                    self::mark_connection_reconnect_required($message, $remote_error);
                }
                return $error;
            }

            $connection = isset($body['connection']) && is_array($body['connection']) ? $body['connection'] : array();
            $accounts = isset($connection['merchant_accounts']) && is_array($connection['merchant_accounts'])
                ? $connection['merchant_accounts']
                : array();
            $connection_id = sanitize_text_field((string)($connection['connection_id'] ?? ''));
            $email = sanitize_email((string)($connection['google_account_email'] ?? ''));
            $local_connection_id = sanitize_text_field((string)get_option('ptms_connection_id', ''));
            $merchant_id = preg_replace('/\D+/', '', (string)get_option('ptms_merchant_id', ''));
            $merchant_api = isset($body['merchant_api']) && is_array($body['merchant_api']) ? $body['merchant_api'] : array();

            if ($connection_id === '') {
                if ($local_connection_id !== ''
                    || (bool)get_option('ptms_google_connected', false)
                    || (bool)get_option('ptms_connection_reconnect_required', false)) {
                    self::mark_connection_reconnect_required(
                        __('Google Merchant connection must be re-established.', 'pw-merchant-api-system'),
                        'connection_not_found'
                    );
                } else {
                    update_option('ptms_connection_state', 'not_connected', false);
                    update_option('ptms_connection_checked_at', time(), false);
                }
                delete_transient('ptms_verified_connection_status');
                return array(
                    'ok' => true,
                    'connection_found' => false,
                    'connection_state' => (string)get_option('ptms_connection_state', 'not_connected'),
                    'accounts' => array(),
                    'merchant_api_status' => sanitize_key((string)($merchant_api['status'] ?? 'not_connected')),
                );
            }

            update_option('ptms_pending_connection_id', $connection_id, false);
            update_option('ptms_pending_google_email', $email, false);
            update_option('ptms_pending_merchant_accounts', $accounts, false);

            $merchant_available = $merchant_id !== '' && self::merchant_account_is_available($merchant_id, $accounts);
            if ($merchant_available && (
                $local_connection_id === $connection_id
                || $local_connection_id === ''
                || (bool)get_option('ptms_connection_reconnect_required', false)
            )) {
                self::accept_verified_connection($connection_id, $email, $accounts, $merchant_id);
            } elseif ($local_connection_id !== '' && $local_connection_id !== $connection_id) {
                self::mark_connection_reconnect_required(
                    __('The Google Merchant connection registered on the server has changed. Reconnect your Google account.', 'pw-merchant-api-system'),
                    'connection_mismatch'
                );
            } elseif ($merchant_id !== '' && !$merchant_available) {
                self::mark_connection_reconnect_required(
                    __('The selected Merchant Center account is no longer available for this Google connection.', 'pw-merchant-api-system'),
                    'merchant_not_allowed'
                );
            } else {
                update_option('ptms_connection_state', 'account_selection', false);
                update_option('ptms_connection_checked_at', time(), false);
            }

            $result = array(
                'ok' => true,
                'connection_found' => true,
                'connection_state' => (string)get_option('ptms_connection_state', 'account_selection'),
                'accounts' => $accounts,
                'merchant_api_status' => sanitize_key((string)($merchant_api['status'] ?? 'unknown')),
            );
            set_transient('ptms_verified_connection_status', $result, 5 * MINUTE_IN_SECONDS);
            return $result;
        }

        return new WP_Error('ptms_status_failed', __('Failed to retrieve PW Connect connection status.', 'pw-merchant-api-system'));
    }

    private static function merchant_account_is_available($merchant_id, array $accounts) {
        $merchant_id = preg_replace('/\D+/', '', (string)$merchant_id);
        if ($merchant_id === '') return false;
        foreach ($accounts as $account) {
            if (!is_array($account)) continue;
            if (preg_replace('/\D+/', '', (string)($account['accountId'] ?? '')) === $merchant_id) return true;
        }
        return false;
    }

    public static function accept_verified_connection($connection_id, $email, array $accounts, $merchant_id) {
        $connection_id = sanitize_text_field((string)$connection_id);
        $merchant_id = preg_replace('/\D+/', '', (string)$merchant_id);
        if ($connection_id === '' || !self::merchant_account_is_available($merchant_id, $accounts)) return false;

        update_option('ptms_connection_id', $connection_id, false);
        update_option('ptms_google_connected', true, false);
        update_option('ptms_google_email', sanitize_email((string)$email), false);
        update_option('ptms_connection_state', 'active', false);
        update_option('ptms_connection_checked_at', time(), false);
        delete_option('ptms_connection_reconnect_required');
        delete_option('ptms_connection_status_message');
        delete_option('ptms_previous_connection_id');
        delete_option('ptms_pending_connection_id');
        delete_option('ptms_pending_google_email');
        delete_option('ptms_pending_merchant_accounts');
        delete_transient('ptms_verified_connection_status');
        self::resume_connection_queues();
        return true;
    }

    public static function is_connection_reconnect_error($error) {
        if (!is_wp_error($error)) return false;
        $data = $error->get_error_data();
        $remote_error = is_array($data) ? sanitize_key((string)($data['remote_error'] ?? '')) : '';
        $code = sanitize_key((string)$error->get_error_code());
        foreach (array(
            'connection_not_found',
            'connection_mismatch',
            'reauthorization_required',
            'token_refresh_failed',
            'merchant_not_allowed',
        ) as $needle) {
            if ($remote_error === $needle || strpos($code, $needle) !== false) return true;
        }
        return false;
    }

    private static function mark_connection_reconnect_required($message, $remote_error = '') {
        $already_required = (bool)get_option('ptms_connection_reconnect_required', false);
        $current = sanitize_text_field((string)get_option('ptms_connection_id', ''));
        if ($current !== '') update_option('ptms_previous_connection_id', $current, false);
        delete_option('ptms_connection_id');
        update_option('ptms_google_connected', false, false);
        update_option('ptms_connection_reconnect_required', true, false);
        update_option('ptms_connection_state', 'reconnect_required', false);
        update_option('ptms_connection_checked_at', time(), false);
        update_option('ptms_connection_status_message', sanitize_text_field((string)$message), false);
        delete_transient('ptms_verified_connection_status');
        if ((string)get_option('ptms_sync_status', '') === 'running') {
            update_option('ptms_sync_status', 'connection_paused', false);
            self::unschedule_workers();
        }
        if (!$already_required) {
            self::add_sync_log('warning', __('Google Merchant connection must be re-established; pending transactions were preserved.', 'pw-merchant-api-system'), array(
                'remote_error' => sanitize_key((string)$remote_error),
            ));
        }
    }

    private static function connection_ready_for_sync() {
        if ((bool)get_option('ptms_connection_reconnect_required', false)) {
            return new WP_Error(
                'ptms_connection_reconnect_required',
                __('Google Merchant connection must be re-established.', 'pw-merchant-api-system'),
                array('remote_error' => 'connection_not_found')
            );
        }
        if (!(bool)get_option('ptms_google_connected', false)
            || (string)get_option('ptms_connection_id', '') === ''
            || (string)get_option('ptms_merchant_id', '') === '') {
            return new WP_Error('connection_missing', __('Complete the Google and Merchant Center connection first.', 'pw-merchant-api-system'));
        }
        $result = self::refresh_connection_status(false);
        if (is_wp_error($result)) return $result;
        if (empty($result['connection_found']) || (string)($result['connection_state'] ?? '') !== 'active') {
            return new WP_Error(
                'ptms_connection_reconnect_required',
                __('Google Merchant connection must be re-established.', 'pw-merchant-api-system'),
                array('remote_error' => 'connection_not_found')
            );
        }
        return true;
    }

    public static function maybe_verify_connection() {
        if (!is_admin() || !current_user_can('manage_options') || wp_doing_ajax()) return;
        if (!(bool)get_option('ptms_google_connected', false)
            && !(bool)get_option('ptms_connection_reconnect_required', false)) return;
        if (get_transient('ptms_admin_connection_check_lock')) return;
        set_transient('ptms_admin_connection_check_lock', '1', 5 * MINUTE_IN_SECONDS);
        self::refresh_connection_status(false);
    }

    public static function scheduled_connection_verification() {
        self::refresh_connection_status(true);
    }



    public static function schedule_product_sync($post_id, $post, $update) {
        if (wp_is_post_revision($post_id) || wp_is_post_autosave($post_id) || get_post_type($post_id) !== 'product' || get_post_status($post_id) !== 'publish') return;
        if (!(bool) get_option('ptms_auto_sync_enabled', true)) return;
        self::mark_live_product((int) $post_id, $update ? __('Product change detected.', 'pw-merchant-api-system') : __('New product detected.', 'pw-merchant-api-system'));
    }

    public static function schedule_wc_product_sync($product_id) {
        if (!(bool) get_option('ptms_auto_sync_enabled', true)) return;
        $product_id = absint($product_id);
        if ($product_id > 0) self::mark_live_product($product_id, __('WooCommerce product change detected.', 'pw-merchant-api-system'));
    }

    public static function schedule_updated_props_sync($product, $updated_props) {
        if (!(bool) get_option('ptms_auto_sync_enabled', true) || !is_object($product) || !method_exists($product, 'get_id')) return;
        $relevant = array('name','status','description','short_description','sku','regular_price','sale_price','price','date_on_sale_from','date_on_sale_to','total_sales','tax_status','tax_class','manage_stock','stock_quantity','stock_status','backorders','weight','length','width','height','attributes','default_attributes','category_ids','tag_ids','image_id','gallery_image_ids','purchase_note','menu_order','virtual','downloadable');
        $updated_props = array_map('strval', (array) $updated_props);
        if (!$updated_props || !array_intersect($updated_props, $relevant)) return;
        $product_id = (int) $product->get_id();
        if (method_exists($product, 'is_type') && $product->is_type('variation') && method_exists($product, 'get_parent_id')) {
            $product_id = (int) $product->get_parent_id();
        }
        if ($product_id > 0) self::mark_live_product($product_id, __('A price, stock, content or image change has been detected.', 'pw-merchant-api-system'));
    }

    public static function schedule_stock_sync($product) {
        if (!(bool) get_option('ptms_auto_sync_enabled', true) || !is_object($product) || !method_exists($product, 'get_id')) return;
        $id = (int) $product->get_id();
        if ($id > 0 && get_post_type($id) === 'product') self::mark_live_product($id, __('Stock change detected.', 'pw-merchant-api-system'));
    }

    public static function schedule_variation_parent_sync($variation) {
        if (!(bool) get_option('ptms_auto_sync_enabled', true) || !is_object($variation) || !method_exists($variation, 'get_parent_id')) return;
        $parent_id = (int) $variation->get_parent_id();
        if ($parent_id > 0) self::mark_live_product($parent_id, __('Variation stock change detected.', 'pw-merchant-api-system'));
    }

    private static function mark_live_product($product_id, $reason = '') {
        $product_id = absint($product_id);
        if (!$product_id) return;
        self::$live_sync_pending[$product_id] = (string) $reason;
    }

    public static function flush_live_sync_queue() {
        if (!self::$live_sync_pending || !(bool) get_option('ptms_auto_sync_enabled', true)) return;
        $pending = self::$live_sync_pending;
        self::$live_sync_pending = array();
        $queue_snapshot = array_values(array_unique(array_filter(array_map('intval', (array) get_option('ptms_sync_queue', array())))));

        $valid = array();
        foreach ($pending as $product_id => $reason) {
            $product_id = absint($product_id);
            if (!$product_id || get_post_status($product_id) !== 'publish' || self::is_product_excluded($product_id)) continue;
            $valid[$product_id] = (string) $reason;
        }
        if (!$valid) return;

        $threshold = max(1, min(25, (int) get_option('ptms_live_direct_threshold', 10)));
        $status = (string) get_option('ptms_sync_status', 'ready');
        // PSSF: durdurulmuş eski bir toplu kuyruk, tekil canlı güncellemeyi engellemez.
        // Yalnız gerçekten çalışan/duraklatılmış worker veya aktif worker kilidi canlı akışı kuyruğa yönlendirir.
        $bulk_busy = in_array($status, array('running', 'paused'), true)
            || (bool) get_transient('ptms_sync_worker_lock');

        // PSSF Version Lock: tekil ve küçük ürün değişiklikleri cron beklemeden
        // anlık/asenkron API akışıyla gönderilir. Yoğun değişiklikler güvenli kuyruğa geçer.
        if (!$bulk_busy && count($valid) <= $threshold) {
            self::add_sync_log('info', __('Product change detected; instant Google Merchant update has been launched.', 'pw-merchant-api-system'), array(
                'product_ids' => array_keys($valid),
                'count' => count($valid),
                'mode' => 'direct',
            ));
            foreach ($valid as $live_product_id => $live_reason) {
                self::add_live_sync_event((int) $live_product_id, 'detected', __('Instant update launched', 'pw-merchant-api-system'), (string) $live_reason, 'info');
            }

            // PHP-FPM mevcutsa HTTP yanıtını kullanıcıya hemen tamamla, işlemi aynı
            // PHP sürecinde güvenilir biçimde arka planda sürdür. Diğer ortamlarda
            // imzalı non-blocking loopback kullanılır.
            if (function_exists('fastcgi_finish_request')) {
                @fastcgi_finish_request();
                self::process_live_direct_products(array_keys($valid), $valid);
                self::protect_live_queue_growth($queue_snapshot, array_keys($valid));
                return;
            }
            if (self::dispatch_live_direct_worker($valid)) { self::protect_live_queue_growth($queue_snapshot, array_keys($valid)); return; }
        }

        foreach ($valid as $product_id => $reason) {
            self::enqueue_live_product((int) $product_id, (string) $reason);
        }
        self::protect_live_queue_growth($queue_snapshot, array_keys($valid));
    }

    private static function protect_live_queue_growth(array $queue_snapshot, array $valid_ids) {
        $current = array_values(array_unique(array_filter(array_map('intval', (array) get_option('ptms_sync_queue', array())))));
        $allowed_growth = max(3, count($valid_ids));
        if (count($current) <= count($queue_snapshot) + $allowed_growth) return;

        $origin = (string) get_option('ptms_sync_queue_origin', '');
        if (in_array($origin, array('initial', 'manual', 'scheduled'), true)) return;

        // Live Sync sırasında tüm kataloğun yanlışlıkla kuyruğa alınmasını engeller.
        $safe = $queue_snapshot;
        foreach (array_map('intval', $valid_ids) as $id) {
            if ($id > 0 && !in_array($id, $safe, true)) $safe[] = $id;
        }
        update_option('ptms_sync_queue', array_values(array_unique($safe)), false);
        self::set_queue_origin('live-retry', array('repaired' => true, 'blocked_count' => count($current)));
        update_option('ptms_sync_total', count($safe), false);
        update_option('ptms_sync_done', 0, false);
        update_option('ptms_sync_failed', 0, false);
        self::add_sync_log('error', __('The live product change\'s attempt to queue the entire catalog was blocked.', 'pw-merchant-api-system'), array(
            'before' => count($queue_snapshot),
            'blocked' => count($current),
            'kept' => count($safe),
        ));
    }

    private static function dispatch_live_direct_worker(array $pending) {
        $ids = array_values(array_unique(array_filter(array_map('intval', array_keys($pending)))));
        if (!$ids) return false;
        $token = wp_generate_uuid4();
        $payload = array('ids' => $ids, 'reasons' => $pending, 'created_at' => time());
        set_transient('ptms_live_direct_' . md5($token), $payload, 10 * MINUTE_IN_SECONDS);
        $signature = hash_hmac('sha256', $token . '|' . implode(',', $ids) . '|' . (string) get_option('ptms_install_id', ''), wp_salt('auth'));
        $response = wp_remote_post(admin_url('admin-ajax.php'), array(
            'timeout' => 1,
            'blocking' => false,
            'sslverify' => apply_filters('https_local_ssl_verify', false),
            'body' => array(
                'action' => 'ptms_live_direct_worker',
                'token' => $token,
                'signature' => $signature,
            ),
        ));
        return !is_wp_error($response);
    }

    public static function handle_live_direct_worker() {
        $token = sanitize_text_field((string) ($_POST['token'] ?? ''));
        $signature = sanitize_text_field((string) ($_POST['signature'] ?? ''));
        $payload = (array) get_transient('ptms_live_direct_' . md5($token));
        $ids = array_values(array_unique(array_filter(array_map('intval', (array) ($payload['ids'] ?? array())))));
        $expected = $ids ? hash_hmac('sha256', $token . '|' . implode(',', $ids) . '|' . (string) get_option('ptms_install_id', ''), wp_salt('auth')) : '';
        if ($token === '' || $signature === '' || $expected === '' || !hash_equals($expected, $signature)) {
            status_header(403);
            exit;
        }
        delete_transient('ptms_live_direct_' . md5($token));
        self::process_live_direct_products($ids, (array) ($payload['reasons'] ?? array()));
        wp_die('', '', array('response' => 200));
    }

    private static function process_live_direct_products(array $ids, array $reasons = array()) {
        $ids = array_values(array_unique(array_filter(array_map('intval', $ids))));
        if (!$ids) return;
        $started_at = microtime(true);
        foreach ($ids as $live_id) {
            self::add_live_sync_event($live_id, 'sending', __('Sending to PW Connect', 'pw-merchant-api-system'), __('Google Merchant API request is being prepared.', 'pw-merchant-api-system'), 'info');
        }

        if (get_transient('ptms_live_direct_lock')) {
            foreach ($ids as $id) self::enqueue_live_retry_product($id, (string) ($reasons[$id] ?? __('The product change will be tried again.', 'pw-merchant-api-system')), __('Live submission has been delayed due to another transaction.', 'pw-merchant-api-system'));
            return;
        }
        set_transient('ptms_live_direct_lock', '1', 2 * MINUTE_IN_SECONDS);
        try {
            $scope = PTMS_License_Client_V2::require_scope('merchant.sync', false);
            if (is_wp_error($scope)) {
                foreach ($ids as $id) self::enqueue_live_retry_product($id, (string) ($reasons[$id] ?? __('The product change will be tried again.', 'pw-merchant-api-system')), __('Live submission has been delayed due to another transaction.', 'pw-merchant-api-system'));
                self::add_sync_log('warning', __('The push update was pushed to the queue due to license verification.', 'pw-merchant-api-system'), array('count' => count($ids)));
                foreach ($ids as $live_id) self::add_live_sync_event($live_id, 'queued', __('Placed in retry queue', 'pw-merchant-api-system'), __('Since the license verification could not be completed, the product was preserved and transferred to the queue.', 'pw-merchant-api-system'), 'warning');
                return;
            }

            $result = self::sync_products($ids);
            if (is_wp_error($result) || (int) ($result['failed'] ?? 0) > 0) {
                $message = is_wp_error($result) ? $result->get_error_message() : __('Google could not process some products.', 'pw-merchant-api-system');
                if (is_wp_error($result) && self::is_connection_reconnect_error($result)) {
                    self::mark_connection_reconnect_required($message, 'connection_not_found');
                    self::pause_live_products_for_connection($ids, $reasons, $message);
                    $duration = round(microtime(true) - $started_at, 2);
                    foreach ($ids as $live_id) {
                        self::add_live_sync_event(
                            $live_id,
                            'connection_paused',
                            __('Google connection must be re-established', 'pw-merchant-api-system'),
                            __('The product is protected in the queue and will be sent automatically after reconnection.', 'pw-merchant-api-system'),
                            'warning',
                            $duration
                        );
                    }
                    return;
                }
                foreach ($ids as $id) self::enqueue_live_retry_product($id, (string) ($reasons[$id] ?? __('The product change will be tried again.', 'pw-merchant-api-system')), $message);
                self::add_sync_log('warning', __('Instant Google Merchant update could not be completed; The products have been transferred to the secure retry queue.', 'pw-merchant-api-system'), array(
                    'product_ids' => $ids,
                    'message' => $message,
                ));
                $duration = round(microtime(true) - $started_at, 2);
                self::update_history_stats(0, count($ids), 'live');
                foreach ($ids as $live_id) self::add_live_sync_event($live_id, 'queued', __('Flash update could not be completed', 'pw-merchant-api-system'), $message . __('Placed in the retry queue.', 'pw-merchant-api-system'), 'warning', $duration);
                return;
            }

            update_option('ptms_last_sync_result', $result, false);
            self::update_history_stats((int) ($result['successful'] ?? count($ids)), (int) ($result['failed'] ?? 0), 'live');
            update_option('ptms_last_sync_activity_at', time(), false);
            delete_option('ptms_last_sync_error');
            self::update_live_product_status($ids, 'synced', __('Synced with Google Merchant.', 'pw-merchant-api-system'));
            self::remove_live_retry_products($ids);
            $duration = round(microtime(true) - $started_at, 2);
            self::add_sync_log('success', count($ids) === 1
                ? __('The product change was immediately sent to Google Merchant Center.', 'pw-merchant-api-system')
                : count($ids) . __('The product change was instantly sent to Google Merchant Center.', 'pw-merchant-api-system'), array(
                    'product_ids' => $ids,
                    'successful' => (int) ($result['successful'] ?? count($ids)),
                    'mode' => 'direct',
                    'duration' => $duration,
                ));
            foreach ($ids as $live_id) self::add_live_sync_event($live_id, 'synced', __('Google Merchant update completed', 'pw-merchant-api-system'), __('Google request accepted.', 'pw-merchant-api-system'), 'success', $duration);
        } catch (Throwable $e) {
            foreach ($ids as $id) self::enqueue_live_retry_product($id, (string) ($reasons[$id] ?? __('The product change will be tried again.', 'pw-merchant-api-system')), $e->getMessage());
            self::add_sync_log('warning', __('The instant update could not be completed for an unexpected reason; products have been transferred to the independent retry queue.', 'pw-merchant-api-system'), array('message' => $e->getMessage()));
            $duration = round(microtime(true) - $started_at, 2);
            self::update_history_stats(0, count($ids), 'live');
            foreach ($ids as $live_id) self::add_live_sync_event($live_id, 'queued', __('An unexpected problem occurred', 'pw-merchant-api-system'), $e->getMessage() . __('The product has been placed in the retry queue.', 'pw-merchant-api-system'), 'warning', $duration);
        } finally {
            delete_transient('ptms_live_direct_lock');
        }
    }

    private static function update_live_product_status(array $ids, $status, $message) {
        $map = (array) get_option('ptms_live_product_status', array());
        foreach ($ids as $id) {
            $map[(int) $id] = array('status' => sanitize_key($status), 'message' => sanitize_text_field($message), 'updated_at' => time());
        }
        if (count($map) > 200) $map = array_slice($map, -200, null, true);
        update_option('ptms_live_product_status', $map, false);
    }

    private static function enqueue_live_product($product_id, $reason = '') {
        // Geriye uyumluluk: eski çağrılar artık toplu kuyruğa değil bağımsız canlı retry kuyruğuna gider.
        self::enqueue_live_retry_product($product_id, $reason, __('Live trading has been retried.', 'pw-merchant-api-system'));
    }

    private static function enqueue_live_retry_product($product_id, $reason = '', $error = '') {
        $product_id = absint($product_id);
        if (!$product_id || get_post_status($product_id) !== 'publish' || self::is_product_excluded($product_id)) return;
        if ((bool)get_option('ptms_connection_reconnect_required', false)) {
            self::pause_live_products_for_connection(array($product_id), array($product_id => $reason), $error);
            return;
        }

        $queue = (array) get_option('ptms_live_retry_queue', array());
        $current = isset($queue[$product_id]) && is_array($queue[$product_id]) ? $queue[$product_id] : array();
        $attempt = max(0, (int) ($current['attempt'] ?? 0)) + 1;
        $delays = array(1 => 60, 2 => 180, 3 => 600);

        if ($attempt > 3) {
            unset($queue[$product_id]);
            update_option('ptms_live_retry_queue', $queue, false);
            self::update_live_product_status(array($product_id), 'failed', __('The product failed to ship after three automated attempts.', 'pw-merchant-api-system'));
            self::add_live_sync_event($product_id, 'failed', __('Live update failed', 'pw-merchant-api-system'), __('Three automated attempts were completed. The product should be checked in the Non-Added and Problematic Products section.', 'pw-merchant-api-system'), 'error');
            self::add_sync_log('error', __('The live product update failed to complete after three attempts.', 'pw-merchant-api-system'), array('product_id' => $product_id, 'technical_message' => $error));
            self::add_local_problem_product($product_id, $error !== '' ? $error : __('The live update failed to complete after three attempts.', 'pw-merchant-api-system'));
            return;
        }

        $next_at = time() + $delays[$attempt];
        $queue[$product_id] = array(
            'product_id' => $product_id,
            'reason' => sanitize_text_field($reason),
            'last_error' => sanitize_text_field($error),
            'attempt' => $attempt,
            'next_at' => $next_at,
            'updated_at' => time(),
        );
        update_option('ptms_live_retry_queue', $queue, false);
        self::update_live_product_status(array($product_id), 'retry', __('Automatic retry is scheduled.', 'pw-merchant-api-system'));
        self::add_live_sync_event($product_id, 'queued', __('Automatic retry scheduled', 'pw-merchant-api-system'), $attempt . '. deneme ' . wp_date('d.m.Y H:i:s', $next_at) . __('It will be held on.', 'pw-merchant-api-system'), 'warning');
        self::add_sync_log('warning', __('The live product update has been placed in the independent retry queue.', 'pw-merchant-api-system'), array(
            'product_id' => $product_id,
            'attempt' => $attempt,
            'next_at' => $next_at,
            'technical_message' => $error,
        ));
        self::schedule_live_retry_action($delays[$attempt]);
    }

    private static function pause_live_products_for_connection(array $ids, array $reasons = array(), $error = '') {
        $queue = (array)get_option('ptms_live_retry_queue', array());
        foreach (array_values(array_unique(array_filter(array_map('intval', $ids)))) as $product_id) {
            if (get_post_status($product_id) !== 'publish' || self::is_product_excluded($product_id)) continue;
            $current = isset($queue[$product_id]) && is_array($queue[$product_id]) ? $queue[$product_id] : array();
            $queue[$product_id] = array(
                'product_id' => $product_id,
                'reason' => sanitize_text_field((string)($reasons[$product_id] ?? $current['reason'] ?? __('The product change will be sent after the Google connection is re-established.', 'pw-merchant-api-system'))),
                'last_error' => sanitize_text_field((string)$error),
                'attempt' => max(0, (int)($current['attempt'] ?? 0)),
                'next_at' => 0,
                'paused' => true,
                'pause_reason' => 'google_reconnect_required',
                'updated_at' => time(),
            );
        }
        update_option('ptms_live_retry_queue', $queue, false);
        self::update_live_product_status($ids, 'waiting_connection', __('Waiting for Google connection; the product is protected in the queue.', 'pw-merchant-api-system'));
    }

    private static function remove_live_retry_products(array $ids) {
        $queue = (array) get_option('ptms_live_retry_queue', array());
        foreach (array_map('intval', $ids) as $id) unset($queue[$id]);
        update_option('ptms_live_retry_queue', $queue, false);
    }

    private static function schedule_live_retry_action($delay = 60) {
        $timestamp = time() + max(5, (int) $delay);
        if (!wp_next_scheduled('ptms_live_retry_event')) wp_schedule_single_event($timestamp, 'ptms_live_retry_event');
        if (function_exists('as_has_scheduled_action') && function_exists('as_schedule_single_action')) {
            if (!as_has_scheduled_action('ptms_live_retry_event', array(), 'pt-merchant-live')) {
                as_schedule_single_action($timestamp, 'ptms_live_retry_event', array(), 'pt-merchant-live', true);
            }
        }
    }

    public static function run_live_retry_queue() {
        if ((bool)get_option('ptms_connection_reconnect_required', false)) return;
        if (get_transient('ptms_live_retry_lock')) {
            self::schedule_live_retry_action(60);
            return;
        }
        $queue = (array) get_option('ptms_live_retry_queue', array());
        if (!$queue) return;

        $now = time();
        $ids = array();
        $reasons = array();
        foreach ($queue as $id => $row) {
            $id = absint($id);
            if (!$id || !is_array($row) || (int) ($row['next_at'] ?? 0) > $now) continue;
            $ids[] = $id;
            $reasons[$id] = (string) ($row['reason'] ?? 'Otomatik yeniden deneme');
            if (count($ids) >= 10) break;
        }
        if (!$ids) {
            $next = min(array_map(static function ($row) { return max(time() + 5, (int) ($row['next_at'] ?? time() + 60)); }, $queue));
            self::schedule_live_retry_action(max(5, $next - time()));
            return;
        }

        set_transient('ptms_live_retry_lock', '1', 5 * MINUTE_IN_SECONDS);
        try {
            self::process_live_direct_products($ids, $reasons);
        } finally {
            delete_transient('ptms_live_retry_lock');
        }

        if (get_option('ptms_live_retry_queue', array())) self::schedule_live_retry_action(60);
    }

    public static function resume_connection_queues() {
        $live_queue = (array)get_option('ptms_live_retry_queue', array());
        if ($live_queue) {
            foreach ($live_queue as $id => $row) {
                if (!is_array($row)) continue;
                unset($row['paused'], $row['pause_reason']);
                $row['next_at'] = time() + 5;
                $row['updated_at'] = time();
                $live_queue[$id] = $row;
            }
            update_option('ptms_live_retry_queue', $live_queue, false);
            self::schedule_live_retry_action(5);
        }

        $delete_queue = (array)get_option('ptms_delete_queue', array());
        if ($delete_queue) {
            foreach ($delete_queue as $offer_id => $row) {
                if (!is_array($row)) continue;
                unset($row['paused'], $row['pause_reason']);
                $row['next_at'] = time() + 5;
                $delete_queue[$offer_id] = $row;
            }
            update_option('ptms_delete_queue', $delete_queue, false);
            self::schedule_delete_retry_action(5);
        }

        if ((string)get_option('ptms_sync_status', '') === 'connection_paused'
            && (array)get_option('ptms_sync_queue', array())) {
            update_option('ptms_sync_status', 'running', false);
            update_option('ptms_last_sync_activity_at', time(), false);
            delete_option('ptms_last_sync_error');
            self::schedule_next_batch(5);
        }
    }

    private static function schedule_live_wakeup() {
        // Eski çağrılar için geriye uyumluluk. Canlı retry artık toplu Worker'dan bağımsızdır.
        self::schedule_live_retry_action(60);
        self::schedule_delete_retry_action(60);
    }

    public static function run_live_sync_wakeup() {
        self::run_live_retry_queue();
        self::run_delete_retry_queue();
    }

    public static function capture_trashed_product($post_id) {
        $post = get_post($post_id);
        if ($post && in_array($post->post_type, array('product','product_variation'), true)) {
            self::queue_product_tree_delete($post_id, $post);
        }
    }

    public static function capture_deleted_product($post_id, $post) {
        if ($post && in_array($post->post_type, array('product','product_variation'), true)) {
            self::queue_product_tree_delete($post_id, $post);
        }
    }

    private static function queue_product_tree_delete($product_id, $post) {
        if ($post && $post->post_type === 'product' && function_exists('wc_get_product')) {
            $product = wc_get_product((int)$product_id);
            if ($product && $product->is_type('variable')) {
                foreach ((array)$product->get_children() as $variation_id) {
                    self::queue_product_delete((int)$variation_id, get_post((int)$variation_id));
                }
            }
        }
        // Ana ürün eski sürümlerde ayrı teklif olarak gönderilmiş olabilir.
        self::queue_product_delete($product_id, $post);
    }

    private static function queue_product_delete($product_id, $post = null) {
        if (!(bool) get_option('ptms_auto_sync_enabled', true)) return;
        $product_id = absint($product_id);
        if (!$product_id) return;

        // Ürün kaydı silinmeden önce Offer ID mutlaka yakalanır.
        $sku = trim((string) get_post_meta($product_id, '_sku', true));
        if ($sku === '' && function_exists('wc_get_product')) {
            $product = wc_get_product($product_id);
            if ($product) $sku = trim((string) $product->get_sku());
        }
        $offer_id = $sku !== '' ? $sku : (string) $product_id;
        $title = $post && isset($post->post_title) ? (string) $post->post_title : (string) get_the_title($product_id);
        self::queue_offer_delete($offer_id, $product_id, $title);
    }

    public static function queue_offer_delete($offer_id, $product_id = 0, $title = '', $dispatch = true) {
        $offer_id = substr(trim(sanitize_text_field((string) $offer_id)), 0, 150);
        if ($offer_id === '') return false;
        $queue = (array) get_option('ptms_delete_queue', array());
        $existing = isset($queue[$offer_id]) && is_array($queue[$offer_id]) ? $queue[$offer_id] : array();
        $queue[$offer_id] = array_merge($existing, array(
            'offer_id' => $offer_id,
            'product_id' => absint($product_id),
            'product_title' => sanitize_text_field($title),
            'content_language' => self::merchant_content_language(),
            'feed_label' => self::merchant_feed_label(),
            'queued_at' => (int) ($existing['queued_at'] ?? time()),
            'attempt' => (int) ($existing['attempt'] ?? 0),
            'next_at' => time(),
            'last_error' => '',
        ));
        update_option('ptms_delete_queue', $queue, false);
        self::add_sync_log('info', __('Product deletion detected; Google Merchant removal request initiated.', 'pw-merchant-api-system'), array('offer_id' => $offer_id, 'product_id' => $product_id));
        if ((bool)get_option('ptms_connection_reconnect_required', false)) {
            self::pause_deleted_offers_for_connection(array($offer_id), __('Waiting for Google connection.', 'pw-merchant-api-system'));
            return true;
        }

        // Silme akışı toplu kuyruğun Durdur/Devam Et durumundan bağımsızdır.
        // Loopback/asenkron admin-ajax isteği bazı hostinglerde kabul edilip hiç çalışmayabilir.
        // Bu nedenle doğrudan tetiklemeyi denerken güvenli zamanlayıcı yedeğini DAİMA kur.
        self::schedule_delete_retry_action(30);
        if ($dispatch && !get_transient('ptms_live_delete_lock')) {
            self::dispatch_live_direct_delete(array($offer_id));
        }
        return true;
    }

    private static function dispatch_live_direct_delete(array $offer_ids) {
        $offer_ids = array_values(array_unique(array_filter(array_map('strval', $offer_ids))));
        if (!$offer_ids) return false;
        $token = wp_generate_uuid4();
        set_transient('ptms_live_delete_' . md5($token), $offer_ids, 10 * MINUTE_IN_SECONDS);
        $signature = hash_hmac('sha256', $token . '|' . implode(',', $offer_ids) . '|' . (string) get_option('ptms_install_id', ''), wp_salt('auth'));
        $response = wp_remote_post(admin_url('admin-ajax.php'), array(
            'timeout' => 1,
            'blocking' => false,
            'sslverify' => apply_filters('https_local_ssl_verify', false),
            'body' => array('action' => 'ptms_live_direct_delete', 'token' => $token, 'signature' => $signature),
        ));
        return !is_wp_error($response);
    }

    public static function handle_live_direct_delete() {
        $token = sanitize_text_field((string) ($_POST['token'] ?? ''));
        $signature = sanitize_text_field((string) ($_POST['signature'] ?? ''));
        $offer_ids = array_values(array_unique(array_filter(array_map('strval', (array) get_transient('ptms_live_delete_' . md5($token))))));
        $expected = $offer_ids ? hash_hmac('sha256', $token . '|' . implode(',', $offer_ids) . '|' . (string) get_option('ptms_install_id', ''), wp_salt('auth')) : '';
        if ($token === '' || $signature === '' || $expected === '' || !hash_equals($expected, $signature)) { status_header(403); exit; }
        delete_transient('ptms_live_delete_' . md5($token));

        if (get_transient('ptms_live_delete_lock')) {
            self::schedule_delete_retry_action(60);
            wp_die('', '', array('response' => 202));
        }
        set_transient('ptms_live_delete_lock', '1', 5 * MINUTE_IN_SECONDS);
        try {
            $result = self::sync_deleted_offers($offer_ids);
            if (!is_wp_error($result)) {
                self::complete_deleted_offers($offer_ids);
                self::add_sync_log('success', __('The deleted product has been removed from Google Merchant Center.', 'pw-merchant-api-system'), array('offer_ids' => $offer_ids));
            } elseif (self::is_connection_reconnect_error($result)) {
                self::mark_connection_reconnect_required($result->get_error_message(), 'connection_not_found');
                self::pause_deleted_offers_for_connection($offer_ids, $result->get_error_message());
            } else {
                self::fail_deleted_offers($offer_ids, $result->get_error_message());
            }
        } finally {
            delete_transient('ptms_live_delete_lock');
        }
        wp_die('', '', array('response' => 200));
    }

    private static function complete_deleted_offers(array $offer_ids) {
        $queue = (array) get_option('ptms_delete_queue', array());
        foreach ($offer_ids as $offer_id) unset($queue[(string) $offer_id]);
        update_option('ptms_delete_queue', $queue, false);
    }

    private static function fail_deleted_offers(array $offer_ids, $error) {
        $queue = (array) get_option('ptms_delete_queue', array());
        $failed = (array) get_option('ptms_delete_failed_queue', array());
        $delays = array(1 => 60, 2 => 180, 3 => 600);
        foreach ($offer_ids as $offer_id) {
            $offer_id = (string) $offer_id;
            $row = isset($queue[$offer_id]) && is_array($queue[$offer_id]) ? $queue[$offer_id] : array('offer_id' => $offer_id, 'queued_at' => time());
            $attempt = max(0, (int) ($row['attempt'] ?? 0)) + 1;
            if ($attempt > 3) {
                $row['attempt'] = 3;
                $row['failed_at'] = time();
                $row['last_error'] = sanitize_text_field($error);
                $failed[$offer_id] = $row;
                unset($queue[$offer_id]);
                self::add_sync_log('error', __('The product could not be removed from Google Merchant after three attempts.', 'pw-merchant-api-system'), array('offer_id' => $offer_id, 'technical_message' => $error));
                continue;
            }
            $row['attempt'] = $attempt;
            $row['next_at'] = time() + $delays[$attempt];
            $row['last_error'] = sanitize_text_field($error);
            $queue[$offer_id] = $row;
            self::add_sync_log('warning', __('The Google Merchant product removal request will be retried.', 'pw-merchant-api-system'), array(
                'offer_id' => $offer_id,
                'attempt' => $attempt,
                'next_at' => $row['next_at'],
                'technical_message' => $error,
            ));
        }
        update_option('ptms_delete_queue', $queue, false);
        update_option('ptms_delete_failed_queue', $failed, false);
        if ($queue) self::schedule_delete_retry_action(60);
    }

    private static function pause_deleted_offers_for_connection(array $offer_ids, $error = '') {
        $queue = (array)get_option('ptms_delete_queue', array());
        foreach ($offer_ids as $offer_id) {
            $offer_id = (string)$offer_id;
            $row = isset($queue[$offer_id]) && is_array($queue[$offer_id])
                ? $queue[$offer_id]
                : array('offer_id' => $offer_id, 'queued_at' => time(), 'attempt' => 0);
            $row['next_at'] = 0;
            $row['paused'] = true;
            $row['pause_reason'] = 'google_reconnect_required';
            $row['last_error'] = sanitize_text_field((string)$error);
            $queue[$offer_id] = $row;
        }
        update_option('ptms_delete_queue', $queue, false);
    }

    private static function schedule_delete_retry_action($delay = 60) {
        $timestamp = time() + max(5, (int) $delay);
        if (!wp_next_scheduled('ptms_delete_retry_event')) wp_schedule_single_event($timestamp, 'ptms_delete_retry_event');
        if (function_exists('as_has_scheduled_action') && function_exists('as_schedule_single_action')) {
            if (!as_has_scheduled_action('ptms_delete_retry_event', array(), 'pt-merchant-delete')) {
                as_schedule_single_action($timestamp, 'ptms_delete_retry_event', array(), 'pt-merchant-delete', true);
            }
        }
    }

    public static function run_delete_retry_queue() {
        if ((bool)get_option('ptms_connection_reconnect_required', false)) return;
        if (get_transient('ptms_live_delete_lock')) {
            self::schedule_delete_retry_action(60);
            return;
        }
        $queue = (array) get_option('ptms_delete_queue', array());
        if (!$queue) return;
        $now = time();
        $offer_ids = array();
        foreach ($queue as $offer_id => $row) {
            if (!is_array($row) || (int) ($row['next_at'] ?? 0) > $now) continue;
            $offer_ids[] = (string) $offer_id;
            if (count($offer_ids) >= 25) break;
        }
        if (!$offer_ids) {
            self::schedule_delete_retry_action(60);
            return;
        }

        set_transient('ptms_live_delete_lock', '1', 5 * MINUTE_IN_SECONDS);
        try {
            $result = self::sync_deleted_offers($offer_ids);
            if (is_wp_error($result) && self::is_connection_reconnect_error($result)) {
                self::mark_connection_reconnect_required($result->get_error_message(), 'connection_not_found');
                self::pause_deleted_offers_for_connection($offer_ids, $result->get_error_message());
            } elseif (is_wp_error($result)) self::fail_deleted_offers($offer_ids, $result->get_error_message());
            else {
                self::complete_deleted_offers($offer_ids);
                self::add_sync_log('success', __('Products in the pending deletion queue have been removed from Google Merchant Center.', 'pw-merchant-api-system'), array('offer_ids' => $offer_ids));
            }
        } finally {
            delete_transient('ptms_live_delete_lock');
        }
        if (get_option('ptms_delete_queue', array())) self::schedule_delete_retry_action(60);
    }

    private static function add_local_problem_product($product_id, $message) {
        $product_id = absint($product_id);
        if (!$product_id) return;
        $cache = (array) get_option('ptms_merchant_issues_cache', array());
        $products = (array) ($cache['products'] ?? array());
        $offer_id = self::product_offer_id($product_id);
        $replacement = array(
            'offer_id' => $offer_id,
            'title' => get_the_title($product_id),
            'status' => 'disapproved',
            'issues' => array(array('title' => sanitize_text_field($message), 'code' => 'pw_live_sync_failed')),
            'local_product_id' => $product_id,
        );
        $found = false;
        foreach ($products as $index => $product) {
            if ((string) ($product['offer_id'] ?? '') === (string) $offer_id) {
                $products[$index] = array_merge((array) $product, $replacement);
                $found = true;
                break;
            }
        }
        if (!$found) $products[] = $replacement;
        $cache['ok'] = true;
        $cache['products'] = $products;
        $cache['checked_timestamp'] = time();
        update_option('ptms_merchant_issues_cache', $cache, false);
    }

    private static function normalize_queue_origin($origin) {
        $origin = sanitize_key((string) $origin);
        $allowed = array('initial', 'manual', 'scheduled', 'retry', 'controlled', 'live-retry');
        return in_array($origin, $allowed, true) ? $origin : '';
    }

    private static function set_queue_origin($origin, array $context = array()) {
        $origin = self::normalize_queue_origin($origin);
        if ($origin === '') return false;
        update_option('ptms_sync_queue_origin', $origin, false);
        update_option('ptms_sync_queue_session', wp_generate_uuid4(), false);
        update_option('ptms_sync_queue_context', array_merge(array(
            'origin' => $origin,
            'created_at' => time(),
        ), $context), false);
        return true;
    }

    private static function clear_queue_origin() {
        delete_option('ptms_sync_queue_origin');
        delete_option('ptms_sync_queue_session');
        delete_option('ptms_sync_queue_context');
    }

    public static function schedule_full_sync($timestamp, $mode = 'scheduled') {
        $timestamp = max(time() + 5, (int) $timestamp);
        self::cancel_scheduled_sync();
        $token = wp_generate_uuid4();
        update_option('ptms_sync_schedule', array('token' => $token, 'timestamp' => $timestamp, 'mode' => sanitize_key($mode), 'origin' => 'scheduled', 'status' => 'planned', 'created_at' => time()), false);
        if (function_exists('as_schedule_single_action')) as_schedule_single_action($timestamp, 'ptms_start_scheduled_sync', array($token), 'pt-merchant', true);
        wp_schedule_single_event($timestamp, 'ptms_start_scheduled_sync', array($token), true);
        self::add_sync_log('info', __('Full synchronization planned.', 'pw-merchant-api-system'), array('timestamp' => $timestamp, 'mode' => $mode));
        return $timestamp;
    }

    public static function cancel_scheduled_sync() {
        $schedule = (array) get_option('ptms_sync_schedule', array());
        $token = (string) ($schedule['token'] ?? '');
        if ($token !== '') wp_clear_scheduled_hook('ptms_start_scheduled_sync', array($token));
        if (function_exists('as_unschedule_all_actions')) as_unschedule_all_actions('ptms_start_scheduled_sync', null, 'pt-merchant');
        update_option('ptms_sync_schedule', array(), false);
    }

    public static function run_scheduled_sync($token = '') {
        $schedule = (array) get_option('ptms_sync_schedule', array());
        $expected = (string) ($schedule['token'] ?? '');
        if ($expected === '' || !hash_equals($expected, (string) $token)) return;
        if ((string) get_option('ptms_sync_status', 'ready') === 'running') {
            $next = time() + 10 * MINUTE_IN_SECONDS;
            update_option('ptms_sync_schedule', array_merge($schedule, array('timestamp' => $next, 'status' => 'deferred')), false);
            wp_schedule_single_event($next, 'ptms_start_scheduled_sync', array($token), true);
            self::add_sync_log('warning', __('The planned synchronization was delayed by 10 minutes due to a running process.', 'pw-merchant-api-system'));
            return;
        }
        $scope = PTMS_License_Client_V2::require_scope('merchant.bulk_send', true);
        if (is_wp_error($scope)) {
            update_option('ptms_sync_schedule', array_merge($schedule, array(
                'status' => 'blocked',
                'blocked_at' => time(),
                'error' => $scope->get_error_code(),
            )), false);
            self::add_sync_log('warning', __('The planned synchronization was not started due to license security check.', 'pw-merchant-api-system'), array(
                'scope' => 'merchant.bulk_send',
                'error_code' => $scope->get_error_code(),
            ));
            return;
        }

        update_option('ptms_sync_schedule', array_merge($schedule, array('status' => 'started', 'started_at' => time())), false);
        self::start_full_sync('scheduled');
        update_option('ptms_sync_schedule', array(), false);
    }

    public static function sync_schedule_status() {
        return (array) get_option('ptms_sync_schedule', array());
    }

    private static function ensure_catalog_renewal_schedule() {
        if (!wp_next_scheduled('ptms_catalog_renewal_event')) {
            wp_schedule_event(time() + HOUR_IN_SECONDS, 'daily', 'ptms_catalog_renewal_event');
        }
    }

    public static function run_catalog_renewal() {
        self::ensure_catalog_renewal_schedule();
        if (!(bool)get_option('ptms_has_completed_initial_sync', false)) return;
        $last = (int)get_option('ptms_last_catalog_renewal_at', 0);
        if ($last <= 0) $last = (int)get_option('ptms_last_sync_completed_at', 0);
        if ($last > 0 && time() - $last < 25 * DAY_IN_SECONDS) return;
        if ((string)get_option('ptms_sync_status', 'ready') === 'running') return;
        if ((array)get_option('ptms_sync_queue', array()) || (array)get_option('ptms_sync_failed_queue', array())) return;

        $scope = class_exists('PTMS_License_Client_V2')
            ? PTMS_License_Client_V2::require_scope('merchant.sync', false)
            : new WP_Error('license_client_missing', __('License security client is unavailable.', 'pw-merchant-api-system'));
        if (is_wp_error($scope)) return;

        update_option('ptms_catalog_renewal_in_progress', time(), false);
        $result = self::start_full_sync('scheduled');
        if (is_wp_error($result)) delete_option('ptms_catalog_renewal_in_progress');
        elseif ((int)$result === 0) self::complete_catalog_renewal_if_needed();
    }

    private static function complete_catalog_renewal_if_needed() {
        if (!get_option('ptms_catalog_renewal_in_progress')) return;
        update_option('ptms_last_catalog_renewal_at', time(), false);
        delete_option('ptms_catalog_renewal_in_progress');
    }

    public static function product_offer_id($product_id) {
        $p = function_exists('wc_get_product') ? wc_get_product((int) $product_id) : null;
        if (!$p) return (string) (int) $product_id;
        $sku = trim((string) $p->get_sku());
        return $sku !== '' ? $sku : (string) (int) $product_id;
    }

    public static function merchant_content_language() {
        $locale = str_replace('-', '_', (string) determine_locale());
        $language = strtolower((string) strtok($locale, '_'));
        if (!preg_match('/^[a-z]{2,3}$/', $language)) $language = 'en';
        return (string) apply_filters('ptms_merchant_content_language', $language);
    }

    public static function merchant_feed_label() {
        $country = '';
        if (function_exists('wc_get_base_location')) {
            $location = (array) wc_get_base_location();
            $country = strtoupper((string) ($location['country'] ?? ''));
        }
        if (!preg_match('/^[A-Z]{2}$/', $country)) {
            $locale = str_replace('-', '_', (string) determine_locale());
            $parts = array_values(array_filter(explode('_', $locale)));
            $country = isset($parts[1]) ? strtoupper(substr((string) $parts[1], 0, 2)) : 'US';
        }
        return (string) apply_filters('ptms_merchant_feed_label', $country);
    }

    public static function is_product_excluded($product_id) {
        return get_post_meta((int) $product_id, '_ptms_exclude', true) === 'yes';
    }

    public static function product_identifiers($product_id, $product = null) {
        $product = $product ?: (function_exists('wc_get_product') ? wc_get_product((int) $product_id) : null);
        $gtin = class_exists('PTMS_Data_Detector') ? PTMS_Data_Detector::detect_gtin($product) : array('value'=>'','source'=>'');
        $mpn  = class_exists('PTMS_Data_Detector') ? PTMS_Data_Detector::detect_mpn($product) : array('value'=>'','source'=>'');
        return array('gtin'=>$gtin, 'mpn'=>$mpn);
    }

    private static function record_api_usage($action, $count, $ok, $message = '') {
        $day = wp_date('Y-m-d');
        $all = (array) get_option('ptms_api_usage', array());
        $row = isset($all[$day]) && is_array($all[$day]) ? $all[$day] : array('requests'=>0,'products'=>0,'success'=>0,'failed'=>0,'last_at'=>0,'last_action'=>'','last_message'=>'');
        $row['requests'] = (int)$row['requests'] + 1;
        $row['products'] = (int)$row['products'] + max(0,(int)$count);
        $row[$ok ? 'success' : 'failed'] = (int)$row[$ok ? 'success' : 'failed'] + 1;
        $row['last_at'] = time(); $row['last_action'] = sanitize_key($action); $row['last_message'] = sanitize_text_field($message);
        $all[$day] = $row;
        if (count($all) > 31) $all = array_slice($all, -31, null, true);
        update_option('ptms_api_usage', $all, false);
    }

    public static function api_usage_status() {
        $all=(array)get_option('ptms_api_usage',array()); $day=wp_date('Y-m-d');
        return array('today'=>isset($all[$day])?(array)$all[$day]:array('requests'=>0,'products'=>0,'success'=>0,'failed'=>0,'last_at'=>0,'last_action'=>'','last_message'=>''),'history'=>$all);
    }

    private static function payload_text($value, $limit) {
        $value = wp_strip_all_tags((string) $value, true);
        $value = html_entity_decode($value, ENT_QUOTES | ENT_HTML5, get_bloginfo('charset') ?: 'UTF-8');
        $value = trim((string) preg_replace('/\s+/u', ' ', $value));
        if ($limit > 0 && function_exists('mb_substr')) { return mb_substr($value, 0, $limit); }
        return $limit > 0 ? substr($value, 0, $limit) : $value;
    }

    private static function optimize_product_payload(array $payload) {
        $attrs = isset($payload['productAttributes']) && is_array($payload['productAttributes']) ? $payload['productAttributes'] : array();
        $attrs['title'] = self::payload_text($attrs['title'] ?? '', 150);
        $attrs['description'] = self::payload_text($attrs['description'] ?? '', 4500);
        $attrs['link'] = esc_url_raw((string) ($attrs['link'] ?? ''));
        $attrs['imageLink'] = esc_url_raw((string) ($attrs['imageLink'] ?? ''));
        if (!empty($attrs['brand'])) { $attrs['brand'] = self::payload_text($attrs['brand'], 70); }
        if (!empty($attrs['mpn'])) { $attrs['mpn'] = self::payload_text($attrs['mpn'], 70); }
        if (!empty($attrs['gtins']) && is_array($attrs['gtins'])) {
            $attrs['gtins'] = array_slice(array_values(array_unique(array_filter(array_map('sanitize_text_field', $attrs['gtins'])))), 0, 10);
        }
        foreach ($attrs as $key => $value) {
            if ($value === '' || $value === null || $value === array()) { unset($attrs[$key]); }
        }
        $payload['productAttributes'] = $attrs;
        return $payload;
    }

    public static function friendly_sync_error($message) {
        $message = trim((string) $message);
        $lower = function_exists('mb_strtolower') ? mb_strtolower($message, 'UTF-8') : strtolower($message);
        if (strpos($lower, 'curl error 28') !== false || strpos($lower, 'timed out') !== false) {
            return __('No timely response was received from Google. The product line will be retested safely.', 'pw-merchant-api-system');
        }
        if (strpos($lower, 'request body') !== false || strpos($lower, __('request body', 'pw-merchant-api-system')) !== false || strpos($lower, 'payload') !== false || strpos($lower, 'too large') !== false) {
            return __('The transmission data exceeds the allowed limit. PW Payload Optimizer will try again by reducing the product line.', 'pw-merchant-api-system');
        }
        return $message !== '' ? $message : __('Merchant product shipment failed.', 'pw-merchant-api-system');
    }

    public static function product_payload($product_id) {
        if (!function_exists('wc_get_product')) return new WP_Error('woocommerce_missing',__('WooCommerce is not enabled.', 'pw-merchant-api-system'));
        $p=wc_get_product($product_id); if(!$p) return new WP_Error('product_missing',__('Product not found.', 'pw-merchant-api-system'));
        $parent_id = $p->is_type('variation') ? (int) $p->get_parent_id() : 0;
        if (self::is_product_excluded($product_id) || ($parent_id && self::is_product_excluded($parent_id))) {
            return new WP_Error('product_excluded',__('The product has been excluded from Google Merchant submission.', 'pw-merchant-api-system'));
        }
        $parent = $parent_id ? wc_get_product($parent_id) : null;
        $offer=self::product_offer_id($product_id);
        $description=self::payload_text((string)($p->get_description() ?: $p->get_short_description()),4500);
        if ($description === '' && $parent) {
            $description=self::payload_text((string)($parent->get_description() ?: $parent->get_short_description()),4500);
        }
        $brandData=class_exists('PTMS_Data_Detector') ? PTMS_Data_Detector::detect_brand($p) : array();
        if (empty($brandData['value']) && $parent && class_exists('PTMS_Data_Detector')) {
            $brandData=PTMS_Data_Detector::detect_brand($parent);
        }
        $brand=(string)($brandData['value']??'');
        $ids=self::product_identifiers($product_id,$p);
        if ($parent && empty($ids['gtin']['value']) && empty($ids['mpn']['value'])) {
            $ids=self::product_identifiers($parent_id,$parent);
        }
        $gtin=(string)($ids['gtin']['value']??''); $mpn=(string)($ids['mpn']['value']??'');
        $price=(float)$p->get_price();
        $image_id=(int)$p->get_image_id();
        if (!$image_id && $parent) $image_id=(int)$parent->get_image_id();
        $link = method_exists($p, 'get_permalink') ? $p->get_permalink() : get_permalink($parent_id ?: $product_id);
        $attrs=[
          'title'=>self::payload_text($p->get_name(),150),
          'description'=>$description!==''?$description:self::payload_text($p->get_name(),150),
          'link'=>$link,
          'imageLink'=>wp_get_attachment_image_url($image_id,'full') ?: '',
          'availability'=>$p->is_in_stock()?'IN_STOCK':'OUT_OF_STOCK',
          'condition'=>'NEW',
          'price'=>['amountMicros'=>(string)round($price*1000000),'currencyCode'=>function_exists('get_woocommerce_currency')?get_woocommerce_currency():'TRY'],
        ];
        if ($parent) {
            $attrs['itemGroupId']=self::product_offer_id($parent_id);
            foreach ((array) $p->get_attributes() as $attribute_key => $attribute_value) {
                $key=strtolower((string)$attribute_key);
                $value=self::payload_text($attribute_value,100);
                if ($value==='') continue;
                if (in_array($key,array('pa_color','pa_colour','pa_renk','color','colour','renk'),true)) $attrs['color']=$value;
                if (in_array($key,array('pa_size','pa_beden','size','beden'),true)) $attrs['size']=$value;
            }
        }
        if($brand!=='') $attrs['brand']=$brand;
        if($mpn!=='') $attrs['mpn']=$mpn;
        if($gtin!=='') $attrs['gtins']=array_values(array_filter(array_map('trim',preg_split('/[,;\s]+/',$gtin))));
        if($brand==='' && $mpn==='' && $gtin==='') $attrs['identifierExists']=false;
        return self::optimize_product_payload([
            'contentLanguage'=>self::merchant_content_language(),
            'feedLabel'=>self::merchant_feed_label(),
            'offerId'=>$offer,
            'productAttributes'=>$attrs,
        ]);
    }

    private static function expand_product_ids(array $ids) {
        $expanded=array();
        foreach (array_values(array_unique(array_map('intval',$ids))) as $id) {
            $product=function_exists('wc_get_product') ? wc_get_product($id) : null;
            if (!$product) continue;
            if ($product->is_type('variable')) {
                foreach ((array)$product->get_children() as $variation_id) {
                    $variation=wc_get_product((int)$variation_id);
                    if ($variation && $variation->get_status()==='publish') $expanded[]=(int)$variation_id;
                }
                continue;
            }
            $expanded[]=$id;
        }
        return array_values(array_unique(array_filter($expanded)));
    }

    private static function send_product_payload_group(array $products, array $install, $connection, $merchant) {
        $data=['install_id'=>(string)$install['install_id'],'connection_id'=>$connection,'merchant_id'=>$merchant,'action'=>'upsert','products_json'=>wp_json_encode($products),'ts'=>time(),'nonce'=>bin2hex(random_bytes(16))];
        $data=self::signed_payload($data,(string)$install['install_secret']);
        $encoded=wp_json_encode($data);
        $resp=wp_remote_post(trailingslashit(PTMS_CONNECT_SERVICE_URL).'api/product-sync.php',['timeout'=>75,'headers'=>['Content-Type'=>'application/json','Accept'=>'application/json'],'body'=>$encoded,'sslverify'=>true]);
        if(is_wp_error($resp)) return new WP_Error($resp->get_error_code(), self::friendly_sync_error($resp->get_error_message()));
        $body=json_decode((string)wp_remote_retrieve_body($resp),true);
        if(wp_remote_retrieve_response_code($resp)!==200||!is_array($body)||empty($body['ok'])) {
            $message=is_array($body)&&!empty($body['message'])?(string)$body['message']:__('Merchant product shipment failed.', 'pw-merchant-api-system');
            $remote_error=is_array($body)?sanitize_key((string)($body['error']??'sync_failed')):'sync_failed';
            $error=new WP_Error('ptms_connect_'.$remote_error,self::friendly_sync_error($message),array(
                'remote_error'=>$remote_error,
                'http_status'=>(int)wp_remote_retrieve_response_code($resp),
            ));
            if(self::is_connection_reconnect_error($error)) self::mark_connection_reconnect_required($message,$remote_error);
            return $error;
        }
        return $body;
    }

    public static function sync_products(array $ids) {
        $ready=self::connection_ready_for_sync(); if(is_wp_error($ready)) return $ready;
        $connection=(string)get_option('ptms_connection_id',''); $merchant=(string)get_option('ptms_merchant_id','');
        if($connection===''||$merchant==='') return new WP_Error('connection_missing',__('Complete the Google and Merchant Center connection first.', 'pw-merchant-api-system'));
        $install=self::ensure_install(); if(is_wp_error($install)) return $install;
        $products=[]; $offer_to_product=[];
        foreach(self::expand_product_ids($ids) as $id){
            $payload=self::product_payload($id);
            if(!is_wp_error($payload)){
                $products[]=$payload;
                $offer_id=(string)($payload['offerId']??'');
                if($offer_id!=='') $offer_to_product[$offer_id]=(int)$id;
            }
        }
        if(!$products) return new WP_Error('no_products',__('No valid products were found to send.', 'pw-merchant-api-system'));

        // PT Payload Optimizer: web sunucusu ve API gövde sınırlarını aşmamak için
        // ürünleri hem adet hem gerçek JSON boyutuna göre küçük gruplara ayırır.
        $groups=[]; $current=[]; $max_bytes=80*1024; $max_products=8;
        foreach($products as $product){
            $candidate=array_merge($current,array($product));
            $candidate_bytes=strlen((string)wp_json_encode($candidate));
            if($current && (count($candidate)>$max_products || $candidate_bytes>$max_bytes)){
                $groups[]=$current; $current=array($product);
            } else { $current=$candidate; }
        }
        if($current) $groups[]=$current;

        $successful=0; $failed=0; $failed_ids=[]; $last_body=array('ok'=>true);
        foreach($groups as $group){
            $result=self::send_product_payload_group($group,$install,$connection,$merchant);
            if(is_wp_error($result)){
                // Büyük gövde hatasında tek ürünlük gruplara kadar güvenli biçimde küçült.
                if(count($group)>1 && strpos($result->get_error_message(),__('exceeds the limit', 'pw-merchant-api-system'))!==false){
                    foreach($group as $single){
                        $single_result=self::send_product_payload_group(array($single),$install,$connection,$merchant);
                        $offer_id=(string)($single['offerId']??'');
                        if(is_wp_error($single_result)){
                            $failed++;
                            if(isset($offer_to_product[$offer_id])) $failed_ids[]=$offer_to_product[$offer_id];
                            self::record_api_usage('upsert',1,false,$single_result->get_error_message());
                        } else {
                            $single_failed=(int)($single_result['failed']??0);
                            $single_success=(int)($single_result['successful']??($single_failed>0?0:1));
                            $successful+=$single_success; $failed+=$single_failed;
                            foreach((array)($single_result['results']??array()) as $row){
                                if (!empty($row['ok'])) {
                                    $sent_offer=(string)($row['offerId']??$offer_id);
                                    if(isset($offer_to_product[$sent_offer])) update_post_meta($offer_to_product[$sent_offer],'_ptms_last_successful_sync_at',time());
                                }
                                if(empty($row['ok'])){
                                    $failed_offer=(string)($row['offerId']??$offer_id);
                                    if(isset($offer_to_product[$failed_offer])) $failed_ids[]=$offer_to_product[$failed_offer];
                                }
                            }
                            self::record_api_usage('upsert',1,$single_failed===0,$single_failed===0?__('Google request accepted.', 'pw-merchant-api-system'):__('Google did not accept some products.', 'pw-merchant-api-system'));
                            $last_body=$single_result;
                        }
                    }
                    continue;
                }
                foreach($group as $row){
                    $offer_id=(string)($row['offerId']??'');
                    if(isset($offer_to_product[$offer_id])) $failed_ids[]=$offer_to_product[$offer_id];
                }
                self::record_api_usage('upsert',count($group),false,$result->get_error_message());
                return $result;
            }
            $group_success=(int)($result['successful']??count($group));
            $group_failed=(int)($result['failed']??0);
            $successful+=$group_success;
            $failed+=$group_failed;
            foreach((array)($result['results']??array()) as $row){
                if (!empty($row['ok'])) {
                    $sent_offer=(string)($row['offerId']??'');
                    if(isset($offer_to_product[$sent_offer])) update_post_meta($offer_to_product[$sent_offer],'_ptms_last_successful_sync_at',time());
                }
                if(empty($row['ok'])){
                    $failed_offer=(string)($row['offerId']??'');
                    if(isset($offer_to_product[$failed_offer])) $failed_ids[]=$offer_to_product[$failed_offer];
                }
            }
            self::record_api_usage('upsert',count($group),$group_failed===0,$group_failed===0?__('Google request accepted.', 'pw-merchant-api-system'):__('Google did not accept some products.', 'pw-merchant-api-system'));
            $last_body=$result;
        }
        $last_body['ok']=true;
        $last_body['successful']=$successful;
        $last_body['failed']=$failed;
        $last_body['failed_ids']=array_values(array_unique(array_filter(array_map('intval',$failed_ids))));
        $last_body['payload_optimizer']=array('enabled'=>true,'groups'=>count($groups),'max_group_products'=>$max_products,'max_group_bytes'=>$max_bytes);
        return $last_body;
    }

    private static function sync_deleted_offers(array $offer_ids) {
        $offer_ids = array_values(array_unique(array_filter(array_map('strval', $offer_ids))));
        if (!$offer_ids) return array('ok' => true, 'successful' => 0, 'failed' => 0);
        $ready=self::connection_ready_for_sync(); if(is_wp_error($ready)) return $ready;
        $connection=(string)get_option('ptms_connection_id',''); $merchant=(string)get_option('ptms_merchant_id','');
        if($connection===''||$merchant==='') return new WP_Error('connection_missing',__('Complete the Google and Merchant Center connection first.', 'pw-merchant-api-system'));
        $install=self::ensure_install(); if(is_wp_error($install)) return $install;
        $queue=(array)get_option('ptms_delete_queue',array());
        $delete_items=array();
        foreach(array_slice($offer_ids,0,25) as $offer_id){
            $row=isset($queue[$offer_id])&&is_array($queue[$offer_id])?$queue[$offer_id]:array();
            $delete_items[]=array(
                'offer_id'=>$offer_id,
                'content_language'=>(string)($row['content_language']??self::merchant_content_language()),
                'feed_label'=>(string)($row['feed_label']??self::merchant_feed_label()),
            );
        }
        $data=['install_id'=>(string)$install['install_id'],'connection_id'=>$connection,'merchant_id'=>$merchant,'action'=>'delete','offer_ids_json'=>wp_json_encode($delete_items),'ts'=>time(),'nonce'=>bin2hex(random_bytes(16))];
        $data=self::signed_payload($data,(string)$install['install_secret']);
        $resp=wp_remote_post(trailingslashit(PTMS_CONNECT_SERVICE_URL).'api/product-sync.php',['timeout'=>60,'headers'=>['Content-Type'=>'application/json','Accept'=>'application/json'],'body'=>wp_json_encode($data),'sslverify'=>true]);
        if(is_wp_error($resp)){ self::record_api_usage('delete',count($offer_ids),false,$resp->get_error_message()); return $resp; } $body=json_decode((string)wp_remote_retrieve_body($resp),true);
        $http_code=wp_remote_retrieve_response_code($resp);
        $failed_count=is_array($body)?(int)($body['failed']??0):count($offer_ids);
        if($http_code!==200||!is_array($body)||empty($body['ok'])||$failed_count>0){
            $message=is_array($body)&&!empty($body['message'])?(string)$body['message']:__('Merchant product deletion failed.', 'pw-merchant-api-system');
            self::record_api_usage('delete',count($offer_ids),false,$message);
            $remote_error=is_array($body)?sanitize_key((string)($body['error']??'delete_failed')):'delete_failed';
            $error=new WP_Error('ptms_connect_'.$remote_error,$message,array('remote_error'=>$remote_error,'http_status'=>(int)$http_code));
            if(self::is_connection_reconnect_error($error)) self::mark_connection_reconnect_required($message,$remote_error);
            return $error;
        }
        self::record_api_usage('delete',count($offer_ids),true,__('Google deletion request accepted.', 'pw-merchant-api-system'));
        return $body;
    }

    public static function delete_offer_now($offer_id) {
        $offer_id=substr(trim(sanitize_text_field((string)$offer_id)),0,150);
        if($offer_id==='') return new WP_Error('offer_id_missing',__('Offer ID cannot be left blank.', 'pw-merchant-api-system'));
        $result=self::sync_deleted_offers(array($offer_id));
        if(is_wp_error($result)){
            self::add_sync_log('error',__('Offer ID could not be removed from Google Merchant Center.', 'pw-merchant-api-system'),array('offer_id'=>$offer_id,'technical_message'=>$result->get_error_message()));
            return $result;
        }
        self::add_sync_log('success',__('Offer ID has been removed from Google Merchant Center.', 'pw-merchant-api-system'),array('offer_id'=>$offer_id));
        return $result;
    }

    public static function merchant_issue_dictionary() {
        return array(
            'missing_gtin' => array('title'=>'GTIN (barkod) eksik','explanation'=>__('Google could not find a valid GTIN value to properly identify the product.', 'pw-merchant-api-system'),'solution'=>__('Enter the GTIN/EAN barcode provided by the manufacturer in the PW Feed field on the product editing screen.', 'pw-merchant-api-system'),'priority'=>'high'),
            'invalid_gtin' => array('title'=>__('GTIN (barcode) is invalid', 'pw-merchant-api-system'),'explanation'=>__('The entered barcode format or check digit was not found valid by Google.', 'pw-merchant-api-system'),'solution'=>__('Verify the barcode from the packaging or manufacturer source; Do not use spaces and special characters.', 'pw-merchant-api-system'),'priority'=>'high'),
            'missing_brand' => array('title'=>'Marka bilgisi eksik','explanation'=>__('The brand information of the product could not be found by Google.', 'pw-merchant-api-system'),'solution'=>__('Check the WooCommerce brand domain or brand source detected by PW Feed.', 'pw-merchant-api-system'),'priority'=>'high'),
            'missing_mpn' => array('title'=>'MPN bilgisi eksik','explanation'=>__('Manufacturer part number not found. MPN is important for branded products that do not have a GTIN.', 'pw-merchant-api-system'),'solution'=>__('Enter the manufacturer\'s part number in the PW Feed field on the product edit screen.', 'pw-merchant-api-system'),'priority'=>'medium'),
            'image_link_invalid' => array('title'=>__('Product image cannot be accessed', 'pw-merchant-api-system'),'explanation'=>__('Google was unable to download the main product image or found the image link not valid.', 'pw-merchant-api-system'),'solution'=>__('Check that the main image is open to the public, uses HTTPS, and is not blocked by robots/firewall.', 'pw-merchant-api-system'),'priority'=>'high'),
            'image_link_broken' => array('title'=>__('Product image cannot be opened', 'pw-merchant-api-system'),'explanation'=>__('The submitted image link returns an error.', 'pw-merchant-api-system'),'solution'=>__('Re-save the product\'s main image and check the image URL in the incognito window in the browser.', 'pw-merchant-api-system'),'priority'=>'high'),
            'price_mismatch' => array('title'=>__('price dispute', 'pw-merchant-api-system'),'explanation'=>__('The price sent to Google is not the same as the price seen on the product page.', 'pw-merchant-api-system'),'solution'=>__('Check WooCommerce price, sale price dates, currency and structured data; then resend the product.', 'pw-merchant-api-system'),'priority'=>'high'),
            'availability_mismatch' => array('title'=>__('Stock status does not match', 'pw-merchant-api-system'),'explanation'=>__('The stock status sent to Google and the status on the product page appear to be different.', 'pw-merchant-api-system'),'solution'=>__('Control WooCommerce stock management and cache; Resubmit after the product page is updated.', 'pw-merchant-api-system'),'priority'=>'high'),
            'missing_description' => array('title'=>__('Product description is missing', 'pw-merchant-api-system'),'explanation'=>__('Google could not find an adequate description for the product.', 'pw-merchant-api-system'),'solution'=>__('Add a unique and clear description explaining the product\'s key features.', 'pw-merchant-api-system'),'priority'=>'medium'),
            'missing_title' => array('title'=>__('Product name is missing', 'pw-merchant-api-system'),'explanation'=>__('The product title submitted to Google is empty or insufficient.', 'pw-merchant-api-system'),'solution'=>__('Edit the WooCommerce product name to include the brand, model, and base product type.', 'pw-merchant-api-system'),'priority'=>'high'),
            'landing_page_error' => array('title'=>__('Unable to access product page', 'pw-merchant-api-system'),'explanation'=>__('Google received an error when opening the product link.', 'pw-merchant-api-system'),'solution'=>__('Check that the product page returns a 200 status code, does not require login, and is not blocked by security plugins.', 'pw-merchant-api-system'),'priority'=>'high'),
            'policy_violation' => array('title'=>__('Problem with Google policy', 'pw-merchant-api-system'),'explanation'=>__('Google has reported a policy compliance issue for the product or store.', 'pw-merchant-api-system'),'solution'=>__('Review Merchant Center policy detail; Correct product content and store policies according to Google guidelines.', 'pw-merchant-api-system'),'priority'=>'high'),
            'pending_initial_crawl' => array('title'=>'Google incelemesi bekleniyor','explanation'=>__('The product has not yet been fully scanned and evaluated by Google.', 'pw-merchant-api-system'),'solution'=>__('The process is usually completed automatically. Make sure the product page is accessible and wait.', 'pw-merchant-api-system'),'priority'=>'low'),
            'local_inventory_data_missing' => array('title'=>'Yerel envanter verileri eksik','explanation'=>__('This is a Merchant Center account setting, not product content. Google is waiting for stock data based on physical stores.', 'pw-merchant-api-system'),'solution'=>__('If you only sell online, turn off Free local listings and Local inventory ads in Merchant Center.', 'pw-merchant-api-system'),'priority'=>'low'),
        );
    }

    private static function normalize_issue_code($code) {
        $code = strtolower(trim((string)$code));
        $code = preg_replace('/[^a-z0-9]+/', '_', $code);
        return trim((string)$code, '_');
    }

    public static function translate_merchant_issue(array $issue) {
        $code = self::normalize_issue_code($issue['code'] ?? '');
        $dictionary = self::merchant_issue_dictionary();
        $match = $dictionary[$code] ?? null;
        if (!$match) {
            foreach ($dictionary as $key => $row) {
                if ($code !== '' && (strpos($code, $key) !== false || strpos($key, $code) !== false)) { $match = $row; break; }
            }
        }

        // Google bazen genel bir kod döndürür; açıklama/ayrıntı içindeki anlamlı
        // anahtar kelimelerden daha faydalı bir Türkçe açıklama seçmeye çalış.
        if (!$match) {
            $raw_haystack = strtolower(implode(' ', array(
                (string)($issue['code'] ?? ''),
                (string)($issue['description'] ?? ''),
                (string)($issue['detail'] ?? ''),
                (string)($issue['attribute'] ?? ''),
            )));
            $keyword_map = array(
                'gtin' => 'missing_gtin',
                'barcode' => 'missing_gtin',
                'barkod' => 'missing_gtin',
                'brand' => 'missing_brand',
                'marka' => 'missing_brand',
                'mpn' => 'missing_mpn',
                'image' => 'image_link_invalid',
                __('visual', 'pw-merchant-api-system') => 'image_link_invalid',
                'price' => 'price_mismatch',
                'fiyat' => 'price_mismatch',
                'availability' => 'availability_mismatch',
                'stock' => 'availability_mismatch',
                'stok' => 'availability_mismatch',
                'landing page' => 'landing_page_error',
                'destination url' => 'landing_page_error',
                'policy' => 'policy_violation',
                'politika' => 'policy_violation',
                'description' => 'missing_description',
                'title' => 'missing_title',
                'processing' => 'pending_initial_crawl',
                'crawl' => 'pending_initial_crawl',
            );
            foreach ($keyword_map as $needle => $dict_key) {
                if (strpos($raw_haystack, $needle) !== false && isset($dictionary[$dict_key])) {
                    $match = $dictionary[$dict_key];
                    break;
                }
            }
        }

        if (!$match) {
            $match = array(
                'title' => __('Product suitability issue', 'pw-merchant-api-system'),
                'explanation' => __('Google has reported a compliance issue in one or more release areas of this product. The scope of the problem is shown in the non-technical details below.', 'pw-merchant-api-system'),
                'solution' => __('Check the affected area, broadcast method and country first. If the product information does not require changes, wait for Google to reprocess it or open the relevant help link.', 'pw-merchant-api-system'),
                'priority' => 'medium',
            );
        }
        $severity = strtoupper((string)($issue['severity'] ?? ''));
        if ($severity === 'DISAPPROVED') $match['priority'] = 'high';
        elseif ($severity === 'DEMOTED' && $match['priority'] === 'low') $match['priority'] = 'medium';
        $match['code'] = $code !== '' ? $code : 'unknown_issue';
        $match['severity'] = $severity;
        $match['attribute'] = sanitize_key((string)($issue['attribute'] ?? ''));
        $contexts = (array)($issue['reportingContexts'] ?? array());
        if (!$contexts && !empty($issue['reportingContext'])) $contexts = array((string)$issue['reportingContext']);
        $match['reporting_contexts'] = array_values(array_unique(array_filter(array_map('sanitize_text_field', $contexts))));
        $match['applicable_countries'] = array_values(array_unique(array_filter(array_map('sanitize_text_field', (array)($issue['applicableCountries'] ?? array())))));
        $match['resolution'] = sanitize_key(strtolower((string)($issue['resolution'] ?? '')));
        $match['documentation'] = esc_url_raw((string)($issue['documentation'] ?? ''));
        $match['occurrence_count'] = max(1, (int)($issue['occurrenceCount'] ?? 1));
        return $match;
    }

    private static function schedule_merchant_snapshot_refresh($delay = 300) {
        $timestamp = time() + max(60, (int) $delay);
        if (!wp_next_scheduled('ptms_refresh_merchant_snapshot_event')) {
            wp_schedule_single_event($timestamp, 'ptms_refresh_merchant_snapshot_event');
        }
        if (function_exists('as_has_scheduled_action') && function_exists('as_schedule_single_action')) {
            if (!as_has_scheduled_action('ptms_refresh_merchant_snapshot_event', array(), 'pt-merchant-status')) {
                as_schedule_single_action($timestamp, 'ptms_refresh_merchant_snapshot_event', array(), 'pt-merchant-status', true);
            }
        }
    }

    public static function refresh_merchant_snapshot_after_sync() {
        $result = self::refresh_merchant_issues(true);
        if (is_wp_error($result)) {
            self::add_sync_log('warning', __('After the mass action, Google product status has not yet been renewed; Will check again later.', 'pw-merchant-api-system'), array('error' => $result->get_error_message()));
            self::schedule_merchant_snapshot_refresh(300);
            return;
        }
        self::add_sync_log('success', __('Google product numbers were updated after batch processing.', 'pw-merchant-api-system'));
    }

    public static function refresh_merchant_issues($force = false) {
        $cached = (array)get_option('ptms_merchant_issues_cache', array());
        $checked = (int)($cached['checked_timestamp'] ?? 0);
        if (!$force && !empty($cached['ok']) && $checked > time() - 15 * MINUTE_IN_SECONDS) return $cached;
        $ready=self::connection_ready_for_sync(); if(is_wp_error($ready)) return $ready;
        $connection=(string)get_option('ptms_connection_id',''); $merchant=(string)get_option('ptms_merchant_id','');
        if($connection===''||$merchant==='') return new WP_Error('connection_missing',__('Complete the Google and Merchant Center connection first.', 'pw-merchant-api-system'));
        $install=self::ensure_install(); if(is_wp_error($install)) return $install;
        $data=array('install_id'=>(string)$install['install_id'],'connection_id'=>$connection,'merchant_id'=>$merchant,'ts'=>time(),'nonce'=>bin2hex(random_bytes(16)));
        $data=self::signed_payload($data,(string)$install['install_secret']);
        $resp=wp_remote_post(trailingslashit(PTMS_CONNECT_SERVICE_URL).'api/product-issues.php',array('timeout'=>180,'headers'=>array('Content-Type'=>'application/json','Accept'=>'application/json'),'body'=>wp_json_encode($data),'sslverify'=>true));
        if(is_wp_error($resp)) return $resp;
        $body=json_decode((string)wp_remote_retrieve_body($resp),true);
        if(wp_remote_retrieve_response_code($resp)!==200||!is_array($body)||empty($body['ok'])) {
            $message=is_array($body)&&!empty($body['message'])?(string)$body['message']:__('Merchant issues could not be received.', 'pw-merchant-api-system');
            $remote_error=is_array($body)?sanitize_key((string)($body['error']??'issues_failed')):'issues_failed';
            $error=new WP_Error('ptms_connect_'.$remote_error,$message,array('remote_error'=>$remote_error,'http_status'=>(int)wp_remote_retrieve_response_code($resp)));
            if(self::is_connection_reconnect_error($error)) self::mark_connection_reconnect_required($message,$remote_error);
            return $error;
        }

        // Offer ID bazında ikinci kez tekilleştir. PW Connect zaten tekilleştirir; bu katman
        // eski servis sürümleri ve yinelenen API kayıtları için savunma sağlar.
        $products_by_offer=array(); $issue_counts=array();
        foreach((array)($body['products']??array()) as $product){
            if(!is_array($product)) continue;
            $offer=sanitize_text_field((string)($product['offerId']??''));
            $fallback=sanitize_text_field((string)($product['name']??''));
            $key=$offer!==''?'offer:'.$offer:'name:'.$fallback;
            if($key==='name:') continue;
            if(!isset($products_by_offer[$key])){
                $products_by_offer[$key]=array(
                    'offer_id'=>$offer,
                    'title'=>sanitize_text_field((string)($product['title']??'')),
                    'link'=>esc_url_raw((string)($product['link']??'')),
                    'image'=>esc_url_raw((string)($product['imageLink']??'')),
                    'brand'=>sanitize_text_field((string)($product['brand']??'')),
                    'issues'=>array(),
                    'status'=>'approved',
                );
            }

            $translated=array();
            foreach((array)($product['issues']??array()) as $issue){
                if(!is_array($issue)) continue;
                $row=self::translate_merchant_issue($issue);
                // Aynı sorun farklı yayın hedefi/ülke için tekrar gelebilir. Kullanıcıya
                // aynı açıklamayı çoğaltmak yerine kapsam bilgilerini tek kayıtta birleştir.
                $issue_key=md5(wp_json_encode(array($row['code'],$row['severity'],$row['attribute'],$row['resolution'])));
                if(!isset($translated[$issue_key])){
                    $translated[$issue_key]=$row;
                } else {
                    $translated[$issue_key]['reporting_contexts']=array_values(array_unique(array_filter(array_merge(
                        (array)($translated[$issue_key]['reporting_contexts']??array()),
                        (array)($row['reporting_contexts']??array())
                    ))));
                    $translated[$issue_key]['applicable_countries']=array_values(array_unique(array_filter(array_merge(
                        (array)($translated[$issue_key]['applicable_countries']??array()),
                        (array)($row['applicable_countries']??array())
                    ))));
                    $translated[$issue_key]['occurrence_count']=max(1,(int)($translated[$issue_key]['occurrence_count']??1))+max(1,(int)($row['occurrence_count']??1));
                    if(empty($translated[$issue_key]['documentation'])&&!empty($row['documentation'])){
                        $translated[$issue_key]['documentation']=$row['documentation'];
                    }
                }
            }
            foreach($translated as $issue_key=>$row){
                if(isset($products_by_offer[$key]['issues'][$issue_key])){
                    $existing=(array)$products_by_offer[$key]['issues'][$issue_key];
                    $row['reporting_contexts']=array_values(array_unique(array_filter(array_merge((array)($existing['reporting_contexts']??array()),(array)($row['reporting_contexts']??array())))));
                    $row['applicable_countries']=array_values(array_unique(array_filter(array_merge((array)($existing['applicable_countries']??array()),(array)($row['applicable_countries']??array())))));
                    $row['occurrence_count']=max(1,(int)($existing['occurrence_count']??1))+max(1,(int)($row['occurrence_count']??1));
                    if(empty($row['documentation'])&&!empty($existing['documentation'])) $row['documentation']=$existing['documentation'];
                }
                $products_by_offer[$key]['issues'][$issue_key]=$row;
            }

            // PW Connect v1.5.4 hedef ülke ve etkin yayın yöntemlerine göre nihai
            // uygunluk durumunu hesaplar. Yerel katman itemLevelIssues var diye ürünü
            // otomatik sorunlu saymaz; aksi halde bilgilendirme uyarıları tüm kataloğu şişirir.
            $remote_status=sanitize_key((string)($product['eligibilityStatus']??''));
            $allowed_statuses=array('approved','limited','pending','disapproved');
            if(in_array($remote_status,$allowed_statuses,true)){
                $new_status=$remote_status;
            } else {
                // Eski PW Connect sürümleri için geriye dönük güvenli yorumlama.
                $has_approved=false; $has_pending=false; $has_disapproved=false;
                foreach((array)($product['destinationStatuses']??array()) as $destination){
                    if(!empty($destination['approvedCountries'])) $has_approved=true;
                    if(!empty($destination['pendingCountries'])) $has_pending=true;
                    if(!empty($destination['disapprovedCountries'])) $has_disapproved=true;
                }
                $new_status=$has_approved?'approved':($has_pending?'pending':($has_disapproved?'disapproved':'pending'));
            }
            $rank=array('approved'=>0,'limited'=>1,'pending'=>2,'disapproved'=>3);
            if(($rank[$new_status]??0)>($rank[$products_by_offer[$key]['status']]??0)) $products_by_offer[$key]['status']=$new_status;
        }

        $problem_products=array(); $severity_counts=array('high'=>0,'medium'=>0,'low'=>0); $critical_products=0; $total_findings=0;
        foreach($products_by_offer as $product){
            $product['issues']=array_values((array)$product['issues']);
            // Merchant Center ana sayaçlarıyla aynı anlam: yalnızca hedef ülkede hiçbir
            // etkin yayın yönteminde uygun olmayan veya inceleme bekleyen ürün sorunludur.
            // "limited" ürünler yayınlanabildiği için uygun toplamına dahildir.
            $is_problem=in_array((string)$product['status'],array('disapproved','pending'),true);
            if(!$is_problem) continue;

            $has_critical=((string)$product['status']==='disapproved');
            foreach($product['issues'] as $row){
                $issue_counts[$row['code']]=($issue_counts[$row['code']]??0)+1;
                $severity_counts[$row['priority']]=($severity_counts[$row['priority']]??0)+1;
                $total_findings++;
                if($row['priority']==='high' && (string)$product['status']==='disapproved') $has_critical=true;
            }
            if($has_critical) $critical_products++;
            $problem_products[]=$product;
        }

        $remote_account_issues = array();
        foreach ((array)($body['account_issues'] ?? array()) as $account_issue) {
            if (!is_array($account_issue)) continue;
            $remote_account_issues[] = array(
                'code' => self::normalize_issue_code($account_issue['code'] ?? ''),
                'scope' => 'account',
                'category' => sanitize_key((string)($account_issue['category'] ?? 'account_setting')),
                'severity' => sanitize_key(strtolower((string)($account_issue['severity'] ?? 'warning'))),
                'title' => sanitize_text_field((string)($account_issue['title'] ?? __('Merchant Center account setting', 'pw-merchant-api-system'))),
                'explanation' => sanitize_text_field((string)($account_issue['explanation'] ?? __('There is a product-independent settings alert in your Merchant Center account.', 'pw-merchant-api-system'))),
                'solution' => sanitize_text_field((string)($account_issue['solution'] ?? __('Check Merchant Center account settings.', 'pw-merchant-api-system'))),
                'documentation' => esc_url_raw((string)($account_issue['documentation'] ?? '')),
                'affected_products' => max(0, (int)($account_issue['affected_products'] ?? 0)),
            );
        }

        $remote_summary=(array)($body['summary']??array());
        $summary=array(
            'total'=>(int)($remote_summary['total']??count($products_by_offer)),
            'with_issues'=>(int)($remote_summary['with_issues']??count($problem_products)),
            'clean'=>(int)($remote_summary['clean']??max(0,count($products_by_offer)-count($problem_products))),
            'critical_products'=>(int)($remote_summary['critical_products']??$critical_products),
            'total_findings'=>(int)($remote_summary['total_findings']??$total_findings),
            'disapproved'=>(int)($remote_summary['disapproved']??0),
            'limited'=>(int)($remote_summary['limited']??0),
            'pending'=>(int)($remote_summary['pending']??0),
            'approved'=>(int)($remote_summary['approved']??0),
        );
        // Ekrandaki ana sayaçlar daima yerel tekilleştirilmiş listeyle tutarlı olsun.
        $summary['with_issues']=count($problem_products);
        $summary['clean']=max(0,$summary['total']-$summary['with_issues']);
        $summary['critical_products']=$critical_products;
        $summary['total_findings']=$total_findings;

        arsort($issue_counts);
        $remote_data_source=(array)($body['data_source']??array());
        if((string)($remote_data_source['display_name']??'') !== 'PW Merchant API' || empty($remote_data_source['version_lock'])) {
            return new WP_Error('datasource_lock_failed',__('Issue Center can only read the PW Merchant API data source. Check for PW Connect update.', 'pw-merchant-api-system'));
        }

        $cache=array(
            'ok'=>true,'merchant_id'=>$merchant,'summary'=>$summary,'products'=>$problem_products,
            'data_source'=>array(
                'name'=>sanitize_text_field((string)($remote_data_source['name']??'')),
                'display_name'=>'PW Merchant API',
                'version_lock'=>true,
                'target_countries'=>array_values(array_map('sanitize_text_field',(array)($remote_data_source['target_countries']??array('TR')))),
            ),
            'ignored_other_sources'=>(int)($body['ignored_other_sources']??0),
            'issue_counts'=>$issue_counts,'severity_counts'=>$severity_counts,
            'account_issues'=>$remote_account_issues,
            'online_only_mode'=>!empty($body['online_only_mode']),
            'collection_complete'=>array_key_exists('collection_complete',$body)?!empty($body['collection_complete']):empty($body['truncated']),
            'truncated'=>!empty($body['truncated']),'pages_read'=>(int)($body['pages_read']??0),
            'checked_at'=>sanitize_text_field((string)($body['checked_at']??gmdate('c'))),'checked_timestamp'=>time(),
        );
        update_option('ptms_merchant_issues_cache',$cache,false);
        return $cache;
    }

    public static function merchant_issues($page=1,$per_page=50,$filter='all',$code='') {
        $cache=(array)get_option('ptms_merchant_issues_cache',array());
        if(empty($cache['ok'])) {
            $fresh=self::refresh_merchant_issues(false);
            if(is_wp_error($fresh)) return $fresh;
            $cache=$fresh;
        }
        $rows=array(); $filter=sanitize_key($filter); $code=self::normalize_issue_code($code);
        foreach((array)($cache['products']??array()) as $product){
            $issues=(array)($product['issues']??array());
            if($code!=='') $issues=array_values(array_filter($issues,static function($i)use($code){return (string)($i['code']??'')===$code;}));
            if($filter==='disapproved' && (string)($product['status']??'')!=='disapproved') continue;
            if($filter==='pending' && (string)($product['status']??'')!=='pending') continue;
            if($filter==='limited' && (string)($product['status']??'')!=='limited') continue;
            if($code!=='' && !$issues) continue;
            $product['issues']=$issues; $rows[]=$product;
        }
        $total=count($rows); $per_page=max(1,min(100,(int)$per_page)); $pages=max(1,(int)ceil($total/$per_page)); $page=max(1,min($pages,(int)$page));
        return array('rows'=>array_slice($rows,($page-1)*$per_page,$per_page),'total'=>$total,'page'=>$page,'pages'=>$pages,'per_page'=>$per_page,'cache'=>$cache);
    }

    private static function run_migrations($stored = null) {
        $stored = $stored === null ? (string) get_option('ptms_version', '') : (string) $stored;
        self::migrate_worker_state($stored);
        self::migrate_error_state($stored);
        self::migrate_retry_queue_state($stored);
        self::migrate_delete_queue_state($stored);
        self::migrate_connection_state($stored);
        update_option('ptms_version', PTMS_VERSION, false);
    }

    private static function migrate_worker_state($stored) {
        if (version_compare($stored ?: '0', '1.4.0', '>=')) { return; }

        $total = (int) get_option('ptms_sync_total', 0);
        $remaining = count((array) get_option('ptms_sync_queue', array()));
        $done = (int) get_option('ptms_sync_done', 0);
        $failed = (int) get_option('ptms_sync_failed', 0);
        $claimed_by_old_worker = max(0, $total - $remaining - $done - $failed);

        // v1.3.1 kuyruğu batch'i API çağrısından önce düşürüyordu. Kabul edilen fakat
        // sayaçlara yazılamayan ürünleri mevcut kuyruk matematiğinden güvenle uzlaştır.
        if ($claimed_by_old_worker > 0 && (string) get_option('ptms_sync_status', '') === 'running') {
            update_option('ptms_sync_done', $done + $claimed_by_old_worker, false);
            update_option('ptms_last_sync_activity_at', time(), false);
            self::add_sync_log('info', __('Processed items from previous worker session are reconciled with counters.', 'pw-merchant-api-system'), array('count' => $claimed_by_old_worker));
        }

        delete_transient('ptms_sync_worker_lock');
        delete_option('ptms_sync_inflight');
    }


    private static function migrate_error_state($stored) {
        if (version_compare($stored ?: '0', '1.5.6', '>=')) { return; }

        $status = (string) get_option('ptms_sync_status', 'ready');
        $remaining = count((array) get_option('ptms_sync_queue', array()));
        $failed_queue_ids = array_values(array_unique(array_filter(array_map('intval', (array) get_option('ptms_sync_failed_queue', array())))));
        $failed_queue = count($failed_queue_ids);

        // Tamamlanmış veya beklemedeki işlemden kalan teknik hata, aktif hata değildir.
        // Ayrıntı Worker günlüklerinde korunur; kullanıcı ekranındaki kalıcı kırmızı uyarı temizlenir.
        if (in_array($status, array('completed', 'ready'), true) && $remaining === 0) {
            delete_option('ptms_last_sync_error');
        } else {
            $error = (string) get_option('ptms_last_sync_error', '');
            if ($error !== '') {
                update_option('ptms_last_sync_error', self::friendly_sync_error($error), false);
            }
        }
    }


    private static function migrate_retry_queue_state($stored) {
        if (version_compare($stored ?: '0', '1.9.9', '>=')) { return; }

        $failed = max(0, (int) get_option('ptms_sync_failed', 0));
        $queue = array_values(array_unique(array_filter(array_map('intval', (array) get_option('ptms_sync_failed_queue', array())))));

        // Eski sürümlerde bir grupta tek ürün hata verdiğinde grubun tamamı retry
        // kuyruğuna yazılabiliyordu. Kullanıcıya yanlış adet göstermemek ve gereksiz
        // yeniden gönderimleri önlemek için kuyruk son oturumun gerçek başarısız
        // ürün adediyle sınırlandırılır.
        if (count($queue) > $failed) {
            $queue = $failed > 0 ? array_slice($queue, 0, $failed) : array();
            update_option('ptms_sync_failed_queue', $queue, false);
            self::add_sync_log('info', __('Old retry records have been reconciled with the actual number of failed items.', 'pw-merchant-api-system'), array(
                'failed_products' => $failed,
                'retry_queue' => count($queue),
            ));
        }

    }

    private static function migrate_delete_queue_state($stored) {
        if (version_compare($stored ?: '0', '1.9.32', '>=')) { return; }

        $old = (array) get_option('ptms_delete_queue', array());
        $new = array();
        foreach ($old as $key => $row) {
            $offer_id = is_array($row)
                ? trim((string) ($row['offer_id'] ?? $key))
                : trim((string) $row);
            if ($offer_id === '') continue;
            $existing = is_array($row) ? $row : array();
            $new[$offer_id] = array_merge(array(
                'offer_id' => $offer_id,
                'product_id' => 0,
                'product_title' => '',
                'content_language' => self::merchant_content_language(),
                'feed_label' => self::merchant_feed_label(),
                'queued_at' => time(),
                'attempt' => 0,
                'next_at' => time(),
                'last_error' => '',
            ), $existing);
        }
        update_option('ptms_delete_queue', $new, false);
    }

    private static function migrate_connection_state($stored) {
        if (version_compare($stored ?: '0', '1.9.35', '>=')) return;
        update_option(
            'ptms_connection_state',
            get_option('ptms_google_connected', false) ? 'unverified' : 'not_connected',
            false
        );
        delete_transient('ptms_verified_connection_status');
        delete_transient('ptms_admin_connection_check_lock');
        if ((bool)get_option('ptms_google_connected', false) && !wp_next_scheduled('ptms_verify_connection_event')) {
            wp_schedule_single_event(time() + 15, 'ptms_verify_connection_event');
        }
    }

    private static function schedule_next_batch($delay = 2) {
        $status = (string) get_option('ptms_sync_status', 'ready');
        if ($status !== 'running') { return false; }

        // Tek bir görev anahtarı, üç farklı uyandırma katmanı tarafından paylaşılır.
        // İlk çalışan tetikleyici anahtarı tüketir; geciken diğer tetikleyiciler işlem yapmadan çıkar.
        $token = wp_generate_uuid4();
        $args = array($token);
        $timestamp = time() + max(1, (int) $delay);
        $scheduled = false;

        update_option('ptms_next_worker_token', $token, false);
        update_option('ptms_next_worker_at', $timestamp, false);

        // 1) WooCommerce Action Scheduler: izlenebilir birincil zamanlayıcı.
        if (function_exists('as_schedule_single_action')) {
            $action_id = as_schedule_single_action($timestamp, 'ptms_sync_batch_event', $args, 'pt-merchant', true);
            if ($action_id) { $scheduled = true; }
        }

        // 2) WordPress Cron: Action Scheduler gecikirse bağımsız yedek tetikleyici.
        $cron_result = wp_schedule_single_event($timestamp, 'ptms_sync_batch_event', $args, true);
        if (!is_wp_error($cron_result) && $cron_result) { $scheduled = true; }

        // 3) PW Async Runner: batch zincirini cron turunu beklemeden uyandırır.
        if (self::dispatch_async_worker($token, $delay)) { $scheduled = true; }

        if (!$scheduled) {
            delete_option('ptms_next_worker_token');
            delete_option('ptms_next_worker_at');
            self::add_sync_log('error', __('The next worker task could not be scheduled. Watchdog will try again.', 'pw-merchant-api-system'));
        }
        return $scheduled;
    }

    private static function dispatch_async_worker($token, $delay = 1) {
        $token = sanitize_text_field((string) $token);
        if ($token === '') { return false; }

        $signature = hash_hmac('sha256', $token . '|' . (string) get_option('ptms_install_id', ''), wp_salt('auth'));
        set_transient('ptms_async_token_' . md5($token), $signature, 10 * MINUTE_IN_SECONDS);

        $response = wp_remote_post(admin_url('admin-ajax.php'), array(
            'timeout' => 0.01,
            'blocking' => false,
            'sslverify' => apply_filters('https_local_ssl_verify', false),
            'body' => array(
                'action' => 'ptms_async_worker',
                'token' => $token,
                'signature' => $signature,
                'delay' => max(0, min(10, (int) $delay)),
            ),
        ));
        return !is_wp_error($response);
    }

    public static function handle_async_worker() {
        $token = sanitize_text_field((string) ($_POST['token'] ?? ''));
        $signature = sanitize_text_field((string) ($_POST['signature'] ?? ''));
        $expected = (string) get_transient('ptms_async_token_' . md5($token));
        if ($token === '' || $signature === '' || $expected === '' || !hash_equals($expected, $signature)) {
            status_header(403);
            exit;
        }
        delete_transient('ptms_async_token_' . md5($token));

        $delay = max(0, min(10, (int) ($_POST['delay'] ?? 0)));
        if ($delay > 0) { sleep($delay); }
        self::process_sync_batch($token);
        wp_die('', '', array('response' => 200));
    }

    private static function unschedule_workers() {
        $token = (string) get_option('ptms_next_worker_token', '');
        if (function_exists('as_unschedule_all_actions')) {
            as_unschedule_all_actions('ptms_sync_batch_event', null, 'pt-merchant');
        }
        if ($token !== '') { wp_clear_scheduled_hook('ptms_sync_batch_event', array($token)); }
        wp_clear_scheduled_hook('ptms_sync_batch_event', array());
        delete_option('ptms_next_worker_token');
        delete_option('ptms_next_worker_at');
    }

    private static function has_scheduled_worker() {
        $next_at = (int) get_option('ptms_next_worker_at', 0);
        if ($next_at > time() - 90) { return true; }

        if (function_exists('as_has_scheduled_action') && as_has_scheduled_action('ptms_sync_batch_event', null, 'pt-merchant')) {
            return true;
        }
        return (bool) wp_next_scheduled('ptms_sync_batch_event');
    }

    public static function worker_watchdog() {
        $status = (string) get_option('ptms_sync_status', 'ready');
        $queue = array_values(array_filter(array_map('intval', (array) get_option('ptms_sync_queue', array()))));
        if ($status !== 'running' || !$queue || get_transient('ptms_sync_worker_lock')) { return; }

        $last_activity = (int) get_option('ptms_last_sync_activity_at', 0);
        $stale = $last_activity === 0 || (time() - $last_activity) > 75;
        if (!$stale && self::has_scheduled_worker()) { return; }

        self::unschedule_workers();
        self::add_sync_log('warning', __('Automatic recovery was initiated because Worker progress was interrupted.', 'pw-merchant-api-system'), array('remaining' => count($queue)));
        update_option('ptms_last_sync_activity_at', time(), false);
        self::schedule_next_batch(1);
    }

    public static function full_sync_guard() {
        $queue = array_values(array_filter(array_map('intval', (array) get_option('ptms_sync_queue', array()))));
        $failed = array_values(array_filter(array_map('intval', (array) get_option('ptms_sync_failed_queue', array()))));
        $status = (string) get_option('ptms_sync_status', 'ready');
        if ($queue || $failed || in_array($status, array('running', 'paused', 'connection_paused', 'stopped'), true)) {
            return new WP_Error(
                'ptms_existing_queue',
                __('The current sync queue is maintained. Complete the waiting items or clear the queue first.', 'pw-merchant-api-system')
            );
        }
        return true;
    }

    public static function bulk_preflight() {
        $queue = array_values(array_filter(array_map('intval', (array) get_option('ptms_sync_queue', array()))));
        $failed = array_values(array_filter(array_map('intval', (array) get_option('ptms_sync_failed_queue', array()))));
        $status = (string) get_option('ptms_sync_status', 'ready');
        $connected = (bool) get_option('ptms_google_connected', false)
            && !(bool)get_option('ptms_connection_reconnect_required', false)
            && (string) get_option('ptms_connection_state', '') === 'active'
            && (string) get_option('ptms_merchant_id', '') !== '';
        $as_ready = function_exists('as_schedule_single_action');
        $wp_cron_ready = !(defined('DISABLE_WP_CRON') && DISABLE_WP_CRON);
        $lock = (bool) get_transient('ptms_sync_worker_lock');
        $scope = PTMS_License_Client_V2::require_scope('merchant.bulk_send', false);
        $license_ok = !is_wp_error($scope);
        $queue_blocked = !empty($queue) || !empty($failed) || in_array($status, array('running', 'paused', 'connection_paused', 'stopped'), true);

        return array(
            'queue_blocked' => $queue_blocked,
            'checks' => array(
                array('label' => 'Lisans yetkisi', 'ok' => $license_ok, 'message' => $license_ok ? __('Bulk sending authorization has been verified.', 'pw-merchant-api-system') : __('Bulk sending authorization could not be verified.', 'pw-merchant-api-system')),
                array('label' => __('PW Connect and Merchant account', 'pw-merchant-api-system'), 'ok' => $connected, 'message' => $connected ? __('Google link and Merchant account selected.', 'pw-merchant-api-system') : __('Google link or Merchant account is missing.', 'pw-merchant-api-system')),
                array('label' => 'Action Scheduler', 'ok' => $as_ready, 'message' => $as_ready ? __('Primary scheduler is ready.', 'pw-merchant-api-system') : __('Action Scheduler not found; WP-Cron backup will be used.', 'pw-merchant-api-system')),
                array('label' => __('WP-Cron backup', 'pw-merchant-api-system'), 'ok' => $wp_cron_ready, 'message' => $wp_cron_ready ? __('WP-Cron backup trigger can be used.', 'pw-merchant-api-system') : __('WP-Cron is disabled via wp-config.php.', 'pw-merchant-api-system')),
                array('label' => 'Worker kilidi', 'ok' => !$lock, 'message' => !$lock ? __('There is no Worker lock stuck.', 'pw-merchant-api-system') : __('A Worker process still appears to be locked.', 'pw-merchant-api-system')),
                array('label' => 'Mevcut kuyruk', 'ok' => !$queue_blocked, 'message' => !$queue_blocked ? __('A new full synchronization can be started.', 'pw-merchant-api-system') : count($queue) . ' bekleyen ve ' . count($failed) . __('The retriable product is protected.', 'pw-merchant-api-system')),
            ),
        );
    }

    public static function start_full_sync($origin = '') {
        $origin = self::normalize_queue_origin($origin);
        if (!in_array($origin, array('initial', 'manual', 'scheduled'), true)) {
            self::add_sync_log('error', __('Unauthorized full queue creation request was blocked.', 'pw-merchant-api-system'), array('origin' => (string) $origin));
            return new WP_Error('ptms_invalid_queue_origin', __('Full synchronization can only be initiated by initial setup, manual action or scheduled task.', 'pw-merchant-api-system'));
        }

        $connection_ready = self::connection_ready_for_sync();
        if (is_wp_error($connection_ready)) return $connection_ready;

        $guard = self::full_sync_guard();
        if (is_wp_error($guard)) { return $guard; }

        self::unschedule_workers();
        delete_transient('ptms_sync_worker_lock');
        delete_option('ptms_sync_inflight');

        $ids = get_posts(array(
            'post_type' => 'product', 'post_status' => 'publish', 'fields' => 'ids',
            'posts_per_page' => -1, 'orderby' => 'ID', 'order' => 'ASC',
            'meta_query' => array('relation'=>'OR', array('key'=>'_ptms_exclude','compare'=>'NOT EXISTS'), array('key'=>'_ptms_exclude','value'=>'yes','compare'=>'!=')),
        ));
        $ids = array_values(array_unique(array_map('intval', $ids)));
        if ((bool)get_option('ptms_has_completed_initial_sync', false)
            && !get_option('ptms_variable_parent_cleanup_done')
            && function_exists('wc_get_product')) {
            foreach ($ids as $parent_id) {
                $parent = wc_get_product($parent_id);
                if ($parent && $parent->is_type('variable')) {
                    // 1.9.31 ve öncesinde gönderilmiş ana değişken ürün teklifini
                    // kaldır; yeni model yalnız yayınlanabilir varyasyonları gönderir.
                    self::queue_offer_delete(self::product_offer_id($parent_id), $parent_id, $parent->get_name(), false);
                }
            }
            update_option('ptms_variable_parent_cleanup_done', time(), false);
        }
        $ids = self::expand_product_ids($ids);
        update_option('ptms_sync_queue', $ids, false);
        self::set_queue_origin($origin, array('total' => count($ids)));
        update_option('ptms_sync_failed_queue', array(), false);
        update_option('ptms_sync_total', count($ids), false);
        update_option('ptms_sync_done', 0, false);
        update_option('ptms_sync_failed', 0, false);
        update_option('ptms_sync_status', $ids ? 'running' : 'completed', false);
        update_option('ptms_last_sync_started_at', time(), false);
        update_option('ptms_last_sync_activity_at', time(), false);
        delete_option('ptms_last_sync_completed_at');
        delete_option('ptms_last_sync_error');
        self::add_sync_log('info', __('Full synchronization started.', 'pw-merchant-api-system'), array('total' => count($ids), 'origin' => $origin));
        if ($ids) { self::schedule_next_batch(2); }
        return count($ids);
    }

    public static function process_sync_batch($token = '') {
        $status = (string) get_option('ptms_sync_status', 'ready');
        if ($status !== 'running') { return; }

        // Her worker oturumu Google'a veri göndermeden önce merchant.sync yetkisini doğrular.
        // Karar 45 saniye token içermeyen kısa cache ile korunur; kuyruk asla silinmez.
        $scope = PTMS_License_Client_V2::require_scope('merchant.sync', false);
        if (is_wp_error($scope)) {
            update_option('ptms_sync_status', 'paused', false);
            update_option('ptms_last_sync_error', $scope->get_error_message(), false);
            update_option('ptms_last_sync_activity_at', time(), false);
            self::unschedule_workers();
            delete_transient('ptms_sync_worker_lock');
            self::add_sync_log('warning', __('Worker is safely paused due to license security check; the tail was preserved.', 'pw-merchant-api-system'), array(
                'scope' => 'merchant.sync',
                'error_code' => $scope->get_error_code(),
                'remaining' => count((array) get_option('ptms_sync_queue', array())),
            ));
            return;
        }

        $token = sanitize_text_field((string) $token);
        $expected_token = (string) get_option('ptms_next_worker_token', '');
        // Aynı batch için Action Scheduler, WP-Cron ve Async Runner birlikte planlanır.
        // İlk gelen anahtarı tüketir; gecikmiş/kopya tetikleyiciler sessizce bırakılır.
        if ($token !== '' && ($expected_token === '' || !hash_equals($expected_token, $token))) { return; }

        if (get_transient('ptms_sync_worker_lock')) {
            self::schedule_next_batch(12);
            return;
        }

        set_transient('ptms_sync_worker_lock', (string) $token ?: 'worker', 5 * MINUTE_IN_SECONDS);
        delete_option('ptms_next_worker_token');
        delete_option('ptms_next_worker_at');
        update_option('ptms_last_worker_started_at', time(), false);
        update_option('ptms_last_sync_activity_at', time(), false);

        $should_continue = false;
        try {
            // Silme kuyruğu bağımsız ptms_delete_retry_event tarafından işlenir.
            $queue = array_values(array_filter(array_map('intval', (array) get_option('ptms_sync_queue', array()))));
            if (!$queue) {
                update_option('ptms_sync_status', 'completed', false);
                self::clear_queue_origin();
                update_option('ptms_has_completed_initial_sync', true, false);
                update_option('ptms_last_sync_completed_at', time(), false);
                self::complete_catalog_renewal_if_needed();
                delete_option('ptms_last_sync_error');
                self::add_sync_log('success', __('Synchronization is complete.', 'pw-merchant-api-system'));
                self::schedule_merchant_snapshot_refresh(300);
                return;
            }

            // Batch, sonuçlar veritabanına yazılana kadar kuyruktan düşürülmez.
            // Beklenmeyen PHP kesintisinde aynı Offer ID güvenle yeniden güncellenir.
            $batch = array_slice($queue, 0, 50);
            update_option('ptms_sync_inflight', array('ids' => $batch, 'started_at' => time(), 'token' => (string) $token), false);

            $successful = 0;
            $failed = 0;
            $failed_ids = array();
            $errors = array();

            foreach (array_chunk($batch, 25) as $part) {
                $res = self::sync_products_with_retry($part);
                if (is_wp_error($res)) {
                    if (self::is_connection_reconnect_error($res)) {
                        self::mark_connection_reconnect_required($res->get_error_message(), 'connection_not_found');
                        update_option('ptms_sync_status', 'connection_paused', false);
                        update_option('ptms_last_sync_error', $res->get_error_message(), false);
                        update_option('ptms_last_sync_activity_at', time(), false);
                        delete_option('ptms_sync_inflight');
                        self::unschedule_workers();
                        self::add_sync_log('warning', __('Synchronization was paused because the Google connection must be re-established; the entire queue was preserved.', 'pw-merchant-api-system'), array(
                            'remaining' => count((array)get_option('ptms_sync_queue', array())),
                        ));
                        return;
                    }
                    $failed += count($part);
                    $failed_ids = array_merge($failed_ids, $part);
                    $errors[] = $res->get_error_message();
                    self::add_sync_log('error', $res->get_error_message(), array('product_ids' => $part));
                    continue;
                }

                $part_success = max(0, (int) ($res['successful'] ?? count($part)));
                $part_failed = max(0, (int) ($res['failed'] ?? 0));
                if (($part_success + $part_failed) < count($part)) {
                    $part_success += count($part) - ($part_success + $part_failed);
                }
                $successful += min(count($part), $part_success);
                $failed += min(count($part), $part_failed);
                if ($part_failed > 0) {
                    $reported_failed_ids = array_values(array_unique(array_filter(array_map('intval', (array) ($res['failed_ids'] ?? array())))));
                    // Yeni Connect yanıtı gerçek başarısız ürünleri Offer ID üzerinden
                    // döndürür. Eski yanıtta kimlik yoksa yalnız grubun tamamı başarısız
                    // olduğunda bütün grup retry kuyruğuna alınır; kısmi hatada başarılı
                    // ürünler yanlışlıkla yeniden deneme kuyruğuna yazılmaz.
                    if ($reported_failed_ids) {
                        $failed_ids = array_merge($failed_ids, $reported_failed_ids);
                    } elseif ($part_failed >= count($part)) {
                        $failed_ids = array_merge($failed_ids, $part);
                    }
                }
                update_option('ptms_last_sync_result', $res, false);
                self::add_sync_log($part_failed > 0 ? 'warning' : 'success', __('The product group has been processed into Google Merchant Center.', 'pw-merchant-api-system'), array('count' => count($part), 'successful' => $part_success, 'failed' => $part_failed));
            }

            $current_queue = array_values(array_filter(array_map('intval', (array) get_option('ptms_sync_queue', array()))));
            $batch_lookup = array_fill_keys($batch, true);
            $current_queue = array_values(array_filter($current_queue, static function ($id) use ($batch_lookup) {
                return !isset($batch_lookup[$id]);
            }));
            update_option('ptms_sync_queue', $current_queue, false);
            delete_option('ptms_sync_inflight');

            if ($failed_ids) {
                $old_failed = (array) get_option('ptms_sync_failed_queue', array());
                update_option('ptms_sync_failed_queue', array_values(array_unique(array_merge(array_map('intval', $old_failed), array_map('intval', $failed_ids)))), false);
            }

            update_option('ptms_sync_done', (int) get_option('ptms_sync_done', 0) + $successful, false);
            update_option('ptms_sync_failed', (int) get_option('ptms_sync_failed', 0) + $failed, false);
            self::update_history_stats($successful, $failed, 'bulk');
            update_option('ptms_last_sync_activity_at', time(), false);
            update_option('ptms_last_worker_completed_at', time(), false);
            if ($errors) { update_option('ptms_last_sync_error', implode(' | ', array_unique($errors)), false); }

            if ($current_queue) {
                $should_continue = true;
            } else {
                update_option('ptms_sync_status', 'completed', false);
                self::clear_queue_origin();
                update_option('ptms_has_completed_initial_sync', true, false);
                update_option('ptms_last_sync_completed_at', time(), false);
                self::complete_catalog_renewal_if_needed();
                delete_option('ptms_last_sync_error');
                self::add_sync_log('success', __('Synchronization queue completed.', 'pw-merchant-api-system'), array('failed' => (int) get_option('ptms_sync_failed', 0)));
                self::schedule_merchant_snapshot_refresh(300);
            }
        } catch (Throwable $e) {
            update_option('ptms_last_sync_error', $e->getMessage(), false);
            update_option('ptms_last_sync_activity_at', time(), false);
            self::add_sync_log('error', 'Worker beklenmeyen bir hatayla kesildi; batch kuyrukta korunuyor.', array('error' => $e->getMessage()));
            $should_continue = true;
        } finally {
            delete_transient('ptms_sync_worker_lock');
            if ($should_continue && (string) get_option('ptms_sync_status', '') === 'running') {
                self::schedule_next_batch(5);
            }
        }
    }

    private static function sync_products_with_retry(array $ids) {
        $last = null;
        for ($attempt = 1; $attempt <= 3; $attempt++) {
            $last = self::sync_products($ids);
            if (!is_wp_error($last)) { return $last; }
            if (self::is_connection_reconnect_error($last)) return $last;
            self::add_sync_log('warning', __('The Google request will be retried.', 'pw-merchant-api-system'), array('attempt' => $attempt, 'message' => $last->get_error_message()));
            if ($attempt < 3) { sleep($attempt * 2); }
        }
        return $last;
    }

    public static function control_sync($command) {
        $command = sanitize_key((string) $command);
        if ($command === 'pause') {
            update_option('ptms_sync_status', 'paused', false);
            self::unschedule_workers();
            self::add_sync_log('warning', __('Synchronization has been paused by the user.', 'pw-merchant-api-system'));
            return __('Synchronization is paused.', 'pw-merchant-api-system');
        }
        if ($command === 'resume') {
            if ((bool)get_option('ptms_connection_reconnect_required', false)) {
                return __('Google Merchant connection must be re-established before synchronization can resume.', 'pw-merchant-api-system');
            }
            $queue = (array) get_option('ptms_sync_queue', array());
            if (!$queue) return __('There are no outstanding products to be resumed.', 'pw-merchant-api-system');
            self::unschedule_workers();
            delete_transient('ptms_sync_worker_lock');
            update_option('ptms_sync_status', 'running', false);
            update_option('ptms_last_sync_activity_at', time(), false);
            self::add_sync_log('info', 'Senkronizasyon devam ettirildi.');
            self::schedule_next_batch(1);
            return 'Senkronizasyon devam ettirildi.';
        }
        if ($command === 'stop') {
            update_option('ptms_sync_status', 'stopped', false);
            self::unschedule_workers();
            delete_transient('ptms_sync_worker_lock');
            self::add_sync_log('warning', 'Senkronizasyon durduruldu; kuyruk korundu.');
            return __('Synchronization stopped. The pending queue was preserved.', 'pw-merchant-api-system');
        }
        if ($command === 'retry') {
            $failed = array_values(array_unique(array_filter(array_map('intval', (array) get_option('ptms_sync_failed_queue', array())))));
            if (!$failed) return __('There are no failed products to retry.', 'pw-merchant-api-system');
            self::unschedule_workers();
            delete_transient('ptms_sync_worker_lock');
            update_option('ptms_sync_queue', $failed, false);
            self::set_queue_origin('retry', array('count' => count($failed)));
            update_option('ptms_sync_failed_queue', array(), false);
            update_option('ptms_sync_failed', 0, false);
            update_option('ptms_sync_total', (int) get_option('ptms_sync_done', 0) + count($failed), false);
            update_option('ptms_sync_status', 'running', false);
            update_option('ptms_last_sync_activity_at', time(), false);
            delete_option('ptms_last_sync_error');
            self::add_sync_log('info', __('Failed products were re-queued.', 'pw-merchant-api-system'), array('count' => count($failed)));
            self::schedule_next_batch(1);
            return count($failed) . __('The failed product was re-queued.', 'pw-merchant-api-system');
        }
        if ($command === 'clear') {
            self::unschedule_workers();
            delete_transient('ptms_sync_worker_lock');
            delete_option('ptms_sync_inflight');
            $snapshot = array(
                'total' => (int) get_option('ptms_sync_total', 0),
                'successful' => (int) get_option('ptms_sync_done', 0),
                'failed' => (int) get_option('ptms_sync_failed', 0),
                'remaining' => count((array) get_option('ptms_sync_queue', array())),
                'cleared_at' => time(),
            );
            update_option('ptms_last_queue_snapshot', $snapshot, false);
            update_option('ptms_sync_queue', array(), false);
            update_option('ptms_sync_failed_queue', array(), false);
            self::clear_queue_origin();
            update_option('ptms_sync_total', 0, false);
            update_option('ptms_sync_done', 0, false);
            update_option('ptms_sync_failed', 0, false);
            // Kuyruk temizleme, tamamlanmış bir senkronizasyon değildir. Son oturum
            // özeti snapshot üzerinden gösterilir; Google'daki ürünlere dokunulmaz.
            update_option('ptms_sync_status', 'cleared', false);
            update_option('ptms_last_sync_activity_at', time(), false);
            delete_option('ptms_last_sync_error');
            self::add_sync_log('info', __('Sync queue cleared; submission history preserved.', 'pw-merchant-api-system'), $snapshot);
            return __('The waiting queue has been cleared. Submission history and Google issue records preserved.', 'pw-merchant-api-system');
        }
        return __('Invalid transaction.', 'pw-merchant-api-system');
    }

    private static function live_product_label($product_id) {
        $product_id = absint($product_id);
        $product = function_exists('wc_get_product') ? wc_get_product($product_id) : null;
        $name = $product && method_exists($product, 'get_name') ? (string) $product->get_name() : (string) get_the_title($product_id);
        $sku = $product && method_exists($product, 'get_sku') ? (string) $product->get_sku() : '';
        return array(
            'id' => $product_id,
            'name' => $name !== '' ? $name : __('Product #', 'pw-merchant-api-system') . $product_id,
            'offer_id' => $sku !== '' ? $sku : (string) $product_id,
        );
    }

    private static function update_history_stats($successful = 0, $failed = 0, $mode = 'bulk') {
        $successful = max(0, (int) $successful);
        $failed = max(0, (int) $failed);
        if ($successful === 0 && $failed === 0) return;
        $stats = (array) get_option('ptms_history_stats', array());
        $stats = wp_parse_args($stats, array(
            'processed' => 0, 'successful' => 0, 'failed' => 0,
            'live_successful' => 0, 'live_failed' => 0, 'updated_at' => 0,
        ));
        $stats['processed'] = (int) $stats['processed'] + $successful + $failed;
        $stats['successful'] = (int) $stats['successful'] + $successful;
        $stats['failed'] = (int) $stats['failed'] + $failed;
        if ($mode === 'live') {
            $stats['live_successful'] = (int) $stats['live_successful'] + $successful;
            $stats['live_failed'] = (int) $stats['live_failed'] + $failed;
        }
        $stats['updated_at'] = time();
        update_option('ptms_history_stats', $stats, false);
    }

    public static function history_stats() {
        $stats = (array) get_option('ptms_history_stats', array());
        if (!$stats) {
            $done = (int) get_option('ptms_sync_done', 0);
            $failed = (int) get_option('ptms_sync_failed', 0);
            $api_products = 0;
            foreach ((array) get_option('ptms_api_usage', array()) as $usage_row) {
                if (is_array($usage_row)) $api_products += max(0, (int) ($usage_row['products'] ?? 0));
            }
            // Eski sürümde kuyruk temizlenirken sayaçlar sıfırlanmış olabilir.
            // Bu durumda son 31 günlük yerel API kullanımından güvenli bir başlangıç değeri üret.
            $processed = max($done + $failed, $api_products);
            $successful = $done > 0 ? $done : $processed;
            $stats = array(
                'processed' => $processed,
                'successful' => $successful,
                'failed' => $failed,
                'live_successful' => 0,
                'live_failed' => 0,
                'updated_at' => time(),
            );
            update_option('ptms_history_stats', $stats, false);
        }
        return wp_parse_args($stats, array(
            'processed' => 0, 'successful' => 0, 'failed' => 0,
            'live_successful' => 0, 'live_failed' => 0, 'updated_at' => 0,
        ));
    }

    public static function cached_problem_products($page = 1, $per_page = 20) {
        $cache = (array) get_option('ptms_merchant_issues_cache', array());
        $products = !empty($cache['ok']) ? (array) ($cache['products'] ?? array()) : array();
        $filtered = array();
        foreach ($products as $product) {
            $status = sanitize_key((string) ($product['status'] ?? ''));
            if (!in_array($status, array('disapproved', 'pending'), true)) continue;
            $filtered[] = $product;
        }

        $per_page = max(1, min(100, (int) $per_page));
        $total = count($filtered);
        $total_pages = max(1, (int) ceil($total / $per_page));
        $page = max(1, min((int) $page, $total_pages));
        $offset = ($page - 1) * $per_page;

        return array(
            'rows' => array_slice($filtered, $offset, $per_page),
            'summary' => (array) ($cache['summary'] ?? array()),
            'checked_timestamp' => (int) ($cache['checked_timestamp'] ?? 0),
            'has_cache' => !empty($cache['ok']),
            'total' => $total,
            'page' => $page,
            'per_page' => $per_page,
            'total_pages' => $total_pages,
        );
    }

    public static function add_live_sync_event($product_id, $stage, $title, $detail = '', $level = 'info', $duration = 0) {
        $rows = (array) get_option('ptms_live_sync_events', array());
        $product = self::live_product_label($product_id);
        array_unshift($rows, array(
            'time' => time(),
            'product_id' => (int) $product['id'],
            'product_name' => sanitize_text_field((string) $product['name']),
            'offer_id' => sanitize_text_field((string) $product['offer_id']),
            'stage' => sanitize_key($stage),
            'title' => sanitize_text_field($title),
            'detail' => sanitize_text_field($detail),
            'level' => sanitize_key($level),
            'duration' => (float) $duration,
        ));
        update_option('ptms_live_sync_events', array_slice($rows, 0, 50), false);
    }

    public static function live_sync_events($limit = 8) {
        return array_slice((array) get_option('ptms_live_sync_events', array()), 0, max(1, (int) $limit));
    }

    public static function add_sync_log($level, $message, array $context = array()) {
        $logs = (array) get_option('ptms_sync_logs', array());
        $level = sanitize_key($level);
        $raw_message = (string) $message;
        if ($level === 'error') {
            $message = self::friendly_sync_error($raw_message);
            if ($raw_message !== $message && !isset($context['technical_message'])) {
                $context['technical_message'] = sanitize_text_field($raw_message);
            }
        }
        array_unshift($logs, array('time' => time(), 'level' => $level, 'message' => sanitize_text_field($message), 'context' => $context));
        update_option('ptms_sync_logs', array_slice($logs, 0, 100), false);
    }

    public static function sync_logs($limit = 10) {
        return array_slice((array) get_option('ptms_sync_logs', array()), 0, max(1, (int) $limit));
    }

    /**
     * PT Dual Log System (PDLS) user layer.
     * Technical context remains stored for developer diagnostics, but the
     * standard administration screens only receive a Turkish, actionable
     * and privacy-safe summary.
     */
    public static function user_log_entry(array $log) {
        $level = sanitize_key((string) ($log['level'] ?? 'info'));
        $raw_message = (string) ($log['message'] ?? __('System event logged.', 'pw-merchant-api-system'));
        $context = isset($log['context']) && is_array($log['context']) ? $log['context'] : array();
        $technical = (string) ($context['technical_message'] ?? $context['message'] ?? $raw_message);
        $message = $raw_message;
        $detail = '';

        if (stripos($technical, 'cURL error 28') !== false || stripos($technical, 'timed out') !== false) {
            $message = __('No timely response was received from Google.', 'pw-merchant-api-system');
            $detail = __('The operation will be retried safely; no user intervention required.', 'pw-merchant-api-system');
            $level = $level === 'error' ? 'warning' : $level;
        } elseif (stripos($technical, 'request body') !== false || stripos($technical, __('body allowed limit', 'pw-merchant-api-system')) !== false || stripos($technical, 'payload') !== false) {
            $message = __('Product data sent to Google exceeded the allowed size.', 'pw-merchant-api-system');
            $detail = __('PW Feed will shrink the data and try again safely.', 'pw-merchant-api-system');
            $level = 'warning';
        } elseif (stripos($technical, 'product_insert') !== false) {
            $message = __('Product submission could not be completed by Google.', 'pw-merchant-api-system');
            $detail = __('The product will be placed in the retry queue.', 'pw-merchant-api-system');
            $level = 'warning';
        } elseif (stripos($technical, 'data_source') !== false) {
            $message = __('Access to the Merchant data source could not be completed temporarily.', 'pw-merchant-api-system');
            $detail = __('The connection will be retried automatically.', 'pw-merchant-api-system');
            $level = 'warning';
        } elseif ($raw_message === __('The Google request will be retried.', 'pw-merchant-api-system')) {
            $attempt = isset($context['attempt']) ? max(1, (int) $context['attempt']) : 0;
            $message = __('The Google request will be retried automatically.', 'pw-merchant-api-system');
            $detail = $attempt > 0 ? $attempt . __('. trial was planned.', 'pw-merchant-api-system') : __('The transaction has been requeued due to a temporary connection issue.', 'pw-merchant-api-system');
            $level = 'warning';
        } elseif ($raw_message === __('Synchronization queue completed.', 'pw-merchant-api-system')) {
            $failed = isset($context['failed']) ? max(0, (int) $context['failed']) : 0;
            $message = __('Synchronization queue completed.', 'pw-merchant-api-system');
            $detail = $failed > 0 ? $failed . __('The product may require a check or retry.', 'pw-merchant-api-system') : __('All product operations have been completed.', 'pw-merchant-api-system');
            $level = $failed > 0 ? 'warning' : 'success';
        } elseif ($raw_message === __('The product group has been processed into Google Merchant Center.', 'pw-merchant-api-system')) {
            $count = isset($context['count']) ? max(0, (int) $context['count']) : 0;
            $successful = isset($context['successful']) ? max(0, (int) $context['successful']) : 0;
            $failed = isset($context['failed']) ? max(0, (int) $context['failed']) : 0;
            $message = $failed > 0 ? __('Part of the product set could not be submitted to Google.', 'pw-merchant-api-system') : __('The product group has been submitted to Google Merchant Center.', 'pw-merchant-api-system');
            if ($count > 0) {
                $detail = $successful . __('successful', 'pw-merchant-api-system');
                if ($failed > 0) $detail .= ', ' . $failed . ' yeniden kontrol edilecek';
                $detail .= '.';
            }
            $level = $failed > 0 ? 'warning' : 'success';
        } elseif ($raw_message === __('Failed products were re-queued.', 'pw-merchant-api-system')) {
            $count = isset($context['count']) ? max(0, (int) $context['count']) : 0;
            $message = __('Products that could not be sent were placed in the retry queue.', 'pw-merchant-api-system');
            $detail = $count > 0 ? $count . __('The product will be resent.', 'pw-merchant-api-system') : '';
            $level = 'info';
        } elseif ($level === 'error') {
            $message = self::friendly_sync_error($technical);
            if ($message === $technical || $message === $raw_message) {
                $message = __('A technical problem occurred during the transaction.', 'pw-merchant-api-system');
                $detail = __('Detail recorded in the developer diary; secure retry will be applied.', 'pw-merchant-api-system');
            }
        }

        return array(
            'time' => (int) ($log['time'] ?? time()),
            'level' => $level,
            'message' => sanitize_text_field($message),
            'detail' => sanitize_text_field($detail),
        );
    }

    public static function user_sync_logs($limit = 10) {
        $rows = self::sync_logs($limit);
        return array_map(array(__CLASS__, 'user_log_entry'), $rows);
    }

    public static function sync_status() {
        self::worker_watchdog();
        $raw_status = (string) get_option('ptms_sync_status', 'ready');
        $status = $raw_status;
        $total = (int) get_option('ptms_sync_total', 0);
        $done = (int) get_option('ptms_sync_done', 0);
        $failed = (int) get_option('ptms_sync_failed', 0);
        $remaining = count((array) get_option('ptms_sync_queue', array()));
        $failed_queue_ids = array_values(array_unique(array_filter(array_map('intval', (array) get_option('ptms_sync_failed_queue', array())))));
        $failed_queue = count($failed_queue_ids);
        $snapshot = (array) get_option('ptms_last_queue_snapshot', array());
        $queue_was_cleared = $raw_status === 'cleared';

        if ($queue_was_cleared && !empty($snapshot)) {
            $total = max(0, (int) ($snapshot['total'] ?? 0));
            $done = max(0, (int) ($snapshot['successful'] ?? 0));
            $failed = max(0, (int) ($snapshot['failed'] ?? 0));
            $remaining = 0;
        }

        $processed = min($total, $done + $failed);
        $unprocessed = max(0, $total - $processed);

        // Kuyruk boş olsa bile tüm ürünler işlenmediyse işlem "tamamlandı" değildir.
        if ($status === 'completed' && $total > 0 && $processed < $total) {
            $status = 'incomplete';
        }

        $percent = $total > 0 ? min(100, (int) round(($processed / $total) * 100)) : 0;
        $started = (int) get_option('ptms_last_sync_started_at', 0);
        $elapsed = $started > 0 ? max(0, time() - $started) : 0;
        $eta = ($processed > 0 && $remaining > 0) ? (int) round(($elapsed / $processed) * $remaining) : 0;
        $last_activity = (int) get_option('ptms_last_sync_activity_at', 0);
        $heartbeat_age = $last_activity > 0 ? max(0, time() - $last_activity) : 0;
        $worker_active = $status === 'running' && ($heartbeat_age <= 90 || (bool) get_transient('ptms_sync_worker_lock') || self::has_scheduled_worker());
        $inflight = get_option('ptms_sync_inflight', array());
        $inflight_ids = is_array($inflight) && isset($inflight['ids']) && is_array($inflight['ids'])
            ? $inflight['ids']
            : array();

        $labels = array(
            'ready' => __('Ready', 'pw-merchant-api-system'),
            'idle' => __('Ready', 'pw-merchant-api-system'),
            'running' => __('In progress', 'pw-merchant-api-system'),
            'completed' => __('completed', 'pw-merchant-api-system'),
            'paused' => __('paused', 'pw-merchant-api-system'),
            'connection_paused' => __('Waiting for Google connection', 'pw-merchant-api-system'),
            'stopped' => __('Stopped', 'pw-merchant-api-system'),
            'cleared' => 'Kuyruk temizlendi',
            'incomplete' => __('Transaction not completed', 'pw-merchant-api-system'),
            'error' => __('Error occurred', 'pw-merchant-api-system'),
        );
        $descriptions = array(
            'ready' => __('There is no active bulk synchronization yet.', 'pw-merchant-api-system'),
            'idle' => __('There is no active bulk synchronization yet.', 'pw-merchant-api-system'),
            'running' => __('Products are sent to Google Merchant in the background. You do not need to keep this page open.', 'pw-merchant-api-system'),
            'completed' => __('All products have been processed. There are no pending products.', 'pw-merchant-api-system'),
            'paused' => __('Transaction has been temporarily paused. The tail is preserved; It can be continued from where it left off with “Continue”.', 'pw-merchant-api-system'),
            'connection_paused' => __('Synchronization is safely paused until the Google Merchant connection is re-established. The entire queue is preserved and will resume automatically after reconnection.', 'pw-merchant-api-system'),
            'stopped' => __('Batch processing has been stopped. Existing products from Google are not affected; If the tail is preserved, it can be continued.', 'pw-merchant-api-system'),
            'cleared' => __('The pending temporary queue was cleared before the batch was completed. Existing products on Google Merchant are not affected.', 'pw-merchant-api-system'),
            'incomplete' => __('The last batch ended before all products were processed. Existing products from Google were not affected.', 'pw-merchant-api-system'),
            'error' => __('There was a problem during the batch processing. You can review the details in the Worker transactions and problematic products section.', 'pw-merchant-api-system'),
        );

        $metric_labels = array(
            'first' => __('The target in this process is', 'pw-merchant-api-system'),
            'second' => in_array($status, array('cleared', 'incomplete'), true) ? __('sent in this transaction', 'pw-merchant-api-system') : __('Sent successfully', 'pw-merchant-api-system'),
            'third' => in_array($status, array('cleared', 'incomplete'), true) ? __('remaining unprocessed', 'pw-merchant-api-system') : __('Cannot be sent', 'pw-merchant-api-system'),
            'fourth' => __('currently waiting', 'pw-merchant-api-system'),
        );
        $metric_values = array(
            'first' => $total,
            'second' => in_array($status, array('cleared', 'incomplete'), true) ? $processed : $done,
            'third' => in_array($status, array('cleared', 'incomplete'), true) ? $unprocessed : $failed,
            'fourth' => $remaining,
        );

        $progress_help = $total > 0
            ? __('This rate only shows the progress of the last batch; Follow the general Merchant product status from the card above.', 'pw-merchant-api-system')
            : __('When bulk synchronization is started, progress is shown here.', 'pw-merchant-api-system');

        return array(
            'status' => $status,
            'raw_status' => $raw_status,
            'label' => $labels[$status] ?? __('Ready', 'pw-merchant-api-system'),
            'description' => $descriptions[$status] ?? $descriptions['ready'],
            'total' => $total,
            'done' => $done,
            'failed' => $failed,
            'failed_queue' => $failed_queue,
            'remaining' => $remaining,
            'processed' => $processed,
            'unprocessed' => $unprocessed,
            'percent' => $percent,
            'progress_help' => $progress_help,
            'metric_labels' => $metric_labels,
            'metric_values' => $metric_values,
            'eta_seconds' => $eta,
            'eta_label' => self::human_duration($eta),
            'last_activity' => $last_activity,
            'heartbeat_age' => $heartbeat_age,
            'worker_active' => $worker_active,
            'worker_scheduled' => self::has_scheduled_worker(),
            'inflight_count' => count($inflight_ids),
            'auto_sync' => (bool) get_option('ptms_auto_sync_enabled', true),
            'delete_queue' => count((array) get_option('ptms_delete_queue', array())),
            'schedule' => self::sync_schedule_status(),
            'activity_logs' => self::user_sync_logs(6),
        );
    }

    private static function human_duration($seconds) {
        $seconds = max(0, (int) $seconds);
        if ($seconds < 60) return $seconds > 0 ? $seconds . ' sn' : '—';
        $minutes = (int) ceil($seconds / 60);
        if ($minutes < 60) return $minutes . ' dk';
        $hours = (int) floor($minutes / 60);
        $mins = $minutes % 60;
        return $hours . ' sa' . ($mins ? ' ' . $mins . ' dk' : '');
    }

    public static function analysis() {
        $checks = array();
        $checks[] = self::check('ssl', 'SSL / HTTPS', is_ssl(), __('The site uses a secure HTTPS connection.', 'pw-merchant-api-system'), __('The site does not use HTTPS.', 'pw-merchant-api-system'));
        $checks[] = self::check('woocommerce', 'WooCommerce', class_exists('WooCommerce'), 'WooCommerce etkin.', __('WooCommerce is not enabled.', 'pw-merchant-api-system'));
        $checks[] = self::check('permalinks', __('Permalinks', 'pw-merchant-api-system'), (string) get_option('permalink_structure') !== '', __('Permalinks are configured.', 'pw-merchant-api-system'), __('Permalinks have a flat structure.', 'pw-merchant-api-system'));
        $checks[] = self::check('visibility', __('Search engine visibility', 'pw-merchant-api-system'), (int) get_option('blog_public', 1) === 1, __('Search engine visibility is on.', 'pw-merchant-api-system'), __('Search engines are prevented from indexing the site.', 'pw-merchant-api-system'));
        $checks[] = self::check('php', __('PHP version', 'pw-merchant-api-system'), version_compare(PHP_VERSION, '7.4', '>='), __('PHP version available:', 'pw-merchant-api-system') . PHP_VERSION, __('PHP 7.4 or above is required.', 'pw-merchant-api-system'));
        $checks[] = self::check('wp', __('WordPress version', 'pw-merchant-api-system'), version_compare(get_bloginfo('version'), '5.8', '>='), __('WordPress version is suitable.', 'pw-merchant-api-system'), __('WordPress version must be updated.', 'pw-merchant-api-system'));

        if (class_exists('WooCommerce')) {
            $count = wp_count_posts('product');
            $published = isset($count->publish) ? (int) $count->publish : 0;
            $checks[] = self::check('products', __('Released products', 'pw-merchant-api-system'), $published > 0, $published . __('The product is live.', 'pw-merchant-api-system'), __('No products found in the publication.', 'pw-merchant-api-system'));
            $currency = function_exists('get_woocommerce_currency') ? get_woocommerce_currency() : '';
            $checks[] = self::check('currency', __('Store currency', 'pw-merchant-api-system'), $currency !== '', 'Para birimi: ' . $currency, __('Store currency not found.', 'pw-merchant-api-system'));
            $country = function_exists('WC') && WC()->countries ? WC()->countries->get_base_country() : '';
            $checks[] = self::check('country', __('Store country', 'pw-merchant-api-system'), $country !== '', __('Store country:', 'pw-merchant-api-system') . $country, __('Store country has not been determined.', 'pw-merchant-api-system'));
        }

        $passed = 0;
        foreach ($checks as $check) { if ($check['ok']) { $passed++; } }
        $score = count($checks) ? (int) round(($passed / count($checks)) * 100) : 0;
        return array('score' => $score, 'passed' => $passed, 'total' => count($checks), 'checks' => $checks);
    }

    private static function check($key, $title, $ok, $success, $failure) {
        return array('key' => $key, 'title' => $title, 'ok' => (bool) $ok, 'message' => $ok ? $success : $failure);
    }

    private static function product_analysis_dataset() {
        $cache_key = 'ptms_product_analysis_dataset_v107';
        $cached = get_transient($cache_key);
        if (is_array($cached) && isset($cached['rows'], $cached['total'], $cached['issue_counts'])) {
            return $cached;
        }

        $dataset = array(
            'rows' => array(),
            'total' => 0,
            'issues' => 0,
            'suitable' => 0,
            'issue_counts' => array(
                'name_missing' => 0,
                'price_missing' => 0,
                'image_missing' => 0,
                'description_missing' => 0,
                'out_of_stock' => 0,
                'brand_missing' => 0,
            ),
            'generated_at' => time(),
        );

        if (!class_exists('WooCommerce')) { return $dataset; }

        $ids = get_posts(array(
            'post_type'              => 'product',
            'post_status'            => 'publish',
            'fields'                 => 'ids',
            'posts_per_page'         => -1,
            'orderby'                => 'ID',
            'order'                  => 'ASC',
            'no_found_rows'          => true,
            'update_post_meta_cache' => false,
            'update_post_term_cache' => false,
            'suppress_filters'       => false,
        ));

        foreach ($ids as $id) {
            $product = wc_get_product($id);
            if (!$product) { continue; }

            $issues = array();
            $issue_keys = array();
            if ($product->get_name() === '') { $issues[] = __('Product name is missing', 'pw-merchant-api-system'); $issue_keys[] = 'name_missing'; }
            if ($product->get_regular_price() === '' && $product->get_price() === '') { $issues[] = 'Fiyat eksik'; $issue_keys[] = 'price_missing'; }
            if (!$product->get_image_id()) { $issues[] = __('Main image is missing', 'pw-merchant-api-system'); $issue_keys[] = 'image_missing'; }
            if (trim(wp_strip_all_tags($product->get_description())) === '') { $issues[] = __('Description missing', 'pw-merchant-api-system'); $issue_keys[] = 'description_missing'; }
            // OUT_OF_STOCK geçerli bir Merchant availability değeridir; veri
            // hatası veya sağlık puanı kaybı olarak sınıflandırılmaz.
            if (!$product->is_in_stock()) { $dataset['issue_counts']['out_of_stock']++; }

            $brand = class_exists('PTMS_Data_Detector')
                ? PTMS_Data_Detector::detect_brand($product)
                : array('value' => '', 'source' => '', 'source_type' => '', 'source_key' => '');
            if ($brand['value'] === '') { $issues[] = __('Brand information not found', 'pw-merchant-api-system'); $issue_keys[] = 'brand_missing'; }

            foreach ($issue_keys as $key) {
                if (isset($dataset['issue_counts'][$key])) { $dataset['issue_counts'][$key]++; }
            }

            $has_issues = !empty($issues);
            $dataset['total']++;
            if ($has_issues) { $dataset['issues']++; } else { $dataset['suitable']++; }
            $dataset['rows'][] = array(
                'id'           => $id,
                'name'         => $product->get_name(),
                'issues'       => $issues,
                'issue_keys'   => $issue_keys,
                'brand'        => $brand['value'],
                'brand_source' => $brand['source'],
                'brand_key'    => $brand['source_key'],
                'edit'         => get_edit_post_link($id, 'raw'),
            );
        }

        set_transient($cache_key, $dataset, 5 * MINUTE_IN_SECONDS);
        return $dataset;
    }

    public static function product_analysis($page = 1, $per_page = 50, $include_items = true, $filter = 'all') {
        $page = max(1, (int) $page);
        $per_page = max(20, min(200, (int) $per_page));
        $allowed_filters = array('all', 'issues', 'suitable');
        $filter = in_array($filter, $allowed_filters, true) ? $filter : 'all';
        $dataset = self::product_analysis_dataset();

        $filtered_rows = array();
        foreach ($dataset['rows'] as $row) {
            $has_issues = !empty($row['issues']);
            if ($filter === 'issues' && !$has_issues) { continue; }
            if ($filter === 'suitable' && $has_issues) { continue; }
            $filtered_rows[] = $row;
        }

        $filtered_total = count($filtered_rows);
        $total_pages = max(1, (int) ceil($filtered_total / $per_page));
        $page = min($page, $total_pages);
        $items = $include_items ? array_slice($filtered_rows, ($page - 1) * $per_page, $per_page) : array();

        return array(
            'total'          => (int) $dataset['total'],
            'issues'         => (int) $dataset['issues'],
            'suitable'       => (int) $dataset['suitable'],
            'issue_counts'   => $dataset['issue_counts'],
            'filtered_total' => $filtered_total,
            'items'          => $items,
            'page'           => $page,
            'per_page'       => $per_page,
            'total_pages'    => $total_pages,
            'filter'         => $filter,
            'generated_at'   => (int) $dataset['generated_at'],
        );
    }

    private static function proportional_score($good, $total, $weight) {
        if ($total <= 0) { return 0; }
        return (int) round(max(0, min(1, $good / $total)) * $weight);
    }

    public static function merchant_health_score() {
        $site = self::analysis();
        $products = self::product_analysis(1, 50, false, 'all');
        $total = max(0, (int) $products['total']);
        $counts = isset($products['issue_counts']) ? $products['issue_counts'] : array();
        $connected = (bool) get_option('ptms_google_connected', false);
        $merchant_id = (string) get_option('ptms_merchant_id', '');
        $wizard = (array) get_option('ptms_wizard_status', array());

        $missing_name = (int) ($counts['name_missing'] ?? 0);
        $missing_brand = (int) ($counts['brand_missing'] ?? 0);
        $missing_desc = (int) ($counts['description_missing'] ?? 0);
        $missing_image = (int) ($counts['image_missing'] ?? 0);
        $missing_price = (int) ($counts['price_missing'] ?? 0);
        $out_of_stock = (int) ($counts['out_of_stock'] ?? 0);
        $suitable = (int) $products['suitable'];

        $required_checks = max(1, $total * 3);
        $required_good = max(0, $required_checks - $missing_name - $missing_brand - $missing_desc);
        $price_stock_checks = max(1, $total * 2);
        $price_stock_good = max(0, $price_stock_checks - $missing_price);
        $wizard_policy_keys = array('website_verification', 'domain_verification', 'google_ads');
        $wizard_policy_done = 0;
        foreach ($wizard_policy_keys as $key) { if (!empty($wizard[$key])) { $wizard_policy_done++; } }

        $categories = array(
            array('key'=>'google','title'=>__('Google link', 'pw-merchant-api-system'),'weight'=>10,'score'=>$connected ? 10 : 0,'detail'=>$connected ? __('Google account is linked.', 'pw-merchant-api-system') : __('Google account is not yet linked.', 'pw-merchant-api-system'),'action'=>__('Complete the Google link.', 'pw-merchant-api-system'),'url'=>admin_url('admin.php?page=pw-merchant-api-system-google')),
            array('key'=>'merchant','title'=>__('Merchant Center account', 'pw-merchant-api-system'),'weight'=>10,'score'=>$merchant_id !== '' ? 10 : 0,'detail'=>$merchant_id !== '' ? __('Merchant Center ID:', 'pw-merchant-api-system') . $merchant_id : __('Merchant Center account is not selected.', 'pw-merchant-api-system'),'action'=>__('Select the Merchant Center account.', 'pw-merchant-api-system'),'url'=>admin_url('admin.php?page=pw-merchant-api-system-google')),
            array('key'=>'data_source','title'=>__('Data source', 'pw-merchant-api-system'),'weight'=>10,'score'=>!empty($wizard['data_source']) ? 10 : 0,'detail'=>!empty($wizard['data_source']) ? __('Data source preparation is completed.', 'pw-merchant-api-system') : __('The data source step was not completed.', 'pw-merchant-api-system'),'action'=>__('Complete the data source step.', 'pw-merchant-api-system'),'url'=>admin_url('admin.php?page=pw-merchant-api-system-sihirbaz')),
            array('key'=>'product_quality','title'=>__('product quality', 'pw-merchant-api-system'),'weight'=>20,'score'=>self::proportional_score($suitable,$total,20),'detail'=>$total . __('from the product', 'pw-merchant-api-system') . $suitable . ' tanesi temel kontrollerde uygun.','action'=>__('Examine problematic products.', 'pw-merchant-api-system'),'url'=>add_query_arg('ptms_filter','issues',admin_url('admin.php?page=pw-merchant-api-system-urun-analizi'))),
            array('key'=>'required','title'=>'Zorunlu alanlar','weight'=>15,'score'=>self::proportional_score($required_good,$required_checks,15),'detail'=>'Eksik marka: ' . $missing_brand . __(', explanation:', 'pw-merchant-api-system') . $missing_desc . __(', product name:', 'pw-merchant-api-system') . $missing_name . '.','action'=>__('Complete the missing product fields.', 'pw-merchant-api-system'),'url'=>add_query_arg('ptms_filter','issues',admin_url('admin.php?page=pw-merchant-api-system-urun-analizi'))),
            array('key'=>'images','title'=>__('Images', 'pw-merchant-api-system'),'weight'=>10,'score'=>self::proportional_score(max(0,$total-$missing_image),$total,10),'detail'=>$missing_image . __('The main image is missing from the product.', 'pw-merchant-api-system'),'action'=>__('Add missing product images.', 'pw-merchant-api-system'),'url'=>add_query_arg('ptms_filter','issues',admin_url('admin.php?page=pw-merchant-api-system-urun-analizi'))),
            array('key'=>'price_stock','title'=>'Fiyat ve stok','weight'=>10,'score'=>self::proportional_score($price_stock_good,$price_stock_checks,10),'detail'=>'Fiyat eksik: ' . $missing_price . ', geçerli stokta yok durumu: ' . $out_of_stock . '.','action'=>__('Fix missing price information.', 'pw-merchant-api-system'),'url'=>add_query_arg('ptms_filter','issues',admin_url('admin.php?page=pw-merchant-api-system-urun-analizi'))),
            array('key'=>'policy','title'=>__('Preparation and policy', 'pw-merchant-api-system'),'weight'=>10,'score'=>(int) round(($wizard_policy_done / count($wizard_policy_keys)) * 10),'detail'=>$wizard_policy_done . '/' . count($wizard_policy_keys) . __('The preparation step is completed.', 'pw-merchant-api-system'),'action'=>__('Complete the missing parts in the preparation wizard.', 'pw-merchant-api-system'),'url'=>admin_url('admin.php?page=pw-merchant-api-system-sihirbaz')),
            array('key'=>'site','title'=>__('Site structure', 'pw-merchant-api-system'),'weight'=>5,'score'=>(int) round(($site['score']/100)*5),'detail'=>__('Site readiness score:', 'pw-merchant-api-system') . $site['score'] . '/100.','action'=>__('Fix site analytics warnings.', 'pw-merchant-api-system'),'url'=>admin_url('admin.php?page=pw-merchant-api-system-site-analizi')),
        );

        $score = 0;
        foreach ($categories as $category) { $score += (int) $category['score']; }
        $score = max(0, min(100, $score));
        $label = $score >= 95 ? __('Perfect', 'pw-merchant-api-system') : ($score >= 80 ? __('Very good', 'pw-merchant-api-system') : ($score >= 65 ? __('should be improved', 'pw-merchant-api-system') : ($score >= 50 ? 'Kritik' : __('emergency response', 'pw-merchant-api-system'))));

        $best = null; $max_loss = -1;
        foreach ($categories as $category) {
            $loss = (int) $category['weight'] - (int) $category['score'];
            if ($loss > $max_loss) { $max_loss = $loss; $best = $category; }
        }

        $history = (array) get_option('ptms_health_history', array());
        $today = wp_date('Y-m-d');
        $history[$today] = $score;
        if (count($history) > 30) { $history = array_slice($history, -30, null, true); }
        update_option('ptms_health_history', $history, false);

        return array('score'=>$score,'label'=>$label,'categories'=>$categories,'best_action'=>$best,'history'=>$history,'product_total'=>$total,'generated_at'=>$products['generated_at']);
    }

}
