<?php
/**
 * FluentCRM Integration
 *
 * Handles FluentCRM contact synchronization with bulk import support
 *
 * @since 0.0.1
 *
 * @package SureContact
 */

namespace SureContact\Integrations;

use SureContact\Logger;
use SureContact\Field_Formatter;

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

/**
 * Class FluentCRM_Integration
 *
 * Integrates FluentCRM contacts with SureContact, supporting bulk import for large datasets
 *
 * @since 0.0.1
 */
class FluentCRM_Integration extends Base_Integration {

	/**
	 * FluentCRM Contact Sync handler.
	 *
	 * @since 1.2.0
	 *
	 * @var FluentCRM_Contact_Sync
	 */
	private $contact_sync;

	/**
	 * Constructor
	 *
	 * @since 0.0.1
	 */
	public function __construct() {
		$this->slug                  = 'fluentcrm';
		$this->name                  = 'FluentCRM';
		$this->description           = __( 'Sync FluentCRM contacts with bulk import support for large datasets', 'surecontact' );
		$this->docs_url              = '';
		$this->require_field_mapping = true;
		$this->dependency            = 'FluentCrm\App\Models\Subscriber';

		parent::__construct();

		// Initialize sync handler (registers with Bulk_Sync_Service).
		$this->contact_sync = new FluentCRM_Contact_Sync( $this, $this->contact_service );

		// Register sync type.
		add_filter( 'surecontact_available_sync_types', array( $this, 'register_sync_type' ) );
	}

	/**
	 * Initialize integration hooks
	 *
	 * @since 0.0.1
	 */
	protected function init() {

		// Real-time sync hooks for FluentCRM contact events.
		add_action( 'fluent_crm/contact_created', array( $this, 'handle_contact_created' ), 10, 1 );
		add_action( 'fluent_crm/contact_updated', array( $this, 'handle_contact_updated' ), 10, 2 );
		add_action( 'fluent_crm/contact_email_changed', array( $this, 'handle_contact_email_changed' ), 10, 2 );

		// Hooks for when lists/tags are added or removed from contacts.
		add_action( 'fluentcrm_contact_added_to_lists', array( $this, 'handle_lists_attached' ), 10, 2 );
		add_action( 'fluentcrm_contact_removed_from_lists', array( $this, 'handle_lists_detached' ), 10, 2 );
		add_action( 'fluentcrm_contact_added_to_tags', array( $this, 'handle_tags_attached' ), 10, 2 );
		add_action( 'fluentcrm_contact_removed_from_tags', array( $this, 'handle_tags_detached' ), 10, 2 );
	}

	/**
	 * Add FluentCRM field group
	 *
	 * @since 0.0.1
	 *
	 * @param array $groups Existing field groups.
	 * @return array Modified field groups
	 */
	public function add_meta_field_group( $groups ) {
		$groups['fluentcrm'] = array(
			'title' => __( 'FluentCRM', 'surecontact' ),
			'url'   => 'https://fluentcrm.com/docs/',
		);

		return $groups;
	}

	/**
	 * Add FluentCRM-specific fields
	 *
	 * @since 0.0.1
	 *
	 * @param array $fields Existing meta fields.
	 * @return array Modified meta fields
	 */
	public function add_meta_fields( $fields ) {
		// FluentCRM contact fields.
		$fluentcrm_fields = array(
			'fc_prefix'          => array(
				'label' => __( 'Name Prefix', 'surecontact' ),
				'type'  => 'text',
			),
			'fc_first_name'      => array(
				'label' => __( 'First Name', 'surecontact' ),
				'type'  => 'text',
			),
			'fc_last_name'       => array(
				'label' => __( 'Last Name', 'surecontact' ),
				'type'  => 'text',
			),
			'fc_email'           => array(
				'label' => __( 'Email', 'surecontact' ),
				'type'  => 'email',
			),
			'fc_phone'           => array(
				'label' => __( 'Phone', 'surecontact' ),
				'type'  => 'phone',
			),
			'fc_address_line_1'  => array(
				'label' => __( 'Address Line 1', 'surecontact' ),
				'type'  => 'text',
			),
			'fc_address_line_2'  => array(
				'label' => __( 'Address Line 2', 'surecontact' ),
				'type'  => 'text',
			),
			'fc_city'            => array(
				'label' => __( 'City', 'surecontact' ),
				'type'  => 'text',
			),
			'fc_state'           => array(
				'label' => __( 'State', 'surecontact' ),
				'type'  => 'text',
			),
			'fc_postal_code'     => array(
				'label' => __( 'Postal Code', 'surecontact' ),
				'type'  => 'text',
			),
			'fc_country'         => array(
				'label' => __( 'Country', 'surecontact' ),
				'type'  => 'text',
			),
			'fc_timezone'        => array(
				'label' => __( 'Timezone', 'surecontact' ),
				'type'  => 'text',
			),
			'fc_date_of_birth'   => array(
				'label' => __( 'Date of Birth', 'surecontact' ),
				'type'  => 'date',
			),
			'fc_status'          => array(
				'label' => __( 'Contact Status', 'surecontact' ),
				'type'  => 'text',
			),
			'fc_contact_type'    => array(
				'label' => __( 'Contact Type', 'surecontact' ),
				'type'  => 'text',
			),
			'fc_source'          => array(
				'label' => __( 'Source', 'surecontact' ),
				'type'  => 'text',
			),
			'fc_life_time_value' => array(
				'label' => __( 'Life Time Value', 'surecontact' ),
				'type'  => 'decimal',
			),
			'fc_total_points'    => array(
				'label' => __( 'Total Points', 'surecontact' ),
				'type'  => 'integer',
			),
			'fc_created_at'      => array(
				'label' => __( 'Contact Created Date', 'surecontact' ),
				'type'  => 'datetime',
			),
			'fc_last_activity'   => array(
				'label' => __( 'Last Activity Date', 'surecontact' ),
				'type'  => 'datetime',
			),
			'fc_updated_at'      => array(
				'label' => __( 'Contact Updated Date', 'surecontact' ),
				'type'  => 'datetime',
			),
		);

		// Add group to all fields.
		foreach ( $fluentcrm_fields as $key => &$config ) {
			$config['group'] = 'fluentcrm';
			$fields[ $key ]  = $config;
		}
		unset( $config );

		return $fields;
	}

