<?php
/**
 * Customizer Reset Module
 *
 * Standalone admin page for backup, reset, export, and import of theme_mods
 * and option-based Customizer settings (Astra, GeneratePress, etc.).
 *
 * @package WP_PowerSuite\CustomizerReset
 * @since 1.0.0
 */

namespace CustomizerReset;

use WP_PowerSuite\Core\AbstractModule;

class Module extends AbstractModule {

	private const MODULE_ID        = 'customizer-reset';
	private const MODULE_VER       = '1.2.1';
	private const PAGE_SLUG        = 'wpps-customizer-reset';
	private const TRANSIENT_PREFIX = 'wpps_cr_backup_';
	private const BACKUP_COUNT     = 10;
	private const BACKUP_TTL       = 30 * DAY_IN_SECONDS;

	/**
	 * Option keys that must never be written from import/restore.
	 * Defense in depth on top of the Customizer-registered allowlist.
	 *
	 * @var list<string>
	 */
	private const DENIED_OPTION_KEYS = [
		'users_can_register',
		'default_role',
		'admin_email',
		'new_admin_email',
		'active_plugins',
		'recently_activated',
		'uninstall_plugins',
		'siteurl',
		'home',
		'rewrite_rules',
		'template',
		'stylesheet',
		'current_theme',
		'cron',
		'auto_update_core',
		'auto_update_plugins',
		'auto_update_themes',
		'wp_force_deactivated_plugins',
		'upload_path',
		'upload_url_path',
	];

	/** Theme mod keys kept when resetting (menus, widgets, logo, etc.). */
	private const PRESERVED_THEME_MOD_KEYS = [
		'nav_menu_locations',
		'sidebars_widgets',
		'custom_css_post_id',
		'custom_logo',
		'wp_classic_sidebars',
	];

	public function getId(): string {
		return self::MODULE_ID;
	}

	public function getName(): string {
		return __( 'Customizer Reset', 'wp-powersuite' );
	}

	public function getVersion(): string {
		return self::MODULE_VER;
	}

	public function getDescription(): string {
		return __( 'Backup, reset, export, or import theme Customizer settings from one admin screen.', 'wp-powersuite' );
	}

	public function getContext(): string|array {
		return [ 'admin', 'ajax' ];
	}

	public function getDependencies(): array {
		return [];
	}

	protected function registerHooks(): void {
		if ( ! is_admin() ) {
			return;
		}

		add_action( 'admin_menu', [ $this, 'addMenuPage' ], 20 );
		add_action( 'admin_head', [ $this, 'injectMenuBadgeStyles' ] );
		add_action( 'admin_enqueue_scripts', [ $this, 'enqueueAssets' ] );

		add_action( 'wp_ajax_wpps_cr_export', [ $this, 'handleExport' ] );
		add_action( 'wp_ajax_wpps_cr_import', [ $this, 'handleImport' ] );
		add_action( 'wp_ajax_wpps_cr_backup', [ $this, 'handleBackup' ] );
		add_action( 'wp_ajax_wpps_cr_restore', [ $this, 'handleRestore' ] );
		add_action( 'wp_ajax_wpps_cr_delete_backup', [ $this, 'handleDeleteBackup' ] );
		add_action( 'wp_ajax_wpps_cr_delete_all', [ $this, 'handleDeleteAllBackups' ] );
		add_action( 'wp_ajax_wpps_cr_download_backup', [ $this, 'handleDownloadBackup' ] );
		add_action( 'wp_ajax_wpps_cr_reset', [ $this, 'handleReset' ] );
		add_action( 'customize_controls_enqueue_scripts', [ $this, 'enqueueCustomizerAssets' ] );
	}

	/**
	 * Add admin page under Appearance menu.
	 */
	public function addMenuPage(): void {
		$menu_title = __( 'Customizer Reset', 'wp-powersuite' ) . ' <span class="wpps-menu-badge">WPPS</span>';
		add_theme_page(
			__( 'Customizer Reset', 'wp-powersuite' ),
			$menu_title,
			'edit_theme_options',
			self::PAGE_SLUG,
			[ $this, 'renderPage' ]
		);
	}

	/**
	 * Inject CSS for WPPS badge in admin menu.
	 */
	public function injectMenuBadgeStyles(): void {
		?>
		<style>
			#adminmenu .wpps-menu-badge {
				display: inline-block;
				margin-left: 3px;
				padding: 1px 3px;
				font-size: 6px;
				font-weight: 600;
				text-transform: uppercase;
				letter-spacing: 0.3px;
				background: linear-gradient(135deg, #2271b1 0%, #135e96 100%);
				color: #fff;
				border-radius: 2px;
				vertical-align: middle;
				line-height: 1.2;
			}
		</style>
		<?php
	}

