<?php
/**
 * Main class of Athemes Sites Importer plugin.
 *
 * @package    Athemes Starter Sites
 * @subpackage Core
 */

if ( ! defined( 'ABSPATH' ) ) {
  exit; // Exit if accessed directly.
}

/**
 * Importer Class
 */
class Athemes_Starter_Sites_Importer {

	/**
	 * The demos of page.
	 *
	 * @var array $demos The demos.
	 */
	public $demos = array();

	/**
	 * Time in milliseconds, marking the beginning of the import.
	 *
	 * @var float
	 */
	private $microtime;

	/**
	 * The WXR importer instance.
	 *
	 * @var ATSS_WXRImporter
	 */
	private $importer;

	/**
	 * Singleton instance
	 *
	 * @var Athemes_Starter_Sites_Import
	 */
	private static $instance;

	/**
	 * Get singleton instance.
	 *
	 * @return Athemes_Starter_Sites_Import
	 */
	public static function instance() {
		if ( is_null( self::$instance ) ) {
			self::$instance = new self();
		}
		return self::$instance;
	}

	/**
	 * Class constructor
	 */
	public function __construct() {
		add_action( 'after_setup_theme', array( $this, 'init' ) );
	}

	/**
	 * Verify import nonce - accepts both legacy (atss_legacy_import) and onboarding wizard nonces.
	 *
	 * @return void Sends JSON error and dies if nonce is invalid.
	 */
	private function verify_import_nonce() {
		$nonce = isset( $_POST['nonce'] ) ? sanitize_text_field( wp_unslash( $_POST['nonce'] ) ) : '';

		// Check legacy nonce first.
		if ( wp_verify_nonce( $nonce, 'atss_legacy_import' ) ) {
			return;
		}

		// Check onboarding wizard nonce.
		if ( wp_verify_nonce( $nonce, 'atss_onboarding_nonce' ) ) {
			return;
		}

		wp_send_json_error( esc_html__( 'Invalid nonce.', 'athemes-starter-sites' ) );
	}

	/**
	 * Initialize plugin.
	 */
	public function init() {

		$this->demos = apply_filters( 'atss_register_demos_list', array() );

		add_action( 'upload_mimes', array( $this, 'add_custom_mimes' ) );
		add_filter( 'wp_check_filetype_and_ext', array( $this, 'real_mime_type_for_xml' ), 10, 4 );

		add_action( 'wp_ajax_atss_import_start', array( $this, 'atss_import_start' ) );
		add_action( 'wp_ajax_atss_import_clean', array( $this, 'atss_import_clean' ) );
		add_action( 'wp_ajax_atss_import_plugin', array( $this, 'ajax_import_plugin' ) );
		add_action( 'wp_ajax_atss_import_contents', array( $this, 'ajax_import_contents' ) );
		add_action( 'wp_ajax_atss_import_widgets', array( $this, 'ajax_import_widgets' ) );
		add_action( 'wp_ajax_atss_import_customizer', array( $this, 'ajax_import_customizer' ) );
		add_action( 'wp_ajax_atss_import_finish', array( $this, 'ajax_import_finish' ) );

		// Hide wpdb errors for avoid wp_send_json brokens.
		if ( isset( $_POST['action'] ) && $_POST['action'] === 'atss_import_plugin' && ( WP_DEBUG || WP_DEBUG_DISPLAY ) ) {
			global $wpdb;
			$wpdb->hide_errors();
		}

	}

	/**
	 * Add custom mimes for the uploader.
	 *
	 * @param array $mimes The mimes.
	 */
	public function add_custom_mimes( $mimes ) {

		// Return if not admin
		if ( ! current_user_can( 'manage_options' ) ) {
			return $mimes;
		}

		// Allow SVG files.
		$mimes['svg']  = 'image/svg+xml';
		$mimes['svgz'] = 'image/svg+xml';

		// Allow XML files.
		$mimes['xml'] = 'text/xml';

		// Allow JSON files.
		$mimes['json'] = 'application/json';

		return $mimes;

	}

	/**
	 * Filters the "real" file type of the given file.
	 *
	 * @param array  $wp_check_filetype_and_ext The wp_check_filetype_and_ext.
	 * @param string $file                      The file.
	 * @param string $filename                  The filename.
	 * @param array  $mimes                     The mimes.
	 */
	public function real_mime_type_for_xml( $wp_check_filetype_and_ext, $file, $filename, $mimes ) {

		if ( '.xml' === substr( $filename, -4 ) ) {
			$wp_check_filetype_and_ext['ext']  = 'xml';
			$wp_check_filetype_and_ext['type'] = 'text/xml';
		}

		return $wp_check_filetype_and_ext;

	}

	/**
	 * Get plugin status.
	 *
	 * @param string $plugin_path Plugin path.
	 */
	public function get_plugin_status( $plugin_path ) {

		if ( ! current_user_can( 'install_plugins' ) ) {
			return;
		}

		if ( ! file_exists( WP_PLUGIN_DIR . '/' . $plugin_path ) ) {
			return 'not_installed';
		} elseif ( in_array( $plugin_path, (array) get_option( 'active_plugins', array() ), true ) || is_plugin_active_for_network( $plugin_path ) ) {
			return 'active';
		} else {
			return 'inactive';
		}

	}

	/**
	 * Install a plugin.
	 *
	 * @param string $plugin_slug Plugin slug.
	 */
	public function install_plugin( $plugin_slug ) {

		if ( ! current_user_can( 'install_plugins' ) ) {
			return;
		}

		if ( ! function_exists( 'plugins_api' ) ) {
			require_once ABSPATH . 'wp-admin/includes/plugin-install.php';
		}
		if ( ! class_exists( 'WP_Upgrader' ) ) {
			require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
		}

		if ( false === filter_var( $plugin_slug, FILTER_VALIDATE_URL ) ) {
			$api = plugins_api(
				'plugin_information',
				array(
					'slug'   => $plugin_slug,
					'fields' => array(
						'short_description' => false,
						'sections'          => false,
						'requires'          => false,
						'rating'            => false,
						'ratings'           => false,
						'downloaded'        => false,
						'last_updated'      => false,
						'added'             => false,
						'tags'              => false,
						'compatibility'     => false,
						'homepage'          => false,
						'donate_link'       => false,
					),
				)
			);

			$download_link = $api->download_link;
		} else {
			$download_link = $plugin_slug;
		}

		// Use AJAX upgrader skin instead of plugin installer skin.
		// ref: function wp_ajax_install_plugin().
		$upgrader = new Plugin_Upgrader( new WP_Ajax_Upgrader_Skin() );

		$install = $upgrader->install( $download_link );

		if ( false === $install ) {
			return false;
		} else {
			return true;
		}

	}

	/**
	 * Activate a plugin.
	 *
	 * @param string $plugin_path Plugin path.
	 */
	public function activate_plugin( $plugin_path ) {

		if ( ! current_user_can( 'install_plugins' ) ) {
			return false;
		}

		$activate = activate_plugin( $plugin_path, '', false, true );

		if ( is_wp_error( $activate ) ) {
			return false;
		} else {
			return true;
		}

	}

	/**
	 * Start import.
	 */
	public function atss_import_start() {

		$this->verify_import_nonce();

		/**
		 * Variables.
		 */
		$demo_id = ( isset( $_POST['demo_id'] ) ) ? sanitize_text_field( wp_unslash( $_POST['demo_id'] ) ) : '';
		$builder_type = ( isset( $_POST['builder_type'] ) ) ? sanitize_text_field( wp_unslash( $_POST['builder_type'] ) ) : '';

		if ( ! $demo_id || ! isset( $this->demos[ $demo_id ] ) ) {
			wp_send_json_error( esc_html__( 'Invalid demo id.', 'athemes-starter-sites' ) );
		}

		if ( ! current_user_can( 'edit_theme_options' ) ) {
			wp_send_json_error( esc_html__( 'You are not permitted to start demo.', 'athemes-starter-sites' ) );
		}

		/**
		 * Action hook.
		 */
		do_action( 'atss_import_start', $demo_id, $builder_type );

		/**
		 * Return successful AJAX.
		 */
		wp_send_json_success();

	}

