<?php
/**
 * AP Family Archive wiring for the vendored BWD Support Copilot widget.
 *
 * ALL Family Archive-specific configuration for the copilot lives here: the
 * module in includes/support-copilot/ is host-agnostic and is vendored VERBATIM
 * from its canonical home (01-Plugin-Repos/70-Shared-Modules/bwd-support-copilot,
 * GitHub blackandwhitedigital/bwd-support-copilot) - never edit the vendored copy
 * in place, and never add Family Archive knowledge to it.
 *
 * This file ships in BOTH builds. The assistant answers on two paths: local
 * answers use the AI provider configured in WordPress (7.0+) and need no
 * account, token or licence of any kind; only the optional external support
 * service authorises - via the site's Freemius install id where available, or
 * an admin-entered token - so no credential ships in the zip and the hub
 * tiers service usage server-side.
 *
 * The strip tokens themselves are deliberately NOT written out here. They are
 * inert in this position - Freemius reads the directive from the main plugin
 * file's header, and the v0.3.0 free build was verified byte-identical to this
 * file - but naming them invites a copy-paste into a place where they would bite.
 *
 * Local answering uses the WordPress core AI Client (the AI provider the
 * site administrator configures in WordPress, 7.0+), with no account, token
 * or service authorisation of any kind. The OPTIONAL external support
 * service is a separate, consent-gated path that can also answer and can
 * reach human support; only that path authorises (module 2.1.0).
 *
 * The Support tab renders through the admin screen's tab seams
 * (apfaar_admin_tabs / apfaar_admin_tab_support).
 *
 * @package AP_Family_Archive
 * @since   0.3.0
 */

defined( 'ABSPATH' ) || exit;

/**
 * Class APFAAR_Support_Copilot_Boot
 */
class APFAAR_Support_Copilot_Boot {

	/**
	 * Register Family Archive's vendored module copy with the newest-wins loader
	 * and schedule the config queue.
	 *
	 * Registration only - no translated strings here. This runs early so this copy
	 * takes part in the loader's newest-wins negotiation; the config (which builds
	 * translated strings) is queued on `init` by init() below, where a text domain
	 * may safely be loaded.
	 *
	 * @return void
	 */
	public static function register() {
		static $done = false;
		if ( $done ) {
			return;
		}
		$done = true;

		require_once APFAAR_PATH . 'includes/support-copilot/support-copilot.php';
		add_action( 'init', array( __CLASS__, 'init' ), 5 );
	}

	/**
	 * Queue Family Archive's widget config and attach the Support tab.
	 *
	 * Runs on `init` because the config carries translated strings, and loading a
	 * text domain before `init` is doing_it_wrong. A queue() arriving after the
	 * loader has booted is boot-ed immediately by the loader, so this is safe.
	 *
	 * @return void
	 */
	public static function init() {
		static $queued = false;
		if ( $queued ) {
			return;
		}
		$queued = true;

		$capability = APFAAR_Admin::CAP;

		\BlackWhiteDigital\SupportCopilot\Loader::queue(
			array(
				'id'                  => 'apfaar',
				'slug'                => 'ap-family-archive',
				'version'             => APFAAR_VERSION,
				// AncestorPress-branded per the estate rollout decision: the panel
				// header carries the suite name, the subtitle names the plugin.
				'display_name'        => __( 'AncestorPress Support Copilot', 'ap-family-archive' ),
				'endpoint_default'    => 'https://bwtlsupport.com/wp-json/bwsa/v1',
				'endpoint_option'     => 'apfaar_copilot_endpoint',
				'token_option'        => 'apfaar_copilot_token',
				'consent_option'      => 'apfaar_copilot_consent',
				'capability'          => $capability,
				'settings_capability' => $capability,
				'capability_check'    => array( __CLASS__, 'user_can' ),
				'freemius_install_id' => array( __CLASS__, 'freemius_install_id' ),
				'activation_url'      => array( __CLASS__, 'activation_url' ),
				// Local answering bridges the WordPress core AI Client (a site
				// facility, WordPress 7.0+): when the site has a configured AI
				// provider, the assistant answers locally with no account,
				// token or service authorisation of any kind (module 2.1.0).
				'local_ai'            => array( __CLASS__, 'local_ai' ),
				'local_ai_available'  => array( __CLASS__, 'local_ai_available' ),
				'is_plugin_screen'    => array( __CLASS__, 'is_apfaar_screen' ),
				'settings_keys'       => array(
					'apfaar_display_style',
					'apfaar_whatsnew_days',
					'apfaar_whatsnew_max',
				),
				'diagnostics'         => array( __CLASS__, 'diagnostics' ),
				'suite'               => array( __CLASS__, 'suite_manifest' ),
				'accent'              => '#2F9950',
				'strings'             => self::strings(),
			)
		);

		add_filter( 'apfaar_admin_tabs', array( __CLASS__, 'add_tab' ) );
		add_action( 'apfaar_admin_tab_support', array( __CLASS__, 'render_panel' ) );
		add_action( 'admin_post_apfaar_save_copilot_settings', array( __CLASS__, 'handle_save' ) );
	}

