<?php
/**
 * AIOSEO Importer
 *
 * Imports SEO meta from All in One SEO into SEOvault.
 * AIOSEO v4+ stores per-post/term data in aioseo_posts / aioseo_terms tables.
 *
 * @package SEOvaultSEO\Migration\Providers
 */

namespace SEOvaultSEO\Migration\Providers;

use SEOvaultSEO\Compatibility\Detector;
use SEOvaultSEO\Migration\BaseImporter;
use SEOvaultSEO\Migration\ImporterUtils;

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

class AIOSEO extends BaseImporter {

	const PLUGIN_NAME = 'All in One SEO';
	const PLUGIN_FILE = 'all-in-one-seo-pack/all_in_one_seo_pack.php';
	const META_PREFIX = '_aioseo_';

	const EXCLUDED_KEYS = array(
		'_aioseo_seo_score',
		'_aioseo_seo_score_history',
	);

	const NOT_ALLOWED_TYPES = array(
		'elementor_library',
		'product_shipping_class',
		'aioseo-location',
	);

	const LEGACY_GENERAL_MAPPING = array(
		'_aioseop_title'       => 'seo_title',
		'_aioseop_description' => 'seo_description',
		'_aioseop_custom_link' => 'canonical_url',
		'_aioseop_keywords'    => 'focus_keyword',
	);

	const POSTMETA_GENERAL_MAPPING = array(
		'_aioseo_title'         => 'seo_title',
		'_aioseo_description'   => 'seo_description',
		'_aioseo_canonical_url' => 'canonical_url',
		'_aioseo_keywords'      => 'focus_keyword',
	);

	const POSTMETA_SOCIAL_MAPPING = array(
		'_aioseo_og_title'       => 'og_title',
		'_aioseo_og_description' => 'og_description',
		'_aioseo_og_image'       => 'og_image',
	);

	const PLACEHOLDERS = array(
		'#site_title'           => '%site_name%',
		'#tagline'              => '%site_description%',
		'#separator_sa'         => '-',
		'#post_title'           => '%post_title%',
		'#post_excerpt'         => '%post_excerpt%',
		'#post_excerpt_only'    => '%post_excerpt%',
		'#post_content'         => '%post_excerpt%',
		'#post_date'            => '%published_date%',
		'#author_name'          => '%author_name%',
		'#taxonomy_title'       => '%term_name%',
		'#taxonomy_description' => '%term_description%',
		'#category_title'       => '%term_name%',
		'#category_description' => '%term_description%',
		'#tag_title'            => '%term_name%',
		'#current_year'         => '%current_year%',
		'#current_date'         => '%current_date%',
		'#current_day'          => '%current_day%',
		'#current_month'        => '%current_month%',
		'#page_number'          => '%page%',
	);

	/**
	 * Memoized custom table existence.
	 *
	 * @var array{posts: bool, terms: bool}|null
	 */
	private static $tables_exist_cache = null;

	public function get_plugin_name() {
		return self::PLUGIN_NAME;
	}

	public function get_plugin_file() {
		if ( defined( 'AIOSEO_FILE' ) ) {
			return plugin_basename( AIOSEO_FILE );
		}

		return self::PLUGIN_FILE;
	}

	public function is_plugin_active() {
		$real_active = defined( 'AIOSEO_VERSION' ) || function_exists( 'aioseo' ) || class_exists( 'All_in_One_SEO_Pack' ) || defined( 'AIOSEOP_VERSION' );

		return Detector::is_migration_plugin_active( 'all-in-one-seo-pack', $real_active );
	}

	protected function get_meta_key_prefix() {
		return self::META_PREFIX;
	}

	protected function get_excluded_meta_keys() {
		return self::EXCLUDED_KEYS;
	}

	protected function get_not_allowed_types() {
		return self::NOT_ALLOWED_TYPES;
	}

	protected function get_source_meta_data( $id, $is_taxonomy, $type = '' ) {
		return $this->get_aioseo_meta_data( $id, $is_taxonomy );
	}