	/**
	 * Clean previous import.
	 */
	public function atss_import_clean() {

		$this->verify_import_nonce();

		/**
		 * Variables.
		 */
		$demo_id = ( isset( $_POST['demo_id'] ) ) ? sanitize_text_field( wp_unslash( $_POST['demo_id'] ) ) : '';

		if ( ! $demo_id || ! isset( $this->demos[ $demo_id ] ) ) {
			wp_send_json_error( esc_html__( 'Invalid demo id.', 'athemes-starter-sites' ) );
		}

		if ( ! current_user_can( 'edit_theme_options' ) ) {
			wp_send_json_error( esc_html__( 'You are not permitted to clean previous import.', 'athemes-starter-sites' ) );
		}

		/**
		 * Suspend bunches of stuff in WP core.
		 */
		wp_suspend_cache_invalidation( true );

		global $wpdb;

		/**
		 * Delete posts.
		 */
		$query  = $wpdb->prepare( "SELECT post_id FROM {$wpdb->postmeta} WHERE meta_key = %s", '_athemes_sites_imported_post' );
		$result = $wpdb->get_col( $query );

		if ( ! empty( $result ) ) {
			foreach ( $result as $post_id ) {
				$post_type = get_post_type( $post_id );
				if ( $post_type === 'elementor_library' ) {
					$_GET['force_delete_kit'] = true;
				}
				wp_delete_post( $post_id, true );
			}
		}

		/**
		 * Delete terms.
		 */
		$query  = $wpdb->prepare( "SELECT term_id FROM {$wpdb->termmeta} WHERE meta_key = %s", '_athemes_sites_imported_term' );
		$result = $wpdb->get_col( $query );

		if ( ! empty( $result ) ) {
			foreach ( $result as $term_id ) {
				$term = get_term( $term_id );
				if ( ! is_wp_error( $term ) && ! empty( $term ) && is_object( $term ) ) {
					wp_delete_term( $term->term_id, $term->taxonomy );
				}
			}
		}

		/**
		 * Re-enable stuff in core
		 */
		wp_suspend_cache_invalidation( false );

		/**
		 * Clean widget settings.
		 */
		$this->clean_widget_settings();

		/**
		 * Clean customizer settings.
		 */
		$this->clean_customizer_settings();

		/**
		 * Action hook.
		 */
		do_action( 'atss_import_clean' );

		/**
		 * Return successful AJAX.
		 */
		wp_send_json_success();

	}

	/**
	 * Clean widget settings.
	 */
	public function clean_widget_settings() {

		$imported_widgets = get_option( '_athemes_sites_imported_widgets', array() );

		if ( ! empty( $imported_widgets ) ) {

			$imported_widget_ids = array();

			foreach ( $imported_widgets as $imported_widget ) {
				if ( ! empty( $imported_widget ) && is_array( $imported_widget ) ) {
					$imported_widget_ids = array_merge( $imported_widget_ids, $imported_widget );
				}
			}

			$sidebars_widgets = get_option( 'sidebars_widgets', array() );

			if ( ! empty( $imported_widget_ids ) && ! empty( $sidebars_widgets ) ) {
				foreach ( $sidebars_widgets as $sidebar_id => $widgets ) {
					if ( ! empty( $widgets ) && is_array( $widgets ) ) {
						$widgets = (array) $widgets;
						foreach ( $widgets as $widget_id ) {
							if ( in_array( $widget_id, $imported_widget_ids, true ) ) {
								$sidebars_widgets['wp_inactive_widgets'][] = $widget_id;
								$sidebars_widgets[ $sidebar_id ] = array_diff( $sidebars_widgets[ $sidebar_id ], array( $widget_id ) );
							}
						}
					}
				}
				update_option( 'sidebars_widgets', $sidebars_widgets );
			}

			delete_option( '_athemes_sites_imported_widgets' );

		}

	}

	/**
	 * Clean customizer settings.
	 */
	public function clean_customizer_settings() {

		/**
		 * Clean imported customizer mods.
		 */
		$imported_customizer_mods = get_option( '_athemes_sites_imported_customizer_mods', array() );

		if ( ! empty( $imported_customizer_mods ) ) {

			foreach ( $imported_customizer_mods as $mod_key => $mod_name ) {
				remove_theme_mod( $mod_key );
			}

			remove_theme_mods();

			delete_option( '_athemes_sites_imported_customizer_mods' );

		}

		/**
		 * Clean imported customizer options.
		 */
		$imported_customizer_options = get_option( '_athemes_sites_imported_customizer_options', array() );

		if ( ! empty( $imported_customizer_options ) ) {

			foreach ( $imported_customizer_options as $option_key => $option_name ) {
				delete_option( $option_key );
			}

			delete_option( '_athemes_sites_imported_customizer_options' );

		}

	}

	/**
	 * AJAX callback to install and activate a plugin.
	 */
	public function ajax_import_plugin() {

		$this->verify_import_nonce();

		/**
		 * Variables.
		 */
		$demo_id = ( isset( $_POST['demo_id'] ) ) ? sanitize_text_field( wp_unslash( $_POST['demo_id'] ) ) : '';
		$slug    = ( isset( $_POST['slug'] ) ) ? sanitize_text_field( wp_unslash( $_POST['slug'] ) ) : '';
		$path    = ( isset( $_POST['path'] ) ) ? sanitize_text_field( wp_unslash( $_POST['path'] ) ) : '';

		if ( empty( $slug ) ) {
			wp_send_json_error( esc_html__( 'Unknown slug in a plugin.', 'athemes-starter-sites' ) );
		}

		if ( empty( $path ) ) {
			wp_send_json_error( esc_html__( 'Unknown path in a plugin.', 'athemes-starter-sites' ) );
		}

		// Require a valid demo only when a demo-based import is being used.
		if ( ! empty( $demo_id ) && ! isset( $this->demos[ $demo_id ] ) ) {
			wp_send_json_error( esc_html__( 'Invalid demo id.', 'athemes-starter-sites' ) );
		}

		if ( ! current_user_can( 'install_plugins' ) ) {
			wp_send_json_error( esc_html__( 'Insufficient permissions to install the plugin.', 'athemes-starter-sites' ) );
		}

		if ( 'not_installed' === $this->get_plugin_status( $path ) ) {

			$this->install_plugin( $slug );

			$this->activate_plugin( $path );

		} elseif ( 'inactive' === $this->get_plugin_status( $path ) ) {

			$this->activate_plugin( $path );

		}

		/**
		 * Action hook.
		 */
		do_action( 'atss_import_plugin', $slug, $path );

		/**
		 * Return successful AJAX.
		 */
		if ( 'active' === $this->get_plugin_status( $path ) ) {
			wp_send_json_success();
		}

		wp_send_json_error( esc_html__( 'Failed to initialize or activate importer plugin.', 'athemes-starter-sites' ) );

	}

