<?php
/**
 * Base Importer
 *
 * Abstract base class providing shared import logic for all provider importers.
 * Shared import pipeline: detect -> import general -> import robots ->
 * import social -> persist.
 *
 * @package SEOvaultSEO\Migration
 */

namespace SEOvaultSEO\Migration;

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

abstract class BaseImporter implements ImporterInterface {

	/**
	 * Post types supported by the importer.
	 *
	 * @var array
	 */
	protected $post_types = array();

	/**
	 * Taxonomies supported by the importer.
	 *
	 * @var array
	 */
	protected $taxonomies = array();

	/**
	 * Prepared SEOvault meta for the current post or term.
	 *
	 * @var array
	 */
	protected $target_meta = array();

	/**
	 * Raw source meta for the current post or term.
	 *
	 * @var array
	 */
	protected $source_meta = array();

	/**
	 * Type of the current post or term.
	 *
	 * @var string
	 */
	protected $type = '';

	/**
	 * Constructor.
	 */
	public function __construct() {
		$this->post_types = get_post_types( array( 'public' => true ), 'names' );
		$this->taxonomies = ImporterUtils::get_public_taxonomies();
	}

	/**
	 * Get the name of the source plugin.
	 *
	 * @return string
	 */
	abstract public function get_plugin_name();

	/**
	 * Get the meta key prefix for the source plugin.
	 *
	 * @return string
	 */
	abstract protected function get_meta_key_prefix();

	/**
	 * Get meta keys to exclude from import.
	 *
	 * @return array
	 */
	abstract protected function get_excluded_meta_keys();

	/**
	 * Get not-allowed post/taxonomy types.
	 *
	 * @return array
	 */
	abstract protected function get_not_allowed_types();

	/**
	 * Get source meta data for a post or term.
	 *
	 * @param int    $id          Post or term ID.
	 * @param bool   $is_taxonomy Whether it's a taxonomy.
	 * @param string $type        Post type or taxonomy name.
	 * @return array
	 */
	abstract protected function get_source_meta_data( $id, $is_taxonomy, $type = '' );

	/**
	 * Detect whether the source plugin has data for the given post.
	 *
	 * @param int $post_id Post ID.
	 * @return array
	 */
	public function detect_post( $post_id ) {
		$meta          = get_post_meta( $post_id );
		$excluded_keys = $this->get_excluded_meta_keys();

		if ( $this->has_source_meta( $meta, $excluded_keys ) ) {
			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
		);
	}

