<?php
/**
 * Emails service for managing application emails and templates.
 *
 * @package RewardsWP
 */

namespace Awesomemotive\Rewardswp\Services;

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

use Awesomemotive\Rewardswp\Abstracts\AbstractEmailNotification;
use Awesomemotive\Rewardswp\Abstracts\AbstractModelService;
use Awesomemotive\Rewardswp\Controllers\Admin\EmailsAdminController;
use Awesomemotive\Rewardswp\Database\DatabaseHandler;
use Awesomemotive\Rewardswp\Database\Tables\EmailsTable;
use Awesomemotive\Rewardswp\Factories\MailerFactory;
use Awesomemotive\Rewardswp\Models\Email;
use DI\Container;
use Exception;
use function Awesomemotive\Rewardswp\Helpers\calculate_relative_luminance;
use function Awesomemotive\Rewardswp\Helpers\get_contrasting_text_color as helper_get_contrasting_text_color;
use function Awesomemotive\Rewardswp\Helpers\get_current_time;
use function Awesomemotive\Rewardswp\Helpers\get_minimum_required_plan_for_feature;
use function Awesomemotive\Rewardswp\Helpers\is_highest_plan_tier;
use function Awesomemotive\Rewardswp\Helpers\rewardswp_log_error;

/**
 * Class EmailsService
 *
 * Handles business logic for email management.
 *
 * @since 1.0.0
 */
class EmailsService extends AbstractModelService {

	/**
	 * Default size for email banner previews.
	 *
	 * @since 1.1.0
	 *
	 * @var array{0:int,1:int}
	 */
	public const EMAIL_BANNER_DEFAULT_SIZE = [ 600, 180 ];

	/**
	 * Mailer factory instance.
	 *
	 * @since 1.1.0
	 * @var MailerFactory
	 */
	private MailerFactory $mailer_factory;

	/**
	 * Constructor.
	 *
	 * @since 1.1.0
	 *
	 * @param DatabaseHandler $db_handler Database handler instance.
	 * @param EmailsTable     $db_table Emails table instance.
	 * @param Container       $container DI container instance.
	 * @param MailerFactory   $mailer_factory Mailer factory instance.
	 */
	public function __construct(
		DatabaseHandler $db_handler,
		EmailsTable $db_table,
		Container $container,
		MailerFactory $mailer_factory
	) {
		parent::__construct( $db_handler, $db_table, $container );
		$this->mailer_factory = $mailer_factory;
	}

	/**
	 * Retrieve emails based on the given parameters and pagination options.
	 *
	 * @since 1.0.0
	 *
	 * @param array $where    An associative array of conditions to filter emails.
	 *                        See {@see EmailsTable::get_columns()} for available parameters.
	 * @param int   $per_page Optional. The number of items per page for pagination. Defaults to 20.
	 * @param int   $page     Optional. The page number for pagination. Defaults to 1.
	 *
	 * @return Email[] Array of Email objects.
	 */
	public function get_emails( array $where = [], int $per_page = 20, int $page = 1 ): array {
		return $this->get_objects( Email::class, $where, $per_page, $page );
	}

	/**
	 * Get the number of emails based on the given parameters.
	 *
	 * @since 1.0.0
	 *
	 * @param array $where Optional. An associative array of conditions to filter emails.
	 *
	 * @return int Number of emails.
	 */
	public function count_emails( array $where = [] ): int {
		return $this->db_handler->count( $this->db_table->get_prefixed_table_name(), $where );
	}

	/**
	 * Get email by name.
	 *
	 * @since 1.0.0
	 *
	 * @param string $name The email name/identifier.
	 *
	 * @return Email|null The email object or null if not found.
	 */
	public function get_email_by_name( string $name ): ?Email {
		$emails = $this->get_emails( [ 'name' => $name ], 1 );
		return ! empty( $emails ) ? $emails[0] : null;
	}

	/**
	 * Get email by ID.
	 *
	 * @since 1.0.0
	 *
	 * @param int $id The email ID.
	 *
	 * @return Email|null The email object or null if not found.
	 */
	public function get_email_by_id( int $id ): ?Email {
		$emails = $this->get_emails( [ 'id' => $id ], 1 );
		return ! empty( $emails ) ? $emails[0] : null;
	}