	/**
	 * The booted widget instance. Null until the loader has negotiated and booted
	 * (plugins_loaded 99 / init 0).
	 *
	 * @return \BlackWhiteDigital\SupportCopilot\Copilot|null
	 */
	public static function instance() {
		return class_exists( '\BlackWhiteDigital\SupportCopilot\Loader' )
			? \BlackWhiteDigital\SupportCopilot\Loader::instance( 'apfaar' )
			: null;
	}

	/**
	 * Is the given admin screen one of Family Archive's? Every screen hangs off the
	 * 'apfaar' menu slug, so every screen id contains "apfaar" - the same test the admin
	 * class itself uses to decide whether to enqueue its own assets.
	 *
	 * @param WP_Screen $screen Current screen.
	 * @return bool
	 */
	public static function is_apfaar_screen( $screen ) {
		return is_object( $screen ) && false !== strpos( (string) $screen->id, 'apfaar' );
	}

	/**
	 * Module capability gate routed through Family Archive's access model. All
	 * admin surfaces use the single APFAAR_Admin::CAP constant, so this maps to
	 * current_user_can - but it stays a named host callable so the module never
	 * calls current_user_can directly and AP Access has one seam to take over.
	 *
	 * @param string $capability Capability the module is checking.
	 * @return bool
	 */
	public static function user_can( $capability ) {
		return current_user_can( $capability );
	}

	/**
	 * The site's Freemius install id for licence-based auto-authorisation. When > 0
	 * the module authorises with the support hub using the install id instead of an
	 * admin-pasted token. Returns 0 until the site is registered with Freemius, in
	 * which case the module falls back to token mode.
	 *
	 * @return int
	 */
	public static function freemius_install_id() {
		if ( ! function_exists( 'apfaar_fs' ) ) {
			return 0;
		}
		$fs = apfaar_fs();
		if ( ! is_object( $fs ) || ! method_exists( $fs, 'is_registered' ) || ! $fs->is_registered() ) {
			return 0;
		}
		$site = method_exists( $fs, 'get_site' ) ? $fs->get_site() : null;
		return ( is_object( $site ) && ! empty( $site->id ) ) ? (int) $site->id : 0;
	}

	/**
	 * Family Archive's Freemius opt-in URL, linked from the onboarding
	 * "Connect the support service" prompt. Empty when Freemius is
	 * unavailable, in which case the module omits that prompt.
	 *
	 * @return string
	 */
	public static function activation_url() {
		if ( ! function_exists( 'apfaar_fs' ) ) {
			return '';
		}
		$fs = apfaar_fs();
		if ( is_object( $fs ) && method_exists( $fs, 'get_activation_url' ) ) {
			return (string) $fs->get_activation_url();
		}
		return '';
	}

	/**
	 * Local AI bridge: generate one reply on the WordPress core AI Client
	 * (the AI provider the site administrator configured in WordPress).
	 * Nothing here reads or depends on Freemius, tokens or any service
	 * state, and no genealogical data is involved - the module supplies
	 * only its own documentation context and the admin's question.
	 *
	 * @param  string $system System prompt.
	 * @param  string $user   User/content prompt.
	 * @param  array  $opts   max_tokens.
	 * @return string|\WP_Error
	 */
	public static function local_ai( $system, $user, array $opts = array() ) {
		if ( ! function_exists( 'wp_ai_client_prompt' ) ) {
			return new \WP_Error( 'apfaar_no_ai_client', __( 'The WordPress AI Client is not available on this site.', 'ap-family-archive' ) );
		}
		$max_tokens = isset( $opts['max_tokens'] ) ? (int) $opts['max_tokens'] : 1400;
		$system     = (string) $system;
		try {
			// Combined prompt is the baseline every provider honours; a separate
			// system instruction is preferred where the client supports it. The
			// builder resolves methods via __call, so probe by calling inside
			// try/catch - method_exists() reports false for magic methods.
			$builder = wp_ai_client_prompt( '' === $system ? $user : $system . "\n\n" . $user )->using_max_tokens( $max_tokens );
			if ( '' !== $system ) {
				try {
					$b2 = wp_ai_client_prompt( $user )->using_max_tokens( $max_tokens )->using_system_instruction( $system );
					if ( is_object( $b2 ) ) {
						$builder = $b2;
					}
				} catch ( \Throwable $e ) {
					unset( $e );
				}
			}
			$text = $builder->generate_text();
			if ( is_wp_error( $text ) ) {
				return $text;
			}
			return (string) $text;
		} catch ( \Throwable $e ) {
			return new \WP_Error( 'apfaar_ai_failed', __( 'The site AI provider could not produce a reply.', 'ap-family-archive' ) );
		}
	}