	/**
	 * Get integration-specific settings fields
	 *
	 * @since 0.0.1
	 *
	 * @return array Settings fields configuration
	 */
	public function get_settings_fields() {
		$settings = array(
			'enable_realtime_sync' => array(
				'label'       => __( 'Enable Real-time Sync', 'surecontact' ),
				'description' => __( 'Automatically sync FluentCRM contacts when they are created or updated', 'surecontact' ),
				'type'        => 'checkbox',
				'default'     => true,
			),
			'sync_status_filter'   => array(
				'label'       => __( 'Sync Only Subscribed Contacts', 'surecontact' ),
				'description' => __( 'Only sync contacts with "subscribed" status (recommended)', 'surecontact' ),
				'type'        => 'checkbox',
				'default'     => true,
			),
			'sync_lists'           => array(
				'label'       => __( 'Sync Lists', 'surecontact' ),
				'description' => __( 'Sync FluentCRM lists assigned to each contact (applies to both real-time and bulk sync)', 'surecontact' ),
				'type'        => 'checkbox',
				'default'     => true,
			),
			'sync_tags'            => array(
				'label'       => __( 'Sync Tags', 'surecontact' ),
				'description' => __( 'Sync FluentCRM tags assigned to each contact (applies to both real-time and bulk sync)', 'surecontact' ),
				'type'        => 'checkbox',
				'default'     => true,
			),
			'sync_custom_fields'   => array(
				'label'       => __( 'Sync Custom Fields', 'surecontact' ),
				'description' => __( 'Sync FluentCRM custom fields from each contact (applies to both real-time and bulk sync)', 'surecontact' ),
				'type'        => 'checkbox',
				'default'     => true,
			),
			'bulk_sync_lists'      => array(
				'label'       => __( 'Add to Lists', 'surecontact' ),
				'description' => __( 'Select lists to add to ALL FluentCRM contacts when synced (in addition to their individual lists)', 'surecontact' ),
				'type'        => 'list-select',
				'default'     => array(),
			),
			'bulk_sync_tags'       => array(
				'label'       => __( 'Add Tags', 'surecontact' ),
				'description' => __( 'Select tags to add to ALL FluentCRM contacts when synced (in addition to their individual tags)', 'surecontact' ),
				'type'        => 'tag-select',
				'default'     => array(),
			),
		);

		return $settings;
	}

	/**
	 * Handle contact created event
	 *
	 * @since 0.0.1
	 *
	 * @param \FluentCrm\App\Models\Subscriber $subscriber FluentCRM Subscriber object.
	 * @return void
	 */
	public function handle_contact_created( $subscriber ) {
		if ( ! $this->get_setting( 'enable_realtime_sync', true ) ) {
			return;
		}

		// Check status filter.
		if ( $this->get_setting( 'sync_status_filter', true ) && $subscriber->status !== 'subscribed' ) {
			return;
		}

		$subscriber_data = $this->prepare_subscriber_data( $subscriber );
		$mapped_data     = $this->normalize_data( $subscriber_data );

		// Skip if email is missing or invalid (validation done in prepare_subscriber_data).
		if ( empty( $mapped_data['primary_fields']['email'] ) ) {
			Logger::warning(
				'FluentCRM Integration',
				sprintf(
					'Skipping subscriber ID %d on creation: Invalid or missing email address (%s)',
					$subscriber->id,
					$subscriber->email ?? 'empty'
				)
			);
			return;
		}

		$result = $this->send_to_crm( $mapped_data, $subscriber->user_id ?? 0 );

		if ( ! is_wp_error( $result ) && isset( $result['contact_id'] ) ) {
			$subscriber->updateMeta( 'surecontact_contact_uuid', $result['contact_id'], 'surecontact' );
		}
	}

	/**
	 * Handle contact updated event
	 *
	 * @since 0.0.1
	 *
	 * @param \FluentCrm\App\Models\Subscriber $subscriber   FluentCRM Subscriber object.
	 * @param array                            $dirty_fields Changed fields.
	 * @return void
	 */
	public function handle_contact_updated( $subscriber, $dirty_fields ) {
		if ( ! $this->get_setting( 'enable_realtime_sync', true ) ) {
			return;
		}

		// Skip if no meaningful changes.
		if ( empty( $dirty_fields ) ) {
			return;
		}

		// Check status filter.
		if ( $this->get_setting( 'sync_status_filter', true ) && $subscriber->status !== 'subscribed' ) {
			return;
		}

		$subscriber_data = $this->prepare_subscriber_data( $subscriber );
		$mapped_data     = $this->normalize_data( $subscriber_data );

		// Skip if email is missing or invalid (validation done in prepare_subscriber_data).
		if ( empty( $mapped_data['primary_fields']['email'] ) ) {
			Logger::warning(
				'FluentCRM Integration',
				sprintf(
					'Skipping subscriber ID %d on update: Invalid or missing email address (%s)',
					$subscriber->id,
					$subscriber->email ?? 'empty'
				)
			);
			return;
		}

		$this->send_to_crm( $mapped_data, $subscriber->user_id ?? 0 );
	}

