<?php
/**
 * Managed AI, commercial "pay monthly" AI tier for clients with no API key.
 *
 * Mirrors the CloudScale SEO plugin's managed-API model: instead of bringing
 * their own Anthropic/Gemini key, a client subscribes ($10/month via PayPal)
 * and all Anthropic-bound AI calls are routed through the shared CloudScale
 * proxy at api.cloudscale.consulting using a per-site license key. The same
 * subscription/license also powers the SEO plugin's managed API.
 *
 * Responsibilities:
 *   - Store + read the proxy subscription state (single option array).
 *   - Route AI requests through the proxy when the tier is enabled + licensed.
 *   - Drive the subscribe / poll / refresh / toggle / cancel / refund / boost
 *     flow via admin-ajax handlers.
 *   - Render the Home dashboard status card + the full management panel.
 *
 * @package CloudScale_DevTools
 * @since   1.10.150
 */

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

class CSDT_Managed_AI {

	/** Single wp_options key holding the whole subscription state. */
	const OPTION = 'csdt_devtools_managed_ai';

	/** Nonce action for all managed-AI ajax handlers. */
	const NONCE = 'csdt_managed_ai_nonce';

	/** CloudScale managed-AI proxy base URL (no trailing slash). */
	const PROXY = 'https://api.cloudscale.consulting';

	/** Default monthly request allowance, used before the first status refresh. */
	const DEFAULT_LIMIT = 200;

	/** Refund-guarantee window in days. */
	const REFUND_DAYS = 7;

	/**
	 * Wires up every ajax handler and the admin asset enqueue. Call once from
	 * the plugin's init.
	 *
	 * @since  1.10.150
	 * @return void
	 */
	public static function register(): void {
		// Lifecycle handlers (checkout, poll, recover, sync, disconnect, clear_pending)
		// are now registered by csdt_licence()->register_ajax() in cs-code-block.php.
		// Plugin-specific handlers kept here:
		add_action( 'wp_ajax_csdt_managed_ai_boost', array( __CLASS__, 'ajax_boost' ) );
		add_action( 'wp_ajax_csdt_managed_ai_set_enabled', array( __CLASS__, 'ajax_set_enabled' ) );
		add_action( 'wp_ajax_csdt_managed_ai_refund', array( __CLASS__, 'ajax_refund' ) );
		add_action( 'wp_ajax_csdt_managed_ai_billing_portal', array( __CLASS__, 'ajax_billing_portal' ) );
		add_action( 'wp_ajax_csdt_managed_ai_cancel', array( __CLASS__, 'ajax_cancel' ) );
		add_action( 'wp_ajax_csdt_managed_ai_recover', array( __CLASS__, 'ajax_recover' ) );
		add_action( 'wp_ajax_csdt_managed_ai_refresh_status', array( __CLASS__, 'ajax_refresh_status' ) );
		add_action( 'wp_ajax_csdt_managed_ai_poll_session', array( __CLASS__, 'ajax_poll_session' ) );
		add_action( 'wp_ajax_csdt_managed_ai_contact', array( __CLASS__, 'ajax_contact' ) );
		add_action( 'admin_enqueue_scripts', array( __CLASS__, 'enqueue_assets' ) );
		csdt_licence()->register_ajax();
	}

	// ── State ──────────────────────────────────────────────────────────────

	/**
	 * Returns the subscription option merged with defaults.
	 *
	 * @since  1.10.150
	 * @return array<string,mixed>
	 */
	public static function opts(): array {
		$saved = get_option( self::OPTION, array() );
		if ( ! is_array( $saved ) ) {
			$saved = array(); }
		return array_merge(
            array(
				'proxy_enabled'           => 0,
				'proxy_license_key'       => '',
				'proxy_status'            => '',   // '', pending, active, past_due, cancelled, refunded, inactive
				'proxy_subscription_code' => '', // Paystack subscription code (SUB_…), needed to cancel
				'proxy_usage'             => 0,
				'proxy_limit'             => self::DEFAULT_LIMIT,
				'proxy_boost'             => 0,   // rolled-over boost credits (never expire)
				'proxy_reset_date'        => '',
				'proxy_session_id'        => '',
				'proxy_email'             => '',
				'proxy_subscribed_at'     => '',
				'proxy_status_ts'         => 0,
            ),
            $saved
        );
	}

	/**
	 * Persists the subscription option.
	 *
	 * @since  1.10.150
	 * @param  array<string,mixed> $opts Full option array to store.
	 * @return void
	 */
	private static function save( array $opts ): void {
		update_option( self::OPTION, $opts );
	}

	/**
	 * True when AI calls should be routed through the managed proxy: the tier is
	 * toggled on AND a license key is stored. The proxy itself enforces the live
	 * subscription status (403 when inactive), so this gate stays lightweight.
	 *
	 * @since  1.10.150
	 * @return bool
	 */
	public static function should_use(): bool {
		$o = self::opts();
		return (int) $o['proxy_enabled'] === 1 && $o['proxy_license_key'] !== '';
	}

	/** True when the client currently holds an active subscription. */
	public static function is_active(): bool {
		return self::opts()['proxy_status'] === 'active';
	}

	// ── AI dispatch ──────────────────────────────────────────────────────────