	/**
	 * Create a new email.
	 *
	 * @since 1.0.0
	 *
	 * @param array $data Email data.
	 *
	 * @return Email|null The created email object or null on failure.
	 * @throws Exception If creation fails.
	 */
	public function create_email( array $data ): ?Email {
		$email = $this->container->make( Email::class );
		$email->hydrate( $data );

		if ( $email->create() ) {
			return $email;
		}

		return null;
	}

	/**
	 * Update an existing email.
	 *
	 * @since 1.0.0
	 *
	 * @param int   $id   The email ID.
	 * @param array $data Updated email data.
	 *
	 * @return bool True on success, false on failure.
	 * @throws Exception If update fails.
	 */
	public function update_email( int $id, array $data ): bool {
		$email = $this->get_email_by_id( $id );

		if ( ! $email ) {
			return false;
		}

		$email->hydrate( $data );
		return $email->update();
	}

	/**
	 * Toggle the enabled status of an email.
	 *
	 * @since 1.0.0
	 *
	 * @param int $id The email ID.
	 *
	 * @return bool True on success, false on failure.
	 * @throws Exception If toggle fails.
	 */
	public function toggle_email_status( int $id ): bool {
		$email = $this->get_email_by_id( $id );

		if ( ! $email ) {
			return false;
		}

		return $email->toggle_enabled();
	}

	/**
	 * Delete an email.
	 *
	 * @since 1.0.0
	 *
	 * @param int $id The email ID.
	 *
	 * @return bool True on success, false on failure.
	 * @throws Exception If deletion fails.
	 */
	public function delete_email( int $id ): bool {
		$email = $this->get_email_by_id( $id );

		if ( ! $email ) {
			return false;
		}

		return $email->delete();
	}

	/**
	 * Check if an email with the given name exists.
	 *
	 * @since 1.0.0
	 *
	 * @param string $name The email name.
	 *
	 * @return bool True if exists, false otherwise.
	 */
	public function email_exists( string $name ): bool {
		return $this->get_email_by_name( $name ) !== null;
	}

	/**
	 * Get emails by context.
	 *
	 * @since 1.0.0
	 *
	 * @param string $context The context (account, referral, points, order).
	 * @param bool   $enabled_only Optional. Get only enabled emails. Default true.
	 *
	 * @return Email[] Array of Email objects.
	 */
	public function get_emails_by_context( string $context, bool $enabled_only = true ): array {
		$where = [ 'context' => $context ];

		if ( $enabled_only ) {
			$where['is_enabled'] = 1;
		}

		return $this->get_emails( $where );
	}

	/**
	 * Get emails by recipient type.
	 *
	 * @since 1.0.0
	 *
	 * @param string $recipient_type The recipient type (member, admin, user).
	 * @param bool   $enabled_only   Optional. Get only enabled emails. Default true.
	 *
	 * @return Email[] Array of Email objects.
	 */
	public function get_emails_by_recipient_type( string $recipient_type, bool $enabled_only = true ): array {
		$where = [ 'recipient_type' => $recipient_type ];

		if ( $enabled_only ) {
			$where['is_enabled'] = 1;
		}

		return $this->get_emails( $where );
	}

	/**
	 * Get emails by sender.
	 *
	 * @since 1.0.0
	 *
	 * @param string $sender       The sender identifier.
	 * @param bool   $enabled_only Optional. Get only enabled emails. Default true.
	 *
	 * @return Email[] Array of Email objects.
	 */
	public function get_emails_by_sender( string $sender, bool $enabled_only = true ): array {
		$where = [ 'sender' => $sender ];

		if ( $enabled_only ) {
			$where['is_enabled'] = 1;
		}

		return $this->get_emails( $where );
	}

	/**
	 * Get all enabled emails.
	 *
	 * @since 1.0.0
	 *
	 * @return Email[] Array of enabled Email objects.
	 */
	public function get_enabled_emails(): array {
		return $this->get_emails( [ 'is_enabled' => 1 ] );
	}