	/**
	 * Render the admin page (Code Snippet–style layout).
	 */
	public function renderPage(): void {
		$backups      = $this->getBackupList();
		$backup_count = count( $backups );
		$modules_url  = admin_url( 'admin.php?page=powersuite' );
		?>
		<div class="wrap wpps-cr-wrap">
			<hr class="wp-header-end">
			<div class="wpps-cr-header-row">
				<div class="wpps-cr-header-left">
					<a href="<?php echo esc_url( $modules_url ); ?>" class="wpps-cr-back"><?php esc_html_e( '← Back to Modules', 'wp-powersuite' ); ?></a>
					<div class="wpps-cr-header-inner">
						<div class="wpps-cr-header-icon">
							<span class="dashicons dashicons-admin-appearance"></span>
						</div>
						<div>
							<h1 class="wpps-cr-title"><?php esc_html_e( 'Customizer Backup & Reset', 'wp-powersuite' ); ?></h1>
							<p class="wpps-cr-subtitle"><?php esc_html_e( 'Backup, reset, export, and import theme customizations. Works with all themes.', 'wp-powersuite' ); ?></p>
						</div>
					</div>
				</div>
			</div>

			<div id="wpps-cr-notice" class="notice notice-info wpps-cr-notice" style="display:none;"></div>

			<div class="wpps-cr-panel">
				<h2 class="wpps-cr-panel-title"><?php esc_html_e( 'Reset Customizer', 'wp-powersuite' ); ?></h2>
				<div class="wpps-cr-btn-row">
					<button type="button" class="wpps-cr-btn wpps-cr-btn-primary wpps-cr-action" data-action="backup-reset">
						<span class="dashicons dashicons-backup"></span>
						<?php esc_html_e( 'Backup & Reset Customizer', 'wp-powersuite' ); ?>
					</button>
					<button type="button" class="wpps-cr-btn wpps-cr-btn-danger wpps-cr-action" data-action="reset">
						<span class="dashicons dashicons-warning"></span>
						<?php esc_html_e( 'Reset Customizer (No Backup)', 'wp-powersuite' ); ?>
					</button>
				</div>
				<label class="wpps-cr-check">
					<input type="checkbox" id="wpps-cr-reset-css" value="1">
					<?php esc_html_e( 'Also remove Additional CSS', 'wp-powersuite' ); ?>
				</label>
			</div>

			<div class="wpps-cr-panel">
				<h2 class="wpps-cr-panel-title"><?php esc_html_e( 'Import & Export', 'wp-powersuite' ); ?></h2>
				<div class="wpps-cr-btn-row">
					<button type="button" class="wpps-cr-btn wpps-cr-btn-secondary wpps-cr-action" data-action="export">
						<span class="dashicons dashicons-download"></span>
						<?php esc_html_e( 'Export Customizer Settings', 'wp-powersuite' ); ?>
					</button>
					<button type="button" class="wpps-cr-btn wpps-cr-btn-secondary wpps-cr-action" data-action="import">
						<span class="dashicons dashicons-upload"></span>
						<?php esc_html_e( 'Import Customizer Settings', 'wp-powersuite' ); ?>
					</button>
				</div>
				<div class="wpps-cr-dropzone">
					<span class="dashicons dashicons-upload"></span>
					<p><?php esc_html_e( 'Or drag and drop a file here', 'wp-powersuite' ); ?></p>
					<span class="wpps-cr-help"><?php esc_html_e( '.json or .dat file', 'wp-powersuite' ); ?></span>
				</div>
				<label class="wpps-cr-check">
					<input type="checkbox" id="wpps-cr-import-images" value="1">
					<?php esc_html_e( 'Download and import image files from URLs in the file?', 'wp-powersuite' ); ?>
				</label>
				<div class="wpps-cr-export-format">
					<label class="wpps-cr-label"><?php esc_html_e( 'Export Format:', 'wp-powersuite' ); ?></label>
					<label class="wpps-cr-format"><input type="radio" name="wpps-cr-format" value="json" checked> <?php esc_html_e( 'JSON', 'wp-powersuite' ); ?> <span class="wpps-cr-help">(<?php esc_html_e( 'recommended', 'wp-powersuite' ); ?>)</span></label>
					<label class="wpps-cr-format"><input type="radio" name="wpps-cr-format" value="dat"> <?php esc_html_e( 'DAT', 'wp-powersuite' ); ?></label>
				</div>
			</div>

			<div class="wpps-cr-panel wpps-cr-backups-panel">
				<div class="wpps-cr-panel-header">
					<h2 class="wpps-cr-panel-title"><?php esc_html_e( 'Backup History', 'wp-powersuite' ); ?> <span class="wpps-cr-count">(<?php echo (int) $backup_count; ?>)</span></h2>
					<div class="wpps-cr-backup-actions">
						<button type="button" class="wpps-cr-btn wpps-cr-btn-secondary wpps-cr-btn-sm wpps-cr-create-backup">
							<span class="dashicons dashicons-plus-alt"></span>
							<?php esc_html_e( 'Create Backup', 'wp-powersuite' ); ?>
						</button>
						<?php if ( $backup_count > 0 ) : ?>
							<button type="button" class="wpps-cr-btn wpps-cr-btn-danger wpps-cr-btn-sm wpps-cr-delete-all">
								<span class="dashicons dashicons-trash"></span>
								<?php esc_html_e( 'Delete All', 'wp-powersuite' ); ?>
							</button>
						<?php endif; ?>
					</div>
				</div>
				<?php if ( $backup_count > 0 ) : ?>
					<ul class="wpps-cr-backup-list">
						<?php foreach ( $backups as $b ) : ?>
							<li class="wpps-cr-backup-item" data-backup-key="<?php echo esc_attr( $b['key'] ); ?>">
								<div class="wpps-cr-backup-info">
									<strong><?php echo esc_html( $b['label'] ); ?></strong>
									<?php /* translators: 1: number, 2: number. */ ?>
									<small class="wpps-cr-meta"><?php printf( esc_html( _n( '%d setting', '%d settings', $b['count'], 'wp-powersuite' ) ), (int) $b['count'] ); ?></small>
								</div>
								<div class="wpps-cr-backup-btns">
									<button type="button" class="wpps-cr-btn wpps-cr-btn-secondary wpps-cr-btn-sm wpps-cr-download" data-backup-key="<?php echo esc_attr( $b['key'] ); ?>" title="<?php esc_attr_e( 'Download backup', 'wp-powersuite' ); ?>"><span class="dashicons dashicons-download"></span></button>
									<button type="button" class="wpps-cr-btn wpps-cr-btn-secondary wpps-cr-btn-sm wpps-cr-restore" data-backup-key="<?php echo esc_attr( $b['key'] ); ?>"><?php esc_html_e( 'Restore', 'wp-powersuite' ); ?></button>
									<button type="button" class="wpps-cr-btn wpps-cr-btn-danger wpps-cr-btn-sm wpps-cr-delete" data-backup-key="<?php echo esc_attr( $b['key'] ); ?>"><span class="dashicons dashicons-trash"></span></button>
								</div>
							</li>
						<?php endforeach; ?>
					</ul>
				<?php else : ?>
					<p class="wpps-cr-empty-desc"><?php esc_html_e( 'No backups found. Use "Backup & Reset" to create a backup before resetting.', 'wp-powersuite' ); ?></p>
				<?php endif; ?>
			</div>
			<input type="file" id="wpps-cr-import-file" accept=".json,.dat" style="display:none">
		</div>
		<?php
	}