	public function detect_post( $post_id ) {
		$tables = $this->tables_exist();

		if ( $tables['posts'] ) {
			global $wpdb;

			// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- AIOSEO table name from $wpdb->prefix.
			$exists = $wpdb->get_var(
				$wpdb->prepare(
					"SELECT post_id FROM {$wpdb->prefix}aioseo_posts WHERE post_id = %d",
					$post_id
				)
			);
			// phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared

			if ( $exists ) {
				return ImporterUtils::build_response(
					sprintf(
						/* translators: 1: plugin name, 2: post ID */
						__( '%1$s data detected for post %2$d.', 'seovault-ai' ),
						$this->get_plugin_name(),
						$post_id
					),
					true
				);
			}
		}

		$meta    = get_post_meta( $post_id );
		$has_new = false;
		$has_old = false;

		foreach ( array_keys( $meta ) as $key ) {
			if ( str_starts_with( $key, '_aioseo_' ) && ! in_array( $key, self::EXCLUDED_KEYS, true ) ) {
				$has_new = true;
			}
			if ( str_starts_with( $key, '_aioseop_' ) ) {
				$has_old = true;
			}
		}

		if ( $has_new || $has_old ) {
			return ImporterUtils::build_response(
				sprintf(
					/* translators: 1: plugin name, 2: post ID */
					__( '%1$s data detected for post %2$d.', 'seovault-ai' ),
					$this->get_plugin_name(),
					$post_id
				),
				true
			);
		}

		ImporterUtils::update_migration_flag( $post_id );

		return ImporterUtils::build_response(
			sprintf(
				/* translators: 1: plugin name, 2: post ID */
				__( 'No %1$s data found for post %2$d.', 'seovault-ai' ),
				$this->get_plugin_name(),
				$post_id
			),
			false,
			array(),
			true
		);
	}

	public function detect_term( $term_id ) {
		$term = get_term( $term_id );
		if ( ! $term || is_wp_error( $term ) ) {
			return ImporterUtils::not_valid_response( $term_id, true );
		}

		$tables = $this->tables_exist();

		if ( $tables['terms'] ) {
			global $wpdb;

			// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- AIOSEO table name from $wpdb->prefix.
			$exists = $wpdb->get_var(
				$wpdb->prepare(
					"SELECT term_id FROM {$wpdb->prefix}aioseo_terms WHERE term_id = %d",
					$term_id
				)
			);
			// phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared

			if ( $exists ) {
				return ImporterUtils::build_response(
					sprintf(
						/* translators: 1: plugin name, 2: term ID */
						__( '%1$s data detected for term %2$d.', 'seovault-ai' ),
						$this->get_plugin_name(),
						$term_id
					),
					true
				);
			}
		}

		return parent::detect_term( $term_id );
	}

	public function get_count_and_posts( $post_types, $batch_size, $offset ) {
		$tables = $this->tables_exist();

		if ( ! $tables['posts'] ) {
			return parent::get_count_and_posts( $post_types, $batch_size, $offset );
		}

		global $wpdb;

		$placeholders      = implode( ',', array_fill( 0, count( $post_types ), '%s' ) );
		$post_types_values = array_values( $post_types );
		$migration_flag    = ImporterUtils::MIGRATION_FLAG;

		// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter, WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber -- IN placeholders built from post types; values passed to prepare().
		$total_query = $wpdb->prepare(
			"SELECT COUNT(DISTINCT ap.post_id)
			FROM {$wpdb->prefix}aioseo_posts ap
			INNER JOIN {$wpdb->posts} p ON ap.post_id = p.ID
			LEFT JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id AND pm.meta_key = %s
			WHERE p.post_type IN ({$placeholders})
			AND p.post_status != 'auto-draft'
			AND pm.meta_id IS NULL",
			array_merge( array( $migration_flag ), $post_types_values )
		);

		$total_items = (int) $wpdb->get_var( $total_query );

		$ids_query = $wpdb->prepare(
			"SELECT DISTINCT ap.post_id
			FROM {$wpdb->prefix}aioseo_posts ap
			INNER JOIN {$wpdb->posts} p ON ap.post_id = p.ID
			LEFT JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id AND pm.meta_key = %s
			WHERE p.post_type IN ({$placeholders})
			AND p.post_status != 'auto-draft'
			AND pm.meta_id IS NULL
			ORDER BY ap.post_id
			LIMIT %d OFFSET %d",
			array_merge( array( $migration_flag ), $post_types_values, array( $batch_size, $offset ) )
		);
		// phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter, WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber

		// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- $ids_query is prepared above.
		$post_ids = $wpdb->get_col( $ids_query );

		return array(
			'total_items' => $total_items,
			'post_ids'    => array_map( 'intval', $post_ids ? $post_ids : array() ),
		);
	}

