<?php
/**
 * File Path: includes/export/class-mbs-settings-exporter.php
 *
 * Settings Exporter
 *
 * Handles the export direction of the settings transfer workflow:
 * streaming a JSON snapshot of all mbs_setting_* options to the browser.
 *
 * Exclusion list is owned by MBS_Settings_IO_Keys::EXCLUDED_KEYS.
 * Any additions to the exclusion list go there, not here.
 *
 * Called by:
 *   MBS_Tab_Child_Export::handle_ajax_mbs_export_settings()
 *     → MBS_Settings_Exporter::export_settings()
 *
 * Registration gate and capability check are enforced inside export_settings().
 * The AJAX handler layer (MBS_Tab_Child_Export) verifies nonce before
 * calling this class — this class must not re-verify the nonce.
 *
 * Sprint 13: if additional exportable keys or export formats are added,
 * add methods to this class only.
 *
 * @package    My_Book_Showroom
 * @since      1.0.0
 */

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

class MBS_Settings_Exporter { // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedClassFound -- mbs_/MBS_ prefix is correct; scanner not configured with plugin prefix

	/**
	 * Stream a JSON settings export to the browser.
	 *
	 * Sends HTTP headers and writes JSON directly to output. Callers (AJAX
	 * handlers) must verify nonce and capability before calling this method.
	 * The internal nonce check previously in MBS_Settings_IO has been removed
	 * — nonce verification belongs at the AJAX handler layer (NEW-12).
	 *
	 * @return void Does not return — exits after streaming.
	 */
	public static function export_settings(): void {
		if ( ! mbs_is_registered() ) {
			wp_die( esc_html__( 'Settings export requires a registered license. Registration is free — visit the License tab to activate.', 'my-book-showroom' ), 403 );
		}

		if ( ! current_user_can( 'manage_options' ) ) {
			wp_die( esc_html__( 'You do not have permission to export settings.', 'my-book-showroom' ), 403 );
		}

		$settings = self::collect_settings();

		$filename = 'mbs-settings-' . gmdate( 'Y-m-d' ) . '.json';

		header( 'Content-Type: application/json; charset=UTF-8' );
		header( 'Content-Disposition: attachment; filename="' . $filename . '"' );
		header( 'Pragma: no-cache' );
		header( 'Expires: 0' );

		echo wp_json_encode( $settings, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE );
		exit;
	}

	/**
	 * Collect all exportable mbs_setting_* options.
	 *
	 * Direct DB query intentional — settings export requires a LIKE scan of
	 * wp_options with no applicable DAO. No user input touches this query;
	 * both LIKE patterns are hardcoded string literals passed through
	 * $wpdb->esc_like() and $wpdb->prepare().
	 *
	 * Queries the options table for all keys matching mbs_setting_* and
	 * strips any keys present in MBS_Settings_IO_Keys::EXCLUDED_KEYS.
	 * Appends a _section_order block from MBS_Section_Order_DAO (S-02) and
	 * a _meta block with export provenance information.
	 *
	 * @return array Associative array of option_name => value.
	 */
	public static function collect_settings(): array {
		global $wpdb;

		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- direct options table scan intentional; no DAO applicable; both LIKE patterns are hardcoded literals via $wpdb->prepare()
		$rows = $wpdb->get_results(
			$wpdb->prepare(
				"SELECT option_name, option_value
				 FROM {$wpdb->options}
				 WHERE option_name LIKE %s
				   AND option_name NOT LIKE %s",
				$wpdb->esc_like( 'mbs_setting_' ) . '%',
				$wpdb->esc_like( '_transient_' ) . '%'
			),
			ARRAY_A
		);

		$settings = array();

		foreach ( $rows as $row ) {
			$key = $row['option_name'];

			if ( in_array( $key, MBS_Settings_IO_Keys::EXCLUDED_KEYS, true ) ) {
				continue;
			}

			// Deserialize values stored as serialized PHP arrays/objects.
			// SA-M7: Restrict unserialize() to non-object types to prevent object injection.
			$value            = is_serialized( $row['option_value'] )
				? unserialize( $row['option_value'], array( 'allowed_classes' => false ) )
				: $row['option_value'];
			$settings[ $key ] = $value;
		}

		// S-02: Append global default section order from the mbs_section_order table.
		// The wp_options row (mbs_setting_formatting_section_order) is intentionally
		// ephemeral (deleted at shutdown by MBS_Tab_Formatting::save_section_order());
		// the DAO is the only reliable source. Stored as _section_order so the import
		// path can route it to MBS_Section_Order_DAO instead of update_option().
		$global_rows = MBS_Section_Order_DAO::get_order_admin( 0 );
		$settings['_section_order'] = array(
			'order'      => array_column( $global_rows, 'key' ),
			'visibility' => array_combine(
				array_column( $global_rows, 'key' ),
				array_column( $global_rows, 'is_visible' )
			) ?: array(),
		);

		// Add export metadata.
		$settings['_meta'] = array(
			'exported_at' => gmdate( 'c' ),
			'mbs_version' => MBS_VERSION,
			'wp_version'  => get_bloginfo( 'version' ),
			'site_url'    => home_url(),
		);

		return $settings;
	}
}