	/**
	 * Enqueue assets only on our admin page.
	 */
	public function enqueueAssets( string $hook ): void {
		if ( $hook !== 'appearance_page_' . self::PAGE_SLUG ) {
			return;
		}

		$base = defined( 'WP_POWERSUITE_PLUGIN_URL' ) ? WP_POWERSUITE_PLUGIN_URL : plugin_dir_url( dirname( __DIR__, 2 ) . '/wp-powersuite.php' );
		$url  = $base . 'modules/customizer-reset/assets/';
		$ver  = self::MODULE_VER;

		wp_enqueue_style( 'wpps-customizer-reset', $url . 'customizer-reset.css', [], $ver );
		wp_enqueue_script( 'wpps-customizer-reset', $url . 'customizer-reset.js', [ 'jquery' ], $ver, true );

		wp_localize_script(
			'wpps-customizer-reset',
			'wppsCr',
			[
				'confirm'   => __( "Attention!\n\nThis will remove customizer styling for this theme (colors, layout, typography, etc.). Menus, widgets, and your site logo are kept.\n\nThis action is irreversible unless you create a backup first.", 'wp-powersuite' ),
				'resetting' => __( 'Resetting...', 'wp-powersuite' ),
				'exporting' => __( 'Exporting...', 'wp-powersuite' ),
				'importing' => __( 'Importing...', 'wp-powersuite' ),
				'restoring' => __( 'Restoring...', 'wp-powersuite' ),
				'hasBackup'        => $this->hasAnyBackup(),
				'currentTemplate'  => get_template(),
				'nonce'            => [
					'reset'     => wp_create_nonce( 'wpps_cr_reset' ),
					'export'    => wp_create_nonce( 'wpps_cr_export' ),
					'import'    => wp_create_nonce( 'wpps_cr_import' ),
					'backup'    => wp_create_nonce( 'wpps_cr_backup' ),
					'restore'   => wp_create_nonce( 'wpps_cr_restore' ),
					'delete'    => wp_create_nonce( 'wpps_cr_delete' ),
					'deleteAll' => wp_create_nonce( 'wpps_cr_delete_all' ),
					'download'  => wp_create_nonce( 'wpps_cr_download' ),
				],
			]
		);
	}

	private function getAllowedBackupKeys(): array {
		$s    = get_stylesheet();
		$keys = [ self::TRANSIENT_PREFIX . $s ];
		for ( $i = 1; $i < self::BACKUP_COUNT; $i++ ) {
			$keys[] = self::TRANSIENT_PREFIX . $i . '_' . $s;
		}
		return $keys;
	}

	private function getBackupKey( int $index ): string {
		$s = get_stylesheet();
		return $index === 0 ? self::TRANSIENT_PREFIX . $s : self::TRANSIENT_PREFIX . $index . '_' . $s;
	}

	private function hasAnyBackup(): bool {
		foreach ( $this->getAllowedBackupKeys() as $key ) {
			$data = get_transient( $key );
			if ( $data && is_array( $data ) && isset( $data['mods'] ) ) {
				return true;
			}
		}
		return false;
	}

	private function countSnapshotItems( array $data ): int {
		$count = is_array( $data['mods'] ?? null ) ? count( $data['mods'] ) : 0;
		if ( ! empty( $data['options'] ) && is_array( $data['options'] ) ) {
			$count += count( $data['options'] );
		}
		return $count;
	}

	private function getBackupList(): array {
		$list = [];
		foreach ( $this->getAllowedBackupKeys() as $key ) {
			$data = get_transient( $key );
			if ( $data && is_array( $data ) && isset( $data['mods'] ) ) {
				$list[] = [
					'key'   => $key,
					'label' => isset( $data['created'] ) ? wp_date( 'M j, Y g:i A', strtotime( $data['created'] ) ) : __( 'Unknown date', 'wp-powersuite' ),
					'count' => $this->countSnapshotItems( $data ),
					'data'  => $data,
				];
			}
		}
		return $list;
	}

	private function validateBackupKey( string $key ): bool {
		return in_array( $key, $this->getAllowedBackupKeys(), true );
	}

	/**
	 * Bootstrap Customizer manager on standalone admin/AJAX (no preview required).
	 */
	private function getCustomizerManager(): \WP_Customize_Manager {
		global $wp_customize;
		if ( $wp_customize instanceof \WP_Customize_Manager ) {
			return $wp_customize;
		}

		require_once ABSPATH . WPINC . '/class-wp-customize-manager.php';
		$wp_customize = new \WP_Customize_Manager();
		return $wp_customize;
	}