	/**
	 * Get all disabled emails.
	 *
	 * @since 1.0.0
	 *
	 * @return Email[] Array of disabled Email objects.
	 */
	public function get_disabled_emails(): array {
		return $this->get_emails( [ 'is_enabled' => 0 ] );
	}

	/**
	 * Bulk update email status.
	 *
	 * @since 1.0.0
	 *
	 * @param array $email_ids Array of email IDs.
	 * @param bool  $enabled   New status (true for enabled, false for disabled).
	 *
	 * @return int Number of emails updated successfully.
	 */
	public function bulk_update_status( array $email_ids, bool $enabled ): int {
		$updated_count = 0;

		foreach ( $email_ids as $email_id ) {
			try {
				$email = $this->get_email_by_id( (int) $email_id );

				if ( $email && $email->is_enabled !== $enabled ) {
					$email->is_enabled = $enabled;

					if ( $email->update() ) {
						++$updated_count;
					}
				}
			} catch ( Exception $e ) {
				// Log error but continue with other emails.
				rewardswp_log_error(
					'Failed to update email status for ID {email_id}: {message}',
					[
						'email_id' => $email_id,
						'message'  => $e->getMessage(),
					]
				);
			}
		}

		return $updated_count;
	}

	/**
	 * Get available filter options for emails list.
	 *
	 * @since 1.0.0
	 *
	 * @return array Array of filter options.
	 */
	public function get_filter_options(): array {
		return [
			'contexts'        => Email::get_available_contexts(),
			'recipient_types' => Email::get_available_recipient_types(),
			'senders'         => Email::get_available_senders(),
		];
	}

	/**
	 * Search emails by name or subject.
	 *
	 * @since 1.0.0
	 *
	 * @param string $search_term The search term.
	 * @param int    $per_page    Optional. Items per page. Default 20.
	 * @param int    $page        Optional. Page number. Default 1.
	 *
	 * @return Email[] Array of matching Email objects.
	 */
	public function search_emails( string $search_term, int $per_page = 20, int $page = 1 ): array {
		// This would need custom SQL for LIKE searches.
		// For now, we'll implement a basic approach.
		$all_emails = $this->get_emails( [], $per_page * 10 ); // Get more results to filter.

		$filtered_emails = array_filter(
			$all_emails,
			function ( $email ) use ( $search_term ) {
				return stripos( $email->name, $search_term ) !== false ||
					stripos( $email->subject, $search_term ) !== false;
			}
		);

		// Simple pagination.
		$offset = ( $page - 1 ) * $per_page;
		return array_slice( $filtered_emails, $offset, $per_page );
	}

	/**
	 * Extract email data from an email class for seeding purposes.
	 *
	 * This method can be reused whenever new email templates are added to the system.
	 * Context and recipient type are automatically extracted from the email class.
	 *
	 * @since 1.1.0
	 *
	 * @param string $email_class The email class name.
	 * @param string $sender      Optional. The sender identifier. Default 'RewardsWP Core'.
	 *
	 * @throws Exception If the email class cannot be instantiated.
	 * @return array The extracted email data ready for database insertion.
	 */
	public function extract_email_data_from_class(
		string $email_class,
		string $sender = 'RewardsWP Core'
	): array {
		try {
			$mailer = $this->mailer_factory->get_mailer( $email_class );

			if ( ! $mailer ) {
				throw new Exception( "Could not instantiate email class: {$email_class}" );
			}

			return [
				'name'           => $mailer->get_name(),
				// Seed an empty subject so get_subject() falls through to the translatable
				// get_default_subject() at send time. Persisting the resolved default here would
				// freeze the install-time locale into the DB and bypass translation (see #2).
				'subject'        => '',
				'context'        => $mailer->get_context(),
				'recipient_type' => $mailer->get_recipient_type(),
				'sender'         => $sender,
				'is_enabled'     => 1,
			];
		} catch ( Exception $e ) {
			// Log error and return fallback data.
			rewardswp_log_error(
				'Failed to extract email data from class {email_class}: {message}',
				[
					'email_class' => $email_class,
					'message'     => $e->getMessage(),
				]
			);

			// Create a fallback name from class name.
			$fallback_name = strtolower(
				str_replace( [ 'Email', '\\' ], [ '', '_' ], basename( str_replace( '\\', '/', $email_class ) ) )
			);

			return [
				'name'           => $fallback_name,
				'subject'        => __( 'Email Subject', 'rewardswp' ),
				'context'        => 'member', // Fallback context.
				'recipient_type' => 'member', // Fallback recipient.
				'sender'         => $sender,
				'is_enabled'     => 1,
			];
		}
	}