	/**
	 * Handle contact email changed event
	 *
	 * This hook is fired by FluentCRM when a contact's email is changed,
	 * providing both the subscriber (with new email) and the old email address.
	 *
	 * @since 1.0.0
	 *
	 * @param \FluentCrm\App\Models\Subscriber $subscriber FluentCRM Subscriber object with new email.
	 * @param string                           $old_email  The previous email address.
	 * @return void
	 */
	public function handle_contact_email_changed( $subscriber, $old_email ) {
		if ( ! $this->get_setting( 'enable_realtime_sync', true ) ) {
			return;
		}

		$new_email = $subscriber->email ?? '';

		if ( empty( $old_email ) || empty( $new_email ) ) {
			return;
		}

		if ( strtolower( $old_email ) === strtolower( $new_email ) ) {
			return;
		}

		$this->contact_service->update_email( $old_email, $new_email, $subscriber->user_id ?? 0, array( 'source' => $this->slug ) );
	}

	/**
	 * Handle lists being attached to a contact
	 *
	 * @since 0.0.3
	 *
	 * @param array                            $list_ids   Array of FluentCRM list IDs.
	 * @param \FluentCrm\App\Models\Subscriber $subscriber FluentCRM Subscriber object.
	 * @return void
	 */
	public function handle_lists_attached( $list_ids, $subscriber ) {
		$this->handle_metadata_change( $list_ids, $subscriber, 'lists', 'attach' );
	}

	/**
	 * Handle lists being detached from a contact
	 *
	 * @since 0.0.3
	 *
	 * @param array                            $list_ids   Array of FluentCRM list IDs.
	 * @param \FluentCrm\App\Models\Subscriber $subscriber FluentCRM Subscriber object.
	 * @return void
	 */
	public function handle_lists_detached( $list_ids, $subscriber ) {
		$this->handle_metadata_change( $list_ids, $subscriber, 'lists', 'detach' );
	}

	/**
	 * Handle tags being attached to a contact
	 *
	 * @since 0.0.3
	 *
	 * @param array                            $tag_ids    Array of FluentCRM tag IDs.
	 * @param \FluentCrm\App\Models\Subscriber $subscriber FluentCRM Subscriber object.
	 * @return void
	 */
	public function handle_tags_attached( $tag_ids, $subscriber ) {
		$this->handle_metadata_change( $tag_ids, $subscriber, 'tags', 'attach' );
	}

	/**
	 * Handle tags being detached from a contact
	 *
	 * @since 0.0.3
	 *
	 * @param array                            $tag_ids    Array of FluentCRM tag IDs.
	 * @param \FluentCrm\App\Models\Subscriber $subscriber FluentCRM Subscriber object.
	 * @return void
	 */
	public function handle_tags_detached( $tag_ids, $subscriber ) {
		$this->handle_metadata_change( $tag_ids, $subscriber, 'tags', 'detach' );
	}

	/**
	 * Handle lists/tags changes (attach or detach)
	 *
	 * Simplified handler that validates, converts IDs, and calls the API.
	 * If contact doesn't exist in SureContact, creates it first.
	 *
	 * @since 0.0.3
	 *
	 * @param array                            $ids        Array of FluentCRM list/tag IDs.
	 * @param \FluentCrm\App\Models\Subscriber $subscriber FluentCRM Subscriber object.
	 * @param string                           $type       Type: 'lists' or 'tags'.
	 * @param string                           $action     Action: 'attach' or 'detach'.
	 * @return void
	 */
	private function handle_metadata_change( $ids, $subscriber, $type, $action ) {
		// Early validation checks.
		if ( ! $this->should_sync_metadata_change( $subscriber, $type ) || empty( $ids ) ) {
			return;
		}

		// Get contact UUID.
		$contact_uuid = $this->contact_service->find_contact_id_by_email( $subscriber->email );

		// If contact doesn't exist, create it first with the current lists/tags.
		if ( ! $contact_uuid ) {
			$contact_uuid = $this->create_contact_for_metadata_change( $subscriber );
			if ( ! $contact_uuid ) {
				return; // Failed to create contact.
			}
			// After creating, the contact already has all current lists/tags, so no need to attach.
			return;
		}

		// Convert FluentCRM IDs to SureContact UUIDs.
		$uuids = $this->get_metadata_uuids( $ids, $type );
		if ( empty( $uuids ) ) {
			return;
		}

		// Call the appropriate API method.
		$this->call_metadata_api( $contact_uuid, $uuids, $type, $action );
	}

	/**
	 * Create contact in SureContact when lists/tags change
	 *
	 * This is called when lists/tags are modified but the contact doesn't exist yet.
	 * It creates the contact with all current lists/tags.
	 *
	 * @since 0.0.3
	 *
	 * @param \FluentCrm\App\Models\Subscriber $subscriber FluentCRM Subscriber object.
	 * @return string|false Contact UUID or false on failure.
	 */
	private function create_contact_for_metadata_change( $subscriber ) {
		$subscriber_data = $this->prepare_subscriber_data( $subscriber );
		$mapped_data     = $this->normalize_data( $subscriber_data );

		// Create contact.
		$user_id = $subscriber->user_id ? (int) $subscriber->user_id : 0;
		$result  = $this->send_to_crm( $mapped_data, $user_id );

		if ( is_wp_error( $result ) ) {
			return false;
		}

		// Return the contact UUID.
		return $result['contact_id'] ?? false;
	}

