<?php
/**
 * VMO Client Bootstrap — runs ONLY in client mode (VMO_CLIENT_MODE === true).
 *
 * Two systemic responsibilities, both required by the master/client architecture:
 *
 *  1. GLOBAL SERVICE-CALL INTERCEPTOR
 *     Every QuickLaunch-provisioned customer site is a "client" plugin. By design,
 *     client instances hold ZERO sensitive credentials — no OpenAI key, no Stripe
 *     secret, no Twilio token. The master at getvmo.com (Kinsta) is the only place
 *     real provider keys live. Whenever ANY code path in the client plugin attempts
 *     to call api.openai.com / api.stripe.com / api.twilio.com directly, this
 *     interceptor transparently rewrites the request to the master proxy at
 *     https://getvmo.com/wp-json/vmo-builder/v1/proxy/{service}/{endpoint},
 *     strips any (placeholder) Authorization header, and attaches the VMO
 *     installation/license/user/plan headers the master needs to authorize and
 *     meter the call. The master then talks to the real provider with its own
 *     real key and returns the response verbatim.
 *
 *     This is implemented as a single 'pre_http_request' filter so it catches
 *     ALL existing call sites (30+ scattered across the plugin) and ALL future
 *     ones with zero per-call-site changes. Master mode is untouched — the
 *     filter no-ops when VMO_CLIENT_MODE is not defined or false.
 *
 *  2. AUTO-UPDATE CHANNEL
 *     New features added on master should land on every customer's instance the
 *     next time they log in, with no manual reinstall. We hook the WordPress
 *     plugin update system ('pre_set_site_transient_update_plugins' +
 *     'plugins_api') and poll the master's manifest endpoint
 *     /wp-json/vmo-builder/v1/client-updates/manifest. If master reports a newer
 *     version, WordPress's own auto-updater downloads and installs it via
 *     standard core machinery — no custom installer code needed.
 *
 * @package VMO
 */

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

// Hard gate: nothing in this file should ever run on the master.
if (!defined('VMO_CLIENT_MODE') || VMO_CLIENT_MODE !== true) {
    return;
}