	/**
	 * Extract data from multiple email classes.
	 *
	 * Useful for bulk operations when seeding or migrating email templates.
	 * Context and recipient type are automatically extracted from each email class.
	 *
	 * @since 1.1.0
	 *
	 * @param array $email_classes Array of email class names or arrays with 'class' and optional 'sender'.
	 *
	 * @return array Array of extracted email data.
	 */
	public function extract_multiple_email_data( array $email_classes ): array {
		$results = [];

		foreach ( $email_classes as $email_class_data ) {
			// Support both string class names and array format.
			if ( is_string( $email_class_data ) ) {
				$class  = $email_class_data;
				$sender = 'RewardsWP Core';
			} else {
				$class  = $email_class_data['class'] ?? '';
				$sender = $email_class_data['sender'] ?? 'RewardsWP Core';
			}

			if ( empty( $class ) ) {
				rewardswp_log_error( 'Empty email class provided to extract_multiple_email_data' );
				continue;
			}

			$results[] = $this->extract_email_data_from_class( $class, $sender );
		}

		return $results;
	}

	/**
	 * Get emails grouped by context.
	 *
	 * @since 1.1.0
	 *
	 * @param bool $enabled_only Optional. Get only enabled emails. Default true.
	 *
	 * @return array Array of emails grouped by context.
	 */
	public function get_emails_grouped_by_context( bool $enabled_only = true ): array {
		$where = [];

		if ( $enabled_only ) {
			$where['is_enabled'] = 1;
		}

		$emails  = $this->get_emails( $where, 1000 ); // Get all emails.
		$grouped = [];

		foreach ( $emails as $email ) {
			if ( ! isset( $grouped[ $email->context ] ) ) {
				$grouped[ $email->context ] = [];
			}
			$grouped[ $email->context ][] = $email;
		}

		// Sort each context group by name.
		foreach ( $grouped as &$context_emails ) {
			usort(
				$context_emails,
				function ( $a, $b ) {
					return strcmp( $a->name, $b->name );
				}
			);
		}

		return $grouped;
	}

	/**
	 * Get email with description from email class.
	 *
	 * @since 1.1.0
	 *
	 * @param Email $email The email object.
	 *
	 * @return array Email data with description from email class.
	 */
	public function get_email_with_description( Email $email ): array {
		// Convert email object to array manually.
		$email_data = [
			'id'             => $email->id,
			'name'           => $email->name,
			'subject'        => '',
			'context'        => $email->context,
			'recipient_type' => $email->recipient_type,
			'sender'         => $email->sender,
			'is_enabled'     => $email->is_enabled,
			'created_at'     => $email->created_at,
		];

		$email_data['description'] = '';

		$mailer        = null;
		$template_html = '';

		try {
			$mailer                = $this->mailer_factory->get_mailer( $email->name );
			$email_data['subject'] = $email->subject;
			if ( ( '' === trim( (string) $email_data['subject'] ) ) && $mailer && method_exists( $mailer, 'get_subject' ) ) {
				$email_data['subject'] = $mailer->get_subject();
			}
			if ( $mailer && method_exists( $mailer, 'get_default_html_template_for_seeding' ) ) {
				$template_html = $mailer->get_default_html_template_for_seeding();
			}
			// Get display title using helper method.
			$email_data['display_title'] = $this->get_email_display_title( $email, $mailer );
			// Get description.
			if ( $mailer && method_exists( $mailer, 'get_description' ) ) {
				$email_data['description'] = $mailer->get_description();
			}
		} catch ( Exception $e ) {
			// Fallback: use helper without mailer instance.
			$email_data['display_title'] = $this->get_email_display_title( $email );
			$email_data['description']   = $email_data['display_title'];
			$email_data['subject']       = $email->subject;
		}

		$email_data['content'] = $template_html;

		$access_details = $this->resolve_access_details_from_mailer( $mailer );

		return array_merge( $email_data, $access_details );
	}