	/**
	 * Check if metadata change should be synced
	 *
	 * @since 0.0.3
	 *
	 * @param \FluentCrm\App\Models\Subscriber $subscriber FluentCRM Subscriber object.
	 * @param string                           $type       Type: 'lists' or 'tags'.
	 * @return bool
	 */
	private function should_sync_metadata_change( $subscriber, $type ) {
		// Check if real-time sync is enabled.
		if ( ! $this->get_setting( 'enable_realtime_sync', true ) ) {
			return false;
		}

		// Check if this metadata type is enabled.
		$setting_key = $type === 'lists' ? 'sync_lists' : 'sync_tags';
		if ( ! $this->is_setting_enabled( $setting_key, true ) ) {
			return false;
		}

		// Check status filter.
		if ( $this->get_setting( 'sync_status_filter', true ) && $subscriber->status !== 'subscribed' ) {
			return false;
		}

		// Quick check for empty email (full validation done in prepare_subscriber_data).
		if ( empty( $subscriber->email ) ) {
			return false;
		}

		return true;
	}

	/**
	 * Get SureContact UUIDs from FluentCRM IDs
	 *
	 * @since 0.0.3
	 *
	 * @param array  $fc_ids FluentCRM list/tag IDs.
	 * @param string $type   Type: 'lists' or 'tags'.
	 * @return array Array of SureContact UUIDs.
	 */
	private function get_metadata_uuids( $fc_ids, $type ) {
		if ( ! is_array( $fc_ids ) ) {
			$fc_ids = array( $fc_ids );
		}

		// Add 'fc_' prefix to IDs.
		$fc_ids_with_prefix = array_map(
			function ( $id ) {
				return 'fc_' . $id;
			},
			$fc_ids
		);

		// Try to get UUIDs from existing mappings.
		$uuids = $this->convert_ids_to_uuids( $fc_ids_with_prefix, $type );

		// If some IDs are not mapped, sync them first.
		if ( count( $uuids ) < count( $fc_ids ) ) {
			$items_data = $this->fetch_metadata_from_fluentcrm( $fc_ids, $type );
			if ( ! empty( $items_data ) ) {
				$uuids = $this->sync_and_get_uuids( $items_data, $type );
			}
		}

		return $uuids;
	}

	/**
	 * Fetch metadata from FluentCRM by IDs
	 *
	 * @since 0.0.3
	 *
	 * @param array  $ids  FluentCRM list/tag IDs.
	 * @param string $type Type: 'lists' or 'tags'.
	 * @return array Array of metadata items.
	 */
	private function fetch_metadata_from_fluentcrm( $ids, $type ) {
		if ( $type === 'lists' && class_exists( 'FluentCrm\App\Models\Lists' ) ) {
			return \FluentCrm\App\Models\Lists::whereIn( 'id', $ids )
				->get()
				->map(
					function ( $item ) {
						return array(
							'id'          => $item->id,
							'name'        => $item->title,
							'description' => $item->description ?? '',
						);
					}
				)
				->toArray();
		}

		if ( $type === 'tags' && class_exists( 'FluentCrm\App\Models\Tag' ) ) {
			return \FluentCrm\App\Models\Tag::whereIn( 'id', $ids )
				->get()
				->map(
					function ( $item ) {
						return array(
							'id'   => $item->id,
							'name' => $item->title,
						);
					}
				)
				->toArray();
		}

		return array();
	}

	/**
	 * Call the appropriate Lists/Tags API method
	 *
	 * @since 0.0.3
	 *
	 * @param string $contact_uuid Contact UUID.
	 * @param array  $uuids        Array of list/tag UUIDs.
	 * @param string $type         Type: 'lists' or 'tags'.
	 * @param string $action       Action: 'attach' or 'detach'.
	 * @return void
	 */
	private function call_metadata_api( $contact_uuid, $uuids, $type, $action ) {
		// Use Contact_Service for single source of truth.
		if ( $type === 'lists' ) {
			$result = ( $action === 'attach' )
				? $this->contact_service->attach_lists_to_contact( $contact_uuid, $uuids, array( 'source' => $this->slug ) )
				: $this->contact_service->detach_lists_from_contact( $contact_uuid, $uuids, array( 'source' => $this->slug ) );
		} else { // tags.
			$result = ( $action === 'attach' )
				? $this->contact_service->attach_tags_to_contact( $contact_uuid, $uuids, array( 'source' => $this->slug ) )
				: $this->contact_service->detach_tags_from_contact( $contact_uuid, $uuids, array( 'source' => $this->slug ) );
		}
	}