	/**
	 * AJAX callback to import contents and media files from contents.xml.
	 */
	public function ajax_import_contents() {

		$this->verify_import_nonce();

		/**
		 * Variables.
		 */
		$demo_id      = ( isset( $_POST['demo_id'] ) ) ? sanitize_text_field( wp_unslash( $_POST['demo_id'] ) ) : '';
		$builder_type = ( isset( $_POST['builder_type'] ) ) ? sanitize_text_field( wp_unslash( $_POST['builder_type'] ) ) : '';
		$content_type = ( isset( $_POST['content_type'] ) ) ? sanitize_text_field( wp_unslash( $_POST['content_type'] ) ) : '';

		if ( ! $demo_id || ! isset( $this->demos[ $demo_id ] ) ) {
			wp_send_json_error( esc_html__( 'Invalid demo id.', 'athemes-starter-sites' ) );
		}

		if ( ! isset( $this->demos[ $demo_id ]['import'][ $builder_type ] ) ) {
			wp_send_json_error( esc_html__( 'Invalid builder type.', 'athemes-starter-sites' ) );
		}

		if ( ! isset( $this->demos[ $demo_id ]['import'][ $builder_type ]['content'] ) ) {
			wp_send_json_error( esc_html__( 'The url address of the demo content is not specified.', 'athemes-starter-sites' ) );
		}

		if ( ! current_user_can( 'edit_theme_options' ) ) {
			wp_send_json_error( esc_html__( 'You are not permitted to import contents.', 'athemes-starter-sites' ) );
		}

		$xml_file_url = $this->demos[ $demo_id ]['import'][ $builder_type ]['content'];

		$xml_file_path = get_transient( 'atss_importer_data' );

		if ( ! $xml_file_path || ! file_exists( $xml_file_path ) ) {

			/**
			 * Download contents.xml
			 */
			if ( ! function_exists( 'download_url' ) ) {
				require_once ABSPATH . 'wp-admin/includes/file.php';
			}

			// Set timeout.
			$timeout_seconds = 5;

			// Download file to temp dir.
			$temp_file = download_url( $xml_file_url, $timeout_seconds );

			if ( is_wp_error( $temp_file ) ) {
				wp_send_json_error( esc_html__( 'Failed to download temporary demo xml file.', 'athemes-starter-sites' ) );
			}

			// Array based on $_FILE as seen in PHP file uploads.
			$file_args = array(
				'name'     => basename( $xml_file_url ),
				'tmp_name' => $temp_file,
				'error'    => 0,
				'size'     => @filesize( $temp_file ),
			);

			$overrides = array(
				'test_form'   => false,
				'test_size'   => true,
				'test_upload' => true,
				'mimes'       => array(
					'xml'  => 'text/xml',
				),
			);

			// Move the temporary file into the uploads directory.
			$download_response = wp_handle_sideload( $file_args, $overrides );

			// Error when downloading XML file.
			if ( isset( $download_response['error'] ) ) {
				wp_send_json_error( esc_html__( 'Failed to download demo xml file.', 'athemes-starter-sites' ) );
			}

			// Define the downloaded contents.xml file path.
			$xml_file_path = $download_response['file'];

			set_transient( 'atss_importer_data', $xml_file_path, HOUR_IN_SECONDS );

		}

		/**
		 * Import content and media files using WXR Importer.
		 */
		if ( ! class_exists( 'WP_Importer' ) ) {
			if ( ! defined( 'WP_LOAD_IMPORTERS' ) ) {
				define( 'WP_LOAD_IMPORTERS', true );
			}
			require_once ABSPATH . 'wp-admin/includes/class-wp-importer.php';
		}

		/**
		 * Import Core.
		 */
		require_once ATSS_PATH . 'v2/vendor/wp-content-importer-v2/WPImporterLogger.php';
		require_once ATSS_PATH . 'v2/vendor/wp-content-importer-v2/WPImporterLoggerCLI.php';
		require_once ATSS_PATH . 'v2/vendor/wp-content-importer-v2/WXRImportInfo.php';
		require_once ATSS_PATH . 'v2/vendor/wp-content-importer-v2/WXRImporter.php';
		require_once ATSS_PATH . 'v2/vendor/wp-content-importer-v2/Logger.php';

		/**
		 * Prepare the importer.
		 */
		// Time to run the import!
		set_time_limit( 0 );

		$this->microtime = microtime( true );

		// Are we allowed to create users?
		add_filter( 'wxr_importer.pre_process.user', '__return_null' );

		// Check, if we need to send another AJAX request and set the importing author to the current user.
		add_filter( 'wxr_importer.pre_process.post', array( $this, 'ajax_request_maybe' ) );

		// Post content replace attachment urls
  	add_filter( 'wxr_importer.pre_process.post', array( $this, 'post_content_replace_attachment_urls' ) );

		// Post meta replace attachment urls
  	add_filter( 'wxr_importer.pre_process.post_meta', array( $this, 'post_meta_replace_attachment_urls' ), 10, 2 );

		// Track imported post
  	add_filter( 'wxr_importer.processed.post', array( $this, 'track_imported_post' ) );

		// Track imported term
  	add_filter( 'wxr_importer.processed.term', array( $this, 'track_imported_term' ) );

		// Convert images to placeholder
		if ( $content_type === 'placeholder' ) {
  		add_filter( 'atss_importer.processed.attachment', array( $this, 'convert_attachment_to_placeholder' ) );
		}

		// WooCommerce product attributes registration.
		if ( class_exists( 'WooCommerce' ) ) {
			add_filter( 'wxr_importer.pre_process.term', array( $this, 'woocommerce_product_attributes_registration' ) );
			add_filter( 'add_term_metadata', array( $this, 'woocommerce_product_attributes_filter' ), 10, 5 );
		}


		// Set the WordPress Importer v2 as the importer used in this plugin.
		// More: https://github.com/humanmade/WordPress-Importer.
		$importer = new ATSS_WXRImporter( array(
			'fetch_attachments' => true,
			'default_author'    => get_current_user_id(),
		) );

		// Store importer instance for access in filters
		$this->importer = $importer;

		// Logger options for the logger used in the importer.
		$logger_options = apply_filters( 'atss_logger_options', array(
			'logger_min_level' => 'warning',
		) );

		// Configure logger instance and set it to the importer.
		$logger            = new ATSS_Logger();
		$logger->min_level = $logger_options['logger_min_level'];

		// Set logger.
		$importer->set_logger( $logger );

		// Initialize page filter for selective page import (from wizard).
		$wizard_state = get_option( 'atss_wizard_state', array() );
		if ( ! empty( $wizard_state['data']['pages']['selectedPages'] ) ) {
			require_once ATSS_PATH . 'v2/onboarding/includes/class-page-filter.php';
			new ATSS_Onboarding_Page_Filter();
		}

		// Initialize contact replacer if contact data exists.
		if ( ! empty( $wizard_state['data']['contact'] ) ) {
			require_once ATSS_PATH . 'v2/onboarding/includes/class-contact-replacer.php';
			new ATSS_Onboarding_Contact_Replacer();
		}

		/**
		 * Process import.
		 */
		$importer->import( $xml_file_path );

		// Is error ?.
		if ( is_wp_error( $importer ) ) {
			wp_send_json_error( esc_html__( 'An error occurred while importing contents.', 'athemes-starter-sites' ) );
		}

		if ( $logger->error_output ) {
			wp_send_json_error( $logger->error_output );
		}

		/**
		 * Action hook.
		 */
		do_action( 'atss_import_contents' );

		/**
		 * Return successful AJAX.
		 */
		wp_send_json_success();

	}

	/**
	 * Check if we need to create a new AJAX request, so that server does not timeout.
	 *
	 * @param array $data current post data.
	 * @return array
	 */
	public function ajax_request_maybe( $data ) {

		$time = microtime( true ) - $this->microtime;

		// Check attachment count for splitting - only split if significant time has also passed
		// This prevents infinite split loops and ensures we only split when actually needed
		$split_after_attachments = apply_filters( 'atss_split_after_attachments', 10 ); // Increased from 10 to 20
		$should_split_by_count = false;

		if ( $this->importer && method_exists( $this->importer, 'get_attachment_count' ) ) {
			$attachment_count = $this->importer->get_attachment_count();
			// Only split if we've processed enough attachments AND some time has passed (at least 15 seconds)
			// This prevents splits when all files already exist (instant processing)
			if ( $attachment_count >= $split_after_attachments && $time > 15 ) {
				$should_split_by_count = true;
			}
		}

		// We should make a new ajax call, if the time is right OR attachment count is reached.
		// Reduced from 300 to 120 to align better with JS timeout of 120 seconds
		$time_limit = apply_filters( 'atss_time_for_one_ajax_call', 120 );
		if ( $time > $time_limit || $should_split_by_count ) {

			// Reset the attachment counter for the next AJAX call
			if ( $this->importer && method_exists( $this->importer, 'reset_attachment_count' ) ) {
				$this->importer->reset_attachment_count();
			}

			$response = array(
				'success' => true,
				'status'  => 'newAJAX',
				'message' => 'Time for new AJAX request!: ' . $time,
			);

			// Send the request for a new AJAX call.
			wp_send_json( $response );

		}

		if ( ! empty( $data ) && is_array( $data ) ) {

			// Set importing author to the current user.
			// Fixes the [WARNING] Could not find the author for ... log warning messages.
			$current_user_obj = wp_get_current_user();

			$data['post_author'] = $current_user_obj->user_login;

		}

		return $data;

	}

	/**
	 * Replace attachment urls.
	 */
	public static function replace_attachment_urls( $content ) {
		if ( is_serialized( $content ) ) {
			return $content;
		}

		preg_match_all( '/(?:http(?:s?):)(?:[\/\\\\\\\\|.|\w|\s|-])*\.(?:jpg|jpeg|jpe|png|gif|webp|svg)/m', $content, $image_urls );

		if ( ! empty( $image_urls[0] ) ) {

			$image_urls = array_unique( $image_urls[0] );

			foreach ( $image_urls as $image_url ) {

				$clean_url = wp_unslash( $image_url );

				if ( ! strpos( $clean_url, '/uploads/' ) ) {
					continue;
				}

				$url_parts = parse_url( $clean_url );

				if ( ! isset( $url_parts['host'] ) ) {
					continue;
				}

				$url_parts['path'] = explode( '/', $url_parts['path'] );
				$url_parts['path'] = array_slice( $url_parts['path'], -3 );

				$uploads_dir = wp_get_upload_dir();
				$uploads_url = $uploads_dir['baseurl'];

				$new_url = esc_url( $uploads_url . '/' . join( '/', $url_parts['path'] ) );
				$content = str_replace( $image_url, $new_url, $content );

				$content = self::maybe_replace_attachment_urls_with_media_library_images( $content, $image_url, $new_url );

			}

		}

		return $content;
	}

	/**
	 * Maybe replace attachment urls with media library images.
	 */
	public static function maybe_replace_attachment_urls_with_media_library_images( $content, $remote_image_url, $new_url ) {
		global $wpdb;

		$filename = basename( $remote_image_url );

		// Remove any extension
		$filename = preg_replace( '/\.[^.]+$/', '', $filename );

		// Remove "-scaled" whether present
		$filename = str_replace( '-scaled', '', $filename );

		$filename = $wpdb->esc_like( $filename );

		$query   = $wpdb->prepare("SELECT ID FROM {$wpdb->prefix}posts WHERE post_type = 'attachment' AND post_name LIKE '%s'", "%$filename%");
		$results = $wpdb->get_results($query, ARRAY_A);

		$attachment_id = $results[0]['ID'] ?? '';

		if ( empty( $attachment_id ) ) {
			return str_replace( $new_url, $remote_image_url, $content );
		}

		$attachment_url = wp_get_attachment_image_url( $attachment_id, 'full' );

		$content = str_replace( $new_url, $attachment_url, $content );

		return $content;
	}

	/**
	 * Post content replace attachment urls.
	 */
	public function post_content_replace_attachment_urls( $data ) {

		if ( ! empty( $data ) && ! empty( $data['post_content'] ) ) {
			$data['post_content'] = self::replace_attachment_urls( $data['post_content'] );
		}

		return $data;

	}

	/**
	 * Post meta replace attachment urls.
	 */
	public function post_meta_replace_attachment_urls( $meta_item, $post_id ) {

		if ( ! empty( $meta_item ) ) {

			// Replace mega menu attachments urls.
			if ( $meta_item['key'] === '_is_mega_menu_item_content_custom_html' && ! empty( $meta_item['value'] ) ) {
				$meta_item['value'] = self::replace_attachment_urls( $meta_item['value'] );
			}

			// Replace elementor attachments urls.
			if ( $meta_item['key'] === '_elementor_data' && ! empty( $meta_item['value'] ) ) {
				$meta_item['value'] = self::replace_attachment_urls( maybe_unserialize( $meta_item['value'] ) );
			}

	    // Set elementor default kit.
	    if ( $meta_item['key'] === '_elementor_template_type' && $meta_item['value'] === 'kit' ) {
	      update_option( 'elementor_active_kit', $post_id );
	    }

		}

		return $meta_item;

	}