	/**
	 * Detect whether the source plugin has data for the given term.
	 *
	 * @param int $term_id Term ID.
	 * @return array
	 */
	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 );
		}

		$meta = get_term_meta( $term_id );
		if ( $this->has_source_meta( $meta, $this->get_excluded_meta_keys() ) ) {
			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
			);
		}

		ImporterUtils::update_migration_flag( $term_id, false );

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

	/**
	 * Import data for a specific post.
	 *
	 * @param int $post_id Post ID.
	 * @return array
	 */
	public function import_post( $post_id ) {
		$post_type = get_post_type( $post_id );
		$is_valid  = ImporterUtils::is_type_valid( $post_type, $this->get_not_allowed_types(), $this->post_types );
		if ( ! $is_valid ) {
			return ImporterUtils::not_valid_response( $post_id );
		}

		$this->type        = $post_type;
		$this->target_meta = array();
		$this->source_meta = $this->get_source_meta_data( $post_id, false, $this->type );

		[ $any_success, $combine_data ] = $this->import_and_persist_meta( $post_id, false );

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

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

	/**
	 * Import data for a specific term.
	 *
	 * @param int $term_id Term ID.
	 * @return array
	 */
	public function import_term( $term_id ) {
		$term = get_term( $term_id );
		if ( ! $term || is_wp_error( $term ) ) {
			return ImporterUtils::not_valid_response( $term_id, true );
		}
		$is_valid = ImporterUtils::is_type_valid( $term->taxonomy, $this->get_not_allowed_types(), $this->taxonomies, true );
		if ( ! $is_valid ) {
			return ImporterUtils::not_valid_response( $term_id, true );
		}

		$this->type        = $term->taxonomy;
		$this->target_meta = array();
		$this->source_meta = $this->get_source_meta_data( $term_id, true, $this->type );

		[ $any_success, $combine_data ] = $this->import_and_persist_meta( $term_id, true );

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

		return ImporterUtils::build_response(
			sprintf(
				/* translators: 1: plugin name, 2: term ID */
				__( 'No %1$s fields were imported for term %2$d.', 'seovault-ai' ),
				$this->get_plugin_name(),
				$term_id
			),
			false,
			$combine_data
		);
	}

	/**
	 * Persist SEOvault settings for a post.
	 *
	 * @param int   $post_id Post ID.
	 * @param array $data    Prepared meta array.
	 * @return array
	 */
	public function update_post_meta_data( $post_id, array $data ) {
		return ImporterUtils::update_post_meta_data( $post_id, $data );
	}

	/**
	 * Persist SEOvault settings for a term.
	 *
	 * @param int   $term_id Term ID.
	 * @param array $data    Prepared meta array.
	 * @return array
	 */
	public function update_term_meta_data( $term_id, array $data ) {
		return ImporterUtils::update_term_meta_data( $term_id, $data );
	}

	/**
	 * Clean up source-plugin data after import.
	 *
	 * @return array
	 */
	public function cleanup() {
		return ImporterUtils::build_response(
			sprintf(
				/* translators: %s: plugin name */
				__( 'No clean-up required for %s data.', 'seovault-ai' ),
				$this->get_plugin_name()
			),
			true
		);
	}

	/**
	 * Get count and post IDs for batch processing.
	 *
	 * @param array $post_types  Post types.
	 * @param int   $batch_size  Batch size.
	 * @param int   $offset      Offset.
	 * @return array
	 */
	public function get_count_and_posts( $post_types, $batch_size, $offset ) {
		$meta_prefix   = $this->get_meta_key_prefix() . '%';
		$excluded_keys = $this->get_excluded_meta_keys();
		$total_items   = ImporterUtils::get_total_posts_count( $post_types, $excluded_keys, $meta_prefix );
		$post_ids      = ImporterUtils::get_post_ids( $post_types, $excluded_keys, $meta_prefix, $batch_size, $offset );

		return array(
			'total_items' => $total_items,
			'post_ids'    => $post_ids,
		);
	}

	/**
	 * Get count and term IDs for batch processing.
	 *
	 * @param array $taxonomies        Taxonomy names.
	 * @param array $taxonomies_objects Taxonomy objects.
	 * @param int   $batch_size        Batch size.
	 * @param int   $offset            Offset.
	 * @return array
	 */
	public function get_count_and_terms( $taxonomies, $taxonomies_objects, $batch_size, $offset ) {
		$meta_prefix   = $this->get_meta_key_prefix() . '%';
		$excluded_keys = $this->get_excluded_meta_keys();
		$total_items   = ImporterUtils::get_total_terms_count( $taxonomies, $excluded_keys, $meta_prefix );
		$term_ids      = ImporterUtils::get_term_ids( $taxonomies, $excluded_keys, $meta_prefix, $batch_size, $offset );

		return array(
			'total_items' => $total_items,
			'term_ids'    => $term_ids,
		);
	}

	/**
	 * Check if source meta data exists.
	 *
	 * @param array $meta          Meta data.
	 * @param array $excluded_keys Keys to exclude.
	 * @return bool
	 */
	protected function has_source_meta( $meta, array $excluded_keys = array() ) {
		if ( ! $meta || is_wp_error( $meta ) ) {
			return false;
		}
		foreach ( array_keys( $meta ) as $key ) {
			if ( str_starts_with( (string) $key, $this->get_meta_key_prefix() ) && ! in_array( $key, $excluded_keys, true ) ) {
				return true;
			}
		}
		return false;
	}

	/**
	 * Import and persist meta for a post or term.
	 *
	 * Robots: provider `import_*_meta_robots()` must only put robots_noindex /
	 * robots_nofollow on target_meta for explicit source overrides. Blank or
	 * default source robots must leave those keys unset so post-type noindex
	 * remains authoritative at runtime (Frontend\Robots).
	 *
	 * @param int  $id          Post or term ID.
	 * @param bool $is_taxonomy Whether it's a taxonomy.
	 * @return array [bool, array]
	 */
	protected function import_and_persist_meta( $id, $is_taxonomy ) {
		$general = $is_taxonomy ? $this->import_term_general_settings( $id ) : $this->import_post_general_settings( $id );
		$robots  = $is_taxonomy ? $this->import_term_meta_robots( $id ) : $this->import_post_meta_robots( $id );
		$social  = $is_taxonomy ? $this->import_term_social( $id ) : $this->import_post_social( $id );

		$persist = $is_taxonomy
			? $this->update_term_meta_data( $id, $this->target_meta )
			: $this->update_post_meta_data( $id, $this->target_meta );

		$combine_data = array(
			'general' => $general,
			'robots'  => $robots,
			'social'  => $social,
			'updated' => $persist,
		);
		$any_success  = $general['success'] || $robots['success'] || $social['success'] || $persist['success'];

		return array( $any_success, $combine_data );
	}

	/**
	 * Process a meta key mapping and populate target_meta.
	 *
	 * @param array $mapping [ source_key => target_key ].
	 * @return bool Whether any field was imported.
	 */
	protected function process_meta_mapping( array $mapping ) {
		$imported = false;

		foreach ( $mapping as $source_key => $target_key ) {
			if ( ! isset( $this->source_meta[ $source_key ] ) ) {
				continue;
			}

			$value = $this->source_meta[ $source_key ];
			if ( is_array( $value ) ) {
				$value = reset( $value );
			}

			if ( null === $value || '' === $value ) {
				continue;
			}

			$this->target_meta[ $target_key ] = $value;
			$imported                         = true;
		}

		return $imported;
	}

	/**
	 * Get a single source meta value.
	 *
	 * @param string $key     Meta key.
	 * @param mixed  $default Default value.
	 * @return mixed
	 */
	protected function get_source( $key, $default = '' ) {
		if ( ! isset( $this->source_meta[ $key ] ) ) {
			return $default;
		}
		$value = $this->source_meta[ $key ];
		if ( is_array( $value ) ) {
			$value = reset( $value );
		}
		return ( null === $value || '' === $value ) ? $default : $value;
	}
}