	/**
	 * UNIFIED: Prepare complete subscriber data with all metadata
	 * Single method for both real-time and bulk sync - eliminates duplication
	 *
	 * @since 0.0.3
	 *
	 * @param \FluentCrm\App\Models\Subscriber $subscriber FluentCRM Subscriber object.
	 * @return array Contact data with fc_* fields, lists, tags, custom fields
	 */
	public function prepare_subscriber_data( $subscriber ) {
		// Build contact data with fc_* prefixed fields.
		// Note: Field length validation (255 chars for names) handled by Field_Formatter during normalization.
		$data = array_filter(
			array(
				'fc_email'           => $subscriber->email ?? '',
				'fc_prefix'          => $subscriber->prefix,
				'fc_first_name'      => $subscriber->first_name,
				'fc_last_name'       => $subscriber->last_name,
				'fc_phone'           => $subscriber->phone,
				'fc_address_line_1'  => $subscriber->address_line_1,
				'fc_address_line_2'  => $subscriber->address_line_2,
				'fc_city'            => $subscriber->city,
				'fc_state'           => $subscriber->state,
				'fc_postal_code'     => $subscriber->postal_code,
				'fc_country'         => $subscriber->country,
				'fc_timezone'        => $subscriber->timezone,
				'fc_date_of_birth'   => $subscriber->date_of_birth,
				'fc_status'          => $subscriber->status,
				'fc_contact_type'    => $subscriber->contact_type,
				'fc_source'          => $subscriber->source,
				'fc_life_time_value' => $subscriber->life_time_value,
				'fc_total_points'    => $subscriber->total_points,
				'fc_created_at'      => $subscriber->created_at ?? null,
				'fc_last_activity'   => $subscriber->last_activity ?? null,
				'fc_updated_at'      => $subscriber->updated_at ?? null,
			)
		);

		// Add lists, tags, and custom fields.
		$sync_lists_enabled  = $this->is_setting_enabled( 'sync_lists', true );
		$sync_tags_enabled   = $this->is_setting_enabled( 'sync_tags', true );
		$sync_custom_enabled = $this->is_setting_enabled( 'sync_custom_fields', true );

		// Collect list UUIDs (subscriber's individual lists + global bulk_sync_lists).
		$list_uuids = array();

		if ( $sync_lists_enabled ) {
			if ( ! $subscriber->relationLoaded( 'lists' ) ) {
				$subscriber->load( 'lists' );
			}

			if ( ! empty( $subscriber->lists ) ) {
				$fc_list_ids   = array();
				$fc_lists_data = array();

				foreach ( $subscriber->lists as $list ) {
					$fc_list_ids[]   = 'fc_' . $list->id;
					$fc_lists_data[] = array(
						'id'          => $list->id,
						'name'        => $list->title,
						'description' => ! empty( $list->description ) ? $list->description : '',
					);
				}

				// Convert FluentCRM list IDs to SureContact UUIDs.
				$list_uuids = $this->convert_ids_to_uuids( $fc_list_ids, 'lists' );

				// If some lists are not mapped, sync them on-demand.
				if ( count( $list_uuids ) < count( $fc_list_ids ) ) {
					$list_uuids = $this->sync_and_get_uuids( $fc_lists_data, 'lists' );
				}
			}
		}

		// Add global bulk_sync_lists from settings.
		$bulk_lists = $this->get_setting( 'bulk_sync_lists', array() );
		if ( ! empty( $bulk_lists ) ) {
			$bulk_list_uuids = $this->extract_uuids( $bulk_lists );
			$list_uuids      = array_merge( $list_uuids, $bulk_list_uuids );
		}

		if ( ! empty( $list_uuids ) ) {
			$data['list_uuids'] = $this->extract_uuids( $list_uuids );
		}

		// Collect tag UUIDs (subscriber's individual tags + global bulk_sync_tags).
		$tag_uuids = array();

		if ( $sync_tags_enabled ) {
			if ( ! $subscriber->relationLoaded( 'tags' ) ) {
				$subscriber->load( 'tags' );
			}

			if ( ! empty( $subscriber->tags ) ) {
				$fc_tag_ids   = array();
				$fc_tags_data = array();

				foreach ( $subscriber->tags as $tag ) {
					$fc_tag_ids[]   = 'fc_' . $tag->id;
					$fc_tags_data[] = array(
						'id'   => $tag->id,
						'name' => $tag->title,
					);
				}

				// Convert FluentCRM tag IDs to SureContact UUIDs.
				$tag_uuids = $this->convert_ids_to_uuids( $fc_tag_ids, 'tags' );

				// If some tags are not mapped, sync them on-demand.
				if ( count( $tag_uuids ) < count( $fc_tag_ids ) ) {
					$tag_uuids = $this->sync_and_get_uuids( $fc_tags_data, 'tags' );
				}
			}
		}

		// Add global bulk_sync_tags from settings.
		$bulk_tags = $this->get_setting( 'bulk_sync_tags', array() );
		if ( ! empty( $bulk_tags ) ) {
			$bulk_tag_uuids = $this->extract_uuids( $bulk_tags );
			$tag_uuids      = array_merge( $tag_uuids, $bulk_tag_uuids );
		}

		if ( ! empty( $tag_uuids ) ) {
			$data['tag_uuids'] = $this->extract_uuids( $tag_uuids );
		}

		// Add custom fields.
		if ( $sync_custom_enabled ) {
			$custom_fields = $subscriber->custom_fields();
			if ( ! empty( $custom_fields ) ) {
				$mapped_custom_fields = $this->sync_and_map_custom_fields( $custom_fields );
				if ( ! empty( $mapped_custom_fields ) ) {
					$data['custom_fields'] = $mapped_custom_fields;
				}
			}
		}

		return $data;
	}

	/**
	 * Override normalize_data to preserve FluentCRM-specific fields
	 *
	 * The parent normalize_data only processes fields in global field mappings.
	 * FluentCRM adds list_uuids, tag_uuids, and custom_fields directly which
	 * would be lost during normalization. This override preserves them.
	 *
	 * @since 0.0.3
	 *
	 * @param array $raw_data Raw data from prepare_subscriber_data.
	 * @return array Normalized data with FluentCRM fields preserved
	 */
	public function normalize_data( $raw_data ) {
		// Extract FluentCRM-specific fields before normalization.
		$list_uuids    = $raw_data['list_uuids'] ?? null;
		$tag_uuids     = $raw_data['tag_uuids'] ?? null;
		$custom_fields = $raw_data['custom_fields'] ?? null;

		// Remove them from raw_data so parent doesn't process them.
		unset( $raw_data['list_uuids'], $raw_data['tag_uuids'], $raw_data['custom_fields'] );

		// Call parent to normalize fc_* fields.
		$normalized = parent::normalize_data( $raw_data );

		// Add back FluentCRM-specific fields.
		if ( ! empty( $list_uuids ) ) {
			$normalized['list_uuids'] = $list_uuids;
		}

		if ( ! empty( $tag_uuids ) ) {
			$normalized['tag_uuids'] = $tag_uuids;
		}

		if ( ! empty( $custom_fields ) ) {
			if ( ! isset( $normalized['custom_fields'] ) ) {
				$normalized['custom_fields'] = array();
			}
			$normalized['custom_fields'] = array_merge( $normalized['custom_fields'], $custom_fields );
		}

		return $normalized;
	}