	/**
	 * Track imported post for clean previous install.
	 */
	public function track_imported_post( $post_id = 0 ) {

		update_post_meta( $post_id, '_athemes_sites_imported_post', true );

	}

	/**
	 * Track imported term for clean previous install.
	 */
	public function track_imported_term( $term_id = 0 ) {

		update_term_meta( $term_id, '_athemes_sites_imported_term', true );

	}

	/**
	 * Convert attachment to placeholder.
	 */
	public function convert_attachment_to_placeholder( $data ) {

		if ( ! empty( $data['file'] ) ) {

			$imagedata = @getimagesize( $data['file'] );

			if ( empty( $imagedata ) ) {
				return $data;
			}

			list( $image_width, $image_height, $image_type ) = $imagedata;

			if ( empty( $image_width ) || empty( $image_height ) || empty( $image_type ) ) {
				return $data;
			}

			$image = @imagecreatetruecolor( $image_width, $image_height );

			@imagefilter( $image, IMG_FILTER_COLORIZE, 240, 240, 240 );

			switch ( $image_type ) {

				case IMAGETYPE_GIF:
					@imagegif( $image, $data['file'] );
				break;

				case IMAGETYPE_PNG:
					@imagepng( $image, $data['file'] );
				break;

				case IMAGETYPE_JPEG:
					@imagejpeg( $image, $data['file'] );
				break;

			}

		}

		return $data;

	}

	/**
	 *
	 * Hook into the pre-process term filter of the content import and register the
	 * custom WooCommerce product attributes, so that the terms can then be imported normally.
	 *
	 * This should probably be removed once the WP importer 2.0 support is added in WooCommerce.
	 *
	 * Fixes: [WARNING] Failed to import pa_size L warnings in content import.
	 * Code from: woocommerce/includes/admin/class-wc-admin-importers.php (ver 2.6.9).
	 *
	 * Github issue: https://github.com/awesomemotive/one-click-demo-import/issues/71
	 *
	 * @param  array $date The term data to import.
	 * @return array       The unchanged term data.
	 *
	 */
	public function woocommerce_product_attributes_registration( $data ) {

		global $wpdb;

		if ( strstr( $data['taxonomy'], 'pa_' ) ) {

			if ( ! taxonomy_exists( $data['taxonomy'] ) ) {

				$attribute_name = wc_sanitize_taxonomy_name( str_replace( 'pa_', '', $data['taxonomy'] ) );
				$attribute_type = 'select';

				// To do: Generate .xml import file with "attribute_type".
				if( $attribute_name === 'color' ) {
					$attribute_type = 'color';
				} else if( $attribute_name === 'size' ) {
					$attribute_type = 'button';
				} else if( $attribute_name === 'image' ) {
					$attribute_type = 'image';
				}

				// Create the taxonomy
				if ( ! in_array( $attribute_name, wc_get_attribute_taxonomies() ) ) {

					$attribute = array(
						'attribute_label'   => ucwords( str_replace( '-', ' ', $attribute_name ) ),
						'attribute_name'    => $attribute_name,
						'attribute_type'    => $attribute_type,
						'attribute_orderby' => 'menu_order',
						'attribute_public'  => 0
					);

					$wpdb->insert( $wpdb->prefix . 'woocommerce_attribute_taxonomies', $attribute );

					delete_transient( 'wc_attribute_taxonomies' );

				}

				// Register the taxonomy now so that the import works!
				register_taxonomy(
					$data['taxonomy'],
					apply_filters( 'woocommerce_taxonomy_objects_' . $data['taxonomy'], array( 'product' ) ),
					apply_filters( 'woocommerce_taxonomy_args_' . $data['taxonomy'], array(
						'hierarchical' => true,
						'show_ui'      => false,
						'query_var'    => true,
						'rewrite'      => false,
					) )
				);

			}

		}

		return $data;

	}

	/**
	 * WooCommerce product attribute filter.
	 */
	public function woocommerce_product_attributes_filter( $check, $object_id, $meta_key, $meta_value, $unique ) {

		$meta_keys = array(
			'product_attribute_color',
			'product_attribute_image',
		);

		if ( in_array( $meta_key, $meta_keys ) && $meta_value === '' ) {
			return false;
		}

		return $check;
	}