	/**
	 * Whether the WordPress core AI Client is present with a provider that
	 * supports text generation right now. Probe-guarded: the client ships
	 * with WordPress 7.0, and this plugin supports 6.2 upwards.
	 *
	 * @return bool
	 */
	public static function local_ai_available() {
		if ( ! function_exists( 'wp_ai_client_prompt' ) ) {
			return false;
		}
		try {
			return (bool) wp_ai_client_prompt( 'test' )->is_supported_for_text_generation();
		} catch ( \Throwable $e ) {
			return false;
		}
	}

	/**
	 * Read-only diagnostics extras sent with each chat message.
	 *
	 * Whitelist discipline is stricter here than elsewhere in the estate because
	 * this plugin sits on shared genealogical data behind a privacy floor: NOTHING
	 * derived from people, families, events, media or sources is reported, and
	 * neither is the selected tree's name (a tree is usually named for a family, so
	 * the name itself is personal data). Only this plugin's own display
	 * configuration and setup state leave the site.
	 *
	 * @return array
	 */
	public static function diagnostics() {
		$out = array();

		// Setup state as a yes/no, never the dataset name.
		$out['dataset_selected'] = '' !== (string) get_option( 'apfaar_gedcom', '' ) ? 'yes' : 'no';
		$out['display_style']    = (string) get_option( 'apfaar_display_style', 'heritage' );

		// How much of the archive has been published, as a count of mapped pages.
		$mapped = 0;
		foreach ( array( 'hub', 'surnames', 'search', 'person', 'family', 'places', 'anniversaries', 'whatsnew', 'mostwanted', 'statistics', 'ahnentafel', 'descendants', 'source', 'media', 'mediaitem', 'relationship' ) as $page_key ) {
			if ( absint( get_option( 'apfaar_page_' . $page_key, 0 ) ) > 0 ) {
				++$mapped;
			}
		}
		$out['pages_mapped'] = $mapped;

		if ( class_exists( 'AncestorPress\Core\Loader' ) && method_exists( 'AncestorPress\Core\Loader', 'active_version' ) ) {
			$out['ancp_core'] = (string) \AncestorPress\Core\Loader::active_version();
		}

		// Edition is not reported from here: the support hub derives it from the
		// site's licence when it authorises the request.
		return $out;
	}

	/**
	 * Suite manifest: the other active AncestorPress plugins and their versions, so
	 * support can see the whole suite a question is being asked from.
	 *
	 * @return array [ [slug, version], ... ]
	 */
	public static function suite_manifest() {
		if ( ! function_exists( 'get_plugins' ) ) {
			require_once ABSPATH . 'wp-admin/includes/plugin.php';
		}

		$manifest = array();
		foreach ( (array) get_option( 'active_plugins', array() ) as $basename ) {
			$slug = dirname( $basename );
			if ( '.' === $slug || 'ap-family-archive' === $slug ) {
				continue;
			}
			if ( 0 !== strpos( $slug, 'ap-' ) && 0 !== strpos( $slug, 'ancp-' ) ) {
				continue;
			}
			$file = WP_PLUGIN_DIR . '/' . $basename;
			if ( ! file_exists( $file ) ) {
				continue;
			}
			$data       = get_plugin_data( $file, false, false );
			$manifest[] = array(
				'slug'    => $slug,
				'version' => (string) ( $data['Version'] ?? '' ),
			);
		}
		return $manifest;
	}

	// ---------------------------------------------------------------------------
	// Support tab (hung on the admin screen's tab seams)
	// ---------------------------------------------------------------------------