	/**
	 * Sync lists/tags on-demand and return their UUIDs
	 *
	 * Syncs items with SureContact (creating or matching) and returns their UUIDs.
	 * Updates the stored mappings for future use.
	 *
	 * @since 0.0.3
	 *
	 * @param array  $items_data Array of items with 'id' and 'name' (and optionally 'description').
	 * @param string $type       Type of metadata: 'lists' or 'tags'.
	 * @return array Array of SureContact UUIDs
	 */
	public function sync_and_get_uuids( $items_data, $type ) {
		if ( empty( $items_data ) ) {
			return array();
		}

		// Get existing mappings to avoid unnecessary API calls.
		$existing_mappings = $this->get_metadata_mapping( $type );

		$all_mappings  = $existing_mappings; // Start with existing mappings.
		$items_to_sync = array();

		// Separate already-mapped items from unmapped ones.
		foreach ( $items_data as $item ) {
			$external_id = 'fc_' . $item['id'];

			if ( ! isset( $existing_mappings[ $external_id ] ) ) {
				// Not mapped - needs to be synced.
				$items_to_sync[] = $item;
			}
		}

		// Only sync unmapped items.
		if ( ! empty( $items_to_sync ) ) {
			// Determine the correct type for sync_metadata (singular: 'list' or 'tag').
			$sync_type = rtrim( $type, 's' );

			// Sync unmapped items and get mappings (external_id => crm_uuid).
			$new_mappings = $this->contact_service->sync_metadata( $items_to_sync, $sync_type );

			if ( ! empty( $new_mappings ) ) {
				// Update stored mappings.
				$this->update_metadata_mapping( $type, $new_mappings );

				// Merge new mappings with existing ones.
				$all_mappings = array_merge( $all_mappings, $new_mappings );
			}
		}

		// Extract UUIDs in the same order as items_data to maintain consistency.
		$ordered_uuids = array();
		foreach ( $items_data as $item ) {
			$external_id = 'fc_' . $item['id'];
			if ( isset( $all_mappings[ $external_id ] ) ) {
				$ordered_uuids[] = $all_mappings[ $external_id ];
			}
		}

		return $this->extract_uuids( $ordered_uuids );
	}

	/**
	 * Sync and map FluentCRM custom fields to SureContact
	 *
	 * Ensures FluentCRM custom fields exist in SureContact and returns mapped values.
	 *
	 * Flow:
	 * 1. Get FluentCRM field definitions for actual field types (once per call)
	 * 2. Check existing mapping - if mapped, use it and skip CRM API call
	 * 3. If not mapped, fetch CRM fields to check if field exists
	 * 4. If exists in CRM, create mapping from response data
	 * 5. If not in CRM, create new field then map it
	 *
	 * @since 0.0.3
	 *
	 * @param array $custom_fields FluentCRM custom fields array (key => value).
	 * @return array Mapped custom fields ready for SureContact (field_name => value)
	 */
	private function sync_and_map_custom_fields( $custom_fields ) {
		if ( empty( $custom_fields ) ) {
			return array();
		}

		$mapped_fields  = array();
		$field_mappings = $this->get_metadata_mapping( 'custom_fields' );

		// Get FluentCRM field definitions once for all custom fields in this call.
		$fc_field_definitions = $this->get_fluentcrm_field_definitions();

		// We'll fetch CRM fields only once when needed (lazy loading).
		$existing_fields_map = null;

		foreach ( $custom_fields as $fc_key => $value ) {
			// Skip empty values.
			if ( $value === null || $value === '' ) {
				continue;
			}

			// Get actual field type from FluentCRM definitions, fallback to detection.
			$field_type = $this->get_field_type_from_definitions( $fc_key, $fc_field_definitions, $value );

			// STEP 1: Check if field is already mapped in our local mapping.
			if ( isset( $field_mappings[ $fc_key ] ) ) {
				// Field is already mapped - use existing mapping, skip CRM API call.
				$crm_field_name = $field_mappings[ $fc_key ];
			} else {
				// STEP 2: Field not mapped yet - determine CRM field name.
				// Remove any existing fc_ prefix to avoid double prefixing (fc_fc_...).
				$clean_key = $fc_key;
				if ( strpos( $fc_key, 'fc_' ) === 0 ) {
					$clean_key = substr( $fc_key, 3 ); // Remove fc_ prefix.
				}
				$crm_field_name = 'fc_' . $clean_key; // Always add fc_ prefix to clean key.

				// Fetch existing CRM fields only once when first unmapped field is encountered.
				if ( $existing_fields_map === null ) {
					$existing_fields_map = array();
					$fields_array        = $this->contact_service->get_custom_fields();

					// Contact_Service returns parsed array or WP_Error.
					if ( ! is_wp_error( $fields_array ) && is_array( $fields_array ) ) {
						// Create a map for quick lookup: field_name => field_data.
						foreach ( $fields_array as $field ) {
							if ( is_array( $field ) && isset( $field['name'] ) ) {
								$existing_fields_map[ $field['name'] ] = $field;
							}
						}
					}
				}

				// STEP 3: Check if field exists in CRM.
				if ( isset( $existing_fields_map[ $crm_field_name ] ) ) {
					// Field exists in CRM - create mapping from response data.
					$field_mappings[ $fc_key ] = $crm_field_name;
					$this->update_metadata_mapping( 'custom_fields', array( $fc_key => $crm_field_name ) );
				} else {
					// STEP 4: Field doesn't exist in CRM - create it.
					// Get field config from definitions for complete field data.
					$fc_field_config = $fc_field_definitions[ $fc_key ] ?? array( 'type' => 'text' );
					$field_data      = $this->prepare_custom_field_for_sync( $crm_field_name, $fc_field_config );

					$result = $this->contact_service->sync_custom_field( $field_data, array( 'source' => $this->slug ) );

					if ( ! is_wp_error( $result ) ) {
						// Successfully created - store mapping.
						$field_mappings[ $fc_key ] = $crm_field_name;
						$this->update_metadata_mapping( 'custom_fields', array( $fc_key => $crm_field_name ) );

						// Add to existing fields map for future iterations in this loop.
						$existing_fields_map[ $crm_field_name ] = array( 'name' => $crm_field_name );
					} else {
						continue;
					}
				}
			}
			// Format value using Field_Formatter for proper validation/sanitization.
			$formatted_value = Field_Formatter::format_value_by_type( $value, $field_type, $crm_field_name );

			// Only include if value passes validation.
			if ( Field_Formatter::should_include_value( $formatted_value ) ) {
				$mapped_fields[ $crm_field_name ] = $formatted_value;
			}
		}

		return $mapped_fields;
	}