	/**
	 * @return string[]
	 */
	private function getCoreCustomizerOptions(): array {
		return [ 'blogname', 'blogdescription', 'show_on_front', 'page_on_front', 'page_for_posts' ];
	}

	/**
	 * Snapshot top-level options registered in the Customizer (Astra, GeneratePress, etc.).
	 *
	 * @return array<string, mixed>
	 */
	private function collectBackupOptions( \WP_Customize_Manager $manager ): array {
		$options      = [];
		$core_options = $this->getCoreCustomizerOptions();

		foreach ( $manager->settings() as $key => $setting ) {
			if ( $setting->type !== 'option' ) {
				continue;
			}
			$key_lower = strtolower( (string) $key );
			if ( str_starts_with( $key_lower, 'widget_' ) || str_starts_with( $key_lower, 'sidebars_' ) ) {
				continue;
			}

			$bracket_pos = strpos( (string) $key, '[' );
			$option_name = false !== $bracket_pos ? substr( (string) $key, 0, $bracket_pos ) : (string) $key;

			if ( in_array( $option_name, $core_options, true ) || self::isDeniedOptionKey( $option_name ) ) {
				continue;
			}
			if ( ! array_key_exists( $option_name, $options ) ) {
				$options[ $option_name ] = get_option( $option_name );
			}
		}

		return $this->mergeFilterExportOptions( $options );
	}

	/**
	 * Extra option keys from export filters (WPZOOM + Customizer Reset 1.4.1).
	 *
	 * @return string[]
	 */
	private function getExportOptionKeys(): array {
		$keys = array_merge(
			(array) apply_filters( 'customizer_reset_export_option_keys', [] ),
			(array) apply_filters( 'customizer_export_option_keys', [] )
		);
		$keys = array_filter( $keys, 'is_string' );
		return array_values( array_unique( $keys ) );
	}

	/**
	 * @param array<string, mixed> $options
	 * @return array<string, mixed>
	 */
	private function mergeFilterExportOptions( array $options ): array {
		foreach ( $this->getExportOptionKeys() as $option_key ) {
			if ( $option_key !== '' && ! self::isDeniedOptionKey( $option_key ) ) {
				$options[ $option_key ] = get_option( $option_key );
			}
		}
		return $options;
	}

	/**
	 * Validate import file parent theme (Customizer Reset 1.4.1 compatibility).
	 *
	 * @param array<string, mixed> $data Import payload.
	 */
	private function validateImportTemplate( array $data ): bool {
		if ( empty( $data['template'] ) || ! is_string( $data['template'] ) ) {
			return true;
		}

		/**
		 * Require import template to match the active parent theme.
		 *
		 * @since 1.2.0
		 * @param bool  $strict Whether to enforce template matching.
		 * @param array $data   Import payload.
		 */
		$strict = (bool) apply_filters( 'wp_powersuite_customizer_reset_strict_template_check', true, $data );
		if ( ! $strict ) {
			return true;
		}

		return $data['template'] === get_template();
	}

	/**
	 * Whether an option key is security-sensitive and must not be imported.
	 */
	public static function isDeniedOptionKey( string $option_name ): bool {
		$name = strtolower( $option_name );
		if ( in_array( $name, self::DENIED_OPTION_KEYS, true ) ) {
			return true;
		}
		if ( str_ends_with( $name, 'user_roles' ) ) {
			return true;
		}
		foreach ( [ 'wp_powersuite_', 'wpps_', '_transient_', '_site_transient_' ] as $prefix ) {
			if ( str_starts_with( $name, $prefix ) ) {
				return true;
			}
		}

		return false;
	}

	/**
	 * Keep only Customizer-registered (or filter-exported) option keys that are not denied.
	 *
	 * @param array<string, mixed> $options Incoming option map.
	 * @return array<string, mixed>
	 */
	public function filterImportableOptions( \WP_Customize_Manager $manager, array $options ): array {
		$allowed = array_fill_keys( array_keys( $this->collectBackupOptions( $manager ) ), true );
		$kept    = [];

		foreach ( $options as $option_key => $option_value ) {
			if ( ! is_string( $option_key ) || $option_key === '' ) {
				continue;
			}
			if ( ! isset( $allowed[ $option_key ] ) ) {
				continue;
			}
			if ( self::isDeniedOptionKey( $option_key ) ) {
				continue;
			}
			$kept[ $option_key ] = $option_value;
		}

		return $kept;
	}

	/**
	 * Restore option-type settings via WP_Customize_Setting::update().
	 *
	 * @param array<string, mixed> $options
	 */
	private function restoreOptionSettings( \WP_Customize_Manager $manager, array $options ): int {
		$options = $this->filterImportableOptions( $manager, $options );
		$count   = 0;
		foreach ( $options as $option_key => $option_value ) {
			if ( ! is_string( $option_key ) || $option_key === '' ) {
				continue;
			}

			$setting = new class( $manager, $option_key, [
				'default'    => '',
				'type'       => 'option',
				'capability' => 'edit_theme_options',
			] ) extends \WP_Customize_Setting {
				/** @param mixed $value Value to persist. */
				public function import( $value ): void {
					$this->update( $value );
				}
			};
			$setting->import( $option_value );
			++$count;
		}
		return $count;
	}