	/**
	 * Get emails with descriptions grouped by context.
	 *
	 * @since 1.1.0
	 *
	 * @param bool $enabled_only Optional. Get only enabled emails. Default true.
	 *
	 * @return array Array of emails with descriptions grouped by context.
	 */
	public function get_emails_with_descriptions_grouped_by_context( bool $enabled_only = true ): array {
		$grouped_emails = $this->get_emails_grouped_by_context( $enabled_only );
		$result         = [];

		foreach ( $grouped_emails as $context => $emails ) {
			$result[ $context ] = [];
			foreach ( $emails as $email ) {
				$result[ $context ][] = $this->get_email_with_description( $email );
			}
		}

		return $result;
	}

	/**
	 * Get email with complete edit data (merge tags, banners, etc.)
	 *
	 * Replicates the logic from EmailsAdminController::ajax_get_email_for_edit()
	 * to provide all data needed for inline editing without AJAX dependency.
	 *
	 * @since 1.1.0
	 *
	 * @param Email $email The email object.
	 *
	 * @return array Complete email data for editing.
	 */
	public function get_email_with_complete_edit_data( Email $email ): array {
		// Get mailer instance for fetching all email-related data.
		$mailer = $this->mailer_factory->get_mailer( $email->name );

		// Start with basic email data.
		$template_html = '';

		if ( $mailer && method_exists( $mailer, 'get_default_html_template_for_seeding' ) ) {
			$template_html = $mailer->get_default_html_template_for_seeding();
		}

		$email_data = [
			'id'            => $email->id,
			'name'          => $email->name,
			'display_title' => $this->get_email_display_title( $email, $mailer ),
			'subject'       => $email->subject,
			'content'       => $template_html,
			'is_enabled'    => (bool) $email->is_enabled,
		];

		if ( ( '' === trim( (string) $email_data['subject'] ) ) && $mailer && method_exists( $mailer, 'get_subject' ) ) {
			$email_data['subject'] = $mailer->get_subject();
		}

		// Get email merge tags dynamically.
		$merge_tags = [];

		if ( $mailer && method_exists( $mailer, 'get_merge_tag_definitions' ) ) {
			$merge_tags = $mailer->get_merge_tag_definitions();
		} elseif ( $mailer && method_exists( $mailer, 'get_available_merge_tags' ) ) {
			// Fallback for backward compatibility - convert simple array to definitions format.
			$simple_tags = $mailer->get_available_merge_tags();
			foreach ( $simple_tags as $tag ) {
				$merge_tags[] = [
					'tag'         => $tag,
					'label'       => ucwords( str_replace( '_', ' ', $tag ) ),
					'description' => '',
					'example'     => '',
				];
			}
		} else {
			// Last fallback: extract from email content and subject.
			$content_to_scan = $template_html . ' ' . $email->subject;
			$simple_tags     = \Awesomemotive\Rewardswp\Helpers\extract_placeholders( $content_to_scan );
			foreach ( $simple_tags as $tag ) {
				$merge_tags[] = [
					'tag'         => $tag,
					'label'       => ucwords( str_replace( '_', ' ', $tag ) ),
					'description' => '',
					'example'     => '',
				];
			}
		}

		// Get description from mailer.
		$email_data['description'] = ( $mailer && method_exists( $mailer, 'get_description' ) ) ? $mailer->get_description() : '';
		$email_data['merge_tags']  = $merge_tags;

		// Get banner metadata (fallback to legacy logo_* for BC).
		$banner_attachment_id = (int) ( $email->get_meta( 'banner_attachment_id', true ) ? $email->get_meta( 'banner_attachment_id', true ) : $email->get_meta( 'logo_attachment_id', true ) );
		$banner_size          = $email->get_meta( 'banner_size', true ) ? $email->get_meta( 'banner_size', true ) : ( $email->get_meta( 'logo_size', true ) ? $email->get_meta( 'logo_size', true ) : self::EMAIL_BANNER_DEFAULT_SIZE );
		$banner_preview_url   = '';

		if ( $banner_attachment_id ) {
			$image_data = wp_get_attachment_image_src( $banner_attachment_id, $banner_size );
			if ( $image_data ) {
				$banner_preview_url = $image_data[0];
			}
		}

		// Add banner data (new keys for UI binding).
		$email_data['banner_attachment_id'] = $banner_attachment_id;
		$email_data['banner_size']          = $banner_size;
		$email_data['banner_preview_url']   = $banner_preview_url;

		// Legacy keys kept for backward compatibility with existing JS until full rollout.
		$email_data['logo_attachment_id'] = $banner_attachment_id;
		$email_data['logo_size']          = $banner_size;
		$email_data['logo_preview_url']   = $banner_preview_url;

		$access_details = $this->resolve_access_details_from_mailer( $mailer );

		return array_merge(
			$email_data,
			$access_details,
			[
				'is_locked' => ! $access_details['has_access'],
			]
		);
	}