	/**
	 * Add the Support tab to the admin screen's tab list (apfaar_admin_tabs).
	 *
	 * Administrator-only, matching the Dashboard and Import / Export tabs: the
	 * assistant's own capability gate is APFAAR_Admin::CAP, so an editor who could
	 * see the tab could not use what is on it.
	 *
	 * @param array $tabs Tab key => label.
	 * @return array
	 */
	public static function add_tab( $tabs ) {
		if ( ! is_array( $tabs ) || ! current_user_can( APFAAR_Admin::CAP ) ) {
			return $tabs;
		}
		$tabs['support'] = __( 'Support', 'ap-family-archive' );
		return $tabs;
	}

	/**
	 * Render the Support tab body (apfaar_admin_tab_support): the vendored widget's
	 * settings fields wrapped in Family Archive's form + nonce.
	 *
	 * @return void
	 */
	public static function render_panel() {
		if ( ! current_user_can( APFAAR_Admin::CAP ) ) {
			return;
		}
		$copilot = self::instance();

		// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- display-only notice flag set by our own redirect.
		$notice = isset( $_GET['apfaar_copilot'] ) ? sanitize_key( wp_unslash( $_GET['apfaar_copilot'] ) ) : '';
		if ( 'saved' === $notice ) {
			echo '<div class="apfa-notice-verdant">' . esc_html__( 'Support settings saved.', 'ap-family-archive' ) . '</div>';
		} elseif ( 'error' === $notice ) {
			echo '<div class="apfa-notice-amber">' . esc_html__( 'Support settings could not be saved. Please reload the page and try again.', 'ap-family-archive' ) . '</div>';
		}

		echo '<h2>' . esc_html__( 'Support Copilot', 'ap-family-archive' ) . '</h2>';
		echo '<p>' . esc_html__( 'The Support Copilot is the floating Help button on every Family Archive screen. It opens a popup chat that answers usage and setup questions instantly, grounded in documentation matched to your installed version. It is read-only: it can explain, but it can never change anything, and it never reads your genealogical data - no person, family, event, source or media record is sent, and neither is the name of the tree you publish.', 'ap-family-archive' ) . '</p>';

		if ( ! $copilot ) {
			echo '<p>' . esc_html__( 'The Support Copilot module is not loaded.', 'ap-family-archive' ) . '</p>';
			return;
		}

		if ( ! $copilot->is_configured() ) {
			echo '<div class="apfa-notice-amber">' . esc_html__( 'The assistant has no answer path available yet. Configure an AI provider in WordPress for local answers, or register with the optional AncestorPress support service for its limited free service-answered questions (or paste a support token below). Either way, the Help button still opens with setup guidance.', 'ap-family-archive' ) . '</div>';
		}

		echo '<form method="post" action="' . esc_url( admin_url( 'admin-post.php' ) ) . '">';
		wp_nonce_field( 'apfaar_copilot_save', 'apfaar_copilot_nonce' );
		echo '<input type="hidden" name="action" value="apfaar_save_copilot_settings" />';
		$copilot->render_settings_fields();
		submit_button( __( 'Save support settings', 'ap-family-archive' ) );
		echo '</form>';
	}

	/**
	 * Process a Support settings save (admin_post). The host verifies its outer
	 * nonce and capability here, co-located with the input this handler reads; the
	 * module's save_settings() re-checks its own nonce and the settings capability
	 * and sanitises every field.
	 *
	 * @return void
	 */
	public static function handle_save() {
		if ( ! current_user_can( APFAAR_Admin::CAP ) ) {
			wp_die( esc_html__( 'You are not allowed to change support assistant settings.', 'ap-family-archive' ), '', array( 'response' => 403 ) );
		}
		check_admin_referer( 'apfaar_copilot_save', 'apfaar_copilot_nonce' );

		$copilot = self::instance();
		$result  = 'error';
		if ( $copilot ) {
			// Sanitised field-by-field inside save_settings(), which also re-verifies
			// its own nonce and the settings capability.
			// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
			$saved  = $copilot->save_settings( wp_unslash( $_POST ) );
			$result = is_wp_error( $saved ) ? 'error' : 'saved';
		}

		wp_safe_redirect(
			add_query_arg(
				array(
					'page'         => 'apfaar',
					'tab'          => 'support',
					'apfaar_copilot' => $result,
				),
				admin_url( 'admin.php' )
			)
		);
		exit;
	}