	public function get_count_and_terms( $taxonomies, $taxonomies_objects, $batch_size, $offset ) {
		$tables = $this->tables_exist();

		if ( ! $tables['terms'] ) {
			return parent::get_count_and_terms( $taxonomies, $taxonomies_objects, $batch_size, $offset );
		}

		global $wpdb;

		$taxonomies_values = array_values( $taxonomies );
		$placeholders      = implode( ',', array_fill( 0, count( $taxonomies_values ), '%s' ) );
		$migration_flag    = ImporterUtils::MIGRATION_FLAG;

		// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter, WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber -- IN placeholders built from taxonomies; values passed to prepare().
		$total_query = $wpdb->prepare(
			"SELECT COUNT(DISTINCT at.term_id)
			FROM {$wpdb->prefix}aioseo_terms at
			INNER JOIN {$wpdb->term_taxonomy} tt ON at.term_id = tt.term_id
			LEFT JOIN {$wpdb->termmeta} tm ON at.term_id = tm.term_id AND tm.meta_key = %s
			WHERE tt.taxonomy IN ({$placeholders})
			AND tm.meta_id IS NULL",
			array_merge( array( $migration_flag ), $taxonomies_values )
		);

		$total_items = (int) $wpdb->get_var( $total_query );

		$ids_query = $wpdb->prepare(
			"SELECT DISTINCT at.term_id
			FROM {$wpdb->prefix}aioseo_terms at
			INNER JOIN {$wpdb->term_taxonomy} tt ON at.term_id = tt.term_id
			LEFT JOIN {$wpdb->termmeta} tm ON at.term_id = tm.term_id AND tm.meta_key = %s
			WHERE tt.taxonomy IN ({$placeholders})
			AND tm.meta_id IS NULL
			ORDER BY at.term_id
			LIMIT %d OFFSET %d",
			array_merge( array( $migration_flag ), $taxonomies_values, array( $batch_size, $offset ) )
		);
		// phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter, WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber

		// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- $ids_query is prepared above.
		$term_ids = $wpdb->get_col( $ids_query );

		return array(
			'total_items' => $total_items,
			'term_ids'    => array_map( 'intval', $term_ids ? $term_ids : array() ),
		);
	}

	public function import_post_general_settings( $post_id ) {
		return $this->import_general_settings( $post_id, 'post', true );
	}

	public function import_term_general_settings( $term_id ) {
		return $this->import_general_settings( $term_id, 'term', false );
	}

	public function import_post_meta_robots( $post_id ) {
		return $this->import_robots_settings( $post_id, 'post' );
	}

	public function import_term_meta_robots( $term_id ) {
		return $this->import_robots_settings( $term_id, 'term' );
	}

	public function import_post_social( $post_id ) {
		return $this->import_social_settings( $post_id, 'post' );
	}

	public function import_term_social( $term_id ) {
		return $this->import_social_settings( $term_id, 'term' );
	}