	/**
	 * Forwards an Anthropic request to the managed proxy using the stored license
	 * key. Mirrors CSDT_AI_Dispatcher::call()'s contract so it is a drop-in for
	 * the direct Anthropic path.
	 *
	 * @since  1.10.150
	 * @param  string $system       System prompt.
	 * @param  string $user_message Initial user message.
	 * @param  string $model        Resolved Anthropic model id.
	 * @param  int    $max_tokens   Max tokens to generate.
	 * @return string Response text.
	 * @throws \RuntimeException On network error, quota (429), inactive licence (403), or other failure.
	 */
	public static function dispatch( string $system, string $user_message, string $model, int $max_tokens ): string {
		$o   = self::opts();
		$key = (string) $o['proxy_license_key'];

		$body = wp_json_encode(
            array(
				'model'      => $model,
				'max_tokens' => $max_tokens,
				'system'     => array(
					array(
						'type'          => 'text',
						'text'          => $system,
						'cache_control' => array( 'type' => 'ephemeral' ),
					),
				),
				'messages'   => array(
					array(
						'role'    => 'user',
						'content' => $user_message,
					),
				),
            )
        );
		if ( false === $body ) {
			throw new \RuntimeException( 'Failed to encode managed-AI request as JSON.' );
		}

		$resp = wp_remote_post(
            self::PROXY . '/v1/messages',
            array(
				'timeout' => 180,
				'headers' => array(
					'Content-Type'  => 'application/json',
					'X-License-Key' => $key,
				),
				'body'    => $body,
			)
        );
		if ( is_wp_error( $resp ) ) {
			throw new \RuntimeException( 'Managed AI network error: ' . $resp->get_error_message() ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- exception message; not rendered as HTML
		}

		$code = wp_remote_retrieve_response_code( $resp );
		$data = json_decode( wp_remote_retrieve_body( $resp ), true );

		if ( 429 === $code ) {
			throw new \RuntimeException( 'Managed AI: ' . ( $data['error'] ?? 'Monthly request limit reached, top up with a boost or wait for the monthly reset.' ) ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- exception message; not rendered as HTML
		}
		if ( 403 === $code ) {
			// License inactive/cancelled, cache the reported status so the UI updates.
			$o['proxy_status'] = $data['status'] ?? 'inactive';
			self::save( $o );
			throw new \RuntimeException( 'Managed AI: ' . ( $data['error'] ?? 'Subscription is not active.' ) ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- exception message; not rendered as HTML
		}
		if ( 200 !== $code ) {
			$detail = $data['error']['message'] ?? ( $data['error'] ?? '' );
			throw new \RuntimeException( "Managed AI HTTP {$code}" . ( $detail ? ": {$detail}" : '' ) ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- exception message; not rendered as HTML
		}

		$text = trim( $data['content'][0]['text'] ?? '' );
		// Optimistic local increment, the server counter is authoritative; a 429
		// still fires at the true limit regardless of this cached value.
		$o['proxy_usage'] = (int) $o['proxy_usage'] + 1;
		self::save( $o );
		return $text;
	}

	// ── Ajax: subscribe / billing ──────────────────────────────────────────

	/**
	 * Shared capability gate for every ajax handler. The nonce is verified
	 * directly with check_ajax_referer() at the top of each handler (so PHPCS /
	 * Plugin Check can trace it in-scope); this only enforces manage_options.
	 */
	private static function guard(): void {
		if ( ! current_user_can( 'manage_options' ) ) {
			wp_send_json_error( array( 'message' => 'Forbidden' ), 403 );
		}
	}

	/**
	 * Opens a subscription checkout session and stores the pending session token.
	 *
	 * @since  1.10.150
	 * @return void
	 */
	public static function ajax_checkout(): void {
		check_ajax_referer( self::NONCE, 'nonce' );
		self::guard();

		try {
			// phpcs:ignore WordPress.Security.NonceVerification.Missing -- nonce verified via check_ajax_referer() at handler top
			$email    = sanitize_email( wp_unslash( (string) ( $_POST['email'] ?? '' ) ) );
			$site_url = home_url( '/' );
			if ( ! is_email( $email ) ) {
				wp_send_json_error( array( 'message' => 'Please enter a valid email address.' ) );
			}

			$resp = wp_remote_post(
                self::PROXY . '/checkout',
                array(
					'timeout' => 20,
					'headers' => array( 'Content-Type' => 'application/json' ),
					'body'    => wp_json_encode(
                        array(
							'email'    => $email,
							'site_url' => $site_url,
							'type'     => 'subscription',
							'product'  => 'devtools',
                        )
                    ),
				)
            );
			if ( is_wp_error( $resp ) ) {
				wp_send_json_error( array( 'message' => $resp->get_error_message() ) );
			}

			$data = json_decode( wp_remote_retrieve_body( $resp ), true );
			if ( ! empty( $data['already_subscribed'] ) ) {
				wp_send_json_success(
                    array(
						'already_subscribed' => true,
						'manage_url'         => $data['manage_url'] ?? '',
                    )
                );
			}
			if ( empty( $data['checkout_url'] ) ) {
				wp_send_json_error( array( 'message' => $data['error'] ?? 'Checkout could not be started. Please try again.' ) );
			}

			if ( ! empty( $data['session_token'] ) ) {
				$o                     = self::opts();
				$o['proxy_session_id'] = sanitize_text_field( (string) $data['session_token'] );
				$o['proxy_status']     = 'pending';
				$o['proxy_email']      = $email;
				self::save( $o );
			}

			wp_send_json_success( array( 'checkout_url' => $data['checkout_url'] ) );
		} catch ( \Throwable $e ) {
			wp_send_json_error( $e->getMessage(), 500 );
		}
	}

	/**
	 * Starts a one-time "boost" top-up checkout for an existing subscriber.
	 *
	 * @since  1.10.150
	 * @return void
	 */
	public static function ajax_boost(): void {
		check_ajax_referer( self::NONCE, 'nonce' );
		self::guard();

		try {
			$o   = self::opts();
			$key = (string) $o['proxy_license_key'];
			if ( '' === $key ) {
				wp_send_json_error( array( 'message' => 'No active subscription found.' ) );
			}
			$email = (string) $o['proxy_email'];
			if ( '' === $email ) {
				$email = wp_get_current_user()->user_email ?? '';
			}

			$resp = wp_remote_post(
                self::PROXY . '/checkout',
                array(
					'timeout' => 20,
					'headers' => array( 'Content-Type' => 'application/json' ),
					'body'    => wp_json_encode(
                        array(
							'email'       => $email,
							'license_key' => $key,
							'type'        => 'boost',
                        )
                    ),
				)
            );
			if ( is_wp_error( $resp ) ) {
				wp_send_json_error( array( 'message' => $resp->get_error_message() ) );
			}
			$data = json_decode( wp_remote_retrieve_body( $resp ), true );
			if ( empty( $data['checkout_url'] ) ) {
				wp_send_json_error( array( 'message' => $data['error'] ?? 'Boost checkout could not be started.' ) );
			}
			wp_send_json_success( array( 'checkout_url' => $data['checkout_url'] ) );
		} catch ( \Throwable $e ) {
			wp_send_json_error( $e->getMessage(), 500 );
		}
	}

	/**
	 * Toggles whether the managed proxy is used for AI calls.
	 *
	 * @since  1.10.150
	 * @return void
	 */
	public static function ajax_set_enabled(): void {
		check_ajax_referer( self::NONCE, 'nonce' );
		self::guard();

		try {
			// phpcs:ignore WordPress.Security.NonceVerification.Missing -- nonce verified via check_ajax_referer() at handler top
			$enabled            = (int) (bool) sanitize_key( wp_unslash( $_POST['enabled'] ?? 0 ) );
			$o                  = self::opts();
			$o['proxy_enabled'] = $enabled;
			self::save( $o );
			wp_send_json_success();
		} catch ( \Throwable $e ) {
			wp_send_json_error( $e->getMessage(), 500 );
		}
	}

	/**
	 * Pulls the latest subscription status + usage from the proxy and caches it.
	 *
	 * @since  1.10.150
	 * @return void
	 */
	public static function ajax_refresh_status(): void {
		check_ajax_referer( self::NONCE, 'nonce' );
		self::guard();

		try {
			$o   = self::opts();
			$key = (string) $o['proxy_license_key'];
			if ( '' === $key ) {
				wp_send_json_error( array( 'message' => 'No license key stored.' ) );
			}

			$resp = wp_remote_get( self::PROXY . '/status?key=' . rawurlencode( $key ), array( 'timeout' => 10 ) );
			if ( is_wp_error( $resp ) ) {
				wp_send_json_error( array( 'message' => $resp->get_error_message() ) );
			}
			$data = json_decode( wp_remote_retrieve_body( $resp ), true );
			if ( ! $data ) {
				wp_send_json_error( array( 'message' => 'Invalid response from the subscription service.' ) );
			}

			$o['proxy_status']     = $data['status'] ?? $o['proxy_status'];
			$o['proxy_usage']      = (int) ( $data['monthly_requests'] ?? 0 );
			$o['proxy_limit']      = (int) ( $data['monthly_limit'] ?? self::DEFAULT_LIMIT );
			$o['proxy_boost']      = (int) ( $data['boost_balance'] ?? 0 );
			$o['proxy_reset_date'] = $data['reset_date'] ?? '';
			$o['proxy_status_ts']  = time();
			if ( ! empty( $data['subscription_code'] ) ) {
				$o['proxy_subscription_code'] = sanitize_text_field( (string) $data['subscription_code'] );
			}
			if ( ! empty( $data['created_at'] ) ) {
				$o['proxy_subscribed_at'] = $data['created_at'];
			}
			self::save( $o );
			wp_send_json_success( $data );
		} catch ( \Throwable $e ) {
			wp_send_json_error( $e->getMessage(), 500 );
		}
	}

	/**
	 * Polls the proxy after checkout; on activation writes the license key back.
	 *
	 * @since  1.10.150
	 * @return void
	 */
	public static function ajax_poll_session(): void {
		check_ajax_referer( self::NONCE, 'nonce' );
		self::guard();

		try {
			// phpcs:ignore WordPress.Security.NonceVerification.Missing -- nonce verified via check_ajax_referer() at handler top
			$session = sanitize_text_field( wp_unslash( (string) ( $_POST['session_token'] ?? '' ) ) );
			if ( '' === $session ) {
				wp_send_json_error( array( 'message' => 'No session token.' ) );
			}

			$resp = wp_remote_get( self::PROXY . '/status?session_token=' . rawurlencode( $session ), array( 'timeout' => 10 ) );
			if ( is_wp_error( $resp ) ) {
				wp_send_json_error( array( 'message' => $resp->get_error_message() ) );
			}
			$data = json_decode( wp_remote_retrieve_body( $resp ), true );
			if ( ! $data ) {
				wp_send_json_error( array( 'message' => 'Invalid response.' ) );
			}

			if ( ( $data['status'] ?? '' ) === 'active' && ! empty( $data['license_key'] ) ) {
				$o                      = self::opts();
				$o['proxy_status']      = 'active';
				$o['proxy_license_key'] = sanitize_text_field( (string) $data['license_key'] );
				$o['proxy_usage']       = (int) ( $data['monthly_requests'] ?? 0 );
				$o['proxy_limit']       = (int) ( $data['monthly_limit'] ?? self::DEFAULT_LIMIT );
				$o['proxy_boost']       = (int) ( $data['boost_balance'] ?? 0 );
				$o['proxy_reset_date']  = $data['reset_date'] ?? '';
				$o['proxy_session_id']  = '';
				$o['proxy_enabled']     = 1; // auto-enable on first activation
				$o['proxy_status_ts']   = time();
				if ( ! empty( $data['subscription_code'] ) ) {
					$o['proxy_subscription_code'] = sanitize_text_field( (string) $data['subscription_code'] );
				}
				if ( ! empty( $data['created_at'] ) ) {
					$o['proxy_subscribed_at'] = $data['created_at'];
				}
				self::save( $o );
			}
			wp_send_json_success( $data );
		} catch ( \Throwable $e ) {
			wp_send_json_error( $e->getMessage(), 500 );
		}
	}

	/**
	 * Cancels the active subscription (access continues to period end).
	 *
	 * @since  1.10.150
	 * @return void
	 */
	public static function ajax_cancel(): void {
		check_ajax_referer( self::NONCE, 'nonce' );
		self::guard();

		try {
			$o   = self::opts();
			$key = (string) $o['proxy_license_key'];
			if ( '' === $key ) {
				wp_send_json_error( array( 'message' => 'No active subscription found.' ) );
			}

			$resp = wp_remote_post(
                self::PROXY . '/cancel',
                array(
					'timeout' => 20,
					'headers' => array( 'Content-Type' => 'application/json' ),
					'body'    => wp_json_encode(
                        array(
							'key'               => $key,
							'confirm'           => 'yes',
							'subscription_code' => (string) $o['proxy_subscription_code'],
							'email'             => (string) $o['proxy_email'],
                        )
                    ),
				)
            );
			if ( is_wp_error( $resp ) ) {
				wp_send_json_error( array( 'message' => $resp->get_error_message() ) );
			}
			$data = json_decode( wp_remote_retrieve_body( $resp ), true );
			if ( ! empty( $data['ok'] ) ) {
				$o['proxy_status'] = 'cancelled';
				self::save( $o );
				wp_send_json_success( array( 'message' => 'Subscription cancelled.' ) );
			}
			wp_send_json_error( array( 'message' => $data['error'] ?? 'Cancellation failed. Please try again.' ) );
		} catch ( \Throwable $e ) {
			wp_send_json_error( $e->getMessage(), 500 );
		}
	}

	/**
	 * Requests a full refund within the guarantee window and clears the license.
	 *
	 * @since  1.10.150
	 * @return void
	 */
	public static function ajax_refund(): void {
		check_ajax_referer( self::NONCE, 'nonce' );
		self::guard();

		try {
			$o   = self::opts();
			$key = (string) $o['proxy_license_key'];
			if ( '' === $key ) {
				wp_send_json_error( array( 'message' => 'No active subscription found.' ) );
			}

			$resp = wp_remote_post(
                self::PROXY . '/refund-request',
                array(
					'timeout' => 25,
					'headers' => array( 'Content-Type' => 'application/json' ),
					'body'    => wp_json_encode(
                        array(
							'key'               => $key,
							'subscription_code' => (string) $o['proxy_subscription_code'],
							'email'             => (string) $o['proxy_email'],
                        )
                    ),
				)
            );
			if ( is_wp_error( $resp ) ) {
				wp_send_json_error( array( 'message' => $resp->get_error_message() ) );
			}
			$data = json_decode( wp_remote_retrieve_body( $resp ), true );
			if ( ! empty( $data['ok'] ) ) {
				$o['proxy_status']      = 'refunded';
				$o['proxy_license_key'] = '';
				$o['proxy_enabled']     = 0;
				self::save( $o );
				wp_send_json_success( array( 'message' => $data['message'] ?? 'Refund processed.' ) );
			}
			wp_send_json_error( array( 'message' => $data['error'] ?? 'Refund failed. Please contact support.' ) );
		} catch ( \Throwable $e ) {
			wp_send_json_error( $e->getMessage(), 500 );
		}
	}

	/**
	 * Recovers a lost license key by email (when checkout completed but the
	 * session poll failed to write the key back).
	 *
	 * @since  1.10.150
	 * @return void
	 */
	public static function ajax_recover(): void {
		check_ajax_referer( self::NONCE, 'nonce' );
		self::guard();

		try {
			$o = self::opts();
			// phpcs:ignore WordPress.Security.NonceVerification.Missing -- nonce verified via check_ajax_referer() at handler top
			$email = sanitize_email( wp_unslash( (string) ( $_POST['email'] ?? $o['proxy_email'] ?? '' ) ) );
			if ( ! is_email( $email ) ) {
				wp_send_json_error( array( 'message' => 'Enter the email address you subscribed with.' ) );
			}

			$resp = wp_remote_post(
                self::PROXY . '/recover',
                array(
					'timeout' => 15,
					'headers' => array( 'Content-Type' => 'application/json' ),
					'body'    => wp_json_encode( array( 'email' => $email ) ),
				)
            );
			if ( is_wp_error( $resp ) ) {
				wp_send_json_error( array( 'message' => $resp->get_error_message() ) );
			}
			$data = json_decode( wp_remote_retrieve_body( $resp ), true );
			if ( empty( $data['found'] ) || empty( $data['license_key'] ) ) {
				wp_send_json_error( array( 'message' => 'No active subscription found for that email address.' ) );
			}

			$key                    = sanitize_text_field( (string) $data['license_key'] );
			$o['proxy_license_key'] = $key;
			$o['proxy_status']      = 'active';
			$o['proxy_session_id']  = '';
			$o['proxy_email']       = $email;
			$o['proxy_enabled']     = 1;
			if ( ! empty( $data['subscription_code'] ) ) {
				$o['proxy_subscription_code'] = sanitize_text_field( (string) $data['subscription_code'] );
			}

			// Pull the true usage/limit/boost for the recovered key so the panel does
			// not show a misleading fresh 0/200 (the /recover response carries only the
			// key). Mirrors ajax_poll_session() / ajax_refresh_status().
			$status = wp_remote_get( self::PROXY . '/status?key=' . rawurlencode( $key ), array( 'timeout' => 10 ) );
			if ( ! is_wp_error( $status ) ) {
				$sd = json_decode( wp_remote_retrieve_body( $status ), true );
				if ( is_array( $sd ) ) {
					$o['proxy_status']     = $sd['status'] ?? $o['proxy_status'];
					$o['proxy_usage']      = (int) ( $sd['monthly_requests'] ?? $o['proxy_usage'] );
					$o['proxy_limit']      = (int) ( $sd['monthly_limit'] ?? self::DEFAULT_LIMIT );
					$o['proxy_boost']      = (int) ( $sd['boost_balance'] ?? 0 );
					$o['proxy_reset_date'] = $sd['reset_date'] ?? '';
					$o['proxy_status_ts']  = time();
					if ( ! empty( $sd['subscription_code'] ) ) {
						$o['proxy_subscription_code'] = sanitize_text_field( (string) $sd['subscription_code'] );
					}
					if ( ! empty( $sd['created_at'] ) ) {
						$o['proxy_subscribed_at'] = $sd['created_at'];
					}
				}
			}

			self::save( $o );
			wp_send_json_success( array( 'message' => 'License recovered. Reloading…' ) );
		} catch ( \Throwable $e ) {
			wp_send_json_error( $e->getMessage(), 500 );
		}
	}

	/**
	 * Returns the billing-portal URL for the subscriber to manage their card.
	 *
	 * @since  1.10.150
	 * @return void
	 */
	public static function ajax_billing_portal(): void {
		check_ajax_referer( self::NONCE, 'nonce' );
		self::guard();

		try {
			$key = (string) self::opts()['proxy_license_key'];
			if ( '' === $key ) {
				wp_send_json_error( array( 'message' => 'No license key stored.' ) );
			}
			$resp = wp_remote_get(
                self::PROXY . '/billing-portal?key=' . rawurlencode( $key ),
                array(
					'timeout'     => 15,
					'redirection' => 0,
				)
            );
			if ( is_wp_error( $resp ) ) {
				wp_send_json_error( array( 'message' => $resp->get_error_message() ) );
			}
			$location = wp_remote_retrieve_header( $resp, 'location' );
			if ( $location ) {
				wp_send_json_success( array( 'url' => $location ) );
			}
			$data = json_decode( wp_remote_retrieve_body( $resp ), true );
			wp_send_json_error( array( 'message' => $data['error'] ?? 'Could not open the billing portal.' ) );
		} catch ( \Throwable $e ) {
			wp_send_json_error( $e->getMessage(), 500 );
		}
	}

	/**
	 * Sends a subscriber's free-text message to the site's Telegram channel.
	 *
	 * @since  1.10.150
	 * @return void
	 */
	public static function ajax_contact(): void {
		check_ajax_referer( self::NONCE, 'nonce' );
		self::guard();

		$message = sanitize_textarea_field( wp_unslash( $_POST['message'] ?? '' ) );
		if ( '' === $message ) {
			wp_send_json_error( array( 'message' => 'Message is required.' ) );
		}
		if ( ! class_exists( 'CloudScale_Telegram' ) || ! CloudScale_Telegram::is_configured() ) {
			wp_send_json_error( array( 'message' => 'Messaging is not configured on this site yet.' ) );
		}

		$o      = self::opts();
		$email  = (string) $o['proxy_email'];
		$site   = (string) ( wp_parse_url( home_url(), PHP_URL_HOST ) ?: home_url() );
		$status = (string) $o['proxy_status'] ?: 'unknown';
		$key    = (string) $o['proxy_license_key'];
		$masked = $key !== '' ? '…' . substr( $key, -6 ) : 'none';

		$body = "💬 User message — no action needed\n\nProduct: Cyber & DevTools (Managed AI)\nLicence: {$masked}\nStatus:  {$status}\nFrom:    {$email}\nSite:    {$site}\n\nMessage:\n{$message}";
		$sent = CloudScale_Telegram::send( $body, 'Managed AI', 'info' );

		if ( $sent ) {
			wp_send_json_success( array( 'message' => "Message sent. We'll get back to you." ) );
		} else {
			wp_send_json_error( array( 'message' => 'Could not send your message. Please try again.' ) );
		}
	}

	// ── Assets ───────────────────────────────────────────────────────────────

	/**
	 * Enqueues the managed-AI front-end script on the plugin's tools page only.
	 *
	 * @since  1.10.150
	 * @param  string $hook Current admin page hook suffix.
	 * @return void
	 */
	public static function enqueue_assets( $hook ): void {
		if ( $hook !== 'tools_page_' . CloudScale_DevTools::TOOLS_SLUG ) {
			return;
		}
		wp_enqueue_script(
			'cloudscale-licence',
			plugins_url( 'assets/cloudscale-licence.js', dirname( __DIR__ ) . '/cs-code-block.php' ),
			array( 'jquery' ),
			CloudScale_DevTools::VERSION,
			true
		);
		wp_enqueue_script(
			'csdt-managed-ai',
			plugins_url( 'assets/cs-managed-ai.js', dirname( __DIR__ ) . '/cs-code-block.php' ),
			array( 'cloudscale-licence' ),
			CloudScale_DevTools::VERSION,
			true
		);
		wp_localize_script(
            'csdt-managed-ai',
            'csdtManagedAi',
            array(
				'ajaxUrl' => admin_url( 'admin-ajax.php' ),
				'nonce'   => wp_create_nonce( self::NONCE ),
			)
        );
	}

	// ── Rendering ──────────────────────────────────────────────────────────

	/**
	 * Renders the compact Home-dashboard status card (one grid cell). Matches the
	 * inline-styled card pattern used by the other Home cards.
	 *
	 * @since  1.10.150
	 * @return void
	 */
	public static function render_home_card(): void {
		$o      = self::opts();
		$status = (string) $o['proxy_status'];
		$anchor = '#cs-managed-ai-panel';

		// Palette per state: [bg, border, text].
		if ( 'active' === $status ) {
			$bg    = '#f0fdf4';
			$bd    = '#86efac';
			$tx    = '#15803d';
			$icon  = '&#x2705;';
			$label = __( 'Active', 'cloudscale-cyber-devtools' );
			// Total available = this month's remaining base allowance + rolled-over boost credits.
			$left = max( 0, (int) $o['proxy_limit'] - (int) $o['proxy_usage'] ) + (int) $o['proxy_boost'];
			/* translators: %d: number of AI requests remaining (monthly allowance + boost credits). */
			$detail = sprintf( __( '%d credits left', 'cloudscale-cyber-devtools' ), $left );
			$cta    = __( 'Manage', 'cloudscale-cyber-devtools' );
		} elseif ( 'pending' === $status ) {
			$bg     = '#f0f9ff';
			$bd     = '#7dd3fc';
			$tx     = '#0284c7';
			$icon   = '&#x23F3;';
			$label  = __( 'Activating…', 'cloudscale-cyber-devtools' );
			$detail = __( 'Waiting for payment to confirm.', 'cloudscale-cyber-devtools' );
			$cta    = __( 'View', 'cloudscale-cyber-devtools' );
		} elseif ( 'past_due' === $status ) {
			$bg     = '#fffbeb';
			$bd     = '#fcd34d';
			$tx     = '#92400e';
			$icon   = '&#x26A0;&#xFE0F;';
			$label  = __( 'Payment failed', 'cloudscale-cyber-devtools' );
			$detail = __( 'Update your card to restore AI access.', 'cloudscale-cyber-devtools' );
			$cta    = __( 'Fix', 'cloudscale-cyber-devtools' );
		} elseif ( 'cancelled' === $status ) {
			$bg     = '#f8fafc';
			$bd     = '#e2e8f0';
			$tx     = '#374151';
			$icon   = '&#x1F6D1;';
			$label  = __( 'Cancelled', 'cloudscale-cyber-devtools' );
			$detail = __( 'Active until the end of the billing period.', 'cloudscale-cyber-devtools' );
			$cta    = __( 'Resubscribe', 'cloudscale-cyber-devtools' );
		} else {
			// Not subscribed, the conversion card.
			$bg     = '#eef2ff';
			$bd     = '#c7d2fe';
			$tx     = '#4338ca';
			$icon   = '&#x1F4B3;';
			$label  = __( 'Pay monthly for AI', 'cloudscale-cyber-devtools' );
			$detail = __( 'No API key? $10/mo via PayPal, cancel anytime.', 'cloudscale-cyber-devtools' );
			$cta    = __( 'Subscribe', 'cloudscale-cyber-devtools' );
		}
		?>
		<div style="background:<?php echo esc_attr( $bg ); ?>;border:1px solid <?php echo esc_attr( $bd ); ?>;border-radius:6px;padding:12px 14px;">
			<div style="font-size:10px;font-weight:700;color:#6b7280;text-transform:uppercase;letter-spacing:.05em;margin-bottom:5px;"><?php esc_html_e( 'Managed AI', 'cloudscale-cyber-devtools' ); ?></div>
			<div style="font-size:13px;font-weight:700;color:<?php echo esc_attr( $tx ); ?>;"><?php echo $icon; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- fixed HTML entity ?> <?php echo esc_html( $label ); ?></div>
			<div style="font-size:11px;color:#6b7280;margin-top:2px;line-height:1.4;"><?php echo esc_html( $detail ); ?></div>
			<a href="<?php echo esc_attr( $anchor ); ?>" style="font-size:11px;color:#6366f1;font-weight:600;display:inline-block;margin-top:6px;text-decoration:none;"><?php echo esc_html( $cta ); ?> &rarr;</a>
		</div>
		<?php
	}

	/**
	 * Renders the full management panel (below the Home status grid). Handles all
	 * states: not subscribed, pending, active, past_due, cancelled.
	 *
	 * @since  1.10.150
	 * @return void
	 */
	public static function render_panel(): void {
		$o      = self::opts();
		$status = (string) $o['proxy_status'];
		$usage  = (int) $o['proxy_usage'];
		$limit  = max( 1, (int) $o['proxy_limit'] );
		$pct    = (int) min( 100, round( $usage / $limit * 100 ) );
		$boost  = (int) $o['proxy_boost'];
		// Total available = remaining monthly allowance + rolled-over boost credits.
		$left          = max( 0, $limit - $usage ) + $boost;
		$reset         = (string) $o['proxy_reset_date'];
		$enabled       = (int) $o['proxy_enabled'] === 1;
		$key           = (string) $o['proxy_license_key'];
		$masked_key    = $key !== '' ? str_repeat( '•', max( 0, strlen( $key ) - 4 ) ) . substr( $key, -4 ) : '';
		$sub_code      = (string) $o['proxy_subscription_code'];
		$bar_color     = $pct >= 90 ? '#ef4444' : ( $pct >= 70 ? '#f59e0b' : '#22c55e' );
		$default_email = wp_get_current_user()->user_email ?? '';

		// Refund window: days remaining out of REFUND_DAYS since subscribed_at.
		$refund_days_left = 0;
		if ( $o['proxy_subscribed_at'] ) {
			$sub_ts = strtotime( (string) $o['proxy_subscribed_at'] );
			if ( $sub_ts ) {
				$elapsed          = ( time() - $sub_ts ) / DAY_IN_SECONDS;
				$refund_days_left = (int) max( 0, ceil( self::REFUND_DAYS - $elapsed ) );
			}
		}
		?>
		<div id="cs-managed-ai-panel" style="margin:0 12px 12px;border:1px solid #c7d2fe;border-radius:8px;overflow:hidden;">
			<div style="background:linear-gradient(135deg,#4338ca 0%,#6366f1 100%);padding:10px 14px;display:flex;align-items:center;justify-content:space-between;">
				<span style="color:#fff;font-size:12px;font-weight:700;letter-spacing:.04em;">&#x1F4B3; <?php esc_html_e( 'Managed AI, pay monthly, no API key needed', 'cloudscale-cyber-devtools' ); ?></span>
				<?php if ( 'active' === $status ) : ?>
					<span style="background:#22c55e;color:#fff;padding:2px 10px;border-radius:12px;font-size:11px;font-weight:700;"><?php echo esc_html( sprintf( /* translators: %d: credits remaining. */ __( '%d credits left', 'cloudscale-cyber-devtools' ), $left ) ); ?></span>
				<?php elseif ( 'past_due' === $status ) : ?>
					<span style="background:#f59e0b;color:#fff;padding:2px 10px;border-radius:12px;font-size:11px;font-weight:700;"><?php esc_html_e( 'Payment failed', 'cloudscale-cyber-devtools' ); ?></span>
				<?php elseif ( 'cancelled' === $status ) : ?>
					<span style="background:#6b7280;color:#fff;padding:2px 10px;border-radius:12px;font-size:11px;font-weight:700;"><?php esc_html_e( 'Cancelled', 'cloudscale-cyber-devtools' ); ?></span>
				<?php elseif ( 'pending' === $status ) : ?>
					<span style="background:#3b82f6;color:#fff;padding:2px 10px;border-radius:12px;font-size:11px;font-weight:700;"><?php esc_html_e( 'Activating…', 'cloudscale-cyber-devtools' ); ?></span>
				<?php endif; ?>
			</div>
			<div style="background:#fff;padding:14px;">
			<?php if ( 'active' === $status ) : ?>

				<div style="display:flex;align-items:center;gap:12px;margin-bottom:12px;flex-wrap:wrap;">
					<label style="display:flex;align-items:center;gap:8px;cursor:pointer;font-size:13px;">
						<input type="checkbox" id="cs-managed-ai-toggle" <?php checked( $enabled ); ?>>
						<strong><?php esc_html_e( 'Use Managed AI (instead of your own key)', 'cloudscale-cyber-devtools' ); ?></strong>
					</label>
					<button type="button" class="button button-primary" id="cs-managed-ai-save-toggle"><?php esc_html_e( 'Save', 'cloudscale-cyber-devtools' ); ?></button>
					<span id="cs-managed-ai-save-status" role="status" aria-live="polite" style="font-size:12px;color:#6b7280;"></span>
				</div>

				<div style="margin-bottom:14px;">
					<div style="display:flex;justify-content:space-between;margin-bottom:4px;font-size:12px;">
						<span><?php echo esc_html( sprintf( /* translators: 1: requests used, 2: monthly limit, 3: percent used. */ __( '%1$d / %2$d requests used (%3$d%%)', 'cloudscale-cyber-devtools' ), $usage, $limit, $pct ) ); ?></span>
						<?php
                        if ( $reset ) :
							?>
                            <span style="color:#6b7280;"><?php echo esc_html( sprintf( /* translators: %s: reset date. */ __( 'Resets %s', 'cloudscale-cyber-devtools' ), $reset ) ); ?></span><?php endif; ?>
					</div>
					<div style="background:#e5e7eb;border-radius:4px;height:8px;overflow:hidden;">
						<div style="background:<?php echo esc_attr( $bar_color ); ?>;height:100%;width:<?php echo esc_attr( $pct ); ?>%;transition:width .4s;"></div>
					</div>
					<?php if ( $boost > 0 ) : ?>
					<div style="font-size:12px;color:#15803d;margin-top:6px;font-weight:600;">&#x26A1; <?php echo esc_html( sprintf( /* translators: %d: number of rolled-over boost credits. */ __( '+%d boost credits, roll over, never expire', 'cloudscale-cyber-devtools' ), $boost ) ); ?></div>
					<?php endif; ?>
				</div>

				<div style="display:flex;align-items:center;gap:8px;margin-bottom:14px;flex-wrap:wrap;">
					<input type="text" readonly value="<?php echo esc_attr( $masked_key ); ?>" style="font-family:monospace;width:280px;background:#f9fafb;">
					<button type="button" class="button" id="cs-managed-ai-copy-key" data-key="<?php echo esc_attr( $key ); ?>"><?php esc_html_e( 'Copy Key', 'cloudscale-cyber-devtools' ); ?></button>
				</div>

				<div style="margin-bottom:14px;font-size:12px;color:#6b7280;">
					<?php esc_html_e( 'Paystack subscription:', 'cloudscale-cyber-devtools' ); ?>
					<?php if ( '' !== $sub_code ) : ?>
						<code style="font-size:12px;background:#f3f4f6;padding:2px 6px;border-radius:4px;"><?php echo esc_html( $sub_code ); ?></code>
					<?php else : ?>
						<em><?php esc_html_e( 'not linked yet — click Refresh status to fetch it', 'cloudscale-cyber-devtools' ); ?></em>
					<?php endif; ?>
				</div>

				<div style="display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-bottom:10px;">
					<button type="button" class="button button-primary" id="cs-managed-ai-boost-btn">&#x26A1; <?php esc_html_e( 'Boost +200 credits, $10', 'cloudscale-cyber-devtools' ); ?></button>
					<button type="button" class="button" id="cs-managed-ai-refresh-btn">&#x21BB; <?php esc_html_e( 'Refresh status', 'cloudscale-cyber-devtools' ); ?></button>
					<a href="#" id="cs-managed-ai-billing-link" style="font-size:12px;"><?php esc_html_e( 'Manage card', 'cloudscale-cyber-devtools' ); ?></a>
				</div>
				<div style="max-width:460px;display:flex;flex-direction:column;gap:6px;">
					<button type="button" id="cs-managed-ai-cancel-btn"
						style="width:100%;box-sizing:border-box;display:flex;align-items:center;justify-content:center;gap:8px;background:#fff2f2;border:1.5px solid #dc2626;color:#b91c1c;padding:11px 16px;border-radius:5px;font-size:0.875rem;font-weight:600;cursor:pointer;line-height:1.2;">
						<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="15" y1="9" x2="9" y2="15"/><line x1="9" y1="9" x2="15" y2="15"/></svg>
						<?php esc_html_e( 'Cancel subscription', 'cloudscale-cyber-devtools' ); ?>
					</button>
					<p id="cs-managed-ai-cancel-msg" style="display:none;font-size:0.82rem;margin:0;"></p>
					<button type="button" id="cs-managed-ai-contact-toggle"
						style="width:100%;box-sizing:border-box;display:flex;align-items:center;justify-content:center;gap:8px;background:#f8faff;border:1.5px solid #6366f1;color:#4338ca;padding:11px 16px;border-radius:5px;font-size:0.875rem;font-weight:600;cursor:pointer;line-height:1.2;">
						<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>
						<?php esc_html_e( 'Leave us a message', 'cloudscale-cyber-devtools' ); ?>
					</button>
					<div id="cs-managed-ai-contact-form" style="display:none;margin-top:2px;">
						<textarea id="cs-managed-ai-contact-text" rows="3"
							placeholder="<?php esc_attr_e( 'Type your message\xe2\x80\xa6', 'cloudscale-cyber-devtools' ); ?>"
							style="width:100%;box-sizing:border-box;padding:8px 10px;border:1.5px solid #6366f1;border-radius:5px;font-size:0.875rem;margin-bottom:6px;resize:vertical;"></textarea>
						<button type="button" id="cs-managed-ai-contact-btn"
							style="width:100%;box-sizing:border-box;display:flex;align-items:center;justify-content:center;gap:8px;background:#4338ca;color:#fff;border:none;border-radius:5px;padding:11px 16px;font-size:0.875rem;font-weight:600;cursor:pointer;">
							<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="22" y1="2" x2="11" y2="13"/><polygon points="22 2 15 22 11 13 2 9 22 2"/></svg>
							<?php esc_html_e( 'Send message', 'cloudscale-cyber-devtools' ); ?>
						</button>
						<p id="cs-managed-ai-contact-msg" style="font-size:0.78rem;margin-top:6px;color:#6b7280;text-align:center;"></p>
					</div>
				</div>

				<?php if ( $refund_days_left > 0 ) : ?>
				<div id="cs-managed-ai-refund-wrap" style="margin-top:12px;padding:10px 12px;background:#fef2f2;border:1px solid #fecaca;border-radius:6px;font-size:12px;">
					<span style="color:#6b7280;"><?php esc_html_e( '7-day guarantee:', 'cloudscale-cyber-devtools' ); ?> </span>
					<strong style="color:#b91c1c;"><?php echo esc_html( sprintf( /* translators: %d: days remaining in the refund window. */ _n( '%d day left', '%d days left', $refund_days_left, 'cloudscale-cyber-devtools' ), $refund_days_left ) ); ?></strong>
					<button type="button" id="cs-managed-ai-refund-btn" style="margin-left:12px;background:none;border:none;color:#b91c1c;font-size:12px;cursor:pointer;text-decoration:underline;padding:0;"><?php esc_html_e( 'Request refund', 'cloudscale-cyber-devtools' ); ?></button>
					<span id="cs-managed-ai-refund-msg" style="display:none;margin-left:8px;"></span>
				</div>
				<?php endif; ?>

			<?php elseif ( 'pending' === $status ) : ?>

				<p style="font-size:14px;margin:0 0 6px;"><strong><?php esc_html_e( 'Finishing your subscription…', 'cloudscale-cyber-devtools' ); ?></strong></p>
				<p style="font-size:13px;color:#6b7280;margin:0 0 12px;"><?php esc_html_e( 'Complete the payment in the checkout tab. This page will update automatically once it confirms (usually a few seconds).', 'cloudscale-cyber-devtools' ); ?></p>
				<span id="cs-managed-ai-pending-session" data-session="<?php echo esc_attr( (string) $o['proxy_session_id'] ); ?>"></span>
				<button type="button" class="button" id="cs-managed-ai-refresh-btn">&#x21BB; <?php esc_html_e( 'Check now', 'cloudscale-cyber-devtools' ); ?></button>
				<div style="margin-top:12px;font-size:12px;color:#6b7280;">
					<?php esc_html_e( 'Already paid but stuck here? Recover your license by email:', 'cloudscale-cyber-devtools' ); ?>
					<div style="display:flex;gap:8px;align-items:center;margin-top:6px;flex-wrap:wrap;">
						<input type="email" id="cs-managed-ai-recover-email" class="regular-text" value="<?php echo esc_attr( (string) $o['proxy_email'] ?: $default_email ); ?>" style="min-width:240px;">
						<button type="button" class="button" id="cs-managed-ai-recover-btn"><?php esc_html_e( 'Recover license', 'cloudscale-cyber-devtools' ); ?></button>
						<span id="cs-managed-ai-recover-msg" style="font-size:12px;"></span>
					</div>
				</div>

			<?php elseif ( 'past_due' === $status ) : ?>

				<p style="font-size:14px;margin:0 0 8px;color:#92400e;"><strong><?php esc_html_e( 'Your last payment failed.', 'cloudscale-cyber-devtools' ); ?></strong></p>
				<p style="font-size:13px;color:#6b7280;margin:0 0 12px;"><?php esc_html_e( 'Update your card details to restore managed AI access.', 'cloudscale-cyber-devtools' ); ?></p>
				<a href="#" class="button button-primary" id="cs-managed-ai-billing-link"><?php esc_html_e( 'Update payment method', 'cloudscale-cyber-devtools' ); ?></a>
				<button type="button" class="button" id="cs-managed-ai-refresh-btn" style="margin-left:8px;">&#x21BB; <?php esc_html_e( 'Refresh status', 'cloudscale-cyber-devtools' ); ?></button>

			<?php else : // not subscribed, cancelled, refunded, inactive ?>

				<?php if ( 'cancelled' === $status ) : ?>
				<p style="font-size:13px;color:#6b7280;margin:0 0 12px;"><?php esc_html_e( 'Your subscription is cancelled and stays active until the end of the billing period. Resubscribe anytime.', 'cloudscale-cyber-devtools' ); ?></p>
				<?php endif; ?>
				<p style="font-size:15px;margin:0 0 8px;"><strong><?php esc_html_e( "No API key? We'll handle the AI for $10/month (USD).", 'cloudscale-cyber-devtools' ); ?></strong></p>
				<ul style="margin:0 0 14px 20px;list-style:disc;font-size:13px;line-height:1.8;">
					<li><?php esc_html_e( '200 AI requests per month included', 'cloudscale-cyber-devtools' ); ?></li>
					<li><?php esc_html_e( 'AI cyber pen-test scans, site audits, and debugging', 'cloudscale-cyber-devtools' ); ?></li>
					<li><?php esc_html_e( 'No Anthropic or Gemini account or API setup needed', 'cloudscale-cyber-devtools' ); ?></li>
					<li><?php esc_html_e( 'Cancel anytime · 7-day money-back guarantee', 'cloudscale-cyber-devtools' ); ?></li>
				</ul>
				<?php
				$_pp_mode   = defined( 'PAYPAL_MODE' ) ? PAYPAL_MODE : ( getenv( 'PAYPAL_MODE' ) ?: 'live' );
				$_pp_plan   = ( 'live' === $_pp_mode ) ? ( defined( 'PAYPAL_DEVTOOLS_LIVE_PLAN_ID' ) ? PAYPAL_DEVTOOLS_LIVE_PLAN_ID : getenv( 'PAYPAL_DEVTOOLS_LIVE_PLAN_ID' ) ) : ( defined( 'PAYPAL_DEVTOOLS_SANDBOX_PLAN_ID' ) ? PAYPAL_DEVTOOLS_SANDBOX_PLAN_ID : getenv( 'PAYPAL_DEVTOOLS_SANDBOX_PLAN_ID' ) );
				$_pp_client = ( 'live' === $_pp_mode ) ? ( defined( 'PAYPAL_LIVE_CLIENT_ID' ) ? PAYPAL_LIVE_CLIENT_ID : getenv( 'PAYPAL_LIVE_CLIENT_ID' ) ) : ( defined( 'PAYPAL_SANDBOX_CLIENT_ID' ) ? PAYPAL_SANDBOX_CLIENT_ID : getenv( 'PAYPAL_SANDBOX_CLIENT_ID' ) );
				if ( $_pp_plan && $_pp_client ) :
					?>
				<div style="max-width:460px;margin-top:12px;">
				<div id="cs-managed-ai-paypal-btn" style="min-height:44px;"></div>
				<p style="margin:4px 0 0;font-size:0.78rem;color:#555;text-align:center;">Billed via PayPal &middot; Cancel anytime</p>
				<p id="cs-managed-ai-subscribe-msg" style="display:none;margin-top:8px;font-size:13px;"></p>
				<?php // phpcs:ignore WordPress.WP.EnqueuedResources.NonEnqueuedScript -- PayPal SDK loaded inline because plan_id is PHP-generated and SDK must load after DOM ?>
				<script>
				(function(){
					function renderCsdtPaypal(){
						window.paypal.Buttons({
							style:{shape:'rect',color:'blue',layout:'vertical',label:'subscribe'},
							createSubscription:function(d,actions){
								return actions.subscription.create({
									plan_id:<?php echo wp_json_encode( $_pp_plan ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- wp_json_encode is safe ?>,
									application_context:{shipping_preference:'NO_SHIPPING',user_action:'SUBSCRIBE_NOW'}
								});
							},
							onApprove:function(data){
								var msg=document.getElementById('cs-managed-ai-subscribe-msg');
								if(msg){msg.style.display='block';msg.style.color='';msg.textContent='Activating subscription…';}
								jQuery.post(csdtManagedAi.ajaxUrl,{
									action:'csdt_managed_ai_paypal_subscription',
									nonce:csdtManagedAi.nonce,
									subscription_id:data.subscriptionID
								}).done(function(r){
									if(r.success&&r.data&&r.data.status==='active'){window.location.reload();}
									else if(r.success){if(msg){msg.textContent='Subscription received! Your licence will activate shortly.';}}
									else{if(msg){msg.style.color='#b91c1c';msg.textContent=(r.data&&r.data.message)||'Activation failed. Please contact support.';}}
								}).fail(function(){if(msg){msg.style.color='#b91c1c';msg.textContent='Network error. Please try again.';}});
							},
							onError:function(){var msg=document.getElementById('cs-managed-ai-subscribe-msg');if(msg){msg.style.display='block';msg.style.color='#b91c1c';msg.textContent='PayPal error. Please try again.';}}
						}).render('#cs-managed-ai-paypal-btn');
					}
					if(window.paypal){renderCsdtPaypal();return;}
					var s=document.createElement('script');
					s.src='https://www.paypal.com/sdk/js?client-id='+encodeURIComponent(<?php echo wp_json_encode( $_pp_client ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- wp_json_encode is safe ?>)+'&vault=true&intent=subscription';
					s.setAttribute('data-sdk-integration-source','button-factory');
					s.onload=renderCsdtPaypal;
					document.head.appendChild(s);
				})();
				</script>
				<?php else : ?>
				<div style="display:flex;gap:8px;align-items:flex-end;flex-wrap:wrap;">
					<div>
						<label for="cs-managed-ai-email" style="display:block;font-size:12px;margin-bottom:4px;font-weight:600;"><?php esc_html_e( 'Email address', 'cloudscale-cyber-devtools' ); ?></label>
						<input type="email" id="cs-managed-ai-email" class="regular-text" value="<?php echo esc_attr( $default_email ); ?>" placeholder="you@example.com" style="min-width:260px;">
					</div>
					<button type="button" class="button button-primary" id="cs-managed-ai-subscribe-btn"><?php esc_html_e( 'Get Started via PayPal', 'cloudscale-cyber-devtools' ); ?></button>
				</div>
				<p id="cs-managed-ai-subscribe-msg" style="display:none;margin-top:8px;font-size:13px;"></p>
				<?php endif; ?>

				<div style="margin-top:10px;display:flex;flex-direction:column;gap:6px;">
					<button type="button" id="cs-managed-ai-recover-toggle"
						style="width:100%;box-sizing:border-box;display:flex;align-items:center;justify-content:center;gap:8px;background:#f0fdf9;border:1.5px solid #0d9488;color:#0d9488;padding:11px 16px;border-radius:5px;font-size:0.875rem;font-weight:600;cursor:pointer;line-height:1.2;">
						<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M21 2l-2 2m-7.61 7.61a5.5 5.5 0 1 1-7.778 7.778 5.5 5.5 0 0 1 7.777-7.777zm0 0L15.5 7.5m0 0l3 3L22 7l-3-3m-3.5 3.5L19 4"/></svg>
						<?php esc_html_e( 'Paid but no key? Recover →', 'cloudscale-cyber-devtools' ); ?>
					</button>
					<div id="cs-managed-ai-recover-form" style="display:none;margin-top:2px;">
						<input type="email" id="cs-managed-ai-recover-email"
							placeholder="<?php esc_attr_e( 'Email used at checkout', 'cloudscale-cyber-devtools' ); ?>"
							style="width:100%;box-sizing:border-box;padding:8px 10px;border:1.5px solid #0d9488;border-radius:5px;font-size:0.875rem;margin-bottom:6px;">
						<button type="button" id="cs-managed-ai-recover-btn"
							style="width:100%;box-sizing:border-box;display:flex;align-items:center;justify-content:center;gap:8px;background:#0d9488;color:#fff;border:none;border-radius:5px;padding:11px 16px;font-size:0.875rem;font-weight:600;cursor:pointer;">
							<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M21 2l-2 2m-7.61 7.61a5.5 5.5 0 1 1-7.778 7.778 5.5 5.5 0 0 1 7.777-7.777zm0 0L15.5 7.5m0 0l3 3L22 7l-3-3m-3.5 3.5L19 4"/></svg>
							<?php esc_html_e( 'Recover Licence', 'cloudscale-cyber-devtools' ); ?>
						</button>
						<p id="cs-managed-ai-recover-msg" style="font-size:0.78rem;margin-top:6px;color:#6b7280;text-align:center;"></p>
					</div>
					<button type="button" id="cs-managed-ai-contact-toggle"
						style="width:100%;box-sizing:border-box;display:flex;align-items:center;justify-content:center;gap:8px;background:#f8faff;border:1.5px solid #6366f1;color:#4338ca;padding:11px 16px;border-radius:5px;font-size:0.875rem;font-weight:600;cursor:pointer;line-height:1.2;">
						<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>
						<?php esc_html_e( 'Leave us a message', 'cloudscale-cyber-devtools' ); ?>
					</button>
					<div id="cs-managed-ai-contact-form" style="display:none;margin-top:2px;">
						<textarea id="cs-managed-ai-contact-text" rows="3"
							placeholder="<?php esc_attr_e( 'Type your message\xe2\x80\xa6', 'cloudscale-cyber-devtools' ); ?>"
							style="width:100%;box-sizing:border-box;padding:8px 10px;border:1.5px solid #6366f1;border-radius:5px;font-size:0.875rem;margin-bottom:6px;resize:vertical;"></textarea>
						<button type="button" id="cs-managed-ai-contact-btn"
							style="width:100%;box-sizing:border-box;display:flex;align-items:center;justify-content:center;gap:8px;background:#4338ca;color:#fff;border:none;border-radius:5px;padding:11px 16px;font-size:0.875rem;font-weight:600;cursor:pointer;">
							<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="22" y1="2" x2="11" y2="13"/><polygon points="22 2 15 22 11 13 2 9 22 2"/></svg>
							<?php esc_html_e( 'Send message', 'cloudscale-cyber-devtools' ); ?>
						</button>
						<p id="cs-managed-ai-contact-msg" style="font-size:0.78rem;margin-top:6px;color:#6b7280;text-align:center;"></p>
					</div>
				</div>
				</div><!-- /max-width wrapper -->

			<?php endif; ?>
			</div>
		</div>
		<?php
	}
}