	/**
	 * Translated UI strings for the widget (module defaults are plain English; i18n
	 * happens here so the vendored module stays text-domain-free).
	 *
	 * The two answer paths carry distinct copy (module 2.1.0): local answers
	 * on the site AI provider need no authorisation of any kind; only the
	 * optional external support service connects/authorises.
	 *
	 * @return array
	 */
	private static function strings() {
		return array(
			'launcher_label'                   => __( 'Help', 'ap-family-archive' ),
			'launcher_title'                   => __( 'Open the AncestorPress support assistant (answers usage and setup questions on this screen, no page change)', 'ap-family-archive' ),
			'panel_subtitle'                   => __( 'Instant answers about AP Family Archive', 'ap-family-archive' ),
			'consent_heading'                  => __( 'Before you start', 'ap-family-archive' ),
			/* translators: %host%: hostname of the support service endpoint (replaced by the widget, not by sprintf). */
			'consent_body'                     => __( 'When this site has an AI provider configured in WordPress, your question and chat history stay on this site and are answered locally; the assistant only fetches product documentation from %host%, sending your site address and plugin version. When the support service answers instead (no local provider, or you have connected the service), it also receives: your question and recent chat history; your site address and, where available, its licence installation identifier, used to authorise that service and apply fair-use limits; the versions of the AncestorPress plugins installed; the admin screen you are on; and a short whitelist of non-sensitive settings. Your genealogical data is never sent on either path: no person, family, event, source or media record leaves your site, and neither does the name of the tree you publish. Passwords and API keys are never collected, and the assistant cannot change anything on your site. Ordinary questions are not kept after they are answered; only if you choose "Send to support" is the conversation retained, so that it can become a support ticket.', 'ap-family-archive' ),
			'consent_accept'                   => __( 'Agree and start chatting', 'ap-family-archive' ),
			'consent_accept_hint'              => __( 'Records your agreement for this whole site and enables the assistant for everyone who can use it. Only a user who can manage Family Archive settings can accept. You can withdraw consent on this tab.', 'ap-family-archive' ),
			'greeting'                         => __( 'Hi! Ask me anything about setting up or using AP Family Archive - importing a GEDCOM, the archive pages, privacy for living people, sources or media. I answer from documentation matched to your installed version.', 'ap-family-archive' ),
			'input_placeholder'                => __( 'Type your question…', 'ap-family-archive' ),
			'send'                             => __( 'Send', 'ap-family-archive' ),
			'thinking'                         => __( 'Thinking…', 'ap-family-archive' ),
			'error_generic'                    => __( 'Sorry - the assistant could not be reached. Please try again in a moment.', 'ap-family-archive' ),
			'soft_gate'                        => __( 'Usage and setup questions are handled right here in the assistant. Support tickets are prioritised for bugs, billing and data issues.', 'ap-family-archive' ),
			'escalate_anyway'                  => __( 'Still need to raise a ticket?', 'ap-family-archive' ),
			'escalate_offer'                   => __( 'Not solved? You can send this conversation to the support team.', 'ap-family-archive' ),
			'escalate_button'                  => __( 'Send to support', 'ap-family-archive' ),
			'escalate_confirm_label'           => __( 'Your email for ticket replies', 'ap-family-archive' ),
			'escalate_confirm_hint'            => __( 'The full chat transcript and the diagnostic snapshot are attached to the ticket so support can see what was already tried.', 'ap-family-archive' ),
			'escalate_send'                    => __( 'Create ticket', 'ap-family-archive' ),
			'escalate_cancel'                  => __( 'Cancel', 'ap-family-archive' ),
			'escalated_prefix'                 => __( 'Ticket created:', 'ap-family-archive' ),
			'close_title'                      => __( 'Close the assistant (your conversation is kept until you leave this page)', 'ap-family-archive' ),
			'new_chat'                         => __( 'New chat', 'ap-family-archive' ),
			'new_chat_title'                   => __( 'Clear this conversation and start again', 'ap-family-archive' ),
			'disabled_notice'                  => __( 'The assistant is not available: this site has no AI provider configured in WordPress, and the optional support service is not connected.', 'ap-family-archive' ),
			'local_no_answer'                  => __( 'The AI provider configured on this site could not answer this from the documentation available. Rephrasing may help; the optional support service (Support tab) can also answer questions and reach human support.', 'ap-family-archive' ),
			'escalate_needs_service'           => __( 'Sending a conversation to human support uses the support service, which is not connected on this site.', 'ap-family-archive' ),
			/* translators: %d: number of free hub-answered questions remaining this month (replaced by the widget, not by sprintf). */
			'allowance_left'                   => __( '%d free assistant answer(s) left this month on this site.', 'ap-family-archive' ),
			'consent_denied'                   => __( 'Only a user who can manage Family Archive settings can enable the assistant for this site.', 'ap-family-archive' ),
			'settings_error_cap'               => __( 'You are not allowed to change support assistant settings.', 'ap-family-archive' ),
			'settings_error_nonce'             => __( 'Security check failed - please reload the page and try again.', 'ap-family-archive' ),
			'settings_error_https'             => __( 'The support endpoint must be an https:// URL.', 'ap-family-archive' ),
			'settings_token_label'             => __( 'Support token', 'ap-family-archive' ),
			'settings_token_ph_set'            => __( '•••••••• (a token is set - leave blank to keep it)', 'ap-family-archive' ),
			'settings_token_ph_unset'          => __( 'Paste the support-service token, if you have one', 'ap-family-archive' ),
			/* translators: %state%: "enabled" or "disabled" (replaced by the widget, not by sprintf). */
			'settings_token_desc'              => __( 'Authorises this site with the OPTIONAL AncestorPress support service (service-answered questions and human support tickets). The service connection is %state%. Local answers on the AI provider configured in WordPress work either way and need no token. The token is stored in this site database and sent only to the support endpoint below - never to the browser.', 'ap-family-archive' ),
			'settings_state_enabled'           => __( 'enabled', 'ap-family-archive' ),
			'settings_state_disabled'          => __( 'disabled', 'ap-family-archive' ),
			'settings_token_clear_label'       => __( 'Clear the stored token (turns off token-based access).', 'ap-family-archive' ),
			'settings_endpoint_label'          => __( 'Support endpoint', 'ap-family-archive' ),
			'settings_endpoint_desc'           => __( 'Base URL of the support service the assistant talks to (must be https://). Leave blank to use the default shown below. Only change this if AncestorPress tells you to.', 'ap-family-archive' ),
			'settings_mode_label'              => __( 'Answer mode', 'ap-family-archive' ),
			'settings_mode_hub_title'          => __( 'Support service', 'ap-family-archive' ),
			'settings_mode_hub_desc'           => __( 'When this site has an AI provider configured in WordPress, questions are answered locally on that provider, grounded in documentation matched to your installed version. Otherwise, or when connected, the AncestorPress support service answers. Your genealogical data is never sent on either path.', 'ap-family-archive' ),
			'settings_consent_label'           => __( 'Privacy consent', 'ap-family-archive' ),
			/* translators: %date%: the date consent was accepted, Y-m-d (replaced by the widget, not by sprintf). */
			'settings_consent_accepted'        => __( 'Accepted on this site on %date%.', 'ap-family-archive' ),
			'settings_consent_accepted_nodate' => __( 'Accepted on this site.', 'ap-family-archive' ),
			'settings_consent_withdraw'        => __( 'Withdraw consent. The assistant will show the privacy notice again before the next chat; nothing is sent to the support service until someone re-accepts.', 'ap-family-archive' ),
			'settings_consent_pending'         => __( 'Not yet accepted. Before the first chat, the assistant shows a notice explaining exactly what is sent (your question plus a read-only, non-sensitive snapshot that contains no genealogical data); a user who can manage Family Archive settings must agree before anything is sent.', 'ap-family-archive' ),
			'onboard_heading'                  => __( 'Get instant answers about AP Family Archive', 'ap-family-archive' ),
			'onboard_activate_body'            => __( 'The optional AncestorPress support service answers questions on our servers, grounded in documentation matched to your installed version, and can reach human support. Connecting registers this site with the service; no payment is required and you can disconnect at any time.', 'ap-family-archive' ),
			'onboard_activate_cta'             => __( 'Connect the support service', 'ap-family-archive' ),
			'onboard_byok_body'                => __( 'This site can also answer locally: configure an AI provider in WordPress and the assistant answers on your own provider, with no account or service connection of any kind.', 'ap-family-archive' ),
			'onboard_byok_cta'                 => __( 'Configure a site AI provider', 'ap-family-archive' ),
			'answered_local'                   => __( 'Answered by your site AI provider', 'ap-family-archive' ),
			'answered_local_title'             => __( 'This reply was generated by the AI provider configured on this site, using product documentation fetched from the support service.', 'ap-family-archive' ),
		);
	}
}