	/**
	 * Get emails with complete edit data grouped by context.
	 *
	 * @since 1.1.0
	 *
	 * @param bool $enabled_only Optional. Get only enabled emails. Default true.
	 *
	 * @return array Array of emails with complete edit data grouped by context.
	 */
	public function get_emails_with_complete_edit_data_grouped_by_context( bool $enabled_only = true ): array {
		$grouped_emails = $this->get_emails_grouped_by_context( $enabled_only );
		$result         = [];

		foreach ( $grouped_emails as $context => $emails ) {
			$result[ $context ] = [];
			foreach ( $emails as $email ) {
				$result[ $context ][] = $this->get_email_with_complete_edit_data( $email );
			}
		}

		return $result;
	}

	/**
	 * Get emails with complete edit data as a flat array indexed by email ID.
	 *
	 * This format is optimized for JavaScript lookup by email ID.
	 *
	 * @since 1.1.0
	 *
	 * @param bool $enabled_only Optional. Get only enabled emails. Default true.
	 *
	 * @return array Array of emails with complete edit data indexed by email ID.
	 */
	public function get_emails_with_complete_edit_data_indexed( bool $enabled_only = true ): array {
		$where = [];

		if ( $enabled_only ) {
			$where['is_enabled'] = 1;
		}

		$emails  = $this->get_emails( $where, 1000 ); // Get all emails.
		$indexed = [];

		foreach ( $emails as $email ) {
			$indexed[ $email->id ] = $this->get_email_with_complete_edit_data( $email );
		}

		return $indexed;
	}

	/**
	 * Determine if the provided email can be managed (edited/sent) in current plan.
	 *
	 * @since 1.1.0
	 *
	 * @param Email $email Email instance.
	 *
	 * @return bool
	 */
	public function can_manage_email( Email $email ): bool {
		$mailer  = $this->mailer_factory->get_mailer( $email->name );
		$details = $this->resolve_access_details_from_mailer( $mailer );

		return ! empty( $details['has_access'] );
	}

	/**
	 * Retrieve access metadata for a given email instance.
	 *
	 * @since 1.1.0
	 *
	 * @param Email $email Email instance.
	 *
	 * @return array{
	 *     feature_slug:string,
	 *     is_pro_feature:bool,
	 *     has_access:bool
	 * }
	 */
	public function get_email_access_details_by_email( Email $email ): array {
		$mailer = $this->mailer_factory->get_mailer( $email->name );

		return $this->resolve_access_details_from_mailer( $mailer );
	}