	/**
	 * Build export/backup payload shared by JSON export and backups.
	 *
	 * @return array<string, mixed>
	 */
	private function buildSnapshotData( bool $include_created = false ): array {
		$manager = $this->getCustomizerManager();
		$theme   = wp_get_theme();
		$mods    = get_theme_mods();

		$data = [
			'theme'      => $theme->get( 'Name' ),
			'version'    => $theme->get( 'Version' ),
			'stylesheet' => get_stylesheet(),
			'template'   => get_template(),
			'mods'       => $mods ?: [],
			'options'    => $this->collectBackupOptions( $manager ),
		];

		if ( $include_created ) {
			$data['created'] = current_time( 'mysql' );
		} else {
			$data['exported'] = current_time( 'mysql' );
		}

		if ( function_exists( 'wp_get_custom_css' ) ) {
			$data['wp_css'] = wp_get_custom_css();
		}

		return $data;
	}

	// --- AJAX Handlers ---

	public function handleExport(): void {
		if ( ! current_user_can( 'edit_theme_options' ) ) {
			wp_send_json_error( 'insufficient_permissions' );
		}
		if ( ! check_ajax_referer( 'wpps_cr_export', 'nonce', false ) ) {
			wp_send_json_error( 'invalid_nonce' );
		}
		$format = isset( $_POST['format'] ) ? sanitize_text_field( wp_unslash( $_POST['format'] ) ) : 'json';
		$format === 'dat' ? $this->exportDat() : $this->exportJson();
	}

	private function exportJson(): void {
		wp_send_json_success( $this->buildSnapshotData( false ) );
	}