	public function import_global_settings() {
		$options = $this->get_aioseo_options();

		if ( empty( $options ) ) {
			return ImporterUtils::build_response(
				__( 'No global settings found to import from AIOSEO.', 'seovault-ai' ),
				false
			);
		}

		$imported  = false;
		$global    = $options['searchAppearance']['global'] ?? array();
		$separator = $global['separator'] ?? '-';

		if ( ! empty( $global['siteTitle'] ) ) {
			\SEOvaultSEO\Core\Options::set_home_title(
				$this->replace_placeholders( (string) $global['siteTitle'], $separator )
			);
			$imported = true;
		}
		if ( ! empty( $global['metaDescription'] ) ) {
			\SEOvaultSEO\Core\Options::set_home_description(
				$this->replace_placeholders( (string) $global['metaDescription'], $separator )
			);
			$imported = true;
		}

		$dynamic    = $this->get_aioseo_dynamic_options();
		$post_types = $dynamic['searchAppearance']['postTypes'] ?? array();
		foreach ( $post_types as $config ) {
			if ( ! is_array( $config ) || empty( $config['title'] ) ) {
				continue;
			}
			\SEOvaultSEO\Core\Options::set_title_template(
				$this->replace_placeholders( (string) $config['title'], $separator )
			);
			$imported = true;
			break;
		}

		$profiles = $options['social']['profiles']['urls'] ?? array();
		if ( ! empty( $profiles['facebookPageUrl'] ) ) {
			\SEOvaultSEO\Core\Options::set( 'facebook_page_url', $profiles['facebookPageUrl'] );
			$imported = true;
		}
		if ( ! empty( $profiles['twitterUrl'] ) ) {
			\SEOvaultSEO\Core\Options::set(
				'twitter_username',
				$this->extract_twitter_username( (string) $profiles['twitterUrl'] )
			);
			$imported = true;
		}

		return ImporterUtils::build_response(
			$imported
				? __( 'Global settings imported from AIOSEO.', 'seovault-ai' )
				: __( 'No global settings found to import from AIOSEO.', 'seovault-ai' ),
			$imported
		);
	}

	private function import_general_settings( $object_id, $object_kind, $import_focus ) {
		$imported  = false;
		$separator = $this->get_separator();

		$title = (string) ( $this->source_meta['title'] ?? '' );
		if ( '' !== $title ) {
			$this->target_meta['seo_title'] = $this->replace_placeholders( $title, $separator );
			$imported                       = true;
		}

		$description = (string) ( $this->source_meta['description'] ?? '' );
		if ( '' !== $description ) {
			$this->target_meta['seo_description'] = $this->replace_placeholders( $description, $separator );
			$imported                             = true;
		}

		if ( ! $imported ) {
			$imported = $this->process_meta_mapping( self::POSTMETA_GENERAL_MAPPING );
			if ( ! $imported ) {
				$imported = $this->process_meta_mapping( self::LEGACY_GENERAL_MAPPING );
			}
			if ( $imported && isset( $this->target_meta['seo_title'] ) ) {
				$this->target_meta['seo_title'] = $this->replace_placeholders( $this->target_meta['seo_title'], $separator );
			}
			if ( $imported && isset( $this->target_meta['seo_description'] ) ) {
				$this->target_meta['seo_description'] = $this->replace_placeholders( $this->target_meta['seo_description'], $separator );
			}
		}

		$canonical = (string) ( $this->source_meta['canonical_url'] ?? '' );
		if ( '' === $canonical ) {
			$canonical = (string) $this->get_source( '_aioseo_canonical_url', '' );
		}
		if ( '' === $canonical ) {
			$canonical = (string) $this->get_source( '_aioseop_custom_link', '' );
		}
		$canonical = esc_url_raw( $canonical );
		if ( '' !== $canonical ) {
			$this->target_meta['canonical_url'] = $canonical;
			$imported                           = true;
		}

		if ( $import_focus ) {
			$focus_keyword = $this->extract_focus_keyword();
			if ( '' !== $focus_keyword ) {
				$this->target_meta['focus_keyword'] = $focus_keyword;
				$imported                           = true;
			}
		}

		return ImporterUtils::build_response(
			ImporterUtils::import_status_message( $imported, $object_id, $object_kind, 'general' ),
			$imported
		);
	}