	/**
	 * Resolve access metadata for a given mailer instance.
	 *
	 * @since 1.1.0
	 *
	 * @param AbstractEmailNotification|null $mailer Mailer instance.
	 *
	 * @return array{
	 *     feature_slug:string,
	 *     is_pro_feature:bool,
	 *     has_access:bool
	 * }
	 */
	private function resolve_access_details_from_mailer( ?AbstractEmailNotification $mailer ): array {
		if ( ! $mailer ) {
			return [
				'feature_slug'   => '',
				'is_pro_feature' => false,
				'has_access'     => true,
				'min_plan'       => '',
			];
		}

		$feature_slug   = '';
		$has_access     = true;
		$is_pro_feature = false;

		if ( method_exists( $mailer, 'get_feature_slug' ) ) {
			$feature_slug = (string) $mailer->get_feature_slug();
		}

		if ( method_exists( $mailer, 'has_access' ) ) {
			$has_access = (bool) $mailer->has_access();
		}

		if ( method_exists( $mailer, 'requires_license' ) ) {
			$is_pro_feature = (bool) $mailer->requires_license();
		} else {
			$is_pro_feature = '' !== $feature_slug;
		}

		// Get minimum required plan for tooltip display.
		$min_plan = '';
		if ( ! empty( $feature_slug ) ) {
			$plan       = get_minimum_required_plan_for_feature( $feature_slug );
			$is_highest = is_highest_plan_tier( $plan );
			$min_plan   = $plan . ( $is_highest ? '' : '+' );
		}

		return [
			'feature_slug'   => $feature_slug,
			'is_pro_feature' => $is_pro_feature,
			'has_access'     => $has_access,
			'min_plan'       => $min_plan,
		];
	}

	/**
	 * Get display title for an email (custom title or auto-generated).
	 *
	 * @since 1.1.0
	 *
	 * @param Email $email The email object.
	 * @param mixed $mailer Optional mailer instance to avoid duplicate lookups.
	 *
	 * @return string The display title.
	 */
	private function get_email_display_title( Email $email, $mailer = null ): string {
		// Auto-generate as fallback.
		$display_title = ucwords( str_replace( '_', ' ', $email->name ) );

		// Get mailer if not provided.
		if ( null === $mailer ) {
			try {
				$mailer = $this->mailer_factory->get_mailer( $email->name );
			} catch ( Exception $e ) {
				// Return fallback if mailer can't be instantiated.
				return $display_title;
			}
		}

		// Use custom title if available.
		if ( $mailer && method_exists( $mailer, 'get_title' ) ) {
			$custom_title = $mailer->get_title();
			if ( ! empty( $custom_title ) ) {
				$display_title = $custom_title;
			}
		}

		return $display_title;
	}

	/**
	 * Calculate contrasting text color (white or black) for a given background color.
	 *
	 * Uses WCAG 2.1 guidelines for accessibility to determine if white or black text
	 * provides better contrast against the background color.
	 *
	 * @since 1.1.0
	 *
	 * @param string $background_color Hex color code (with or without #).
	 *
	 * @return string '#ffffff' for white text or '#000000' for black text.
	 */
	public function get_contrasting_text_color( string $background_color ): string {
		return helper_get_contrasting_text_color( $background_color );
	}

	/**
	 * Get brand color from settings with fallback.
	 *
	 * @since 1.1.0
	 *
	 * @return string Brand color hex code.
	 */
	public function get_brand_color(): string {
		// Force black color for email buttons.
		return '#000000';
	}

	/**
	 * Get contrasting text color for the current brand color.
	 *
	 * @since 1.1.0
	 *
	 * @return string Contrasting text color hex code.
	 */
	public function get_brand_contrasting_text_color(): string {
		// Force white text color for email buttons.
		return '#ffffff';
	}

	/**
	 * Debug method to test contrast calculation.
	 * Can be called from preview to verify calculations.
	 *
	 * @since 1.1.0
	 *
	 * @param string $test_color Color to test.
	 * @return array Debug information.
	 */
	public function debug_contrast_calculation( string $test_color ): array {
		$hex        = ltrim( $test_color, '#' );
		$r          = hexdec( substr( $hex, 0, 2 ) );
		$g          = hexdec( substr( $hex, 2, 2 ) );
		$b          = hexdec( substr( $hex, 4, 2 ) );
		$luminance  = calculate_relative_luminance( $r, $g, $b );
		$text_color = $this->get_contrasting_text_color( $test_color );

		return [
			'input_color'        => $test_color,
			'rgb'                => [ $r, $g, $b ],
			'luminance'          => $luminance,
			'recommended_text'   => $text_color,
			'is_dark_background' => $luminance <= 0.179,
		];
	}
}