	/**
	 * Get FluentCRM custom field definitions
	 *
	 * Retrieves field definitions from FluentCRM including type, label, etc.
	 * Returns a map of field_slug => field_config for quick lookup.
	 *
	 * @since 0.0.3
	 *
	 * @return array Map of field_slug => field_config
	 */
	public function get_fluentcrm_field_definitions() {
		$definitions = array();

		if ( function_exists( 'fluentcrm_get_custom_contact_fields' ) ) {
			$fields = fluentcrm_get_custom_contact_fields();

			if ( is_array( $fields ) ) {
				foreach ( $fields as $field ) {
					if ( isset( $field['slug'] ) ) {
						$definitions[ $field['slug'] ] = $field;
					}
				}
			}
		}

		return $definitions;
	}

	/**
	 * Get field type from FluentCRM definitions
	 *
	 * Looks up the actual field type from FluentCRM, falling back to value-based detection.
	 *
	 * @since 0.0.3
	 *
	 * @param string $fc_key            FluentCRM field key.
	 * @param array  $fc_definitions    FluentCRM field definitions map.
	 * @param mixed  $value             Field value (for fallback detection).
	 * @return string SureContact-compatible field type
	 */
	private function get_field_type_from_definitions( $fc_key, $fc_definitions, $value ) {
		// Check if field exists in FluentCRM definitions.
		if ( isset( $fc_definitions[ $fc_key ] ) && ! empty( $fc_definitions[ $fc_key ]['type'] ) ) {
			$fc_type = $fc_definitions[ $fc_key ]['type'];
			return $this->map_field_type( $fc_type );
		}

		// Fallback: detect type from value.
		return $this->detect_field_type_from_value( $value );
	}

	/**
	 * Get field label from FluentCRM definitions
	 *
	 * @since 0.0.3
	 *
	 * @param string $crm_field_name CRM field name (with fc_ prefix).
	 * @param array  $fc_field_config FluentCRM field configuration with type, label, options, etc.
	 * @return array Field data ready for sync_custom_field API call
	 */
	public function prepare_custom_field_for_sync( $crm_field_name, $fc_field_config ) {
		$fc_type     = $fc_field_config['type'] ?? 'text';
		$mapped_type = $this->map_field_type( $fc_type );

		$field_data = array(
			'name'  => $crm_field_name,
			'label' => $fc_field_config['label'] ?? $this->format_field_label( $crm_field_name ),
			'type'  => $mapped_type,
		);

		// Field types that require options: select, multi_select.
		// FluentCRM types that have options: select-one, select-multi, radio, checkbox (sometimes).
		$types_requiring_options = array( 'select', 'multi_select' );

		if ( in_array( $mapped_type, $types_requiring_options, true ) && ! empty( $fc_field_config['options'] ) ) {
			$options = $this->normalize_field_options( $fc_field_config['options'] );
			if ( ! empty( $options ) ) {
				$field_data['options'] = $options;
			}
		}

		// Add description if available.
		if ( ! empty( $fc_field_config['description'] ) ) {
			$field_data['description'] = $fc_field_config['description'];
		}

		// Add default value if available.
		if ( isset( $fc_field_config['default_value'] ) && $fc_field_config['default_value'] !== '' ) {
			$field_data['default_value'] = $fc_field_config['default_value'];
		}

		return $field_data;
	}

	/**
	 * Normalize field options from FluentCRM format to SureContact format
	 *
	 * FluentCRM stores options as associative array (value => label).
	 * SureContact API expects a simple indexed array of option values.
	 *
	 * @since 0.0.3
	 *
	 * @param mixed $options Options from FluentCRM field config.
	 * @return array Normalized options array for SureContact API
	 */
	private function normalize_field_options( $options ) {
		if ( ! is_array( $options ) || empty( $options ) ) {
			return array();
		}

		// Check if it's an associative array (FluentCRM format: value => label).
		if ( array_keys( $options ) !== range( 0, count( $options ) - 1 ) ) {
			// Associative array - use keys as option values.
			return array_keys( $options );
		}

		// Already an indexed array.
		return $options;
	}