	private function import_robots_settings( $object_id, $object_kind ) {
		$imported = false;

		if ( empty( $this->source_meta['robots_default'] ) ) {
			if ( ! empty( $this->source_meta['robots_noindex'] ) ) {
				$this->target_meta['robots_noindex'] = true;
				$imported                            = true;
			}
			if ( ! empty( $this->source_meta['robots_nofollow'] ) ) {
				$this->target_meta['robots_nofollow'] = true;
				$imported                             = true;
			}
		}

		$noindex  = $this->get_source( '_aioseo_noindex', '' );
		$noindex2 = $this->get_source( '_aioseop_noindex', '' );
		if ( 'on' === $noindex || '1' === $noindex || 'on' === $noindex2 ) {
			$this->target_meta['robots_noindex'] = true;
			$imported                            = true;
		}

		$nofollow  = $this->get_source( '_aioseo_nofollow', '' );
		$nofollow2 = $this->get_source( '_aioseop_nofollow', '' );
		if ( 'on' === $nofollow || '1' === $nofollow || 'on' === $nofollow2 ) {
			$this->target_meta['robots_nofollow'] = true;
			$imported                             = true;
		}

		return ImporterUtils::build_response(
			ImporterUtils::import_status_message( $imported, $object_id, $object_kind, 'meta_robots' ),
			$imported
		);
	}

	private function import_social_settings( $object_id, $object_kind ) {
		$imported  = false;
		$separator = $this->get_separator();

		$og_title = (string) ( $this->source_meta['og_title'] ?? '' );
		if ( '' !== $og_title ) {
			$this->target_meta['og_title'] = $this->replace_placeholders( $og_title, $separator );
			$imported                      = true;
		}

		$og_description = (string) ( $this->source_meta['og_description'] ?? '' );
		if ( '' !== $og_description ) {
			$this->target_meta['og_description'] = $this->replace_placeholders( $og_description, $separator );
			$imported                            = true;
		}

		if ( ! $imported ) {
			$imported = $this->process_meta_mapping( self::POSTMETA_SOCIAL_MAPPING );
		}

		$og_image = esc_url_raw( (string) ( $this->source_meta['og_image_url'] ?? '' ) );
		if ( '' === $og_image ) {
			$og_image = esc_url_raw( (string) ( $this->source_meta['og_image_custom_url'] ?? '' ) );
		}
		if ( '' === $og_image ) {
			$og_image = esc_url_raw( (string) $this->get_source( '_aioseo_og_image', '' ) );
		}
		if ( '' !== $og_image ) {
			$this->target_meta['og_image'] = $og_image;
			$imported                      = true;
		}

		return ImporterUtils::build_response(
			ImporterUtils::import_status_message( $imported, $object_id, $object_kind, 'social' ),
			$imported
		);
	}

	private function get_aioseo_meta_data( $id, $is_taxonomy ) {
		global $wpdb;

		$meta_data = array();
		$tables    = $this->tables_exist();
		$table_key = $is_taxonomy ? 'terms' : 'posts';

		if ( $tables[ $table_key ] ) {
			$table_name = $is_taxonomy ? $wpdb->prefix . 'aioseo_terms' : $wpdb->prefix . 'aioseo_posts';
			$id_column  = $is_taxonomy ? 'term_id' : 'post_id';

			// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Migration read of third-party AIOSEO tables. $table_name derived from $wpdb->prefix + known suffix.
			$row = $wpdb->get_row(
				$wpdb->prepare(
					"SELECT * FROM {$table_name} WHERE {$id_column} = %d", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Table/column names are derived from $wpdb->prefix and fixed keys.
					$id
				),
				ARRAY_A
			);
			// phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching

			if ( is_array( $row ) ) {
				$meta_data = $row;
			}
		}

		$raw = $is_taxonomy ? get_term_meta( $id ) : get_post_meta( $id );
		if ( is_array( $raw ) ) {
			foreach ( $raw as $key => $value ) {
				if (
					( str_starts_with( $key, '_aioseo_' ) || str_starts_with( $key, '_aioseop_' ) )
					&& ! isset( $meta_data[ $key ] )
				) {
					$meta_data[ $key ] = is_array( $value ) ? reset( $value ) : $value;
				}
			}
		}

		return $meta_data;
	}