	/**
	 * AJAX callback to import widgets on all sidebars from widgets.json.
	 */
	public function ajax_import_widgets() {

		try {
			// Increase time limit for widget import to prevent timeout
			if ( ! ini_get( 'safe_mode' ) ) {
				@set_time_limit( 120 ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
			}

			$this->verify_import_nonce();

		/**
		 * Variables.
		 */
		$demo_id      = ( isset( $_POST['demo_id'] ) ) ? sanitize_text_field( wp_unslash( $_POST['demo_id'] ) ) : '';
		$builder_type = ( isset( $_POST['builder_type'] ) ) ? sanitize_text_field( wp_unslash( $_POST['builder_type'] ) ) : '';

		if ( ! $demo_id || ! isset( $this->demos[ $demo_id ] ) ) {
			wp_send_json_error( esc_html__( 'Invalid demo id.', 'athemes-starter-sites' ) );
		}

		if ( ! isset( $this->demos[ $demo_id ]['import'][ $builder_type ] ) ) {
			wp_send_json_error( esc_html__( 'Invalid builder type.', 'athemes-starter-sites' ) );
		}

		if ( ! isset( $this->demos[ $demo_id ]['import'][ $builder_type ]['widgets'] ) ) {
			wp_send_json_error( esc_html__( 'No widgets WIE file specified.', 'athemes-starter-sites' ) );
		}

		if ( ! current_user_can( 'edit_theme_options' ) ) {
			wp_send_json_error( esc_html__( 'You are not permitted to import widgets.', 'athemes-starter-sites' ) );
		}

		$file_url = $this->demos[ $demo_id ]['import'][ $builder_type ]['widgets'];

		/**
		 * Process widgets.json.
		 */
		// Get JSON data from widgets.json with timeout protection.
		$raw = wp_remote_get(
			wp_unslash( $file_url ),
			array(
				'timeout' => 15, // Short timeout to prevent hanging
				'sslverify' => false, // Helps with Playground/CORS issues
			)
		);

		// Check if fetch failed - log and skip instead of stopping import.
		if ( is_wp_error( $raw ) ) {
			// Log error but continue to next step
			error_log( 'ATSS Widget Import: Failed to fetch widget file - ' . $raw->get_error_message() );
			wp_send_json_success( array( 'message' => esc_html__( 'Widget import skipped (file unavailable)', 'athemes-starter-sites' ) ) );
			return;
		}

		// Check response code - skip if not successful.
		if ( 200 !== wp_remote_retrieve_response_code( $raw ) ) {
			// Log error but continue to next step
			error_log( 'ATSS Widget Import: Failed to load widget file. HTTP ' . wp_remote_retrieve_response_code( $raw ) );
			wp_send_json_success( array( 'message' => esc_html__( 'Widget import skipped (file unavailable)', 'athemes-starter-sites' ) ) );
			return;
		}

	 	// Clean widget settings with timeout protection.
		try {
			$this->clean_widget_settings();
		} catch ( Exception $e ) {
			// Log but don't stop import if cleaning fails
			error_log( 'ATSS Widget Import: Failed to clean widget settings - ' . $e->getMessage() );
		} catch ( Error $e ) {
			// Log but don't stop import if cleaning fails
			error_log( 'ATSS Widget Import: Error cleaning widget settings - ' . $e->getMessage() );
		}

		// Decode raw JSON string to associative array.
		$data = json_decode( wp_remote_retrieve_body( $raw ) );

		// Check if JSON decode failed - skip if so.
		if ( empty( $data ) ) {
			error_log( 'ATSS Widget Import: Failed to decode widget JSON data' );
			wp_send_json_success( array( 'message' => esc_html__( 'Widget import skipped (invalid data)', 'athemes-starter-sites' ) ) );
			return;
		}

		// Initialize contact replacer if contact data exists.
		$wizard_state = get_option( 'atss_wizard_state', array() );
		if ( ! empty( $wizard_state['data']['contact'] ) ) {
			require_once ATSS_PATH . 'v2/onboarding/includes/class-contact-replacer.php';
			new ATSS_Onboarding_Contact_Replacer();
		}

		// Process data with timeout protection
		try {
			$data = map_deep( $data, array( 'Athemes_Starter_Sites_Importer', 'replace_attachment_urls' ) );
		} catch ( Exception $e ) {
			error_log( 'ATSS Widget Import: Failed to process URLs - ' . $e->getMessage() );
			// Continue with unprocessed data
		} catch ( Error $e ) {
			error_log( 'ATSS Widget Import: Error processing URLs - ' . $e->getMessage() );
			// Continue with unprocessed data
		}

		$widgets = new ATSS_Widget_Importer();

		// Import with timeout tracking.
		$results = $widgets->import( $data );

		if ( is_wp_error( $results ) ) {
			// Log error but continue to next step instead of stopping
			$error_message = $results->get_error_message();
			error_log( 'ATSS Widget Import: Widget import failed - ' . $error_message );
			wp_send_json_success( array( 'message' => esc_html__( 'Widget import completed with errors', 'athemes-starter-sites' ) ) );
			return;
		}

		/**
		 * Action hook - wrapped in timeout protection.
		 */
		try {
			// Get all available widgets site supports.
			$available_widgets = ATSS_Widget_Importer::available_widgets();

			// Get all existing widget instances.
			$widget_instances = array();

			foreach ( $available_widgets as $widget_data ) {
				if ( is_array( $widget_data ) && isset( $widget_data['id_base'] ) ) {
					$widget_instances[ $widget_data['id_base'] ] = get_option( 'widget_' . $widget_data['id_base'] );
				}
			}

			// Sidebar Widgets
			$sidebar_widgets = get_option( 'sidebars_widgets', array() );

			update_option( '_athemes_sites_imported_widgets', $sidebar_widgets, 'no' );

			do_action( 'atss_import_widgets', $sidebar_widgets, $widget_instances );
		} catch ( Exception $e ) {
			error_log( 'ATSS Widget Import: Post-import actions failed - ' . $e->getMessage() );
			// Continue to success even if hooks fail
		} catch ( Error $e ) {
			error_log( 'ATSS Widget Import: Post-import actions error - ' . $e->getMessage() );
			// Continue to success even if hooks fail
		}

		/**
		 * Return successful AJAX.
		 */
		wp_send_json_success();

		} catch ( Exception $e ) {
			// Log the error but don't crash the import - treat widget import as optional
			error_log(
				sprintf(
					'ATSS Widget Import Exception: %1$s in %2$s on line %3$s',
					$e->getMessage(),
					basename( $e->getFile() ),
					$e->getLine()
				)
			);
			wp_send_json_success( array( 'message' => esc_html__( 'Widget import skipped (unexpected error)', 'athemes-starter-sites' ) ) );
		}

	}

	/**
	 * AJAX callback to import customizer settings from customizer.json.
	 */
	public function ajax_import_customizer() {

		try {
			$this->verify_import_nonce();

		/**
		 * Variables.
		 */
		$demo_id      = ( isset( $_POST['demo_id'] ) ) ? sanitize_text_field( wp_unslash( $_POST['demo_id'] ) ) : '';
		$builder_type = ( isset( $_POST['builder_type'] ) ) ? sanitize_text_field( wp_unslash( $_POST['builder_type'] ) ) : '';

		if ( ! $demo_id || ! isset( $this->demos[ $demo_id ] ) ) {
			wp_send_json_error( esc_html__( 'Invalid demo id.', 'athemes-starter-sites' ) );
		}

		if ( ! isset( $this->demos[ $demo_id ]['import'][ $builder_type ] ) ) {
			wp_send_json_error( esc_html__( 'Invalid builder type.', 'athemes-starter-sites' ) );
		}

		if ( ! isset( $this->demos[ $demo_id ]['import'][ $builder_type ]['customizer'] ) ) {
			wp_send_json_error( esc_html__( 'The url address of the demo customizer is not specified.', 'athemes-starter-sites' ) );
		}

		if ( ! current_user_can( 'edit_theme_options' ) ) {
			wp_send_json_error( esc_html__( 'You are not permitted to import customizer.', 'athemes-starter-sites' ) );
		}

		$file_url = $this->demos[ $demo_id ]['import'][ $builder_type ]['customizer'];

		/**
		 * Process customizer.json.
		 */
		// Increase time limit for customizer import to prevent timeout
		if ( ! ini_get( 'safe_mode' ) ) {
			@set_time_limit( 120 ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
		}

		// Get JSON data from customizer.json with extended timeout.
		$raw = wp_remote_get(
			wp_unslash( $file_url ),
			array(
				'timeout' => 30, // Extended timeout for larger customizer files
				'sslverify' => false,
			)
		);

		// Check if fetch failed.
		if ( is_wp_error( $raw ) ) {
			wp_send_json_error(
				sprintf(
					esc_html__( 'Failed to fetch customizer file: %s', 'athemes-starter-sites' ),
					$raw->get_error_message()
				)
			);
		}

		// Abort if customizer.json response code is not successful.
		if ( 200 != wp_remote_retrieve_response_code( $raw ) ) {
			wp_send_json_error(
				sprintf(
					esc_html__( 'Failed to load customizer demo file. HTTP %d', 'athemes-starter-sites' ),
					wp_remote_retrieve_response_code( $raw )
				)
			);
		}

		// Clean customizer settings.
		$this->clean_customizer_settings();

		// Decode raw JSON string to associative array.
		$data = maybe_unserialize( wp_remote_retrieve_body( $raw ), true );

		// Check if data was properly unserialized
		if ( empty( $data ) || ! is_array( $data ) ) {
			wp_send_json_error( esc_html__( 'Failed to parse customizer data. File may be corrupted.', 'athemes-starter-sites' ) );
		}

		// Initialize contact replacer if contact data exists.
		$wizard_state = get_option( 'atss_wizard_state', array() );
		if ( ! empty( $wizard_state['data']['contact'] ) ) {
			require_once ATSS_PATH . 'v2/onboarding/includes/class-contact-replacer.php';
			new ATSS_Onboarding_Contact_Replacer();
		}

		$customizer = new ATSS_Customizer_Importer();

		// Import.
		$results = $customizer->import( $data );

		if ( is_wp_error( $results ) ) {
			wp_send_json_error(
				sprintf(
					esc_html__( 'Customizer import error: %s', 'athemes-starter-sites' ),
					$results->get_error_message()
				)
			);
		}

		/**
		 * Action hook.
		 */
		do_action( 'atss_import_customizer', $data );

		/**
		 * Return successful AJAX.
		 */
		wp_send_json_success();

		} catch ( Exception $e ) {
			wp_send_json_error(
				sprintf(
					esc_html__( 'Customizer Import Exception: %1$s in %2$s on line %3$s', 'athemes-starter-sites' ),
					$e->getMessage(),
					basename( $e->getFile() ),
					$e->getLine()
				)
			);
		}

	}

	/**
	 * AJAX callback to finish import.
	 */
	public function ajax_import_finish() {

		$this->verify_import_nonce();

		/**
		 * Get Demo ID.
		 */
		$demo_id      	= ( isset( $_POST['demo_id'] ) ) ? sanitize_text_field( wp_unslash( $_POST['demo_id'] ) ) : '';
		$builder_type 	= ( isset( $_POST['builder_type'] ) ) ? sanitize_text_field( wp_unslash( $_POST['builder_type'] ) ) : '';
		$content_type 	= ( isset( $_POST['content_type'] ) ) ? sanitize_text_field( wp_unslash( $_POST['content_type'] ) ) : '';

		if ( ! $demo_id || ! isset( $this->demos[ $demo_id ] ) ) {
			wp_send_json_error( esc_html__( 'Invalid demo id.', 'athemes-starter-sites' ) );
		}

		if ( ! isset( $this->demos[ $demo_id ]['import'][ $builder_type ] ) ) {
			wp_send_json_error( esc_html__( 'Invalid builder type.', 'athemes-starter-sites' ) );
		}

		/**
		 * Delete imported XML file.
		 */
		$xml_file_path = get_transient( 'atss_importer_data' );

		if ( $xml_file_path && file_exists( $xml_file_path ) ) {
			unlink( $xml_file_path );
			delete_transient( 'atss_importer_data' );
		}

		/**
		 * Elementor clear cache.
		 */
		if ( class_exists( 'Elementor\Plugin' ) ) {
			Elementor\Plugin::$instance->files_manager->clear_cache();
		}

		/**
		 * Update nav menu term count for avoid missing menu items.
		 */
		$nav_menu_terms = get_terms( array(
			'taxonomy'   => 'nav_menu',
			'hide_empty' => false,
			'meta_key'   => '_athemes_sites_imported_term',
		) );

		$nav_menu_term_ids = array();

		if ( ! is_wp_error( $nav_menu_terms ) && ! empty( $nav_menu_terms ) ) {
			foreach ( $nav_menu_terms as $nav_menu_term ) {
				if ( empty( $nav_menu_term->count ) ) {
					$nav_menu_term_ids[] = $nav_menu_term->term_id;
				}
			}
		}

		if ( ! empty( $nav_menu_term_ids ) ) {
			wp_update_term_count_now( $nav_menu_term_ids, 'nav_menu' );
		}

		/**
		 * Update stock status to instock (needed for has variation products).
		 */
		if ( class_exists( 'WooCommerce' ) ) {
			$products = get_posts( array(
				'post_type'      => 'product',
				'posts_per_page' => -1,
				'meta_key'       => '_athemes_sites_imported_post',
			) );
			if ( ! is_wp_error( $products ) && ! empty( $products ) ) {
				foreach ( $products as $product ) {
					update_post_meta( $product->ID, '_stock_status', 'instock' );
				}
			}
			// Flush rewrite rules for shop/listing page.
			update_option( 'woocommerce_queue_flush_rewrite_rules', 'yes' );
		}

		/**
		 * Action hook.
		 */
		do_action( 'atss_finish_import', $demo_id, array(), $builder_type );

		/**
		 * Reset wizard state after successful import.
		 * Delete the wizard state so users start fresh if they return to the wizard.
		 */
		delete_option( 'atss_wizard_state' );

		/**
		 * Return successful AJAX.
		 */
		wp_send_json_success();

	}

	/**
	 * Set the importing author to the current user.
	 *
	 * Sync twin of the author side effect inside ajax_request_maybe(), without
	 * the AJAX re-dispatch: ajax_request_maybe() calls wp_send_json() (which
	 * exits) once time/attachment thresholds are reached, which would kill a
	 * headless REST/MCP import mid-run. The synchronous path must never split.
	 *
	 * @param array $data Current post data.
	 * @return array
	 */
	public function set_import_post_author( $data ) {

		if ( ! empty( $data ) && is_array( $data ) ) {
			$data['post_author'] = wp_get_current_user()->user_login;
		}

		return $data;
	}

	/**
	 * Run a full demo import synchronously, in one call.
	 *
	 * Server-side orchestration of the same sequence the JS client drives over
	 * AJAX: start -> required plugins -> contents -> widgets -> customizer ->
	 * finish. Each step is a non-AJAX helper that RETURNS a result instead of
	 * emitting JSON, so this method can aggregate them and short-circuit on a
	 * fatal step. Widgets are treated as optional (a failure is recorded but
	 * does not abort the import), matching the live AJAX flow. Used by the
	 * athemes-starter-sites/import-site ability.
	 *
	 * Capability is the caller's responsibility (the ability checks
	 * edit_theme_options; the plugin-install step additionally needs
	 * install_plugins). No nonce check here — this is not request-driven.
	 *
	 * @param string $demo_id      Demo id (key in the demos list).
	 * @param string $builder_type Builder slug, e.g. 'elementor' | 'gutenberg'.
	 * @param array  $options      { @type bool $clean_previous Delete a previous starter-site import first. }
	 * @return array {
	 *     @type bool   $success Whether the import completed.
	 *     @type string $message Human-readable summary.
	 *     @type array  $steps   Per-step results keyed by step name.
	 * }
	 */
	public function import( $demo_id, $builder_type, $options = array() ) {

		// The ability calls instance(), which lazily constructs a fresh object
		// whose after_setup_theme->init() (the only thing that fills $this->demos)
		// can no longer fire. Repopulate on demand so validation has data.
		if ( empty( $this->demos ) ) {
			$this->demos = apply_filters( 'atss_register_demos_list', array() );
		}

		$demo_id      = sanitize_text_field( (string) $demo_id );
		$builder_type = sanitize_text_field( (string) $builder_type );

		$steps = array();

		// Validate the demo + builder against the registered demos list.
		if ( ! $demo_id || ! isset( $this->demos[ $demo_id ] ) ) {
			return array(
				'success' => false,
				'message' => __( 'Invalid demo id.', 'athemes-starter-sites' ),
				'steps'   => $steps,
			);
		}

		if ( ! isset( $this->demos[ $demo_id ]['import'][ $builder_type ] ) ) {
			return array(
				'success' => false,
				'message' => __( 'Invalid builder type for this demo.', 'athemes-starter-sites' ),
				'steps'   => $steps,
			);
		}

		// A cached XML from a previous (possibly different-demo or aborted) run
		// must not be reused: the sync path always downloads fresh.
		$stale_xml = get_transient( 'atss_importer_data' );
		if ( $stale_xml ) {
			if ( file_exists( $stale_xml ) ) {
				unlink( $stale_xml );
			}
			delete_transient( 'atss_importer_data' );
		}

		// 1. Start.
		$steps['start'] = $this->run_import_start( $demo_id, $builder_type );

		// 1b. Optional: clean a previous starter-site import (sync twin of the
		// wizard's opt-in atss_import_clean step).
		if ( ! empty( $options['clean_previous'] ) ) {
			$steps['clean'] = $this->run_import_clean();
		}

		// 2. Required plugins (Elementor / Woo / etc. declared by the demo).
		$steps['plugins'] = $this->run_import_plugins( $demo_id, $builder_type );
		if ( ! $steps['plugins']['success'] ) {
			return array(
				'success' => false,
				'message' => $steps['plugins']['message'],
				'steps'   => $steps,
			);
		}

		// 3. Contents (XML) — fatal on failure.
		$steps['contents'] = $this->run_import_contents( $demo_id, $builder_type );
		if ( ! $steps['contents']['success'] ) {
			return array(
				'success' => false,
				'message' => $steps['contents']['message'],
				'steps'   => $steps,
			);
		}

		// 4. Widgets — optional. A failure is recorded but does not abort.
		$steps['widgets'] = $this->run_import_widgets( $demo_id, $builder_type );

		// 5. Customizer — fatal on failure.
		$steps['customizer'] = $this->run_import_customizer( $demo_id, $builder_type );
		if ( ! $steps['customizer']['success'] ) {
			return array(
				'success' => false,
				'message' => $steps['customizer']['message'],
				'steps'   => $steps,
			);
		}

		// 6. Finish (cleanup, cache, term counts, stock status).
		$steps['finish'] = $this->run_import_finish( $demo_id, $builder_type );

		return array(
			'success' => true,
			'message' => sprintf(
				/* translators: 1: demo name, 2: builder type. */
				__( 'Imported "%1$s" (%2$s).', 'athemes-starter-sites' ),
				isset( $this->demos[ $demo_id ]['name'] ) ? $this->demos[ $demo_id ]['name'] : $demo_id,
				$builder_type
			),
			'steps'   => $steps,
		);

	}

	/**
	 * Step 1: fire the start hook. Non-AJAX twin of atss_import_start().
	 *
	 * @param string $demo_id      Demo id.
	 * @param string $builder_type Builder slug.
	 * @return array { @type bool $success, @type string $message }
	 */
	private function run_import_start( $demo_id, $builder_type ) {
		do_action( 'atss_import_start', $demo_id, $builder_type );

		return array(
			'success' => true,
			'message' => __( 'Import started.', 'athemes-starter-sites' ),
		);
	}

	/**
	 * Optional clean step: delete everything a previous starter-site import
	 * created. Non-AJAX twin of atss_import_clean() — same post/term/widget/
	 * customizer cleanup, returning a result instead of emitting JSON.
	 *
	 * @return array { @type bool $success, @type string $message }
	 */
	private function run_import_clean() {

		wp_suspend_cache_invalidation( true );

		global $wpdb;

		$query  = $wpdb->prepare( "SELECT post_id FROM {$wpdb->postmeta} WHERE meta_key = %s", '_athemes_sites_imported_post' );
		$result = $wpdb->get_col( $query );

		if ( ! empty( $result ) ) {
			foreach ( $result as $post_id ) {
				$post_type = get_post_type( $post_id );
				if ( $post_type === 'elementor_library' ) {
					$_GET['force_delete_kit'] = true;
				}
				wp_delete_post( $post_id, true );
			}
		}

		$query  = $wpdb->prepare( "SELECT term_id FROM {$wpdb->termmeta} WHERE meta_key = %s", '_athemes_sites_imported_term' );
		$result = $wpdb->get_col( $query );

		if ( ! empty( $result ) ) {
			foreach ( $result as $term_id ) {
				$term = get_term( $term_id );
				if ( ! is_wp_error( $term ) && ! empty( $term ) && is_object( $term ) ) {
					wp_delete_term( $term->term_id, $term->taxonomy );
				}
			}
		}

		wp_suspend_cache_invalidation( false );

		$this->clean_widget_settings();
		$this->clean_customizer_settings();

		do_action( 'atss_import_clean' );

		return array(
			'success' => true,
			'message' => __( 'Previous import cleaned.', 'athemes-starter-sites' ),
		);
	}

	/**
	 * Step 2: install + activate every plugin the demo declares. Non-AJAX twin
	 * of ajax_import_plugin(), looping the demo's plugin specs instead of a
	 * single $_POST slug/path. Demo plugin specs carry name/slug/path/required
	 * and optionally builder ('elementor'|'gutenberg'); builder-scoped entries
	 * for the other builder are skipped. Only entries marked required => true
	 * abort the import on failure. Plugin status is read without a capability
	 * gate; install_plugins / activate_plugins are required only when an install
	 * or activation is actually needed, so already-active plugins import fine
	 * under DISALLOW_FILE_MODS.
	 *
	 * @param string $demo_id      Demo id.
	 * @param string $builder_type Builder slug.
	 * @return array { @type bool $success, @type string $message, @type array $plugins }
	 */
	private function run_import_plugins( $demo_id, $builder_type ) {

		$results = array();
		$plugins = isset( $this->demos[ $demo_id ]['plugins'] ) && is_array( $this->demos[ $demo_id ]['plugins'] )
			? $this->demos[ $demo_id ]['plugins']
			: array();

		if ( ! function_exists( 'is_plugin_active_for_network' ) || ! function_exists( 'activate_plugin' ) ) {
			require_once ABSPATH . 'wp-admin/includes/plugin.php';
		}

		foreach ( $plugins as $plugin ) {

			// Skip plugins scoped to a different builder than the one being imported.
			if ( ! empty( $plugin['builder'] ) && $plugin['builder'] !== $builder_type ) {
				continue;
			}

			$slug = isset( $plugin['slug'] ) ? $plugin['slug'] : '';
			$path = isset( $plugin['path'] ) ? $plugin['path'] : '';

			if ( empty( $slug ) || empty( $path ) ) {
				continue;
			}

			$status = $this->get_plugin_status_uncapped( $path );

			if ( 'not_installed' === $status ) {
				if ( ! current_user_can( 'install_plugins' ) ) {
					// Only a *required* plugin that cannot be installed aborts the import.
					if ( ! empty( $plugin['required'] ) ) {
						$blocked = defined( 'DISALLOW_FILE_MODS' ) && DISALLOW_FILE_MODS;
						return array(
							'success' => false,
							'message' => $blocked
								? sprintf(
									/* translators: %s: plugin name. */
									__( 'Plugin installation is disabled on this site (DISALLOW_FILE_MODS), so the required plugin cannot be installed: %s. Install it manually, then re-run the import.', 'athemes-starter-sites' ),
									isset( $plugin['name'] ) ? $plugin['name'] : $slug
								)
								: sprintf(
									/* translators: %s: plugin name. */
									__( 'Insufficient permissions to install the required plugin: %s', 'athemes-starter-sites' ),
									isset( $plugin['name'] ) ? $plugin['name'] : $slug
								),
							'plugins' => $results,
						);
					}
					$results[ $path ] = $status;
					continue;
				}
				$this->install_plugin( $slug );
				$this->activate_plugin( $path );
			} elseif ( 'inactive' === $status ) {
				if ( current_user_can( 'activate_plugins' ) ) {
					activate_plugin( $path, '', false, true );
				} elseif ( ! empty( $plugin['required'] ) ) {
					return array(
						'success' => false,
						'message' => sprintf(
							/* translators: %s: plugin name. */
							__( 'Insufficient permissions to activate the required plugin: %s', 'athemes-starter-sites' ),
							isset( $plugin['name'] ) ? $plugin['name'] : $slug
						),
						'plugins' => $results,
					);
				}
			}

			do_action( 'atss_import_plugin', $slug, $path );

			$final = $this->get_plugin_status_uncapped( $path );

			// A non-required plugin that won't activate must not abort the import.
			if ( 'active' !== $final && ! empty( $plugin['required'] ) ) {
				return array(
					'success' => false,
					'message' => sprintf(
						/* translators: %s: plugin name. */
						__( 'Failed to install or activate the required plugin: %s', 'athemes-starter-sites' ),
						isset( $plugin['name'] ) ? $plugin['name'] : $slug
					),
					'plugins' => $results,
				);
			}

			$results[ $path ] = $final;
		}

		return array(
			'success' => true,
			'message' => __( 'Required plugins processed.', 'athemes-starter-sites' ),
			'plugins' => $results,
		);
	}

	/**
	 * Plugin status without the install_plugins capability gate.
	 *
	 * get_plugin_status() returns null for users lacking install_plugins (and
	 * under DISALLOW_FILE_MODS, where core maps that cap to do_not_allow),
	 * which made the sync plugins step report already-active plugins as
	 * failures. Reading install state requires no capability; capabilities are
	 * checked only when an install/activation is actually attempted.
	 *
	 * @param string $plugin_path Plugin path (folder/file.php).
	 * @return string 'not_installed' | 'active' | 'inactive'
	 */
	private function get_plugin_status_uncapped( $plugin_path ) {

		if ( ! file_exists( WP_PLUGIN_DIR . '/' . $plugin_path ) ) {
			return 'not_installed';
		}

		if ( in_array( $plugin_path, (array) get_option( 'active_plugins', array() ), true ) || is_plugin_active_for_network( $plugin_path ) ) {
			return 'active';
		}

		return 'inactive';
	}

	/**
	 * Step 3: download + import the demo XML. Non-AJAX twin of
	 * ajax_import_contents() (content_type fixed to the default — no
	 * placeholder-image substitution in a programmatic import).
	 *
	 * @param string $demo_id      Demo id.
	 * @param string $builder_type Builder slug.
	 * @return array { @type bool $success, @type string $message }
	 */
	private function run_import_contents( $demo_id, $builder_type ) {

		if ( ! isset( $this->demos[ $demo_id ]['import'][ $builder_type ]['content'] ) ) {
			return array(
				'success' => false,
				'message' => __( 'The url address of the demo content is not specified.', 'athemes-starter-sites' ),
			);
		}

		$xml_file_url  = $this->demos[ $demo_id ]['import'][ $builder_type ]['content'];
		$xml_file_path = get_transient( 'atss_importer_data' );

		if ( ! $xml_file_path || ! file_exists( $xml_file_path ) ) {

			if ( ! function_exists( 'download_url' ) ) {
				require_once ABSPATH . 'wp-admin/includes/file.php';
			}

			$temp_file = download_url( $xml_file_url, 5 );

			if ( is_wp_error( $temp_file ) ) {
				return array(
					'success' => false,
					'message' => __( 'Failed to download temporary demo xml file.', 'athemes-starter-sites' ),
				);
			}

			$file_args = array(
				'name'     => basename( $xml_file_url ),
				'tmp_name' => $temp_file,
				'error'    => 0,
				'size'     => filesize( $temp_file ),
			);

			$overrides = array(
				'test_form'   => false,
				'test_size'   => true,
				'test_upload' => true,
				'mimes'       => array(
					'xml' => 'text/xml',
				),
			);

			$download_response = wp_handle_sideload( $file_args, $overrides );

			if ( isset( $download_response['error'] ) ) {
				return array(
					'success' => false,
					'message' => __( 'Failed to download demo xml file.', 'athemes-starter-sites' ),
				);
			}

			$xml_file_path = $download_response['file'];

			set_transient( 'atss_importer_data', $xml_file_path, HOUR_IN_SECONDS );
		}

		if ( ! class_exists( 'WP_Importer' ) ) {
			if ( ! defined( 'WP_LOAD_IMPORTERS' ) ) {
				define( 'WP_LOAD_IMPORTERS', true );
			}
			require_once ABSPATH . 'wp-admin/includes/class-wp-importer.php';
		}

		require_once ATSS_PATH . 'v2/vendor/wp-content-importer-v2/WPImporterLogger.php';
		require_once ATSS_PATH . 'v2/vendor/wp-content-importer-v2/WPImporterLoggerCLI.php';
		require_once ATSS_PATH . 'v2/vendor/wp-content-importer-v2/WXRImportInfo.php';
		require_once ATSS_PATH . 'v2/vendor/wp-content-importer-v2/WXRImporter.php';
		require_once ATSS_PATH . 'v2/vendor/wp-content-importer-v2/Logger.php';

		set_time_limit( 0 );

		$this->microtime = microtime( true );

		add_filter( 'wxr_importer.pre_process.user', '__return_null' );
		add_filter( 'wxr_importer.pre_process.post', array( $this, 'set_import_post_author' ) );
		add_filter( 'wxr_importer.pre_process.post', array( $this, 'post_content_replace_attachment_urls' ) );
		add_filter( 'wxr_importer.pre_process.post_meta', array( $this, 'post_meta_replace_attachment_urls' ), 10, 2 );
		add_filter( 'wxr_importer.processed.post', array( $this, 'track_imported_post' ) );
		add_filter( 'wxr_importer.processed.term', array( $this, 'track_imported_term' ) );

		if ( class_exists( 'WooCommerce' ) ) {
			add_filter( 'wxr_importer.pre_process.term', array( $this, 'woocommerce_product_attributes_registration' ) );
			add_filter( 'add_term_metadata', array( $this, 'woocommerce_product_attributes_filter' ), 10, 5 );
		}

		$importer = new ATSS_WXRImporter( array(
			'fetch_attachments' => true,
			'default_author'    => get_current_user_id(),
		) );

		$this->importer = $importer;

		$logger_options = apply_filters( 'atss_logger_options', array(
			'logger_min_level' => 'warning',
		) );

		$logger            = new ATSS_Logger();
		$logger->min_level = $logger_options['logger_min_level'];

		$importer->set_logger( $logger );

		$wizard_state = get_option( 'atss_wizard_state', array() );
		if ( ! empty( $wizard_state['data']['pages']['selectedPages'] ) ) {
			require_once ATSS_PATH . 'v2/onboarding/includes/class-page-filter.php';
			new ATSS_Onboarding_Page_Filter();
		}

		if ( ! empty( $wizard_state['data']['contact'] ) ) {
			require_once ATSS_PATH . 'v2/onboarding/includes/class-contact-replacer.php';
			new ATSS_Onboarding_Contact_Replacer();
		}

		$importer->import( $xml_file_path );

		if ( $logger->error_output ) {
			return array(
				'success' => false,
				'message' => $logger->error_output,
			);
		}

		do_action( 'atss_import_contents' );

		return array(
			'success' => true,
			'message' => __( 'Content imported.', 'athemes-starter-sites' ),
		);
	}

	/**
	 * Step 4 (optional): import widgets. Non-AJAX twin of ajax_import_widgets()
	 * with deliberately softened return semantics — every failure path returns
	 * success=true with a note so the orchestration continues to the customizer
	 * step, matching the live flow's tolerance of widget failures.
	 *
	 * @param string $demo_id      Demo id.
	 * @param string $builder_type Builder slug.
	 * @return array { @type bool $success, @type string $message }
	 */
	private function run_import_widgets( $demo_id, $builder_type ) {

		try {
			if ( ! ini_get( 'safe_mode' ) ) {
				set_time_limit( 120 );
			}

			if ( ! isset( $this->demos[ $demo_id ]['import'][ $builder_type ]['widgets'] ) ) {
				return array(
					'success' => true,
					'message' => __( 'Widget import skipped (none specified).', 'athemes-starter-sites' ),
				);
			}

			$file_url = $this->demos[ $demo_id ]['import'][ $builder_type ]['widgets'];

			$raw = wp_remote_get(
				wp_unslash( $file_url ),
				array(
					'timeout'   => 15,
				)
			);

			if ( is_wp_error( $raw ) || 200 !== wp_remote_retrieve_response_code( $raw ) ) {
				return array(
					'success' => true,
					'message' => __( 'Widget import skipped (file unavailable).', 'athemes-starter-sites' ),
				);
			}

			try {
				$this->clean_widget_settings();
			} catch ( Exception $e ) {
				error_log( 'ATSS Widget Import: Failed to clean widget settings - ' . $e->getMessage() );
			} catch ( Error $e ) {
				error_log( 'ATSS Widget Import: Error cleaning widget settings - ' . $e->getMessage() );
			}

			$data = json_decode( wp_remote_retrieve_body( $raw ) );

			if ( empty( $data ) ) {
				return array(
					'success' => true,
					'message' => __( 'Widget import skipped (invalid data).', 'athemes-starter-sites' ),
				);
			}

			$wizard_state = get_option( 'atss_wizard_state', array() );
			if ( ! empty( $wizard_state['data']['contact'] ) ) {
				require_once ATSS_PATH . 'v2/onboarding/includes/class-contact-replacer.php';
				new ATSS_Onboarding_Contact_Replacer();
			}

			try {
				$data = map_deep( $data, array( 'Athemes_Starter_Sites_Importer', 'replace_attachment_urls' ) );
			} catch ( Exception $e ) {
				error_log( 'ATSS Widget Import: Failed to process URLs - ' . $e->getMessage() );
			} catch ( Error $e ) {
				error_log( 'ATSS Widget Import: Error processing URLs - ' . $e->getMessage() );
			}

			$widgets = new ATSS_Widget_Importer();
			$results = $widgets->import( $data );

			if ( is_wp_error( $results ) ) {
				error_log( 'ATSS Widget Import: Widget import failed - ' . $results->get_error_message() );
				return array(
					'success' => true,
					'message' => __( 'Widget import completed with errors.', 'athemes-starter-sites' ),
				);
			}

			try {
				$available_widgets = ATSS_Widget_Importer::available_widgets();
				$widget_instances  = array();

				foreach ( $available_widgets as $widget_data ) {
					if ( is_array( $widget_data ) && isset( $widget_data['id_base'] ) ) {
						$widget_instances[ $widget_data['id_base'] ] = get_option( 'widget_' . $widget_data['id_base'] );
					}
				}

				$sidebar_widgets = get_option( 'sidebars_widgets', array() );
				update_option( '_athemes_sites_imported_widgets', $sidebar_widgets, 'no' );
				do_action( 'atss_import_widgets', $sidebar_widgets, $widget_instances );
			} catch ( Exception $e ) {
				error_log( 'ATSS Widget Import: Post-import actions failed - ' . $e->getMessage() );
			} catch ( Error $e ) {
				error_log( 'ATSS Widget Import: Post-import actions error - ' . $e->getMessage() );
			}

			return array(
				'success' => true,
				'message' => __( 'Widgets imported.', 'athemes-starter-sites' ),
			);

		} catch ( Throwable $e ) {
			error_log( 'ATSS Widget Import Exception: ' . $e->getMessage() );
			return array(
				'success' => true,
				'message' => __( 'Widget import skipped (unexpected error).', 'athemes-starter-sites' ),
			);
		}
	}

	/**
	 * Step 5: import customizer settings. Non-AJAX twin of
	 * ajax_import_customizer().
	 *
	 * @param string $demo_id      Demo id.
	 * @param string $builder_type Builder slug.
	 * @return array { @type bool $success, @type string $message }
	 */
	private function run_import_customizer( $demo_id, $builder_type ) {

		try {
			if ( ! isset( $this->demos[ $demo_id ]['import'][ $builder_type ]['customizer'] ) ) {
				return array(
					'success' => false,
					'message' => __( 'The url address of the demo customizer is not specified.', 'athemes-starter-sites' ),
				);
			}

			$file_url = $this->demos[ $demo_id ]['import'][ $builder_type ]['customizer'];

			if ( ! ini_get( 'safe_mode' ) ) {
				set_time_limit( 120 );
			}

			$raw = wp_remote_get(
				wp_unslash( $file_url ),
				array(
					'timeout'   => 30,
				)
			);

			if ( is_wp_error( $raw ) ) {
				return array(
					'success' => false,
					'message' => sprintf(
						/* translators: %s: error message. */
						__( 'Failed to fetch customizer file: %s', 'athemes-starter-sites' ),
						$raw->get_error_message()
					),
				);
			}

			if ( 200 !== (int) wp_remote_retrieve_response_code( $raw ) ) {
				return array(
					'success' => false,
					'message' => sprintf(
						/* translators: %d: HTTP status code. */
						__( 'Failed to load customizer demo file. HTTP %d', 'athemes-starter-sites' ),
						(int) wp_remote_retrieve_response_code( $raw )
					),
				);
			}

			$this->clean_customizer_settings();

			$data = maybe_unserialize( wp_remote_retrieve_body( $raw ) );

			if ( empty( $data ) || ! is_array( $data ) ) {
				return array(
					'success' => false,
					'message' => __( 'Failed to parse customizer data. File may be corrupted.', 'athemes-starter-sites' ),
				);
			}

			$wizard_state = get_option( 'atss_wizard_state', array() );
			if ( ! empty( $wizard_state['data']['contact'] ) ) {
				require_once ATSS_PATH . 'v2/onboarding/includes/class-contact-replacer.php';
				new ATSS_Onboarding_Contact_Replacer();
			}

			$customizer = new ATSS_Customizer_Importer();
			$results    = $customizer->import( $data );

			if ( is_wp_error( $results ) ) {
				return array(
					'success' => false,
					'message' => sprintf(
						/* translators: %s: error message. */
						__( 'Customizer import error: %s', 'athemes-starter-sites' ),
						$results->get_error_message()
					),
				);
			}

			do_action( 'atss_import_customizer', $data );

			return array(
				'success' => true,
				'message' => __( 'Customizer imported.', 'athemes-starter-sites' ),
			);

		} catch ( Throwable $e ) {
			return array(
				'success' => false,
				'message' => sprintf(
					/* translators: 1: message, 2: file, 3: line. */
					__( 'Customizer Import Exception: %1$s in %2$s on line %3$s', 'athemes-starter-sites' ),
					$e->getMessage(),
					basename( $e->getFile() ),
					$e->getLine()
				),
			);
		}
	}

	/**
	 * Step 6: finish import (cleanup, cache, term counts, stock). Non-AJAX twin
	 * of ajax_import_finish(). Always returns success.
	 *
	 * @param string $demo_id      Demo id.
	 * @param string $builder_type Builder slug.
	 * @return array { @type bool $success, @type string $message }
	 */
	private function run_import_finish( $demo_id, $builder_type ) {

		$xml_file_path = get_transient( 'atss_importer_data' );

		if ( $xml_file_path && file_exists( $xml_file_path ) ) {
			unlink( $xml_file_path );
			delete_transient( 'atss_importer_data' );
		}

		if ( class_exists( 'Elementor\Plugin' ) ) {
			Elementor\Plugin::$instance->files_manager->clear_cache();
		}

		$nav_menu_terms = get_terms( array(
			'taxonomy'   => 'nav_menu',
			'hide_empty' => false,
			'meta_key'   => '_athemes_sites_imported_term',
		) );

		$nav_menu_term_ids = array();

		if ( ! is_wp_error( $nav_menu_terms ) && ! empty( $nav_menu_terms ) ) {
			foreach ( $nav_menu_terms as $nav_menu_term ) {
				if ( empty( $nav_menu_term->count ) ) {
					$nav_menu_term_ids[] = $nav_menu_term->term_id;
				}
			}
		}

		if ( ! empty( $nav_menu_term_ids ) ) {
			wp_update_term_count_now( $nav_menu_term_ids, 'nav_menu' );
		}

		if ( class_exists( 'WooCommerce' ) ) {
			$products = get_posts( array(
				'post_type'      => 'product',
				'posts_per_page' => -1,
				'meta_key'       => '_athemes_sites_imported_post',
			) );
			if ( ! is_wp_error( $products ) && ! empty( $products ) ) {
				foreach ( $products as $product ) {
					update_post_meta( $product->ID, '_stock_status', 'instock' );
				}
			}
			update_option( 'woocommerce_queue_flush_rewrite_rules', 'yes' );
		}

		do_action( 'atss_finish_import', $demo_id, array(), $builder_type );

		delete_option( 'atss_wizard_state' );

		return array(
			'success' => true,
			'message' => __( 'Import finished.', 'athemes-starter-sites' ),
		);
	}

}

Athemes_Starter_Sites_Importer::instance();