if (!class_exists('VMO_Client_Bootstrap')) {

class VMO_Client_Bootstrap {

    /** Domains we intercept and rewrite to master proxy. */
    const SERVICE_DOMAINS = [
        'openai' => 'api.openai.com',
        'twilio' => 'api.twilio.com',
        'stripe' => 'api.stripe.com',
    ];

    /** Plugin file path, populated by ::boot(). */
    private static $plugin_file = '';

    /** Plugin slug (folder name), populated by ::boot(). */
    private static $plugin_slug = '';

    /** Cached proxy configuration state. */
    private static $master_proxy_configured = null;

    /**
     * Wire all hooks. Call once from the main plugin file after VMO_CLIENT_MODE
     * is defined but before any other VMO subsystem runs.
     *
     * @param string $plugin_file Absolute path to the main plugin file
     *                            (i.e. __FILE__ from vmo-ai-website-builder.php).
     */
    public static function boot($plugin_file) {
        self::$plugin_file = $plugin_file;
        self::$plugin_slug = dirname(plugin_basename($plugin_file));

        // In client mode, a stale wp-config/plugin constant may still define
        // VMO_MASTER_VERSION=true from an older build. Do not disable the client
        // bootstrap in that case; client mode wins. Only show the conflict on a
        // real master/non-client install.
        if (defined('VMO_MASTER_VERSION') && VMO_MASTER_VERSION === true && !(defined('VMO_CLIENT_MODE') && VMO_CLIENT_MODE === true)) {
            add_action('admin_notices', [__CLASS__, 'render_mode_conflict_notice']);
            return;
        }

        if (!self::master_proxy_url_is_configured()) {
            add_action('admin_notices', [__CLASS__, 'render_proxy_configuration_notice']);
        }

        // 1) Service-call interceptor — priority 5 so we run before any other
        //    HTTP filter (including service-lock blockers, which expect the
        //    final upstream URL).
        add_filter('pre_http_request', [__CLASS__, 'intercept_service_calls'], 5, 3);

        // 2) Auto-update channel
        add_filter('pre_set_site_transient_update_plugins', [__CLASS__, 'inject_update_into_transient']);
        add_filter('plugins_api', [__CLASS__, 'plugins_api_details'], 10, 3);
        add_filter('upgrader_pre_download', [__CLASS__, 'attach_auth_to_download'], 10, 3);

        // 3) Silent auto-updates for Vultr-hosted (QuickLaunch) clients only.
        //    The QuickLaunch onboarding script sets the option
        //    `vmo_quicklaunch_hosted_client` to '1' on every Vultr instance it
        //    provisions. Those customers should never have to think about
        //    plugin maintenance — updates apply automatically in the
        //    background and they just see a banner notification.
        //
        //    Direct-WordPress clients (customers who installed our plugin into
        //    their own WordPress) keep the standard WordPress update flow:
        //    they see "Update Available" on the Plugins screen and click
        //    Update like any other plugin. We can't auto-update on hosts we
        //    don't control without their consent.
        if (get_option('vmo_quicklaunch_hosted_client') === '1') {
            add_filter('auto_update_plugin', [__CLASS__, 'enable_silent_auto_update'], 10, 2);
        }

        // 4) License claim UI — the customer completes Stripe checkout on
        //    getvmo.com, receives a one-time claim token by email, installs
        //    this plugin into their own WordPress (or QuickLaunch-hosted
        //    tenant), and pastes the token here to bind their site to a
        //    real license key. We register the page + handler unconditionally
        //    so customers can re-claim after a reinstall.
        add_action('admin_menu',    [__CLASS__, 'register_license_page']);
        add_action('admin_notices', [__CLASS__, 'maybe_render_license_notice']);
        add_action('admin_notices', [__CLASS__, 'maybe_render_whats_new_notice']);
        add_action('admin_post_vmo_claim_license', [__CLASS__, 'handle_claim_submission']);
        add_action('admin_post_vmo_dismiss_whats_new', [__CLASS__, 'handle_dismiss_whats_new']);

        // 5) Post-update "What's new" capture. When WordPress finishes
        //    installing a plugin update for *this* plugin, we promote the
        //    AI-authored banner_summary + tutorial_links (cached during the
        //    last manifest fetch) into a one-time option that the next admin
        //    page load will render as a dismissable notice. This is what gives
        //    the customer the "VMO just updated — here's what's new" experience
        //    without us having to ship a separate notification system.
        add_action('upgrader_process_complete', [__CLASS__, 'capture_post_update_banner'], 10, 2);
    }

    // ─────────────────────────────────────────────────────────────────────
    // 1) GLOBAL SERVICE-CALL INTERCEPTOR
    // ─────────────────────────────────────────────────────────────────────

    /**
     * pre_http_request filter. Returning anything other than false short-circuits
     * the WP HTTP API and treats our return value as the response. We use that to
     * substitute a proxied response when the request was bound for a provider API.
     *
     * @param false|array|WP_Error $preempt Whatever a previous filter returned.
     * @param array                $args    Full wp_remote_* args (method, headers, body, etc.).
     * @param string               $url     Target URL.
     * @return false|array|WP_Error
     */
    public static function intercept_service_calls($preempt, $args, $url) {
        // Respect any earlier short-circuit (e.g. token-manager service lock).
        if ($preempt !== false) {
            return $preempt;
        }

        $service = self::detect_service($url);
        if ($service === null) {
            return false; // Not a provider call — let WP make the request normally.
        }

        // Never recursively intercept our own master proxy calls.
        if (self::is_master_proxy_url($url)) {
            return false;
        }

        if (!self::master_proxy_url_is_configured()) {
            return new WP_Error('vmo_client_proxy_unconfigured', __('VMO client proxy is not configured. Please install a valid VMO client package or contact VMO support.', 'vmo'));
        }

        $endpoint = self::extract_endpoint($url, self::SERVICE_DOMAINS[$service]);

        // Build the master proxy URL the same shape master expects:
        //   {proxy_base}/{service}/{endpoint}
        $proxy_base = self::get_master_proxy_base();

        $target_url = trailingslashit($proxy_base) . $service . '/' . ltrim($endpoint, '/');

        // Strip credentials that only make sense between the client and the
        // real provider — the master adds its own real Authorization header
        // before talking to OpenAI/Stripe/Twilio.
        if (!isset($args['headers']) || !is_array($args['headers'])) {
            $args['headers'] = [];
        }
        // Headers may be supplied as either an assoc array or a list of
        // "Name: value" strings (cURL style). Normalize first.
        $args['headers'] = self::normalize_headers($args['headers']);
        unset($args['headers']['Authorization'], $args['headers']['authorization']);

        $operation_key = '';
        if (isset($args['vmo_operation'])) {
            $operation_key = sanitize_key((string) $args['vmo_operation']);
            unset($args['vmo_operation']);
        }

        $operation_multiplier = 0.0;
        if (isset($args['vmo_operation_multiplier'])) {
            $operation_multiplier = (float) $args['vmo_operation_multiplier'];
            unset($args['vmo_operation_multiplier']);
        }

        $request_id = '';
        if (isset($args['vmo_request_id'])) {
            $request_id = sanitize_text_field((string) $args['vmo_request_id']);
            unset($args['vmo_request_id']);
        }

        // Attach VMO identity headers so master can authorize + meter.
        $user_id = get_current_user_id();
        $plan    = (string) get_user_meta($user_id, 'vmo_subscription_status', true);
        $args['headers'] = array_merge($args['headers'], [
            'X-VMO-Installation' => (string) get_option('vmo_installation_id', ''),
            'X-VMO-License'      => (string) get_option('vmo_license_key', ''),
            'X-VMO-User'         => (string) $user_id,
            'X-VMO-Plan'         => $plan !== '' ? $plan : 'tester',
        ]);

        if ($operation_key === '') {
            $operation_key = self::get_header_value($args['headers'], 'X-VMO-Operation');
        }
        if ($operation_key === '') {
            $operation_key = self::infer_proxy_operation_key($service, $endpoint, $args);
        }
        if ($operation_key !== '') {
            self::set_header_value($args['headers'], 'X-VMO-Operation', $operation_key);
        }

        if ($operation_multiplier <= 0) {
            $operation_multiplier_header = self::get_header_value($args['headers'], 'X-VMO-Operation-Multiplier');
            if ($operation_multiplier_header !== '') {
                $operation_multiplier = (float) $operation_multiplier_header;
            }
        }
        if ($operation_multiplier > 0) {
            self::set_header_value($args['headers'], 'X-VMO-Operation-Multiplier', (string) $operation_multiplier);
        }

        if ($request_id === '') {
            $request_id = self::get_header_value($args['headers'], 'X-VMO-Request-ID');
        }
        if ($request_id === '' && function_exists('wp_generate_uuid4')) {
            $request_id = wp_generate_uuid4();
        }
        if ($request_id !== '') {
            self::set_header_value($args['headers'], 'X-VMO-Request-ID', $request_id);
        }

        // Re-issue the request to the master proxy. Removing the
        // pre_http_request filter temporarily so we don't recurse.
        remove_filter('pre_http_request', [__CLASS__, 'intercept_service_calls'], 5);
        $response = wp_remote_request($target_url, $args);
        add_filter('pre_http_request', [__CLASS__, 'intercept_service_calls'], 5, 3);

        return $response;
    }

    /** Match a URL host against our known provider list. */
    private static function detect_service($url) {
        if (!is_string($url) || $url === '') {
            return null;
        }
        $host = wp_parse_url($url, PHP_URL_HOST);
        if (!$host) {
            return null;
        }
        foreach (self::SERVICE_DOMAINS as $service => $domain) {
            if (strcasecmp($host, $domain) === 0) {
                return $service;
            }
        }
        return null;
    }

    /** Strip "https://api.foo.com/" prefix and return the bare endpoint path. */
    private static function extract_endpoint($url, $domain) {
        $endpoint = preg_replace('#^https?://' . preg_quote($domain, '#') . '/?#i', '', $url);
        return (string) $endpoint;
    }

    /** True when the URL already targets our master proxy (prevent recursion). */
    private static function is_master_proxy_url($url) {
        if (!is_string($url) || $url === '') {
            return false;
        }
        $proxy_base = self::get_master_proxy_base();
        $proxy_host = wp_parse_url($proxy_base, PHP_URL_HOST);
        $url_host   = wp_parse_url($url, PHP_URL_HOST);
        return $proxy_host && $url_host && strcasecmp($proxy_host, $url_host) === 0;
    }

    private static function get_master_proxy_base() {
        $proxy_base = defined('VMO_MASTER_PROXY_URL') && VMO_MASTER_PROXY_URL
            ? (string) VMO_MASTER_PROXY_URL
            : (string) get_option('vmo_master_proxy_url', 'https://getvmo.com/wp-json/vmo-builder/v1/proxy');
        return rtrim(trim($proxy_base), '/');
    }

    private static function master_proxy_url_is_configured() {
        if (self::$master_proxy_configured !== null) {
            return self::$master_proxy_configured;
        }

        $proxy_base = self::get_master_proxy_base();
        $host = wp_parse_url($proxy_base, PHP_URL_HOST);
        $path = rtrim((string) wp_parse_url($proxy_base, PHP_URL_PATH), '/');
        self::$master_proxy_configured = $proxy_base !== ''
            && strpos($proxy_base, '__VMO_MASTER_PROXY_URL__') === false
            && (bool) filter_var($proxy_base, FILTER_VALIDATE_URL)
            && !empty($host)
            && strpos($path, '/wp-json/vmo-builder/v1/proxy') !== false;

        return self::$master_proxy_configured;
    }

    public static function render_mode_conflict_notice() {
        if (!current_user_can('manage_options')) {
            return;
        }
        echo '<div class="notice notice-error"><p>' . esc_html__('VMO is installed with conflicting client/master mode flags. This package cannot run safely until VMO_CLIENT_MODE is true and VMO_MASTER_VERSION is false.', 'vmo') . '</p></div>';
    }

    public static function render_proxy_configuration_notice() {
        if (!current_user_can('manage_options')) {
            return;
        }
        echo '<div class="notice notice-error"><p>' . esc_html__('VMO client services are paused because the master proxy URL is missing or invalid. Install a fresh VMO client package from getvmo.com.', 'vmo') . '</p></div>';
    }

    /** Accept assoc OR ["Name: value", ...] cURL-style header arrays; return assoc. */
    private static function normalize_headers($headers) {
        if (!is_array($headers)) {
            return [];
        }
        $assoc = [];
        foreach ($headers as $k => $v) {
            if (is_int($k) && is_string($v) && strpos($v, ':') !== false) {
                list($name, $val) = array_map('trim', explode(':', $v, 2));
                if ($name !== '') {
                    $assoc[$name] = $val;
                }
            } else {
                $assoc[$k] = $v;
            }
        }
        return $assoc;
    }

    /** Read a header value regardless of original casing. */
    private static function get_header_value($headers, $name) {
        if (!is_array($headers)) {
            return '';
        }
        foreach ($headers as $header_name => $value) {
            if (strcasecmp((string) $header_name, (string) $name) === 0) {
                return trim((string) $value);
            }
        }
        return '';
    }

    /** Set a header while removing differently-cased duplicates. */
    private static function set_header_value(&$headers, $name, $value) {
        if (!is_array($headers)) {
            $headers = [];
        }
        foreach (array_keys($headers) as $header_name) {
            if (strcasecmp((string) $header_name, (string) $name) === 0) {
                unset($headers[$header_name]);
            }
        }
        $headers[$name] = $value;
    }

    /**
     * Infer the operation catalog key for legacy direct provider calls that
     * were intercepted before they reached api.openai.com. Newer call sites set
     * vmo_operation explicitly; this is the compatibility net for older ones.
     */
    private static function infer_proxy_operation_key($service, $endpoint, $args = []) {
        $service = sanitize_key((string) $service);
        $endpoint = strtolower(ltrim((string) $endpoint, '/'));

        if ($service === 'openai') {
            if (strpos($endpoint, 'chat/completions') !== false) {
                $body = [];
                if (!empty($args['body']) && is_string($args['body'])) {
                    $decoded = json_decode($args['body'], true);
                    if (is_array($decoded)) {
                        $body = $decoded;
                    }
                }
                $messages_text = '';
                if (!empty($body['messages']) && is_array($body['messages'])) {
                    $messages_text = strtolower(wp_json_encode($body['messages']));
                }
                if (strpos($messages_text, 'business consultant') !== false || strpos($messages_text, 'business wizard') !== false) {
                    return 'consultation_message';
                }
                return 'chat';
            }
            if (strpos($endpoint, 'audio/speech') !== false) {
                return 'tts';
            }
            if (strpos($endpoint, 'audio/transcriptions') !== false) {
                return 'transcription';
            }
            if (strpos($endpoint, 'images/generations') !== false) {
                return 'image';
            }
        }

        return '';
    }

    // ─────────────────────────────────────────────────────────────────────
    // 2) AUTO-UPDATE CHANNEL
    // ─────────────────────────────────────────────────────────────────────

    /**
     * Ask master if a newer version exists. Cached in a transient so we don't
     * round-trip to getvmo.com on every admin page load.
     *
     * @return array{available:bool,version?:string,package_url?:string,release_notes?:string,required?:bool,release_id?:int}|null
     */
    private static function fetch_update_manifest($current_version) {
        $cache_key = 'vmo_client_update_manifest';
        $cached = get_site_transient($cache_key);
        if (is_array($cached) && ($cached['_cached_for'] ?? '') === $current_version) {
            return $cached['data'];
        }

        if (!self::master_proxy_url_is_configured()) {
            return null;
        }

        $proxy_base = self::get_master_proxy_base();
        // Manifest lives under the same vmo-builder/v1 namespace as the proxy.
        $manifest_url = str_replace('/proxy', '/client-updates/manifest', $proxy_base)
            . '?current_version=' . rawurlencode($current_version);

        $response = wp_remote_get($manifest_url, [
            'timeout' => 15,
            'headers' => [
                'X-VMO-License'      => (string) get_option('vmo_license_key', ''),
                'X-VMO-Installation' => (string) get_option('vmo_installation_id', ''),
            ],
        ]);

        $data = null;
        if (!is_wp_error($response) && wp_remote_retrieve_response_code($response) === 200) {
            $body = json_decode((string) wp_remote_retrieve_body($response), true);
            if (is_array($body) && !empty($body['success'])) {
                if (!empty($body['update_available']) && !empty($body['update'])) {
                    $u = $body['update'];
                    $data = [
                        'available'      => true,
                        'release_id'     => (int) ($u['id'] ?? 0),
                        'version'        => (string) ($u['version'] ?? ''),
                        'package_url'    => (string) ($u['package_url'] ?? ''),
                        'release_notes'  => (string) ($u['release_notes'] ?? ''),
                        'required'       => !empty($u['required']),
                        'banner_summary' => (string) ($u['banner_summary'] ?? ''),
                        'tutorial_links' => is_array($u['tutorial_links'] ?? null) ? $u['tutorial_links'] : [],
                        'release_title'  => (string) ($u['release_title'] ?? ''),
                    ];
                    // Stash by version so the post-update hook can find it
                    // even after WP swaps out plugin files on disk.
                    if ($data['version'] !== '') {
                        set_site_transient(
                            'vmo_pending_whats_new_' . md5($data['version']),
                            [
                                'version'        => $data['version'],
                                'release_title'  => $data['release_title'],
                                'banner_summary' => $data['banner_summary'],
                                'tutorial_links' => $data['tutorial_links'],
                                'release_notes'  => $data['release_notes'],
                            ],
                            7 * DAY_IN_SECONDS
                        );
                    }
                } else {
                    $data = ['available' => false];
                }
            }
        }

        // Cache for 6 hours on success, 30 minutes on failure to avoid hammering master.
        set_site_transient($cache_key, [
            '_cached_for' => $current_version,
            'data'        => $data,
        ], $data === null ? 30 * MINUTE_IN_SECONDS : 6 * HOUR_IN_SECONDS);

        return $data;
    }

    /**
     * Inject our update info into WordPress's plugin-update transient. WP's
     * native updater takes it from there — admin gets the standard "Update
     * available" notice and one-click update.
     */
    public static function inject_update_into_transient($transient) {
        if (!is_object($transient)) {
            $transient = new stdClass();
        }
        if (!isset($transient->response)) {
            $transient->response = [];
        }
        if (!isset($transient->no_update)) {
            $transient->no_update = [];
        }

        $plugin_basename = plugin_basename(self::$plugin_file);
        $plugin_data = self::get_plugin_data();
        $current_version = $plugin_data['Version'] ?? '0.0.0';

        $manifest = self::fetch_update_manifest($current_version);
        if (!$manifest) {
            return $transient;
        }

        if (!empty($manifest['available']) && version_compare($manifest['version'], $current_version, '>')) {
            $transient->response[$plugin_basename] = (object) [
                'id'          => $plugin_basename,
                'slug'        => self::$plugin_slug,
                'plugin'      => $plugin_basename,
                'new_version' => $manifest['version'],
                'url'         => 'https://getvmo.com/',
                'package'     => $manifest['package_url'],
                'tested'      => get_bloginfo('version'),
                'requires_php' => '7.4',
                'compatibility' => new stdClass(),
            ];
            unset($transient->no_update[$plugin_basename]);
        } else {
            $transient->no_update[$plugin_basename] = (object) [
                'id'          => $plugin_basename,
                'slug'        => self::$plugin_slug,
                'plugin'      => $plugin_basename,
                'new_version' => $current_version,
                'url'         => 'https://getvmo.com/',
                'package'     => '',
            ];
        }

        return $transient;
    }

    /**
     * Provide the "View details" lightbox content when the user clicks the
     * "View details" link on the plugin update row.
     */
    public static function plugins_api_details($result, $action, $args) {
        if ($action !== 'plugin_information') {
            return $result;
        }
        if (empty($args->slug) || $args->slug !== self::$plugin_slug) {
            return $result;
        }

        $plugin_data = self::get_plugin_data();
        $current_version = $plugin_data['Version'] ?? '0.0.0';
        $manifest = self::fetch_update_manifest($current_version);

        $info = new stdClass();
        $info->name = $plugin_data['Name'] ?? 'VMO';
        $info->slug = self::$plugin_slug;
        $info->version = $manifest['version'] ?? $current_version;
        $info->author = '<a href="https://getvmo.com">VMO</a>';
        $info->homepage = 'https://getvmo.com/';
        $info->requires = '6.0';
        $info->tested = get_bloginfo('version');
        $info->requires_php = '7.4';
        $info->download_link = $manifest['package_url'] ?? '';
        $info->sections = [
            'description' => $plugin_data['Description'] ?? '',
            'changelog'   => $manifest['release_notes'] ?? 'See https://getvmo.com/changelog.',
        ];
        return $info;
    }

    /**
     * The package URL master serves may be a private/signed URL or may need
     * the same X-VMO-License/X-VMO-Installation headers we send on the
     * manifest call. WP's WP_Upgrader fetches it via download_url() which
     * uses wp_safe_remote_get() — we hook upgrader_pre_download to fetch
     * with auth headers ourselves and hand back a local temp file path.
     */
    public static function attach_auth_to_download($reply, $package, $upgrader) {
        // Only handle our own plugin's package. Detect by domain + path hint.
        if (!is_string($package) || $package === '') {
            return $reply;
        }
        $host = wp_parse_url($package, PHP_URL_HOST);
        if (!$host || stripos($host, 'getvmo.com') === false) {
            return $reply;
        }

        $tmpfname = wp_tempnam($package);
        if (!$tmpfname) {
            return new WP_Error('vmo_update_tmp_fail', __('Could not create a temporary file for the update download.', 'vmo'));
        }

        $response = wp_remote_get($package, [
            'timeout'  => 300,
            'stream'   => true,
            'filename' => $tmpfname,
            'headers'  => [
                'X-VMO-License'      => (string) get_option('vmo_license_key', ''),
                'X-VMO-Installation' => (string) get_option('vmo_installation_id', ''),
            ],
        ]);

        if (is_wp_error($response)) {
            @unlink($tmpfname);
            return $response;
        }
        if (wp_remote_retrieve_response_code($response) !== 200) {
            @unlink($tmpfname);
            return new WP_Error(
                'vmo_update_http_error',
                sprintf(__('Update download failed (HTTP %d).', 'vmo'), (int) wp_remote_retrieve_response_code($response))
            );
        }

        return $tmpfname;
    }

    /** Read this plugin's header data (Name, Version, etc.). */
    private static function get_plugin_data() {
        if (!function_exists('get_plugin_data')) {
            require_once ABSPATH . 'wp-admin/includes/plugin.php';
        }
        return get_plugin_data(self::$plugin_file, false, false);
    }

    /**
     * Tell WordPress core auto-updater "yes, auto-update this plugin" for our
     * own plugin only. Returns the original decision for everything else so we
     * never accidentally enable auto-updates for plugins we don't own.
     */
    public static function enable_silent_auto_update($update, $item) {
        $plugin_basename = plugin_basename(self::$plugin_file);
        $candidate = '';
        if (is_object($item)) {
            $candidate = (string) ($item->plugin ?? $item->slug ?? '');
        } elseif (is_string($item)) {
            $candidate = $item;
        }
        if ($candidate === $plugin_basename || $candidate === self::$plugin_slug) {
            return true;
        }
        return $update;
    }

    // ─────────────────────────────────────────────────────────────────────
    // 4) LICENSE CLAIM UI
    // ─────────────────────────────────────────────────────────────────────

    /**
     * Resolve the master "/claim-license" endpoint URL from the configured
     * proxy base (which ends in "/proxy").
     */
    private static function get_claim_endpoint_url() {
        $proxy_base = self::get_master_proxy_base();
        return preg_replace('#/proxy/?$#', '/claim-license', $proxy_base);
    }

    /**
     * Ensure this install has a stable installation_id we can send to master.
     */
    private static function get_or_create_installation_id() {
        $id = (string) get_option('vmo_installation_id', '');
        if ($id !== '') {
            return $id;
        }
        if (function_exists('wp_generate_uuid4')) {
            $id = wp_generate_uuid4();
        } else {
            try {
                $id = bin2hex(random_bytes(16));
            } catch (Exception $e) {
                $id = md5(uniqid('', true));
            }
        }
        update_option('vmo_installation_id', $id, false);
        return $id;
    }

    /** Register the admin page under Settings. */
    public static function register_license_page() {
        add_options_page(
            __('VMO License', 'vmo'),
            __('VMO License', 'vmo'),
            'manage_options',
            'vmo-license',
            [__CLASS__, 'render_license_page']
        );
    }

    /**
     * Show a top-of-screen admin notice on every page until a license is
     * claimed. After it's claimed the notice disappears entirely.
     */
    public static function maybe_render_license_notice() {
        if (!current_user_can('manage_options')) {
            return;
        }
        if (get_option('vmo_license_key', '') !== '') {
            return;
        }
        $url = admin_url('options-general.php?page=vmo-license');
        echo '<div class="notice notice-warning"><p><strong>'
            . esc_html__('VMO is not yet licensed on this site.', 'vmo')
            . '</strong> '
            . sprintf(
                /* translators: %s: admin URL to the claim page */
                esc_html__('Paste the claim token from your VMO purchase email to activate AI services. %s', 'vmo'),
                '<a href="' . esc_url($url) . '">' . esc_html__('Activate License', 'vmo') . '</a>'
            )
            . '</p></div>';
    }

    /** Render the claim page (or the current-status panel if already claimed). */
    public static function render_license_page() {
        if (!current_user_can('manage_options')) {
            wp_die(__('Insufficient permissions.', 'vmo'));
        }

        $existing_key = (string) get_option('vmo_license_key', '');
        $notice       = get_transient('vmo_claim_notice');
        if ($notice) {
            delete_transient('vmo_claim_notice');
        }

        echo '<div class="wrap"><h1>' . esc_html__('VMO License', 'vmo') . '</h1>';

        if (is_array($notice)) {
            $class = $notice['type'] === 'success' ? 'notice-success' : 'notice-error';
            echo '<div class="notice ' . esc_attr($class) . '"><p>' . esc_html($notice['message']) . '</p></div>';
        }

        if ($existing_key !== '') {
            $masked = substr($existing_key, 0, 4) . str_repeat('•', max(0, strlen($existing_key) - 8)) . substr($existing_key, -4);
            echo '<div class="card" style="max-width:640px;padding:20px;">';
            echo '<h2>' . esc_html__('License Active', 'vmo') . '</h2>';
            echo '<p><strong>' . esc_html__('License Key:', 'vmo') . '</strong> <code>' . esc_html($masked) . '</code></p>';
            echo '<p>' . esc_html__('This site is bound to your VMO subscription. AI services are routed through the VMO master proxy at getvmo.com.', 'vmo') . '</p>';
            echo '<p><em>' . esc_html__('To rebind this site to a different license, paste a new claim token below. The existing key will be replaced.', 'vmo') . '</em></p>';
            echo '</div><br>';
        }

        $action_url = admin_url('admin-post.php');
        ?>
        <div class="card" style="max-width:640px;padding:20px;">
            <h2><?php esc_html_e('Claim Your License', 'vmo'); ?></h2>
            <p><?php esc_html_e('After purchasing a VMO plan at getvmo.com, you receive a one-time claim token by email. Paste it below to activate this site.', 'vmo'); ?></p>
            <form method="post" action="<?php echo esc_url($action_url); ?>">
                <input type="hidden" name="action" value="vmo_claim_license">
                <?php wp_nonce_field('vmo_claim_license_nonce', 'vmo_claim_nonce'); ?>
                <table class="form-table">
                    <tr>
                        <th><label for="vmo_claim_email"><?php esc_html_e('Purchase Email', 'vmo'); ?></label></th>
                        <td><input type="email" id="vmo_claim_email" name="vmo_claim_email" class="regular-text" required></td>
                    </tr>
                    <tr>
                        <th><label for="vmo_claim_token"><?php esc_html_e('Claim Token', 'vmo'); ?></label></th>
                        <td>
                            <input type="text" id="vmo_claim_token" name="vmo_claim_token" class="regular-text code" required pattern="[A-Za-z0-9]{16,128}" autocomplete="off">
                            <p class="description"><?php esc_html_e('32-character token from your purchase confirmation email.', 'vmo'); ?></p>
                        </td>
                    </tr>
                </table>
                <p class="submit">
                    <button type="submit" class="button button-primary"><?php esc_html_e('Activate License', 'vmo'); ?></button>
                </p>
            </form>
            <p style="margin-top:16px;font-size:12px;color:#666;">
                <?php
                printf(
                    /* translators: %s: master endpoint URL */
                    esc_html__('This site will POST to %s to claim your license. No payment data leaves this server.', 'vmo'),
                    '<code>' . esc_html(self::get_claim_endpoint_url()) . '</code>'
                );
                ?>
            </p>
        </div>
        </div>
        <?php
    }

    /** Handle the claim form POST. */
    public static function handle_claim_submission() {
        if (!current_user_can('manage_options')) {
            wp_die(__('Insufficient permissions.', 'vmo'));
        }
        check_admin_referer('vmo_claim_license_nonce', 'vmo_claim_nonce');

        $email = isset($_POST['vmo_claim_email']) ? sanitize_email(wp_unslash($_POST['vmo_claim_email'])) : '';
        $token = isset($_POST['vmo_claim_token']) ? trim(sanitize_text_field(wp_unslash($_POST['vmo_claim_token']))) : '';

        $redirect = admin_url('options-general.php?page=vmo-license');

        if (!is_email($email) || strlen($token) < 16) {
            set_transient('vmo_claim_notice', [
                'type'    => 'error',
                'message' => __('Please provide a valid email and a token of at least 16 characters.', 'vmo'),
            ], 60);
            wp_safe_redirect($redirect);
            exit;
        }

        if (!self::master_proxy_url_is_configured()) {
            set_transient('vmo_claim_notice', [
                'type'    => 'error',
                'message' => __('This VMO client package is missing a valid master proxy URL. Install a fresh client ZIP from getvmo.com before claiming a license.', 'vmo'),
            ], 60);
            wp_safe_redirect($redirect);
            exit;
        }

        $installation_id = self::get_or_create_installation_id();
        $endpoint        = self::get_claim_endpoint_url();

        $response = wp_remote_post($endpoint, [
            'timeout'   => 30,
            'sslverify' => true,
            'headers'   => [
                'Content-Type'       => 'application/json',
                'Accept'             => 'application/json',
                'X-VMO-Installation' => $installation_id,
            ],
            'body'      => wp_json_encode([
                'email'       => $email,
                'claim_token' => $token,
                'site_url'    => home_url('/'),
                'site_name'   => get_bloginfo('name'),
            ]),
        ]);

        if (is_wp_error($response)) {
            set_transient('vmo_claim_notice', [
                'type'    => 'error',
                'message' => __('Could not reach VMO master:', 'vmo') . ' ' . $response->get_error_message(),
            ], 60);
            wp_safe_redirect($redirect);
            exit;
        }

        $code = (int) wp_remote_retrieve_response_code($response);
        $body = json_decode((string) wp_remote_retrieve_body($response), true);
        $license_key = is_array($body) && !empty($body['license_key']) ? (string) $body['license_key'] : '';

        if ($code === 200 && $license_key !== '') {
            update_option('vmo_license_key', $license_key, false);
            if (!empty($body['plan'])) {
                update_option('vmo_plan', sanitize_text_field((string) $body['plan']), false);
            }
            set_transient('vmo_claim_notice', [
                'type'    => 'success',
                'message' => __('License activated successfully. AI services are now live on this site.', 'vmo'),
            ], 60);
        } else {
            $msg = is_array($body) && !empty($body['message'])
                ? (string) $body['message']
                : sprintf(__('License claim failed (HTTP %d). Verify your email and claim token.', 'vmo'), $code);
            set_transient('vmo_claim_notice', [
                'type'    => 'error',
                'message' => $msg,
            ], 60);
        }

        wp_safe_redirect($redirect);
        exit;
    }

    /**
     * Hooked to upgrader_process_complete. Fires AFTER WordPress has finished
     * installing one or more updates. If our plugin was in the batch, look up
     * the AI-authored banner data that fetch_update_manifest() cached during
     * the last manifest poll and promote it into the one-time
     * vmo_post_update_banner option that the admin notice renders.
     *
     * We intentionally do nothing if the metadata isn't there — that just
     * means the update came from a master that doesn't ship banner_summary
     * (older builds) or the operator left banner_summary blank.
     */
    public static function capture_post_update_banner($upgrader, $hook_extra) {
        if (!is_array($hook_extra)) { return; }
        if (($hook_extra['action'] ?? '') !== 'update') { return; }
        if (($hook_extra['type']   ?? '') !== 'plugin') { return; }
        $plugins = (array) ($hook_extra['plugins'] ?? []);
        $our_basename = plugin_basename(self::$plugin_file);
        if (!in_array($our_basename, $plugins, true)) { return; }

        // The freshly-installed version is what WP wrote to disk. Re-read the
        // plugin header instead of trusting whatever was in memory before the
        // swap, so we always look up the right transient.
        if (!function_exists('get_plugin_data')) {
            require_once ABSPATH . 'wp-admin/includes/plugin.php';
        }
        $data = get_plugin_data(self::$plugin_file, false, false);
        $new_version = (string) ($data['Version'] ?? '');
        if ($new_version === '') { return; }

        $key = 'vmo_pending_whats_new_' . md5($new_version);
        $payload = get_site_transient($key);
        if (!is_array($payload)) { return; }

        // Banner_summary is the gate — if the operator left it blank, there's
        // nothing to show the customer and we skip the notice entirely rather
        // than render an empty box.
        if (trim((string) ($payload['banner_summary'] ?? '')) === '') {
            delete_site_transient($key);
            return;
        }

        update_option('vmo_post_update_banner', [
            'version'        => (string) $payload['version'],
            'release_title'  => (string) ($payload['release_title']  ?? ''),
            'banner_summary' => (string) $payload['banner_summary'],
            'tutorial_links' => is_array($payload['tutorial_links'] ?? null) ? $payload['tutorial_links'] : [],
            'release_notes'  => (string) ($payload['release_notes']  ?? ''),
            'installed_at'   => current_time('mysql', true),
        ], false);
        delete_site_transient($key);
    }

    /**
     * Render the "What's new" admin notice on every admin page until the user
     * dismisses it. Dismissal is per-site, not per-user — once any admin
     * dismisses, the notice goes away for everyone (the option is cleared).
     */
    public static function maybe_render_whats_new_notice() {
        if (!current_user_can('manage_options')) { return; }
        $banner = get_option('vmo_post_update_banner', null);
        if (!is_array($banner) || empty($banner['banner_summary'])) { return; }

        $version       = (string) ($banner['version'] ?? '');
        $title         = (string) ($banner['release_title'] ?? 'VMO Update');
        $summary       = (string) $banner['banner_summary'];
        $tutorial_links = is_array($banner['tutorial_links'] ?? null) ? $banner['tutorial_links'] : [];

        $dismiss_url = wp_nonce_url(
            admin_url('admin-post.php?action=vmo_dismiss_whats_new'),
            'vmo_dismiss_whats_new'
        );
        ?>
        <div class="vmo-whats-new-overlay" role="dialog" aria-modal="true" aria-label="VMO update details">
            <div class="vmo-whats-new-card">
                <a class="vmo-whats-new-close" href="<?php echo esc_url($dismiss_url); ?>" aria-label="Dismiss VMO update">×</a>
                <div class="vmo-whats-new-kicker">VMO Update Installed</div>
                <h2>VMO v<?php echo esc_html($version); ?><?php echo $title !== '' ? ' — ' . esc_html($title) : ''; ?></h2>
                <p><?php echo esc_html($summary); ?></p>
                <?php if (!empty($tutorial_links)): ?>
                    <div class="vmo-whats-new-links">
                        <strong><?php esc_html_e('Learn more:', 'vmo-ai-website-builder'); ?></strong>
                        <?php foreach ($tutorial_links as $tl):
                            if (!is_array($tl) || empty($tl['url']) || empty($tl['label'])) { continue; }
                            printf(
                                '<a href="%s" target="_blank" rel="noopener noreferrer">%s</a>',
                                esc_url($tl['url']),
                                esc_html($tl['label'])
                            );
                        endforeach; ?>
                    </div>
                <?php endif; ?>
                <p><a href="<?php echo esc_url($dismiss_url); ?>" class="vmo-whats-new-button"><?php esc_html_e('Continue Building', 'vmo-ai-website-builder'); ?></a></p>
            </div>
        </div>
        <style>
            .vmo-whats-new-overlay{position:fixed;inset:0;z-index:999999;background:rgba(0,8,14,.72);backdrop-filter:blur(8px);display:flex;align-items:center;justify-content:center;padding:24px}
            .vmo-whats-new-card{position:relative;max-width:720px;width:min(720px,94vw);border-radius:24px;padding:28px;color:#eaf6ff;background:linear-gradient(180deg,rgba(255,255,255,.09),rgba(0,0,0,.42));border:1px solid rgba(80,220,255,.48);box-shadow:0 30px 110px rgba(0,0,0,.65),0 0 45px rgba(50,217,245,.18);font-family:Inter,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif}
            .vmo-whats-new-card:before{content:"";position:absolute;inset:-1px;border-radius:24px;pointer-events:none;background:linear-gradient(135deg,rgba(50,217,245,.35),transparent 35%,rgba(120,243,197,.18));mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);-webkit-mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);padding:1px;opacity:.75}
            .vmo-whats-new-kicker{color:#32d9f5;text-transform:uppercase;letter-spacing:.18em;font-size:12px;font-weight:900;margin-bottom:10px;text-shadow:0 0 18px rgba(50,217,245,.6)}
            .vmo-whats-new-card h2{color:#fff;margin:0 0 12px;font-size:28px;line-height:1.18}.vmo-whats-new-card p{font-size:15px;line-height:1.65;color:#cce7f5}.vmo-whats-new-close{position:absolute;right:16px;top:14px;width:34px;height:34px;border-radius:999px;display:flex;align-items:center;justify-content:center;background:#ff4f6d;color:#29040a!important;text-decoration:none!important;font-size:24px;font-weight:900;box-shadow:0 0 20px rgba(255,79,109,.35)}
            .vmo-whats-new-button{display:inline-flex;align-items:center;justify-content:center;min-height:40px;padding:9px 16px;border-radius:10px;background:#32d9f5;color:#031018!important;font-weight:900;text-decoration:none!important}.vmo-whats-new-links{display:flex;flex-wrap:wrap;gap:8px;margin:14px 0;color:#9fb6c9}.vmo-whats-new-links a{color:#32d9f5!important}
        </style>
        <?php
    }

    public static function handle_dismiss_whats_new() {
        if (!current_user_can('manage_options')) { wp_die('forbidden', 403); }
        check_admin_referer('vmo_dismiss_whats_new');
        delete_option('vmo_post_update_banner');
        wp_safe_redirect(wp_get_referer() ?: admin_url());
        exit;
    }
}

} // class_exists
