<?php
/**
 * @fs_premium_only
 *
 * Abstract base class for "target CPT presets" — one-click configurations
 * that map common feed fields onto the meta keys and taxonomies of a
 * specific third-party plugin or theme (Inventory Presser, Motors, etc.).
 *
 * The free version ships universal CPT targeting without presets — users
 * map each feed field by hand. Pro users get presets that auto-fill the
 * mapping for known dealership tooling, eliminating the "I have to read
 * the other plugin's documentation to know which meta keys to use" step.
 *
 * @since 2.5.0
 * @package AutomotiveFeedImport
 */

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

abstract class AFI_Preset_Base
{
	/**
	 * Unique, stable identifier for this preset. Used as the form value
	 * when the user clicks "Apply preset" and to deduplicate registrations.
	 * Use lower-snake-case, e.g. 'inventory_presser'.
	 */
	abstract public function get_id();

	/**
	 * Human-readable name shown in the UI ("Inventory Presser",
	 * "Motors (StyleMix)", etc.).
	 */
	abstract public function get_label();

	/**
	 * Post type slug this preset writes into.
	 */
	abstract public function get_target_cpt();

	/**
	 * Meta-field mapping rules.
	 *
	 * Returns an array of rule entries. Each entry tells the apply step:
	 * "if the user's feed has a field whose name matches one of these
	 * synonyms, set its mapping target to this meta_key on the host CPT."
	 *
	 * Shape:
	 *   [
	 *     [
	 *       'synonyms' => ['stocknumber', 'stockno', 'stock_no', 'inv_id'],
	 *       'target'   => 'inventory_presser_stock_number',
	 *     ],
	 *     ...
	 *   ]
	 *
	 * Synonyms are matched case-insensitively after stripping non-alphanumeric
	 * characters. Order matters — the first matching rule wins.
	 *
	 * @return array<int, array{synonyms: string[], target: string}>
	 */
	abstract public function get_meta_map();

	/**
	 * Taxonomy mapping rules.
	 *
	 * Same shape as get_meta_map() but the 'target' is a taxonomy slug.
	 * When applied, the field's mapping target becomes "tax:<slug>" so
	 * update_inventory() routes the value to wp_set_object_terms.
	 *
	 * @return array<int, array{synonyms: string[], taxonomy: string}>
	 */
	abstract public function get_taxonomy_map();

	/**
	 * Detect whether the host plugin/theme this preset targets is active
	 * on the current site. Used to badge the preset in the UI as "ready
	 * to apply" vs. "host plugin not detected".
	 *
	 * Default checks for the target CPT being registered; subclasses can
	 * additionally check for a host-plugin function or class to be more
	 * specific.
	 *
	 * @return bool
	 */
	public function detect()
	{
		return post_type_exists($this->get_target_cpt());
	}

	/**
	 * Adapt the gallery / featured image write step for the host plugin's
	 * conventions. Default behaviour (no-op) is fine for hosts that use
	 * standard WordPress featured images and post attachments. Override
	 * for hosts that expect a specific serialized meta format or numbered
	 * attachment scheme.
	 *
	 * Called after the importer has finished its own image work for the
	 * post; receives the post ID and the list of attachment IDs that were
	 * imported for the vehicle.
	 *
	 * @param int   $post_id        Post receiving the gallery.
	 * @param int[] $attachment_ids Attachment IDs in display order.
	 */
	public function adapt_gallery($post_id, array $attachment_ids)
	{
		// No-op by default.
	}

	/**
	 * Given the list of feed-field names detected for the user's feed,
	 * build a field_mappings array suitable for storing under this
	 * preset's target CPT. Feed fields whose names don't match any rule
	 * keep their 1:1 default mapping so the user can adjust by hand.
	 *
	 * @param string[] $feed_fields Field names detected in the user's feed.
	 * @return array<string, array{enabled: bool, target: string}>
	 */
	public function build_field_mappings(array $feed_fields)
	{
		$meta_rules = $this->get_meta_map();
		$tax_rules  = $this->get_taxonomy_map();
		$result     = array();

		foreach ($feed_fields as $field) {
			$normalised = $this->normalise($field);
			$target     = $field; // default 1:1

			// Taxonomy rules are checked first so categorical fields like
			// Make/Model become taxonomy terms (which is what host plugins
			// actually use for filterable archives) rather than meta.
			foreach ($tax_rules as $rule) {
				if ($this->matches_synonym($normalised, $rule['synonyms'])) {
					$target = 'tax:' . $rule['taxonomy'];
					break 1;
				}
			}

			if ($target === $field) {
				foreach ($meta_rules as $rule) {
					if ($this->matches_synonym($normalised, $rule['synonyms'])) {
						$target = $rule['target'];
						break 1;
					}
				}
			}

			$result[$field] = array(
				'enabled' => true,
				'target'  => $target,
			);
		}

		return $result;
	}

	/**
	 * Normalise a string for synonym matching: lowercase and stripped of
	 * everything except a-z0-9. "Stock #" → "stock", "Model Year" → "modelyear".
	 */
	protected function normalise($value)
	{
		$value = is_string($value) ? $value : '';
		return preg_replace('/[^a-z0-9]/', '', strtolower($value));
	}

	/**
	 * Does this normalised feed field match any of the supplied synonyms?
	 * Synonyms are normalised the same way before comparison.
	 */
	protected function matches_synonym($normalised, array $synonyms)
	{
		foreach ($synonyms as $syn) {
			if ($normalised === $this->normalise($syn)) {
				return true;
			}
		}
		return false;
	}
}
