<?php
/**
 * Config Service
 *
 * Handles plugin-wide configuration export, import, and preset management.
 * Preset configurations for save, load, export, and import.
 *
 * @package WP_PowerSuite\Core
 * @since 1.0.0
 */

namespace WP_PowerSuite\Core;

/**
 * ConfigService Class
 *
 * @since 1.0.0
 */
class ConfigService {

	private const PRESETS_OPTION = 'wp_powersuite_config_presets';
	private const PRESET_PREFIX = 'wp_powersuite_preset_';

	/**
	 * Export full plugin configuration.
	 *
	 * @return array{version: string, exported_at: string, config: array}
	 */
	public function exportConfig(): array {
		global $wpdb;

		$config = [];

		// Main settings (raw stored value, not merged with defaults)
		$config['wp_powersuite_settings'] = $this->redactSensitiveValue(
			'wp_powersuite_settings',
			NetworkScope::usesNetworkStorage() ? get_site_option( 'wp_powersuite_settings', [] ) : get_option( 'wp_powersuite_settings', [] )
		);

		// All wp_powersuite_* options (excluding presets, preset list, and main settings)
		$like = $wpdb->esc_like( 'wp_powersuite_' ) . '%';
		$preset_like = $wpdb->esc_like( self::PRESET_PREFIX ) . '%';
		$results = $wpdb->get_results(
			$wpdb->prepare(
				"SELECT option_name, option_value FROM {$wpdb->options} WHERE option_name LIKE %s AND option_name NOT IN (%s, %s) AND option_name NOT LIKE %s",
				$like,
				'wp_powersuite_settings',
				self::PRESETS_OPTION,
				$preset_like
			),
			ARRAY_A
		);

		foreach ( $results as $row ) {
			if ( $this->isSensitiveKey( (string) $row['option_name'] ) ) {
				continue;
			}
			$value = maybe_unserialize( $row['option_value'] );
			$config[ $row['option_name'] ] = $this->redactSensitiveValue( (string) $row['option_name'], $value );
		}

		// Redirect Manager: export from custom table (stored there, not in options)
		$redirects_table = $wpdb->prefix . 'wpps_redirects';
		if ( $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $redirects_table ) ) === $redirects_table ) {
			$rows = $wpdb->get_results( "SELECT * FROM {$redirects_table}", ARRAY_A );
			$config['_wpps_redirects_table'] = is_array( $rows ) ? $rows : [];
		}

		// Link Shortener: export from custom tables
		$short_links_table = $wpdb->prefix . 'wpps_short_links';
		if ( $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $short_links_table ) ) === $short_links_table ) {
			$rows = $wpdb->get_results( "SELECT * FROM {$short_links_table}", ARRAY_A );
			$config['_wpps_short_links_table'] = is_array( $rows ) ? $rows : [];
		}

		return [
			'version'     => defined( 'WP_POWERSUITE_VERSION' ) ? WP_POWERSUITE_VERSION : '1.0.0',
			'exported_at' => gmdate( 'c' ),
			'site_url'    => get_site_url(),
			'config'      => $config,
		];
	}

	/**
	 * Import configuration. Merge or replace.
	 *
	 * @param array $data Exported config data.
	 * @param bool  $merge True to merge with existing, false to replace.
	 * @return array{success: bool, message: string, errors?: array}
	 */
	public function importConfig( array $data, bool $merge = true ): array {
		$config = $data['config'] ?? [];
		if ( empty( $config ) ) {
			return [ 'success' => false, 'message' => __( 'Invalid configuration file. No config data found.', 'wp-powersuite' ) ];
		}

		$option_store = OptionStore::getInstance();
		$errors       = [];
		$stripped_pro = [];

		// Main settings
		if ( isset( $config['wp_powersuite_settings'] ) ) {
			$settings = $config['wp_powersuite_settings'];
			if ( is_array( $settings ) ) {
				$settings = ModuleSettingsSanitizer::sanitizeSettingsDocument( $settings, 'import' );

				$license_check = $this->validateImportLicenseForSettings( $settings, $merge );
				if ( ! $license_check['allowed'] ) {
					return [
						'success' => false,
						'message' => $license_check['message'],
					];
				}

				$filter_result = $this->filterProModulesFromSettings( $settings );
				$settings      = $filter_result['settings'];
				$stripped_pro  = $filter_result['stripped'];
				$stored        = NetworkScope::usesNetworkStorage()
					? get_site_option( 'wp_powersuite_settings', [] )
					: get_option( 'wp_powersuite_settings', [] );
				$stored        = is_array( $stored ) ? $stored : [];
				if ( $merge ) {
					$current  = $option_store->getAllSettings();
					$settings = self::mergeConfigArrays( is_array( $current ) ? $current : [], $settings );
				}
				$settings = self::restorePreservedSecrets( $stored, $settings );
				$option_store->clearCache();
				if ( NetworkScope::usesNetworkStorage() ) {
					update_site_option( 'wp_powersuite_settings', $settings );
				} else {
					update_option( 'wp_powersuite_settings', $settings, false );
				}
				$option_store->clearCache();
			}
			unset( $config['wp_powersuite_settings'] );
		}

		// Redirect Manager: import into custom table
		if ( isset( $config['_wpps_redirects_table'] ) && is_array( $config['_wpps_redirects_table'] ) ) {
			$this->importRedirectsTable( $config['_wpps_redirects_table'], $merge, $errors );
			unset( $config['_wpps_redirects_table'] );
		}

		// Link Shortener: import into custom table
		if ( isset( $config['_wpps_short_links_table'] ) && is_array( $config['_wpps_short_links_table'] ) ) {
			$this->importShortLinksTable( $config['_wpps_short_links_table'], $merge, $errors );
			unset( $config['_wpps_short_links_table'] );
		}

		// Other options
		foreach ( $config as $option_name => $value ) {
			if ( ! is_string( $option_name ) || strpos( $option_name, 'wp_powersuite_' ) !== 0 || $this->isSensitiveKey( $option_name ) ) {
				continue;
			}
			// Skip preset payloads and preset index: import does not include blob data, so applying
			// only the list would show ghost presets (metadata without saved config).
			if ( strpos( $option_name, self::PRESET_PREFIX ) === 0 || $option_name === self::PRESETS_OPTION ) {
				continue;
			}
			if ( in_array( $option_name, [ '_wpps_redirects_table', '_wpps_short_links_table' ], true ) ) {
				continue;
			}
			try {
				$current = get_option( $option_name, null );
				if ( $merge && $option_name !== 'wp_powersuite_settings' ) {
					if ( $current !== null && is_array( $current ) && is_array( $value ) ) {
						$value = self::mergeConfigArrays( $current, $value );
					}
				}
				if ( is_array( $value ) ) {
					$module_key = preg_replace( '/^wp_powersuite_/', '', $option_name ) ?? $option_name;
					$value      = ModuleSettingsSanitizer::sanitizeModuleSettings( (string) $module_key, $value, 'import' );
				}
				if ( is_array( $current ) && is_array( $value ) ) {
					$value = self::restorePreservedSecrets( $current, $value );
				}
				update_option( $option_name, $value, false );
			} catch ( \Throwable $e ) {
				$errors[] = sprintf( '%s: %s', $option_name, $e->getMessage() );
			}
		}

		ModuleRegistry::getInstance()->refreshCache();

		$message = __( 'Configuration imported successfully.', 'wp-powersuite' );
		if ( $stripped_pro !== [] ) {
			$message = __( 'Configuration imported successfully. Premium modules were skipped because no valid license is active.', 'wp-powersuite' );
		}

		return [
			'success' => true,
			'message'  => $message,
			'errors'   => $errors,
			'warnings' => $stripped_pro !== []
				? [
					'stripped_pro_modules' => $stripped_pro,
				]
				: [],
		];
	}

	/**
	 * Merge imported config onto current without index-merging lists.
	 *
	 * Sequential arrays (enabled_modules, favorites, and any other list) are
	 * replaced when present in $incoming. Associative arrays merge recursively.
	 *
	 * @param array<string, mixed> $current
	 * @param array<string, mixed> $incoming
	 * @return array<string, mixed>
	 */
	public static function mergeConfigArrays( array $current, array $incoming ): array {
		$out = $current;

		foreach ( $incoming as $key => $value ) {
			if ( is_array( $value ) && isset( $out[ $key ] ) && is_array( $out[ $key ] ) ) {
				if ( array_is_list( $value ) ) {
					$out[ $key ] = $value;
					continue;
				}
				$out[ $key ] = self::mergeConfigArrays( $out[ $key ], $value );
				continue;
			}

			$out[ $key ] = $value;
		}

		return $out;
	}

	/**
	 * Keep live secrets when the import omitted them or only sent a mask.
	 *
	 * Export strips passwords and hashes on purpose. Replace/merge must not
	 * write that redacted document over working credentials.
	 *
	 * @param array<string, mixed> $current
	 * @param array<string, mixed> $incoming
	 * @return array<string, mixed>
	 */
	public static function restorePreservedSecrets( array $current, array $incoming ): array {
		$out = $incoming;

		foreach ( $current as $key => $current_value ) {
			$key_string = is_string( $key ) ? $key : (string) $key;

			if ( ! array_key_exists( $key, $out ) ) {
				if ( SensitiveDataRedactor::isSensitiveKey( $key_string ) ) {
					$out[ $key ] = $current_value;
				}
				continue;
			}

			if ( is_array( $current_value ) && is_array( $out[ $key ] ) ) {
				// Export may leave {} after stripping secrets; PHP stores that as [].
				// Do not treat it as a list replace — keep the live object, then
				// restore secrets into whatever non-secret fields the file kept.
				if ( $out[ $key ] === [] && ! array_is_list( $current_value ) ) {
					$out[ $key ] = $current_value;
					continue;
				}
				if ( array_is_list( $out[ $key ] ) ) {
					continue;
				}
				$out[ $key ] = self::restorePreservedSecrets( $current_value, $out[ $key ] );
				continue;
			}

			if ( SensitiveDataRedactor::isSensitiveKey( $key_string ) && self::isPlaceholderSecret( $out[ $key ] ) ) {
				$out[ $key ] = $current_value;
			}
		}

		return $out;
	}

	/**
	 * Whether an imported secret is a redaction placeholder, not a new value.
	 */
	public static function isPlaceholderSecret( mixed $value ): bool {
		if ( $value === null || $value === '' ) {
			return true;
		}

		if ( is_array( $value ) ) {
			if ( $value === [] ) {
				return true;
			}
			foreach ( $value as $item ) {
				if ( ! self::isPlaceholderSecret( $item ) ) {
					return false;
				}
			}

			return true;
		}

		if ( ! is_string( $value ) ) {
			return false;
		}

		if ( $value === SensitiveDataRedactor::MASK ) {
			return true;
		}

		return (bool) preg_match( '/^[\x{2022}*•]{4,}$/u', $value );
	}

	/**
	 * Replace may clear a custom table only when the file is an explicit empty
	 * list or contains at least one valid row.
	 *
	 * @param array<int, mixed> $payload
	 * @param array<int, mixed> $valid_rows
	 */
	public static function canReplaceCustomTable( array $payload, array $valid_rows ): bool {
		return $payload === [] || $valid_rows !== [];
	}

	/**
	 * @param array<int, mixed> $rows
	 * @param array<int, string> $errors
	 */
	private function importRedirectsTable( array $rows, bool $merge, array &$errors ): void {
		global $wpdb;

		$redirects_table = $wpdb->prefix . 'wpps_redirects';
		if ( $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $redirects_table ) ) !== $redirects_table ) {
			return;
		}

		$valid = [];
		foreach ( $rows as $row ) {
			$clean = $this->sanitizeRedirectRow( $row );
			if ( $clean !== null ) {
				$valid[] = $clean;
			}
		}

		if ( ! $merge ) {
			if ( ! self::canReplaceCustomTable( $rows, $valid ) ) {
				$errors[] = __( 'Redirects were not replaced because the import file had no valid redirect rows.', 'wp-powersuite' );
				return;
			}
			$wpdb->query( "TRUNCATE TABLE `{$redirects_table}`" );
		}

		foreach ( $valid as $row ) {
			$wpdb->replace(
				$redirects_table,
				$row,
				[ '%s', '%s', '%s', '%s', '%s', '%d', '%d', '%s', '%d', '%s', '%s', '%d', '%s', '%s' ]
			);
		}

		delete_transient( 'wp_powersuite_redirect_rules' );
	}

	/**
	 * @param array<int, mixed> $rows
	 * @param array<int, string> $errors
	 */
	private function importShortLinksTable( array $rows, bool $merge, array &$errors ): void {
		global $wpdb;

		$short_links_table = $wpdb->prefix . 'wpps_short_links';
		if ( $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $short_links_table ) ) !== $short_links_table ) {
			return;
		}

		$has_redirect_type = ! empty( $wpdb->get_results( $wpdb->prepare( "SHOW COLUMNS FROM {$short_links_table} LIKE %s", 'redirect_type' ) ) );
		$existing_codes    = [];
		if ( $merge ) {
			$existing       = $wpdb->get_results( "SELECT short_code FROM {$short_links_table}", ARRAY_A );
			$existing_codes = is_array( $existing ) ? array_column( $existing, 'short_code' ) : [];
		}

		$valid = [];
		foreach ( $rows as $row ) {
			$insert = $this->sanitizeShortLinkRow( $row, $has_redirect_type );
			if ( $insert === null ) {
				continue;
			}
			if ( $merge && in_array( $insert['short_code'], $existing_codes, true ) ) {
				continue;
			}
			$valid[] = $insert;
		}

		if ( ! $merge ) {
			if ( ! self::canReplaceCustomTable( $rows, $valid ) ) {
				$errors[] = __( 'Short links were not replaced because the import file had no valid short-link rows.', 'wp-powersuite' );
				return;
			}
			$wpdb->query( "TRUNCATE TABLE `{$short_links_table}`" );
			$analytics_table = $wpdb->prefix . 'wpps_short_links_analytics';
			if ( $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $analytics_table ) ) === $analytics_table ) {
				$wpdb->query( "TRUNCATE TABLE `{$analytics_table}`" );
			}
		}

		foreach ( $valid as $insert ) {
			$format = [ '%s', '%s', '%d', '%s', '%s', '%s', '%s', '%s', '%d' ];
			if ( array_key_exists( 'redirect_type', $insert ) ) {
				$format[] = '%d';
			}
			$wpdb->insert( $short_links_table, $insert, $format );
		}

		wp_cache_delete( 'wpps_short_links_table_exists', '' );
	}

	/**
	 * @param mixed $row Raw import row.
	 * @return array<string, mixed>|null
	 */
	private function sanitizeRedirectRow( mixed $row ): ?array {
		if ( ! is_array( $row ) || empty( $row['id'] ) || empty( $row['source_url'] ) ) {
			return null;
		}

		return [
			'id'              => sanitize_text_field( (string) $row['id'] ),
			'source_url'      => sanitize_text_field( (string) $row['source_url'] ),
			'target_url'      => esc_url_raw( (string) ( $row['target_url'] ?? '' ) ),
			'redirect_type'   => in_array( (string) ( $row['redirect_type'] ?? '301' ), [ '301', '302', '307', '308' ], true ) ? (string) $row['redirect_type'] : '301',
			'match_type'      => in_array( (string) ( $row['match_type'] ?? 'exact' ), [ 'exact', 'wildcard', 'regex' ], true ) ? (string) $row['match_type'] : 'exact',
			'is_active'       => ! empty( $row['is_active'] ) ? 1 : 0,
			'priority'        => (int) ( $row['priority'] ?? 10 ),
			'notes'           => sanitize_textarea_field( (string) ( $row['notes'] ?? '' ) ),
			'strip_params'    => ! empty( $row['strip_params'] ) ? 1 : 0,
			'custom_message'  => sanitize_text_field( (string) ( $row['custom_message'] ?? '' ) ),
			'conditions_json' => is_string( $row['conditions_json'] ?? null ) ? wp_kses_post( (string) $row['conditions_json'] ) : '[]',
			'hit_count'       => max( 0, (int) ( $row['hit_count'] ?? 0 ) ),
			'last_hit'        => ! empty( $row['last_hit'] ) ? sanitize_text_field( (string) $row['last_hit'] ) : null,
			'created_at'      => ! empty( $row['created_at'] ) ? sanitize_text_field( (string) $row['created_at'] ) : current_time( 'mysql' ),
		];
	}

	/**
	 * @param mixed $row Raw import row.
	 * @return array<string, mixed>|null
	 */
	private function sanitizeShortLinkRow( mixed $row, bool $has_redirect_type ): ?array {
		if ( ! is_array( $row ) || empty( $row['short_code'] ) || empty( $row['destination_url'] ) ) {
			return null;
		}

		$insert = [
			'short_code'          => sanitize_key( (string) $row['short_code'] ),
			'destination_url'     => esc_url_raw( (string) $row['destination_url'] ),
			'active'              => isset( $row['active'] ) ? (int) $row['active'] : 1,
			'expires_at'          => ! empty( $row['expires_at'] ) ? sanitize_text_field( (string) $row['expires_at'] ) : null,
			'expire_redirect_url' => ! empty( $row['expire_redirect_url'] ) ? esc_url_raw( (string) $row['expire_redirect_url'] ) : null,
			'password_hash'       => ! empty( $row['password_hash'] ) ? sanitize_text_field( (string) $row['password_hash'] ) : null,
			'created_at'          => ! empty( $row['created_at'] ) ? sanitize_text_field( (string) $row['created_at'] ) : current_time( 'mysql' ),
			'last_clicked_at'     => null,
			'click_count'         => 0,
		];

		if ( $insert['short_code'] === '' || $insert['destination_url'] === '' ) {
			return null;
		}

		if ( $has_redirect_type ) {
			$insert['redirect_type'] = isset( $row['redirect_type'] ) && in_array( (int) $row['redirect_type'], [ 301, 302 ], true ) ? (int) $row['redirect_type'] : 301;
		}

		return $insert;
	}

	private function isSensitiveKey( string $key ): bool {
		// Centralized in SensitiveDataRedactor so export, import, diagnostics, and
		// CLI all agree on what counts as a secret. The centralized matcher is a
		// superset of the historical pattern (it never un-redacts a key).
		return SensitiveDataRedactor::isSensitiveKey( $key );
	}

	/**
	 * Strip premium modules from imported settings when the license is invalid.
	 *
	 * @param array<string, mixed> $settings Sanitized settings document.
	 * @return array{settings: array<string, mixed>, stripped: array<string>}
	 */
	private function filterProModulesFromSettings( array $settings ): array {
		if ( function_exists( 'wp_powersuite_is_license_valid' ) && wp_powersuite_is_license_valid() ) {
			return [
				'settings' => $settings,
				'stripped' => [],
			];
		}

		if ( ! isset( $settings['enabled_modules'] ) || ! is_array( $settings['enabled_modules'] ) ) {
			return [
				'settings' => $settings,
				'stripped' => [],
			];
		}

		$registry = ModuleRegistry::getInstance();
		$registry->init();
		$stripped = [];

		$settings['enabled_modules'] = array_values(
			array_filter(
				$settings['enabled_modules'],
				static function ( $module_id ) use ( $registry, &$stripped ): bool {
					if ( ! is_string( $module_id ) || $module_id === '' ) {
						return false;
					}
					$meta = $registry->getModuleMetadata( $module_id );
					if ( ! empty( $meta['is_pro'] ) ) {
						$stripped[] = $module_id;
						return false;
					}
					return true;
				}
			)
		);

		return [
			'settings' => $settings,
			'stripped' => $stripped,
		];
	}

	/**
	 * Block replace imports that would enable premium modules without a license.
	 *
	 * @param array<string, mixed> $settings Sanitized settings document.
	 * @param bool                 $merge    Whether this is a merge import.
	 * @return array{allowed: bool, message: string}
	 */
	private function validateImportLicenseForSettings( array $settings, bool $merge ): array {
		if ( $merge || ! function_exists( 'wp_powersuite_is_license_valid' ) || wp_powersuite_is_license_valid() ) {
			return [
				'allowed' => true,
				'message' => '',
			];
		}

		if ( ! isset( $settings['enabled_modules'] ) || ! is_array( $settings['enabled_modules'] ) ) {
			return [
				'allowed' => true,
				'message' => '',
			];
		}

		$registry = ModuleRegistry::getInstance();
		$registry->init();
		$pro_modules = [];

		foreach ( $settings['enabled_modules'] as $module_id ) {
			if ( ! is_string( $module_id ) || $module_id === '' ) {
				continue;
			}
			$meta = $registry->getModuleMetadata( $module_id );
			if ( ! empty( $meta['is_pro'] ) ) {
				$pro_modules[] = $meta['name'] ?? $module_id;
			}
		}

		if ( $pro_modules === [] ) {
			return [
				'allowed' => true,
				'message' => '',
			];
		}

		return [
			'allowed' => false,
			'message' => sprintf(
				/* translators: %s: comma-separated premium module names */
				__( 'Import rejected: a valid license is required to enable premium modules (%s).', 'wp-powersuite' ),
				implode( ', ', $pro_modules )
			),
		];
	}

	private function redactSensitiveValue( string $key, mixed $value ): mixed {
		if ( $this->isSensitiveKey( $key ) ) {
			return null;
		}

		if ( ! is_array( $value ) ) {
			return $value;
		}

		$redacted = [];
		foreach ( $value as $child_key => $child_value ) {
			$child_key_string = is_string( $child_key ) ? $child_key : (string) $child_key;
			if ( $this->isSensitiveKey( $child_key_string ) ) {
				continue;
			}
			$redacted[ $child_key ] = $this->redactSensitiveValue( $child_key_string, $child_value );
		}

		return $redacted;
	}

	/**
	 * Get saved presets.
	 *
	 * @return array<array{id: string, name: string, created_at: string}>
	 */
	public function getPresets(): array {
		$presets = get_option( self::PRESETS_OPTION, [] );
		if ( ! is_array( $presets ) ) {
			return [];
		}
		$out = [];
		foreach ( $presets as $id => $meta ) {
			$id = (string) $id;
			if ( $id === '' ) {
				continue;
			}
			// List entries without stored snapshot (e.g. after partial import) are not loadable.
			$blob = get_option( self::PRESET_PREFIX . $id, null );
			if ( ! is_array( $blob ) || empty( $blob['config'] ) || ! is_array( $blob['config'] ) ) {
				continue;
			}
			$name = is_array( $meta ) ? ( $meta['name'] ?? $id ) : (string) $meta;
			$created = is_array( $meta ) ? ( $meta['created_at'] ?? '' ) : '';
			$out[] = [
				'id'         => $id,
				'name'       => $name,
				'created_at' => $created,
			];
		}
		// Self-heal: drop orphan keys from the index so counts and UI stay accurate.
		$indexed = [];
		foreach ( $out as $row ) {
			$indexed[ $row['id'] ] = [
				'name'       => $row['name'],
				'created_at' => $row['created_at'],
			];
		}
		if ( count( $indexed ) !== count( $presets ) ) {
			update_option( self::PRESETS_OPTION, $indexed, false );
		}
		return $out;
	}

	/**
	 * Save current config as preset.
	 *
	 * @param string $name Preset name.
	 * @return array{success: bool, id?: string, message: string}
	 */
	public function savePreset( string $name ): array {
		$name = sanitize_text_field( $name );
		if ( empty( $name ) ) {
			return [ 'success' => false, 'message' => __( 'Preset name is required.', 'wp-powersuite' ) ];
		}

		$data = $this->exportConfig();
		$id = sanitize_key( $name ) . '_' . wp_generate_password( 6, false );

		$presets = get_option( self::PRESETS_OPTION, [] );
		if ( ! is_array( $presets ) ) {
			$presets = [];
		}
		$presets[ $id ] = [
			'name'       => $name,
			'created_at' => gmdate( 'c' ),
		];
		update_option( self::PRESETS_OPTION, $presets, false );
		update_option( self::PRESET_PREFIX . $id, $data, false );

		return [
			'success' => true,
			'id'      => $id,
			'message'  => __( 'Preset saved successfully.', 'wp-powersuite' ),
		];
	}

	/**
	 * Load a preset by ID.
	 *
	 * @param string $id Preset ID.
	 * @param bool   $merge True to merge, false to replace.
	 * @return array{success: bool, message: string, data?: array}
	 */
	public function loadPreset( string $id, bool $merge = false ): array {
		$id = sanitize_key( $id );
		$data = get_option( self::PRESET_PREFIX . $id, null );
		if ( $data === null || ! is_array( $data ) ) {
			return [ 'success' => false, 'message' => __( 'Preset not found.', 'wp-powersuite' ) ];
		}
		return array_merge(
			$this->importConfig( $data, $merge ),
			[ 'data' => $data ]
		);
	}

	/**
	 * Delete a preset.
	 *
	 * @param string $id Preset ID.
	 * @return array{success: bool, message: string}
	 */
	public function deletePreset( string $id ): array {
		$id = sanitize_key( $id );
		delete_option( self::PRESET_PREFIX . $id );
		$presets = get_option( self::PRESETS_OPTION, [] );
		if ( is_array( $presets ) && isset( $presets[ $id ] ) ) {
			unset( $presets[ $id ] );
			update_option( self::PRESETS_OPTION, $presets, false );
		}
		return [ 'success' => true, 'message' => __( 'Preset deleted.', 'wp-powersuite' ) ];
	}
}