	/**
	 * Detect field type from value (fallback method)
	 *
	 * Used when FluentCRM field definitions are not available.
	 *
	 * @since 0.0.3
	 *
	 * @param mixed $value Field value.
	 * @return string Field type compatible with SureContact API
	 */
	private function detect_field_type_from_value( $value ) {
		if ( is_bool( $value ) ) {
			return 'checkbox';
		}

		if ( is_numeric( $value ) ) {
			return 'number';
		}

		if ( is_array( $value ) ) {
			return 'text'; // Store as JSON string.
		}

		// Check if it looks like a date.
		if ( is_string( $value ) && strtotime( $value ) !== false && preg_match( '/^\d{4}-\d{2}-\d{2}/', $value ) ) {
			return 'date';
		}

		return 'text';
	}

	/**
	 * Map FluentCRM field type to SureContact field type
	 *
	 * FluentCRM field types from CustomContactField::getFieldTypes():
	 * - text (type: text, value_type: string)
	 * - textarea (type: textarea, value_type: string)
	 * - number (type: number, value_type: numeric)
	 * - single-select (type: select-one, value_type: string)
	 * - multi-select (type: select-multi, value_type: array)
	 * - radio (type: radio, value_type: string)
	 * - checkbox (type: checkbox, value_type: array)
	 * - date (type: date, value_type: date)
	 * - date_time (type: date_time, value_type: datetime)
	 *
	 * @since 0.0.3
	 *
	 * @param string $fc_type FluentCRM field type.
	 * @return string SureContact field type
	 */
	private function map_field_type( $fc_type ) {
		$type_map = array(
			// Core FluentCRM field types.
			'text'         => 'text',
			'textarea'     => 'textarea',
			'number'       => 'number',
			'select-one'   => 'select',
			'select-multi' => 'multi_select',
			'radio'        => 'select',
			'checkbox'     => 'checkbox',
			'date'         => 'date',
			'date_time'    => 'datetime',
			// Additional mappings for compatibility.
			'select'       => 'select',
			'email'        => 'email',
			'url'          => 'url',
			'phone'        => 'phone',
		);

		return $type_map[ $fc_type ] ?? 'text';
	}

	/**
	 * Format field label from key
	 *
	 * @since 0.0.3
	 *
	 * @param string $key Field key.
	 * @return string Formatted label
	 */
	private function format_field_label( $key ) {
		// Convert snake_case or kebab-case to Title Case.
		$label = str_replace( array( '_', '-' ), ' ', $key );
		return ucwords( $label );
	}

	/**
	 * Override prepare_lists_and_tags to respect FluentCRM integration-specific settings
	 *
	 * This ensures that FluentCRM subscribers only get their individual lists/tags,
	 * NOT the global assigned_lists and assigned_tags from general settings.
	 * The bulk_sync_lists and bulk_sync_tags are applied separately during bulk sync.
	 *
	 * @since 0.0.1
	 *
	 * @param array $data     Contact data.
	 * @param int   $user_id  User ID.
	 * @param array $context  Context with optional list_uuids, tag_uuids, and custom_fields.
	 * @return array Modified contact data with lists, tags, and custom fields.
	 */
	protected function prepare_lists_and_tags( $data, $user_id, $context ) {
		$list_uuids = array();
		$tag_uuids  = array();

		$sync_lists_enabled = $this->is_setting_enabled( 'sync_lists', true );
		$sync_tags_enabled  = $this->is_setting_enabled( 'sync_tags', true );

		if ( $sync_lists_enabled ) {
			// Add lists from context (FluentCRM subscriber's individual lists)
			// DO NOT add global assigned_lists from general settings - FluentCRM handles its own lists.
			if ( ! empty( $context['list_uuids'] ) && is_array( $context['list_uuids'] ) ) {
				$list_uuids = array_merge( $list_uuids, $context['list_uuids'] );
			}
		}

		// Only add tags if sync_tags is enabled.
		if ( $sync_tags_enabled ) {
			// Add tags from context (FluentCRM subscriber's individual tags)
			// DO NOT add global assigned_tags from general settings - FluentCRM handles its own tags.
			if ( ! empty( $context['tag_uuids'] ) && is_array( $context['tag_uuids'] ) ) {
				$tag_uuids = array_merge( $tag_uuids, $context['tag_uuids'] );
			}
		}

		// Add integration-level lists and tags (applies to ALL contacts).
		$integration_lists = $this->get_setting( 'bulk_sync_lists', array() );
		$integration_tags  = $this->get_setting( 'bulk_sync_tags', array() );

		if ( ! empty( $integration_lists ) ) {
			$integration_list_uuids = $this->extract_uuids( $integration_lists );
			if ( ! empty( $integration_list_uuids ) ) {
				$list_uuids = array_merge( $list_uuids, $integration_list_uuids );
			}
		}

		if ( ! empty( $integration_tags ) ) {
			$integration_tag_uuids = $this->extract_uuids( $integration_tags );
			if ( ! empty( $integration_tag_uuids ) ) {
				$tag_uuids = array_merge( $tag_uuids, $integration_tag_uuids );
			}
		}

		// Apply lists and tags to data.
		if ( ! empty( $list_uuids ) ) {
			$data['list_uuids'] = $this->extract_uuids( $list_uuids );
		}

		if ( ! empty( $tag_uuids ) ) {
			$data['tag_uuids'] = $this->extract_uuids( $tag_uuids );
		}

		// Add custom fields from context if present.
		if ( ! empty( $context['custom_fields'] ) && is_array( $context['custom_fields'] ) ) {
			if ( ! isset( $data['custom_fields'] ) ) {
				$data['custom_fields'] = array();
			}
			$data['custom_fields'] = array_merge( $data['custom_fields'], $context['custom_fields'] );
		}

		return $data;
	}

	/**
	 * Get FluentCRM sync types
	 *
	 * @since 1.2.0
	 *
	 * @return array Array of sync type definitions.
	 */
	public function get_sync_types() {
		return $this->contact_sync->get_sync_types();
	}
}