	private function tables_exist() {
		if ( null !== self::$tables_exist_cache ) {
			return self::$tables_exist_cache;
		}

		global $wpdb;

		// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- SHOW TABLES uses prepare() with prefix-derived names.
		$posts_table = $wpdb->get_var(
			$wpdb->prepare( 'SHOW TABLES LIKE %s', $wpdb->prefix . 'aioseo_posts' )
		);
		$terms_table = $wpdb->get_var(
			$wpdb->prepare( 'SHOW TABLES LIKE %s', $wpdb->prefix . 'aioseo_terms' )
		);
		// phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching

		self::$tables_exist_cache = array(
			'posts' => ! empty( $posts_table ),
			'terms' => ! empty( $terms_table ),
		);

		return self::$tables_exist_cache;
	}

	private function get_aioseo_options() {
		$raw = get_option( 'aioseo_options', '' );
		if ( empty( $raw ) ) {
			return array();
		}

		$decoded = is_string( $raw ) ? json_decode( $raw, true ) : $raw;

		return is_array( $decoded ) ? $decoded : array();
	}

	private function get_aioseo_dynamic_options() {
		$raw = get_option( 'aioseo_options_dynamic', '' );
		if ( empty( $raw ) ) {
			return array();
		}

		$decoded = is_string( $raw ) ? json_decode( $raw, true ) : $raw;

		return is_array( $decoded ) ? $decoded : array();
	}

	private function extract_focus_keyword() {
		if ( ! empty( $this->source_meta['keyphrases'] ) && is_string( $this->source_meta['keyphrases'] ) ) {
			$keyphrases = json_decode( $this->source_meta['keyphrases'], true );
			if ( is_array( $keyphrases ) && ! empty( $keyphrases['focus']['keyphrase'] ) ) {
				return trim( (string) $keyphrases['focus']['keyphrase'] );
			}
		}

		$keyword = ImporterUtils::get_primary_keyword( $this->get_source( '_aioseo_keywords', '' ) );
		if ( '' !== $keyword ) {
			return $keyword;
		}

		return ImporterUtils::get_primary_keyword( $this->get_source( '_aioseop_keywords', '' ) );
	}

	private function get_separator() {
		$options = $this->get_aioseo_options();

		if ( ! empty( $options['searchAppearance']['global']['separator'] ) ) {
			return (string) $options['searchAppearance']['global']['separator'];
		}

		return '-';
	}

	private function replace_placeholders( $value, $separator = null ) {
		if ( ! is_string( $value ) ) {
			return (string) $value;
		}

		$placeholders                  = self::PLACEHOLDERS;
		$placeholders['#separator_sa'] = null !== $separator ? $separator : $this->get_separator();

		return str_replace( array_keys( $placeholders ), array_values( $placeholders ), $value );
	}

	private function extract_twitter_username( $value ) {
		$value = trim( $value );
		if ( '' === $value ) {
			return '';
		}

		if ( str_contains( $value, 'twitter.com' ) || str_contains( $value, 'x.com' ) ) {
			$path = wp_parse_url( $value, PHP_URL_PATH );
			if ( is_string( $path ) ) {
				$handle = trim( $path, '/' );
				if ( '' !== $handle ) {
					return ltrim( $handle, '@' );
				}
			}
		}

		return ltrim( $value, '@' );
	}
}