	private function exportDat(): void {
		$manager = $this->getCustomizerManager();
		$data    = [
			'template' => get_template(),
			'mods'     => get_theme_mods() ?: [],
			'options'  => $this->collectBackupOptions( $manager ),
		];
		if ( function_exists( 'wp_get_custom_css' ) ) {
			$data['wp_css'] = wp_get_custom_css();
		}
		wp_send_json_success( serialize( $data ) ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize
	}

	public function handleImport(): void {
		if ( ! current_user_can( 'edit_theme_options' ) ) {
			wp_send_json_error( 'insufficient_permissions' );
		}
		if ( ! check_ajax_referer( 'wpps_cr_import', 'nonce', false ) ) {
			wp_send_json_error( 'invalid_nonce' );
		}
		if ( ! isset( $_POST['import_data'] ) ) {
			wp_send_json_error( 'no_import_data' );
		}

		$raw  = wp_unslash( $_POST['import_data'] );
		$fmt  = isset( $_POST['format'] ) ? sanitize_text_field( wp_unslash( $_POST['format'] ) ) : '';
		$data = null;

		if ( $fmt === 'dat' || $fmt === '' ) {
			$data = @unserialize( $raw, [ 'allowed_classes' => false ] );
		}
		if ( $data === null || ! is_array( $data ) ) {
			$data = json_decode( $raw, true );
		}
		if ( ( $data === null || ! is_array( $data ) ) && $fmt !== 'json' ) {
			$data = @unserialize( $raw, [ 'allowed_classes' => false ] );
		}
		if ( $data === null || ! is_array( $data ) || ! isset( $data['mods'] ) || ! is_array( $data['mods'] ) ) {
			wp_send_json_error( 'invalid_data_format' );
		}

		if ( ! $this->validateImportTemplate( $data ) ) {
			wp_send_json_error(
				[
					'code'    => 'incompatible_theme',
					'message' => __( 'Import error! The customizer settings are not compatible with the current parent theme.', 'wp-powersuite' ),
				]
			);
		}

		if ( isset( $_POST['import_images'] ) && $_POST['import_images'] === '1' ) {
			$data['mods'] = $this->importImages( $data['mods'] );
		}

		$manager = $this->getCustomizerManager();
		do_action( 'customize_save', $manager );

		$n = 0;
		foreach ( $data['mods'] as $k => $v ) {
			$key = sanitize_key( (string) $k );
			if ( $key === '' ) {
				continue;
			}
			do_action( "customize_save_{$key}", $manager );
			set_theme_mod( $key, $v );
			++$n;
		}

		if ( isset( $data['options'] ) && is_array( $data['options'] ) ) {
			$n += $this->restoreOptionSettings( $manager, $data['options'] );
		}

		if ( isset( $data['wp_css'] ) && function_exists( 'wp_update_custom_css_post' ) ) {
			wp_update_custom_css_post( wp_strip_all_tags( (string) $data['wp_css'] ) );
		}

		do_action( 'customize_save_after', $manager );

		wp_send_json_success(
			[
				'message' => __( 'Settings imported successfully', 'wp-powersuite' ),
				'count'   => $n,
			]
		);
	}

	/**
	 * Toolbar inside Appearance → Customize (Customizer Reset 1.4.1-style footer).
	 */
	public function enqueueCustomizerAssets(): void {
		if ( ! current_user_can( 'edit_theme_options' ) ) {
			return;
		}

		$base = defined( 'WP_POWERSUITE_PLUGIN_URL' ) ? WP_POWERSUITE_PLUGIN_URL : plugin_dir_url( dirname( __DIR__, 2 ) . '/wp-powersuite.php' );
		$url  = $base . 'modules/customizer-reset/assets/';
		$ver  = self::MODULE_VER;

		wp_enqueue_style( 'wpps-customizer-reset-panel', $url . 'customizer-panel.css', [], $ver );
		wp_enqueue_script( 'wpps-customizer-reset-panel', $url . 'customizer-panel.js', [ 'jquery', 'customize-controls' ], $ver, true );

		wp_localize_script(
			'wpps-customizer-reset-panel',
			'wppsCrPanel',
			[
				'adminPageUrl' => admin_url( 'themes.php?page=' . self::PAGE_SLUG ),
				'currentTemplate' => get_template(),
				'confirmReset' => __( 'Caution! Proceeding will erase customizer styling for this theme. Menus, widgets, and your site logo are kept.', 'wp-powersuite' ),
				'confirmImport' => __( 'Caution! Import will overwrite your current customizer data. Export a backup first if needed.', 'wp-powersuite' ),
				'nonce' => [
					'reset'  => wp_create_nonce( 'wpps_cr_reset' ),
					'export' => wp_create_nonce( 'wpps_cr_export' ),
					'import' => wp_create_nonce( 'wpps_cr_import' ),
				],
				'labels' => [
					'reset'  => __( 'Reset', 'wp-powersuite' ),
					'export' => __( 'Export', 'wp-powersuite' ),
					'import' => __( 'Import', 'wp-powersuite' ),
					'more'   => __( 'Backup & more', 'wp-powersuite' ),
				],
			]
		);
	}

	private function importImages( array $mods ): array {
		foreach ( $mods as $k => $v ) {
			if ( is_string( $v ) && $this->isImageUrl( $v ) ) {
				$id = $this->sideloadImage( $v );
				if ( $id ) {
					$mods[ $k ] = wp_get_attachment_url( $id );
					if ( str_contains( (string) $k, 'header_image' ) ) {
						update_post_meta( $id, '_wp_attachment_is_custom_header', get_stylesheet() );
						$meta = wp_get_attachment_metadata( $id );
						if ( $meta ) {
							$mods[ $k . '_data' ] = (object) [
								'attachment_id' => $id,
								'url'           => wp_get_attachment_url( $id ),
								'thumbnail_url' => wp_get_attachment_image_url( $id, 'thumbnail' ),
								'height'        => $meta['height'] ?? 0,
								'width'         => $meta['width'] ?? 0,
							];
						}
					}
				}
			}
			if ( is_array( $v ) ) {
				$mods[ $k ] = $this->importImages( $v );
			}
		}
		return $mods;
	}

	private function isImageUrl( string $value ): bool {
		return (bool) preg_match( '/\.(jpe?g|png|gif|webp)(\?.*)?$/i', $value );
	}

	private function sideloadImage( string $url ): int|false {
		$url = esc_url_raw( $url );
		if ( $url === '' || ! filter_var( $url, FILTER_VALIDATE_URL ) ) {
			return false;
		}

		/**
		 * Allow sideloading remote images during customizer import.
		 *
		 * @since 1.1.0
		 * @param bool   $allowed Whether the URL may be downloaded.
		 * @param string $url     Image URL from the import file.
		 */
		if ( ! apply_filters( 'wp_powersuite_customizer_reset_allow_remote_image_import', true, $url ) ) {
			return false;
		}

		if ( ! $this->isSafeRemoteImageUrl( $url ) ) {
			return false;
		}

		global $wpdb;
		$id = $wpdb->get_var( $wpdb->prepare( "SELECT ID FROM {$wpdb->posts} WHERE guid=%s", $url ) );
		if ( $id ) {
			return (int) $id;
		}

		require_once ABSPATH . 'wp-admin/includes/file.php';
		require_once ABSPATH . 'wp-admin/includes/media.php';
		require_once ABSPATH . 'wp-admin/includes/image.php';

		// Do not follow redirects: a public URL could bounce to a private IP.
		$response = wp_safe_remote_get(
			$url,
			[
				'timeout'     => 15,
				'redirection' => 0,
				'sslverify'   => true,
			]
		);
		if ( is_wp_error( $response ) ) {
			return false;
		}
		$code = (int) wp_remote_retrieve_response_code( $response );
		if ( $code < 200 || $code >= 300 ) {
			return false;
		}
		$body = wp_remote_retrieve_body( $response );
		if ( ! is_string( $body ) || $body === '' ) {
			return false;
		}
		$tmp = wp_tempnam( $url );
		if ( ! is_string( $tmp ) || $tmp === '' ) {
			return false;
		}
		// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents
		if ( false === file_put_contents( $tmp, $body ) ) {
			wp_delete_file( $tmp );
			return false;
		}

		preg_match( '/[^\?]+\.(jpe?g|png|gif|webp)/i', $url, $m );
		$file = [
			'name'     => basename( $m[0] ?? 'image.jpg' ),
			'tmp_name' => $tmp,
		];
		$id = media_handle_sideload( $file, 0 );
		if ( is_wp_error( $id ) ) {
			wp_delete_file( $tmp );
			return false;
		}
		return $id;
	}

	/**
	 * Public SSRF gate for tests and filters.
	 */
	public static function is_safe_remote_image_url( string $url ): bool {
		$m = new self();

		return $m->isSafeRemoteImageUrl( $url );
	}

	/**
	 * Block private/reserved/metadata targets before remote image sideload (SSRF).
	 */
	private function isSafeRemoteImageUrl( string $url ): bool {
		$parsed = wp_parse_url( $url );
		if ( ! is_array( $parsed ) || empty( $parsed['host'] ) ) {
			return false;
		}
		// Reject credential-bearing URLs (user:pass@host).
		if ( ! empty( $parsed['user'] ) || ! empty( $parsed['pass'] ) ) {
			return false;
		}
		$scheme = strtolower( (string) ( $parsed['scheme'] ?? '' ) );
		if ( ! in_array( $scheme, [ 'http', 'https' ], true ) ) {
			return false;
		}
		$host = strtolower( (string) $parsed['host'] );
		if ( in_array( $host, [ 'localhost', 'metadata.google.internal' ], true )
			|| str_ends_with( $host, '.local' )
			|| str_ends_with( $host, '.internal' )
			|| str_ends_with( $host, '.localhost' ) ) {
			return false;
		}

		$ips = [];
		if ( filter_var( $host, FILTER_VALIDATE_IP ) ) {
			$ips[] = $host;
		} else {
			$a = @gethostbynamel( $host ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
			if ( is_array( $a ) ) {
				$ips = array_merge( $ips, $a );
			}
			if ( function_exists( 'dns_get_record' ) ) {
				$aaaa = @dns_get_record( $host, DNS_AAAA ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
				if ( is_array( $aaaa ) ) {
					foreach ( $aaaa as $row ) {
						if ( ! empty( $row['ipv6'] ) ) {
							$ips[] = (string) $row['ipv6'];
						}
					}
				}
			}
		}
		if ( $ips === [] ) {
			return false;
		}
		foreach ( $ips as $ip ) {
			$ip = (string) $ip;
			if ( ! filter_var( $ip, FILTER_VALIDATE_IP ) ) {
				return false;
			}
			if ( ! filter_var( $ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE ) ) {
				return false;
			}
			if ( str_starts_with( $ip, '169.254.' ) || str_starts_with( strtolower( $ip ), 'fe80:' ) ) {
				return false;
			}
		}

		return true;
	}

	public function handleBackup(): void {
		if ( ! current_user_can( 'edit_theme_options' ) ) {
			wp_send_json_error( 'insufficient_permissions' );
		}
		if ( ! check_ajax_referer( 'wpps_cr_backup', 'nonce', false ) ) {
			wp_send_json_error( 'invalid_nonce' );
		}

		$data = $this->buildSnapshotData( true );
		$this->rotateBackups();
		set_transient( $this->getBackupKey( 0 ), $data, self::BACKUP_TTL );

		wp_send_json_success(
			[
				'message' => __( 'Backup created successfully', 'wp-powersuite' ),
				'count'   => $this->countSnapshotItems( $data ),
			]
		);
	}

	private function rotateBackups(): void {
		$s = get_stylesheet();
		for ( $i = self::BACKUP_COUNT - 1; $i >= 1; $i-- ) {
			$from = $i === 1 ? self::TRANSIENT_PREFIX . $s : self::TRANSIENT_PREFIX . ( $i - 1 ) . '_' . $s;
			$to   = self::TRANSIENT_PREFIX . $i . '_' . $s;
			$b    = get_transient( $from );
			if ( $b ) {
				set_transient( $to, $b, self::BACKUP_TTL );
			}
		}
	}

	public function handleRestore(): void {
		if ( ! current_user_can( 'edit_theme_options' ) ) {
			wp_send_json_error( 'insufficient_permissions' );
		}
		if ( ! check_ajax_referer( 'wpps_cr_restore', 'nonce', false ) ) {
			wp_send_json_error( 'invalid_nonce' );
		}
		$key = isset( $_POST['backup_key'] ) ? sanitize_text_field( wp_unslash( $_POST['backup_key'] ) ) : '';
		if ( ! $key || ! $this->validateBackupKey( $key ) ) {
			wp_send_json_error( 'invalid_backup_key' );
		}

		$backup = get_transient( $key );
		if ( ! $backup || ! is_array( $backup ) || ! isset( $backup['mods'] ) ) {
			wp_send_json_error( 'backup_not_found' );
		}

		$manager = $this->getCustomizerManager();
		$n       = 0;

		foreach ( $backup['mods'] as $k => $v ) {
			$key_mod = sanitize_key( (string) $k );
			if ( $key_mod === '' ) {
				continue;
			}
			set_theme_mod( $key_mod, $v );
			++$n;
		}

		if ( isset( $backup['options'] ) && is_array( $backup['options'] ) ) {
			$n += $this->restoreOptionSettings( $manager, $backup['options'] );
		}

		if ( isset( $backup['wp_css'] ) && function_exists( 'wp_update_custom_css_post' ) ) {
			wp_update_custom_css_post( $backup['wp_css'] );
		}

		wp_send_json_success(
			[
				'message' => __( 'Backup restored successfully', 'wp-powersuite' ),
				'count'   => $n,
			]
		);
	}

	public function handleDownloadBackup(): void {
		if ( ! current_user_can( 'edit_theme_options' ) ) {
			wp_send_json_error( 'insufficient_permissions' );
		}
		if ( ! check_ajax_referer( 'wpps_cr_download', 'nonce', false ) ) {
			wp_send_json_error( 'invalid_nonce' );
		}
		$key = isset( $_POST['backup_key'] ) ? sanitize_text_field( wp_unslash( $_POST['backup_key'] ) ) : '';
		if ( ! $key || ! $this->validateBackupKey( $key ) ) {
			wp_send_json_error( 'invalid_backup_key' );
		}

		$backup = get_transient( $key );
		if ( ! $backup || ! is_array( $backup ) || ! isset( $backup['mods'] ) ) {
			wp_send_json_error( 'backup_not_found' );
		}

		$export = [
			'theme'      => $backup['theme'] ?? wp_get_theme()->get( 'Name' ),
			'version'    => $backup['version'] ?? wp_get_theme()->get( 'Version' ),
			'stylesheet' => $backup['stylesheet'] ?? get_stylesheet(),
			'template'   => get_template(),
			'exported'   => $backup['created'] ?? current_time( 'mysql' ),
			'mods'       => $backup['mods'],
			'options'    => $backup['options'] ?? [],
		];
		if ( ! empty( $backup['wp_css'] ) ) {
			$export['wp_css'] = $backup['wp_css'];
		}
		wp_send_json_success( $export );
	}

	public function handleDeleteBackup(): void {
		if ( ! current_user_can( 'edit_theme_options' ) ) {
			wp_send_json_error( 'insufficient_permissions' );
		}
		if ( ! check_ajax_referer( 'wpps_cr_delete', 'nonce', false ) ) {
			wp_send_json_error( 'invalid_nonce' );
		}
		$key = isset( $_POST['backup_key'] ) ? sanitize_text_field( wp_unslash( $_POST['backup_key'] ) ) : '';
		if ( ! $key || ! $this->validateBackupKey( $key ) ) {
			wp_send_json_error( 'invalid_backup_key' );
		}
		if ( delete_transient( $key ) ) {
			wp_send_json_success( [ 'message' => __( 'Backup deleted successfully', 'wp-powersuite' ) ] );
		}
		wp_send_json_error( 'backup_not_found_or_already_deleted' );
	}

	public function handleDeleteAllBackups(): void {
		if ( ! current_user_can( 'edit_theme_options' ) ) {
			wp_send_json_error( 'insufficient_permissions' );
		}
		if ( ! check_ajax_referer( 'wpps_cr_delete_all', 'nonce', false ) ) {
			wp_send_json_error( 'invalid_nonce' );
		}
		$n = 0;
		foreach ( $this->getAllowedBackupKeys() as $key ) {
			if ( delete_transient( $key ) ) {
				++$n;
			}
		}
		wp_send_json_success(
			[
				/* translators: %d: number value. */
				'message' => sprintf( __( '%d backups deleted successfully', 'wp-powersuite' ), $n ),
				'count'   => $n,
			]
		);
	}

	public function handleReset(): void {
		if ( ! current_user_can( 'edit_theme_options' ) ) {
			wp_send_json_error( 'insufficient_permissions' );
		}
		if ( ! check_ajax_referer( 'wpps_cr_reset', 'nonce', false ) ) {
			wp_send_json_error( 'invalid_nonce' );
		}

		$this->resetThemeSpecificOptions();

		$manager  = $this->getCustomizerManager();
		$settings = apply_filters( 'customizer_reset_settings', $manager->settings() );

		if ( ! empty( $settings ) ) {
			foreach ( $settings as $setting ) {
				if ( $setting->type === 'theme_mod' && ! in_array( $setting->id, self::PRESERVED_THEME_MOD_KEYS, true ) ) {
					remove_theme_mod( $setting->id );
				}
			}
		}

		$all_mods = get_theme_mods();
		if ( is_array( $all_mods ) ) {
			foreach ( array_keys( $all_mods ) as $key ) {
				if ( ! in_array( $key, self::PRESERVED_THEME_MOD_KEYS, true ) ) {
					remove_theme_mod( $key );
				}
			}
		}

		$reset_css = isset( $_POST['reset_css'] ) && $_POST['reset_css'] === '1';
		if ( $reset_css && function_exists( 'wp_get_custom_css_post' ) ) {
			$post = wp_get_custom_css_post();
			if ( $post ) {
				wp_delete_post( $post->ID, true );
			}
		}

		wp_send_json_success();
	}

	/**
	 * Divi, Astra, GeneratePress option stores cleared before theme_mod loop.
	 */
	private function resetThemeSpecificOptions(): void {
		$theme_name = sanitize_key( strtolower( wp_get_theme()->get( 'Name' ) ) );

		if ( $theme_name === 'divi' ) {
			$stylesheet          = get_stylesheet();
			$customizer_settings = get_option( 'theme_mods_' . $stylesheet );
			$theme_options       = get_option( 'et_' . $theme_name );
			global $options;
			if ( isset( $options ) && is_array( $options ) && ! empty( $theme_options ) ) {
				$keep = [];
				foreach ( $options as $o ) {
					if ( isset( $o['id'], $theme_options[ $o['id'] ] ) ) {
						$keep[ $o['id'] ] = $theme_options[ $o['id'] ];
					}
				}
				if ( ! empty( $keep ) ) {
					update_option( 'et_' . $theme_name, $keep );
				}
			}
			if ( $customizer_settings ) {
				delete_option( 'theme_mods_' . $stylesheet );
			}
		}

		if ( $theme_name === 'astra' && defined( 'ASTRA_THEME_SETTINGS' ) ) {
			$opts = get_option( ASTRA_THEME_SETTINGS );
			$av   = $opts['theme-auto-version'] ?? $opts['astra-addon-auto-version'] ?? false;
			if ( false !== $av ) {
				update_option( ASTRA_THEME_SETTINGS, [ 'theme-auto-version' => $av ] );
			} else {
				delete_option( ASTRA_THEME_SETTINGS );
			}
		}

		if ( $theme_name === 'generatepress' ) {
			delete_option( 'generate_settings' );
			foreach (
				[
					'generate_blog_settings',
					'generate_spacing_settings',
					'generate_menu_plus_settings',
					'generate_backgrounds_settings',
					'generate_colors_settings',
					'generate_copyright',
					'generate_disable_elements',
					'generate_secondary_nav_settings',
				] as $o
			) {
				delete_option( $o );
			}
			delete_option( 'generate_dynamic_css_output' );
			delete_option( 'generate_dynamic_css_cached_version' );
			remove_theme_mods();
		}
	}
}

