<?php

namespace FluentCrm\App\Services;

use FluentCrm\App\Models\Campaign;
use FluentCrm\App\Models\Lists;
use FluentCrm\App\Models\Subscriber;
use FluentCrm\App\Models\SubscriberPivot;
use FluentCrm\App\Models\SystemLog;
use FluentCrm\App\Models\Tag;
use FluentCrm\App\Models\Template;
use FluentCrm\App\Models\UrlStores;
use FluentCrm\App\Models\Webhook;
use FluentCrm\App\Services\BlockRender\BlockEditorHelper;
use FluentCrm\Framework\Support\Arr;
use FluentCrm\Framework\Support\Str;

class Helper
{
    const DEFAULT_CAMPAIGN_TEMPLATE_OPTION = 'default_campaign_template_id';

    /**
     * Determine if the active Easy Digital Downloads version is supported.
     *
     * FluentCRM's EDD integration is EDD 3+ only because the current
     * integration depends on the EDD 3 order APIs and database tables.
     *
     * @return bool
     */
    public static function isEdd3()
    {
        return class_exists('\Easy_Digital_Downloads')
            && defined('EDD_VERSION')
            && version_compare(EDD_VERSION, '3.0', '>=');
    }

    /**
     * Parse mixed input into an array.
     *
     * Accepts either a native array or a JSON string. For string inputs,
     * it attempts decoding the raw payload first, then retries with
     * `wp_unslash()` only when the string changes. Returns `$default` when
     * decoding fails or when the decoded JSON is not an array.
     *
     * @param mixed $value Input value from request/body.
     * @param array $default Fallback value when parsing fails.
     * @return array
     */
    public static function parseArrayOrJson($value, $default = [])
    {
        if (!is_string($value)) {
            return is_array($value) ? $value : $default;
        }

        $payloads = [$value];
        $unslashed = wp_unslash($value);
        if ($unslashed !== $value) {
            $payloads[] = $unslashed;
        }

        foreach ($payloads as $payload) {
            $decoded = json_decode($payload, true);
            if (json_last_error() === JSON_ERROR_NONE && is_array($decoded)) {
                return $decoded;
            }
        }

        return $default;
    }

    /**
     * Safely unserialize a value without ever instantiating objects.
     *
     * Behaves like WordPress core maybe_unserialize() for scalars and arrays, but passes
     * allowed_classes => false so a serialized-object payload can never trigger PHP Object
     * Injection (__wakeup / __destruct gadget chains). Use this instead of maybe_unserialize()
     * for any value that originates from, or can be influenced by, user input (e.g. stored
     * contact/company custom-field values).
     *
     * @param mixed $value The (possibly serialized) value read from storage.
     * @return mixed Unserialized array/scalar; serialized objects become inert incomplete classes.
     */
    public static function safeUnserialize($value)
    {
        if (is_serialized($value)) {
            return @unserialize(trim($value), ['allowed_classes' => false]);
        }

        return $value;
    }

    public static function getLinksFromString($string)
    {
        preg_match_all('/<a[^>]+(href\=["|\'](http.*?)["|\'])/m', $string, $urls);

        if (!empty($urls[2])) {
            return $urls[2];
        }

        return [];
    }

    public static function urlReplaces($string)
    {
        preg_match_all('/<a[^>]+(href=["\'](http[^"\']*)["\'])/m', $string, $urls);
        $replaces = $urls[1];
        $urls = $urls[2];

        // Replace '|' with '%7C' in the URLs
        $urls = array_map(function ($url) {
            return str_replace('|', '%7C', $url);
        }, $urls);

        $formatted = [];
        $baseUrl = self::getSiteUrl();

        foreach ($urls as $index => $url) {
            $urlSlug = UrlStores::getUrlSlug($url);
            if (!$urlSlug) {
                continue;
            }
            $formatted[$replaces[$index]] = add_query_arg([
                'ns_url' => $urlSlug
            ], $baseUrl);
        }
        return $formatted;
    }

    public static function attachUrls($html, $campaignUrls, $insertId, $hash = false)
    {
        $hasSmartUrl = strpos($html, 'smart_url') !== false;

        foreach ($campaignUrls as $src => $url) {
            $url .= '&mid=' . $insertId;
            if ($hash) {
                $url .= '&fch=' . substr($hash, 0, 8);
            }

            if ($hasSmartUrl && strpos($src, 'smart_url') !== false) {
                $url .= '&signed_hash=' . rawurlencode(self::signSmartUrlHash($hash));
            }

            $campaignUrls[$src] = 'href="' . $url . '"';
        }
        return str_replace(array_keys($campaignUrls), array_values($campaignUrls), $html);
    }

    public static function attachAnonymousUrls($html, $campaignUrls, $insertId, $hash = false)
    {
        $hasSmartUrl = strpos($html, 'smart_url') !== false;
        foreach ($campaignUrls as $src => $url) {
            $url .= '&mid=' . $insertId . '&ano=1';
            if ($hash) {
                $url .= '&fch=' . substr($hash, 0, 8);
            }

            if ($hasSmartUrl && strpos($src, 'smart_url') !== false) {
                $url .= '&signed_hash=' . rawurlencode(self::signSmartUrlHash($hash));
            }

            $campaignUrls[$src] = 'href="' . $url . '"';
        }

        return str_replace(array_keys($campaignUrls), array_values($campaignUrls), $html);
    }

    /**
     * Generate an HMAC signature for smart URL verification.
     *
     * Deliberately signed with wp_salt('auth') rather than a plugin-owned key
     * stored in the database: secrets in wp-config are harder to exfiltrate
     * than wp_options rows, and rotating the salts (the standard
     * post-compromise action) is SUPPOSED to revoke outstanding signed
     * artifacts — smart links in already-sent emails included. A signature
     * that fails after rotation only loses the verified-click flag; the
     * redirect and click tracking still work.
     *
     * @param string $hash The email hash to sign.
     * @return string
     */
    public static function signSmartUrlHash($hash)
    {
        return hash_hmac('sha256', $hash, wp_salt('auth'));
    }

    /**
     * Verify a smart URL signed hash.
     *
     * Supports both the new HMAC signatures and legacy bcrypt hashes
     * for backward compatibility with emails sent before the migration.
     *
     * @param string $emailHash The campaign email hash.
     * @param string $signedHash The signed hash from the URL.
     * @return bool
     */
    public static function verifySmartUrlHash($emailHash, $signedHash)
    {
        // HMAC verification (fast, constant-time)
        $expected = self::signSmartUrlHash($emailHash);
        if (hash_equals($expected, $signedHash)) {
            return true;
        }

        // Backward compatibility: verify legacy bcrypt hashes
        // for emails sent before the HMAC migration
        return wp_check_password($emailHash, $signedHash);
    }

    public static function generateEmailHash($insertId = null)
    {
        return wp_generate_uuid4();
    }

    public static function injectTrackerPixel($emailBody, $hash, $emailId = null)
    {
        if (!$hash) {
            return $emailBody;
        }

        $trackingType = fluentcrmTrackEmailOpen();

        if (!$trackingType) {
            return $emailBody;
        }

        $args = [
            FLUENTCRM_EXTERNAL_URL_PARAM => 1,
            'route'                      => 'open',
            '_e_hash'                    => $hash,
            '_e_id'                      => $emailId
        ];

        if ($trackingType === 'anonymous') {
            $args['ano'] = 1;
        }

        $trackImageUrl = add_query_arg($args, self::getSiteUrl());
        $trackPixelHtml = '<img src="' . esc_url($trackImageUrl) . '" alt="" width="1" height="1" border="0" style="display:block;width:1px;height:1px;border:0;outline:none;" />';

        if (strpos($emailBody, '{fluent_track_pixel}') !== false) {
            $emailBody = str_replace('{fluent_track_pixel}', $trackPixelHtml, $emailBody);
        } elseif (stripos($emailBody, '</body>') !== false) {
            // Case-insensitive replace before the first closing body tag.
            $emailBody = preg_replace('#</body>#i', $trackPixelHtml . '$0', $emailBody, 1);
        } else {
            // No body wrapper (e.g. raw_html templates with HTML fragments) —
            // append so the pixel is never silently dropped.
            $emailBody .= $trackPixelHtml;
        }

        return $emailBody;
    }

    public static function getProfileSections()
    {
        $sections = [
            'subscriber'        => [
                'name'    => 'subscriber',
                'title'   => __('Overview', 'fluent-crm'),
                'handler' => 'route'
            ],
            'subscriber_emails' => [
                'name'    => 'subscriber_emails',
                'title'   => __('Emails', 'fluent-crm'),
                'handler' => 'route'
            ],
        ];

        if (apply_filters('fluent_crm/sms_moudle_enabled', false)) {
            $sections['subscriber_sms'] = [
                'name'    => 'subscriber_sms',
                'title'   => __('SMS', 'fluent-crm'),
                'handler' => 'route'
            ];
        }

        if (self::getPurchaseHistoryProviders()) {
            $sections['subscriber_purchases'] = [
                'name'    => 'subscriber_purchases',
                'title'   => __('Purchases', 'fluent-crm'),
                'handler' => 'route'
            ];
        }

        if (defined('FLUENTFORM')) {
            $sections['subscriber_form_submissions'] = [
                'name'    => 'subscriber_form_submissions',
                'title'   => __('Forms', 'fluent-crm'),
                'handler' => 'route'
            ];
        }

        /**
         * Filter the list of support ticket providers.
         *
         * This filter allows you to modify the array of support ticket providers used in FluentCRM.
         *
         * @param array An array of support ticket providers.
         * @since 2.5.1
         *
         */
        $supportProviders = apply_filters('fluentcrm-support_tickets_providers', []);
        if ($supportProviders) {
            $sections['subscriber_support_tickets'] = [
                'name'    => 'subscriber_support_tickets',
                'title'   => __('Tickets', 'fluent-crm'),
                'handler' => 'route'
            ];
        }

        $sections['subscriber_notes'] = [
            'name'    => 'subscriber_notes',
            'title'   => __('Notes', 'fluent-crm'),
            'handler' => 'route'
        ];

        /**
         * Filter the contact profile sections in FluentCRM.
         *
         * This filter allows modification of the contact profile sections array in FluentCRM.
         *
         * @param array $sections An array of profile sections.
         * @since 2.2.0
         *
         */
        return apply_filters('fluentcrm_profile_sections', $sections);
    }

    public static function getDefaultEmailTemplate()
    {
        /**
         * Filter the default email design template.
         *
         * This filter allows you to modify the default email design template used by FluentCRM.
         *
         * @param string The default email design template. Default 'simple'.
         * @since 2.7.0
         *
         */
        return apply_filters('fluent_crm/default_email_design_template', 'simple');
    }

    public static function getDefaultCampaignTemplateId()
    {
        return absint(fluentcrm_get_option(self::DEFAULT_CAMPAIGN_TEMPLATE_OPTION, 0));
    }

    public static function getDefaultCampaignTemplate()
    {
        $templateId = self::getDefaultCampaignTemplateId();

        if (!$templateId) {
            return null;
        }

        $template = Template::emailTemplates(['publish', 'draft'])->find($templateId);

        return $template ?: null;
    }

    public static function setDefaultCampaignTemplateId($templateId)
    {
        $templateId = absint($templateId);

        if (!$templateId) {
            return null;
        }

        $template = Template::emailTemplates(['publish', 'draft'])->find($templateId);

        if (!$template) {
            return null;
        }

        fluentcrm_update_option(self::DEFAULT_CAMPAIGN_TEMPLATE_OPTION, $templateId);

        return $template;
    }

    public static function clearDefaultCampaignTemplateId()
    {
        return (bool) fluentcrm_delete_option(self::DEFAULT_CAMPAIGN_TEMPLATE_OPTION);
    }

    /**
     * Copy an email template's content and design onto a campaign and persist it.
     *
     * Fills the campaign with the template's body, subject, pre-header and design
     * template, then merges the template's config/footer settings over the
     * campaign's existing settings. Falls back to the default email design template
     * when the template has no `_design_template` meta. Saves the campaign and then
     * syncs any visual builder design via `syncVisualBuilderDesign()`.
     *
     * @param Campaign $campaign The campaign to apply the template to (modified and saved).
     * @param Template $template The source email template.
     * @return Campaign The saved campaign.
     */
    public static function applyTemplateToCampaign(Campaign $campaign, Template $template)
    {
        $designTemplate = sanitize_text_field(get_post_meta($template->ID, '_design_template', true));

        if (!$designTemplate) {
            $designTemplate = self::getDefaultEmailTemplate();
        }

        $campaign->fill([
            'template_id'      => absint($template->ID),
            'email_body'       => $template->post_content ?: '',
            'email_subject'    => sanitize_text_field(get_post_meta($template->ID, '_email_subject', true)),
            'email_pre_header' => sanitize_textarea_field($template->post_excerpt ?: ''),
            'design_template'  => $designTemplate,
            'settings'         => self::mergeTemplateSettings($campaign->settings, [
                'template_config' => get_post_meta($template->ID, '_template_config', true),
                'footer_settings' => get_post_meta($template->ID, '_footer_settings', true)
            ], $designTemplate)
        ])->save();

        self::syncVisualBuilderDesign($campaign, $template);

        return $campaign;
    }

    /**
     * Sync the visual builder design meta from a template onto a campaign.
     *
     * When the template's design template is `visual_builder`, copies its
     * `_visual_builder_design` meta onto the campaign (only if a design exists).
     * For any other design template, removes the campaign's stale
     * `_visual_builder_design` meta so it does not leak from a previous template.
     *
     * @param Campaign $campaign The campaign whose visual builder meta is updated.
     * @param Template $template The source email template.
     * @return void
     */
    protected static function syncVisualBuilderDesign(Campaign $campaign, Template $template)
    {
        $designTemplate = get_post_meta($template->ID, '_design_template', true);

        if ($designTemplate !== 'visual_builder') {
            fluentcrm_delete_campaign_meta($campaign->id, '_visual_builder_design');
            return;
        }

        $design = get_post_meta($template->ID, '_visual_builder_design', true);

        if ($design) {
            fluentcrm_update_campaign_meta($campaign->id, '_visual_builder_design', $design);
        }
    }

    /**
     * Merge a template's config and footer settings into a campaign's settings.
     *
     * Builds the resulting `template_config` by layering the template config over
     * the campaign's existing config (or the design template defaults when absent),
     * and normalizes `footer_settings` against a set of defaults. The `disable_footer`
     * and `custom_footer` flags are coerced to strict `'yes'`/`'no'` values, the
     * footer's `disable_footer` is mirrored into `template_config`, and the resolved
     * `design_template` is stamped onto `template_config`.
     *
     * @param array|mixed $campaignSettings The campaign's current settings (non-arrays are treated as empty).
     * @param array $templateSettings Template settings with `template_config` and `footer_settings` keys.
     * @param string $designTemplate The resolved design template slug.
     * @return array The merged settings array with `template_config` and `footer_settings`.
     */
    protected static function mergeTemplateSettings($campaignSettings, $templateSettings, $designTemplate)
    {
        $campaignSettings = is_array($campaignSettings) ? $campaignSettings : [];

        $templateConfig = Arr::get($templateSettings, 'template_config', []);
        $templateConfig = is_array($templateConfig) ? $templateConfig : [];
        $templateConfig = wp_parse_args(
            $templateConfig,
            Arr::get($campaignSettings, 'template_config', self::getTemplateConfig($designTemplate))
        );

        $footerSettings = Arr::get($templateSettings, 'footer_settings', []);
        $footerSettings = is_array($footerSettings) ? $footerSettings : [];
        $footerSettings = wp_parse_args($footerSettings, [
            'disable_footer'   => 'no',
            'custom_footer'    => 'no',
            'footer_content'   => '',
            'font_size'        => 13,
            'font_color'       => '#202020',
            'background_color' => 'transparent',
            'footer_padding'   => 20
        ]);

        $footerSettings['disable_footer'] = ($footerSettings['disable_footer'] === 'yes') ? 'yes' : 'no';
        $footerSettings['custom_footer'] = ($footerSettings['custom_footer'] === 'yes') ? 'yes' : 'no';

        $templateConfig['disable_footer'] = $footerSettings['disable_footer'];
        $templateConfig['design_template'] = $designTemplate;

        $campaignSettings['template_config'] = $templateConfig;
        $campaignSettings['footer_settings'] = $footerSettings;

        return $campaignSettings;
    }

    public static function getGlobalSmartCodes()
    {
        $subscriberCodes = [
            'key'        => 'contact',
            'title'      => __('Contact', 'fluent-crm'),
            /**
             * Filter the smartcodes available for FluentCRM contacts.
             *
             * This filter allows modification of the smartcodes that can be used for FluentCRM contacts.
             *
             * @param array $smartcodes An associative array of smartcodes and their descriptions.
             *     Default smartcodes:
             *     - '{{contact.full_name}}'      => 'Full Name'
             *     - '{{contact.prefix}}'         => 'Name Prefix'
             *     - '{{contact.first_name}}'     => 'First Name'
             *     - '{{contact.last_name}}'      => 'Last Name'
             *     - '{{contact.email}}'          => 'Contact Email'
             *     - '{{contact.id}}'             => 'Contact ID'
             *     - '{{contact.user_id}}'        => 'User ID'
             *     - '{{contact.address_line_1}}' => 'Address Line 1'
             *     - '{{contact.address_line_2}}' => 'Address Line 2'
             *     - '{{contact.city}}'           => 'City'
             *     - '{{contact.state}}'          => 'State'
             *     - '{{contact.postal_code}}'    => 'Postal Code'
             *     - '{{contact.country}}'        => 'Country'
             *     - '{{contact.phone}}'          => 'Phone Number'
             *     - '{{contact.status}}'         => 'Status'
             *     - '{{contact.date_of_birth}}'  => 'Date of Birth'
             * @since 1.0.0
             *
             */
            'shortcodes' => apply_filters('fluentcrm_contact_smartcodes', [
                '{{contact.full_name}}'      => __('Full Name', 'fluent-crm'),
                '{{contact.prefix}}'         => __('Name Prefix', 'fluent-crm'),
                '{{contact.first_name}}'     => __('First Name', 'fluent-crm'),
                '{{contact.last_name}}'      => __('Last Name', 'fluent-crm'),
                '{{contact.email}}'          => __('Contact Email', 'fluent-crm'),
                '{{contact.id}}'             => __('Contact ID', 'fluent-crm'),
                '{{contact.user_id}}'        => __('User ID', 'fluent-crm'),
                '{{contact.address_line_1}}' => __('Address Line 1', 'fluent-crm'),
                '{{contact.address_line_2}}' => __('Address Line 2', 'fluent-crm'),
                '{{contact.city}}'           => __('City', 'fluent-crm'),
                '{{contact.state}}'          => __('State', 'fluent-crm'),
                '{{contact.postal_code}}'    => __('Postal Code', 'fluent-crm'),
                '{{contact.country}}'        => __('Country', 'fluent-crm'),
                '{{contact.phone}}'          => __('Phone Number', 'fluent-crm'),
                '{{contact.status}}'         => __('Status', 'fluent-crm'),
                '{{contact.date_of_birth}}'  => __('Date of Birth', 'fluent-crm')
            ])
        ];

        if (self::isCompanyEnabled()) {
            $subscriberCodes['shortcodes']['{{contact.company.name}}'] = __('Company Name', 'fluent-crm');
            $subscriberCodes['shortcodes']['{{contact.company.industry}}'] = __('Company Industry', 'fluent-crm');
            $subscriberCodes['shortcodes']['{{contact.company.address}}'] = __('Company Address', 'fluent-crm');
        }

        $smartCodes[] = $subscriberCodes;

        $customFields = fluentcrm_get_option('contact_custom_fields', []);

        if ($customFields) {
            $shortcodes = [];
            foreach ($customFields as $item) {
                $shortcodes['{{contact.custom.' . $item['slug'] . '}}'] = $item['label'];
            }
            $smartCodes[] = [
                'key'        => 'contact_custom_fields',
                'title'      => __('Custom Fields', 'fluent-crm'),
                'shortcodes' => $shortcodes
            ];
        }

        $smartCodes[] = [
            'key'        => 'general',
            'title'      => __('General', 'fluent-crm'),
            /**
             * Filter to modify the general smartcodes used in FluentCRM.
             *
             * @param array $shortcodes An associative array of smartcodes and their descriptions.
             *
             *        Default smartcodes:
             *        - '{{crm.business_name}}' => 'Business Name'
             *        - '{{crm.business_address}}' => 'Business Address'
             *        - '{{wp.admin_email}}' => 'Admin Email'
             *        - '##wp.url##' => 'Site URL'
             *        - '{{other.date.+2 days}}' => 'Dynamic Date (ex: +2 days from now)'
             *        - '{{other.date_format.D, d M, Y}}' => 'Custom Date Format (Any PHP Date Format)'
             *        - '{{other.latest_post.title}}' => 'Latest Post Title (Published)'
             *        - '##crm.unsubscribe_url##' => 'Unsubscribe URL'
             *        - '##crm.manage_subscription_url##' => 'Manage Subscription URL'
             *        - '##web_preview_url##' => 'View On Browser URL'
             *        - '{{crm.unsubscribe_html|Unsubscribe}}' => 'Unsubscribe Hyperlink HTML'
             *        - '{{crm.manage_subscription_html|Manage Preference}}' => 'Manage Subscription Hyperlink HTML'
             * @since 2.7.0
             *
             */
            'shortcodes' => apply_filters('fluent_crm/general_smartcodes', [
                '{{crm.business_name}}'                              => __('Business Name', 'fluent-crm'),
                '{{crm.business_address}}'                           => __('Business Address', 'fluent-crm'),
                '{{wp.admin_email}}'                                 => __('Admin Email', 'fluent-crm'),
                '##wp.url##'                                         => __('Site URL', 'fluent-crm'),
                '{{other.date.+2 days}}'                             => __('Dynamic Date (ex: +2 days from now)', 'fluent-crm'),
                '{{other.date_format.D, d M, Y}}'                    => __('Custom Date Format (Any PHP Date Format)', 'fluent-crm'),
                '{{other.latest_post.title}}'                        => __('Latest Post Title (Published)', 'fluent-crm'),
                '##crm.unsubscribe_url##'                            => __('Unsubscribe URL', 'fluent-crm'),
                '##crm.manage_subscription_url##'                    => __('Manage Subscription URL', 'fluent-crm'),
                '##web_preview_url##'                                => __('View On Browser URL', 'fluent-crm'),
                '{{crm.unsubscribe_html|Unsubscribe}}'               => __('Unsubscribe Hyperlink HTML', 'fluent-crm'),
                '{{crm.manage_subscription_html|Manage Preference}}' => __('Manage Subscription Hyperlink HTML', 'fluent-crm'),
            ])
        ];

        /**
         * Filter the smart code groups.
         *
         * This filter allows modification of the smart code groups array.
         *
         * @param array $smartCodes An array of smart code groups.
         * @since 2.7.0
         *
         */
        return apply_filters('fluent_crm/smartcode_groups', $smartCodes);
    }

    public static function getExtendedSmartCodes()
    {
        /**
         * Filter the extended smart codes for FluentCRM.
         *
         * This filter allows you to modify the array of extended smart codes used in FluentCRM.
         *
         * @param array An array of extended smart codes.
         * @since 2.7.0
         *
         */
        return array_values(apply_filters('fluent_crm/extended_smart_codes', []));
    }

    public static function getDoubleOptinSettings()
    {
        if ($settings = fluentcrm_get_option('double_optin_settings', [])) {
            if (empty($settings['after_confirmation_type'])) {
                $settings['after_confirmation_type'] = 'message';
                $settings['after_conf_redirect_url'] = '';
            }
            return $settings;
        }

        $businessName = '';
        $businessEmail = '';
        $businessAddress = '';
        $subject = 'Please Confirm Subscription';
        $business = fluentcrmGetGlobalSettings('business_settings', []);

        if (!empty($business['business_name'])) {
            $businessName = $business['business_name'];
            $subject = "{$businessName} : Please Confirm Subscription";
            if (!empty($business['business_address'])) {
                $businessAddress = $business['business_address'];
            }
        }

        $emailSettings = fluentcrmGetGlobalSettings('email_settings', []);
        if (!empty($emailSettings['from_email'])) {
            $businessEmail = $emailSettings['from_email'];
        }

        return [
            'email_subject'           => $subject,
            'email_pre_header'        => '',
            'design_template'         => 'simple',
            'email_body'              => '<h2>Please Confirm Subscription</h2><p><a style="color: #ffffff; background-color: #454545; font-size: 16px; border-radius: 5px; text-decoration: none; font-weight: normal; font-style: normal; padding: 0.8rem 1rem; border-color: #0072ff;" href="#activate_link#">Yes, subscribe me to the mailing list</a></p><p>&nbsp;</p><p>If you received this email by mistake, simply delete it. You won\'t be subscribed if you don\'t click the confirmation link above.</p><p>For questions about this list, please contact:<br />' . $businessEmail . '</p>',
            'after_confirmation_type' => 'message',
            'after_confirm_message'   => '<h2>Subscription Confirmed</h2><p>Your subscription to our list has been confirmed.</p><p>Thank you for subscribing!</p><p>&nbsp;</p><p>' . $businessName . '</p><p>' . $businessAddress . '</p><p>&nbsp;</p><p><a style="color: #ffffff; background-color: #404040; font-size: 16px; border-radius: 5px; text-decoration: none; font-weight: normal; font-style: normal; padding: 0.8rem 1rem; border-color: #0072ff;" href="' . site_url() . '">Continue to our Website</a></p>',
            'after_conf_redirect_url' => '',
        ];
    }

    public static function getEmailDesignTemplates()
    {
        $defaultDesignConfig = BlockEditorHelper::getDefaultPrefConfig();

        if (defined('FLUENTCAMPAIGN')) {
            $defaultDesignConfig['disable_footer'] = 'no';
        }

        $plainConfig = $defaultDesignConfig;
        $plainConfig['body_bg_color'] = '#FFFFFF';
        $plainConfig['design_template'] = 'plain';

        $classicConfig = $plainConfig;
        $classicConfig['design_template'] = 'classic';

        $emptyConfig = [
            'content_font_family' => "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'",
        ];

        /**
         * Filter the email design templates available in FluentCRM.
         *
         * @param array {
         *     An array of email design templates.
         *
         * @type array $simple {
         * @type string $id The template ID.
         * @type string $label The template label.
         * @type string $image The URL to the template image.
         * @type array $config The configuration array for the template.
         * @type bool $use_gutenberg Whether to use Gutenberg editor.
         *     }
         * @type array $plain {
         * @type string $id The template ID.
         * @type string $label The template label.
         * @type string $image The URL to the template image.
         * @type array $config The configuration array for the template.
         * @type bool $use_gutenberg Whether to use Gutenberg editor.
         *     }
         * @type array $classic {
         * @type string $id The template ID.
         * @type string $label The template label.
         * @type string $image The URL to the template image.
         * @type array $config The configuration array for the template.
         * @type bool $use_gutenberg Whether to use Gutenberg editor.
         *     }
         * @type array $raw_classic {
         * @type string $id The template ID.
         * @type string $label The template label.
         * @type string $image The URL to the template image.
         * @type array $config The configuration array for the template.
         * @type bool $use_gutenberg Whether to use Gutenberg editor.
         * @type string $template_type The type of the template.
         * @type string $template_info Additional information about the template.
         *     }
         * @type array $raw_html {
         * @type string $id The template ID.
         * @type string $label The template label.
         * @type string $image The URL to the template image.
         * @type array $config The configuration array for the template.
         * @type bool $use_gutenberg Whether to use Gutenberg editor.
         * @type string $template_type The type of the template.
         * @type string $template_info Additional information about the template.
         *     }
         * }
         * @since 2.6.51
         *
         */
        $templates = apply_filters('fluent_crm/email_design_templates', [
            'simple'      => [
                'id'            => 'simple',
                'label'         => __('Simple Boxed', 'fluent-crm'),
                'image'         => fluentCrmMix('images/gutenberg-builder.svg'),
                'config'        => $defaultDesignConfig,
                'use_gutenberg' => true
            ],
            'plain'       => [
                'id'            => 'plain',
                'label'         => __('Plain Centered', 'fluent-crm'),
                'image'         => fluentCrmMix('images/plain_centered.svg'),
                'config'        => $plainConfig,
                'use_gutenberg' => true
            ],
            'classic'     => [
                'id'            => 'classic',
                'label'         => __('Plain Left', 'fluent-crm'),
                'image'         => fluentCrmMix('images/plain_left.svg'),
                'config'        => $classicConfig,
                'use_gutenberg' => true
            ],
            'raw_classic' => [
                'id'            => 'raw_classic',
                'label'         => __('Classic Editor', 'fluent-crm'),
                'image'         => fluentCrmMix('images/classic-editor.svg'),
                'config'        => $emptyConfig,
                'use_gutenberg' => false,
                'template_type' => 'classic_editor',
                'template_info' => '<h3>Classic Text Based Email</h3><p>Type your simple email and FluentCRM will send that without altering any design processing. The default footer will be injected after your content if footer is not disabled.</p>'
            ],
            'raw_html'    => [
                'id'            => 'raw_html',
                'label'         => __('Raw HTML', 'fluent-crm'),
                'image'         => fluentCrmMix('images/html-editor.svg'),
                'config'        => [],
                'use_gutenberg' => false,
                'template_type' => 'raw_text_box',
                'template_info' => '<h3>Raw HTML Template</h3><p>You can use any type of valid html and FluentCRM will send that without altering any design processing.</p>'
            ]
        ]);

        if (!defined('FLUENTCAMPAIGN')) {
            $templates['visual_builder'] = [
                'id'            => 'visual_builder',
                'label'         => __('Visual Builder', 'fluent-crm'),
                'image'         => fluentCrmMix('images/visual-builder.svg'),
                'config'        => $emptyConfig,
                'use_gutenberg' => false,
                'template_type' => 'visual_builder_demo'
            ];
        }

        return $templates;
    }

    public static function getTemplateConfig($templateName = '', $withGlobal = true)
    {
        if (!$templateName) {
            $templateName = self::getDefaultEmailTemplate();
        }
        $templates = self::getEmailDesignTemplates();
        if (!isset($templates[$templateName])) {
            $templateName = 'simple';
        }

        $config = Arr::get($templates, $templateName . '.config', []);

        if ($withGlobal) {
            $globalSettings = fluentcrm_get_option('global_email_style_config', []);
            return wp_parse_args($globalSettings, $config);
        }

        return $config;

    }

    public static function getActivatedFeatures()
    {
        return [
            'fluentcampaign'       => defined('FLUENTCAMPAIGN_FRAMEWORK_VERSION'),
            'frontend_portal'      => defined('FLUENTCAMPAIGN_FRAMEWORK_VERSION') && self::isExperimentalEnabled('frontend_portal'),
            'company_module'       => self::isCompanyEnabled(),
            'event_tracking'       => self::isExperimentalEnabled('event_tracking'),
            /**
             * Filter to disable email open tracking in FluentCRM.
             *
             * This filter allows to disable email open tracking globally.
             *
             * @param bool  Whether to disable email open tracking. Default is false.
             * @return bool Filtered value to enable or disable email open tracking.
             * @since 2.8.0
             *
             */
            'email_open_tracking'  => !apply_filters('fluentcrm_disable_email_open_tracking', false),
            /**
             * Filter to enable or disable email click tracking.
             *
             * This filter allows you to control whether email click tracking is enabled or disabled.
             *
             * @param bool Whether to enable email click tracking. Default true.
             * @since 2.8.0
             *
             */
            'email_click_tracking' => apply_filters('fluent_crm/track_click', true),
        ];
    }

    public static function getContactPrefixes($withKeyed = false)
    {
        /**
         * Base contact prefixes with translatable labels.
         * These will show up in Loco Translate under the 'fluent-crm' domain.
         */
        $prefixes = [
            __('Mr', 'fluent-crm'),
            __('Mrs', 'fluent-crm'),
            __('Ms', 'fluent-crm')
        ];

        /**
         * Filter the contact name prefixes.
         *
         * This filter is deprecated. Please use fluent_crm/contact_name_prefixes instead.
         *
         * @param array An array of contact name prefixes.
         * @deprecated 2.7.0 Use fluent_crm/contact_name_prefixes instead.
         *
         * @since 2.5.5
         *
         */
        $prefixes = apply_filters('fluentcrm_contact_name_prefixes', $prefixes);

        /**
         * Filter the contact name prefixes.
         *
         * @param array $prefixes An array of contact name prefixes.
         * @since 2.7.0
         *
         */
        $prefixes = apply_filters('fluent_crm/contact_name_prefixes', $prefixes);

        if ($withKeyed) {
            $keyedNames = [];
            foreach ($prefixes as $prefix) {
                $keyedNames[$prefix] = $prefix;
            }
            return $keyedNames;
        }
        return $prefixes;
    }

    public static function getGlobalEmailSettings()
    {
        $defaultFooter = '{{crm.business_name}}, {{crm.business_address}}<br>Don\'t like these emails? <a href="##crm.unsubscribe_url##">Unsubscribe</a> or <a href="##crm.manage_subscription_url##">Manage Email Subscriptions</a>';

        $defaults = [
            'from_name'         => '',
            'from_email'        => '',
            'emails_per_second' => 15,
            'email_footer'      => $defaultFooter,
            'pref_list_type'    => 'no',
            'pref_list_items'   => [],
            'pref_form'         => 'no',
            'pref_general'      => ['first_name', 'last_name'],
            'pref_custom'       => [],
            'show_on_page'      => 'no',
            'pref_page_id'      => ''
        ];

        if ($settings = fluentcrmGetGlobalSettings('email_settings', [])) {
            if (empty($settings['email_footer'])) {
                $settings['email_footer'] = $defaultFooter;
            }

            if (empty($settings['pref_form'])) {
                $settings['pref_form'] = 'no';
                $settings['pref_general'] = ['first_name', 'last_name'];
                $settings['pref_custom'] = [];
            }

            if (!isset($settings['pref_general'])) {
                $settings['pref_general'] = [];
            }

            if (!isset($settings['pref_custom'])) {
                $settings['pref_custom'] = [];
            }

            return wp_parse_args($settings, $defaults);
        }

        return $defaults;
    }

    public static function getPurchaseHistoryProviders()
    {
        $validProviders = [];

        if (defined('FLUENTCART_VERSION')) {
            $validProviders['fluent_cart'] = [
                'title' => __('FluentCart Purchase History', 'fluent-crm'),
                'name'  => __('FluentCart', 'fluent-crm')
            ];
        }

        if (defined('WC_PLUGIN_FILE')) {
            $validProviders['woocommerce'] = [
                'title' => __('Woocommerce Purchase History', 'fluent-crm'),
                'name'  => __('WooCommerce', 'fluent-crm')
            ];
        }

        if (self::isEdd3()) {
            $validProviders['edd'] = [
                'title' => __('EDD Purchase History', 'fluent-crm'),
                'name'  => __('Easy Digital Downloads', 'fluent-crm')
            ];
        }

        if (defined('WPPAYFORM_VERSION')) {
            $validProviders['payform'] = [
                'title' => __('Paymattic Purchase History', 'fluent-crm'),
                'name'  => __('Paymattic', 'fluent-crm')
            ];
        }

        if (defined('PMPRO_VERSION') && defined('FLUENTCAMPAIGN')) {
            $validProviders['pmpro'] = [
                'title' => __('Paid Membership Pro Purchase History', 'fluent-crm'),
                'name'  => __('Paid Membership Pro', 'fluent-crm')
            ];
        }

        /**
         * Filter the list of valid purchase history providers.
         *
         * This filter allows modification of the valid purchase history providers used in FluentCRM.
         *
         * @param array $validProviders An array of valid purchase history providers.
         * @since 2.7.0
         *
         */
        return apply_filters('fluent_crm/purchase_history_providers', $validProviders);
    }

    public static function getThemePrefScheme()
    {
        static $pref;
        if (!$pref) {

            $color_palette = [
                [
                    "name"  => __("Black", "fluent-crm"),
                    "slug"  => "black",
                    "color" => "#000000"
                ],
                [
                    "name"  => __("Cyan bluish gray", "fluent-crm"),
                    "slug"  => "cyan-bluish-gray",
                    "color" => "#abb8c3"
                ],
                [
                    "name"  => __("White", "fluent-crm"),
                    "slug"  => "white",
                    "color" => "#ffffff"
                ],
                [
                    "name"  => __("Pale pink", "fluent-crm"),
                    "slug"  => "pale-pink",
                    "color" => "#f78da7"
                ],
                [
                    "name"  => __("Luminous vivid orange", "fluent-crm"),
                    "slug"  => "luminous-vivid-orange",
                    "color" => "#ff6900"
                ],
                [
                    "name"  => __("Luminous vivid amber", "fluent-crm"),
                    "slug"  => "luminous-vivid-amber",
                    "color" => "#fcb900"
                ],
                [
                    "name"  => __("Light green cyan", "fluent-crm"),
                    "slug"  => "light-green-cyan",
                    "color" => "#7bdcb5"
                ],
                [
                    "name"  => __("Vivid green cyan", "fluent-crm"),
                    "slug"  => "vivid-green-cyan",
                    "color" => "#00d084"
                ],
                [
                    "name"  => __("Pale cyan blue", "fluent-crm"),
                    "slug"  => "pale-cyan-blue",
                    "color" => "#8ed1fc"
                ],
                [
                    "name"  => __("Vivid cyan blue", "fluent-crm"),
                    "slug"  => "vivid-cyan-blue",
                    "color" => "#0693e3"
                ],
                [
                    "name"  => __("Vivid purple", "fluent-crm"),
                    "slug"  => "vivid-purple",
                    "color" => "#9b51e0"
                ]
            ];

            $font_sizes = [
                [
                    'name'      => __('Small', 'fluent-crm'),
                    'shortName' => 'S',
                    'size'      => 14,
                    'slug'      => 'small'
                ],
                [
                    'name'      => __('Medium', 'fluent-crm'),
                    'shortName' => 'M',
                    'size'      => 18,
                    'slug'      => 'medium'
                ],
                [
                    'name'      => __('Large', 'fluent-crm'),
                    'shortName' => 'L',
                    'size'      => 24,
                    'slug'      => 'large'
                ],
                [
                    'name'      => __('Larger', 'fluent-crm'),
                    'shortName' => 'XL',
                    'size'      => 32,
                    'slug'      => 'larger'
                ]
            ];

            /**
             * Filter the theme preferences for FluentCRM.
             *
             * This filter allows modification of the theme preferences, including colors and font sizes.
             *
             * @param array {
             *     The theme preferences.
             *
             * @type array $colors The color palette.
             * @type array $font_sizes The font sizes.
             * }
             * @since 2.6.51
             *
             */
            $pref = apply_filters('fluent_crm/theme_pref', [
                'colors'     => (array)$color_palette,
                'font_sizes' => (array)$font_sizes
            ]);
        }

        return $pref;

    }

    public static function funnelLabelColors()
    {
        $colors = [
            '#D6D8FF',
            '#D4ECD6',
            '#FEE8B5',
            '#D7E8EF',
            '#FFCACA',
            '#F8D7C4',
            '#D4D7DC',
            '#FFD9E3'
        ];

        /**
         * Filter the funnel label colors.
         *
         * This filter allows modification of the funnel label colors.
         *
         * @param array $colors An array of colors for the funnel labels.
         * @since 2.9.30
         *
         */
        return apply_filters('fluent_crm/funnel_label_color', $colors);
    }

    public static function getColorSchemeValue($colorName)
    {
        static $colorMap = [];
        if (isset($colorMap[$colorName])) {
            return $colorMap[$colorName];
        }
        $pref = self::getThemePrefScheme();
        $colors = $pref['colors'];
        foreach ($colors as $color) {
            $colorMap[$color['slug']] = $color['color'];
            if ($color['slug'] == $colorName) {
                return $color['color'];
            }
        }

        $color_palette = self::getThemeColorPalette();
        return self::getColorBySlug($color_palette, $colorName);
    }

    public static function getColorBySlug($color_palette, $slug)
    {
        if (!$color_palette || !is_array($color_palette)) {
            return null;
        }

        foreach ($color_palette as $color) {
            if (isset($color['slug']) && isset($color['color']) && $color['slug'] === $slug) {
                return $color['color'];
            }
        }

        return null;
    }

    public static function getThemeColorPalette()
    {
        $color_palette = current((array)get_theme_support('editor-color-palette'));
        $theme_json_path = get_theme_file_path('theme.json');

        if (file_exists($theme_json_path)) {
            $theme_json = json_decode(file_get_contents($theme_json_path), true);

            if (isset($theme_json['settings']['color']['palette'])) {
                $color_palette = $theme_json['settings']['color']['palette'];
            }
        }
        if (!$color_palette) {
            $color_palette = [];
        }

        return (array)$color_palette;
    }

    public static function getThemeFontSizes()
    {
        $font_sizes = current((array)get_theme_support('editor-font-sizes'));
        $theme_json_path = get_theme_file_path('theme.json');

        if (file_exists($theme_json_path)) {
            $theme_json = json_decode(file_get_contents($theme_json_path), true);

            if (isset($theme_json['settings']['typography']['fontSizes'])) {
                $font_sizes = $theme_json['settings']['typography']['fontSizes'];
            }
        }

        return $font_sizes;
    }


    public static function generateThemePrefCss()
    {
        static $color_css;
        if ($color_css) {
            return $color_css;
        }
        $pref = self::getThemePrefScheme();

        $css = '';
        if (isset($pref['colors'])) {
            foreach ($pref['colors'] as $color) {
                if (isset($color['slug']) && isset($color['color'])) {
                    $slug = self::kebabCase($color['slug']);
                    $css .= '.has-' . $slug . '-color  { color: ' . $color['color'] . ';} ';
                    $css .= '.has-' . $slug . '-background-color  { background-color: ' . $color['color'] . '; background: ' . $color['color'] . '; } ';
                    $css .= 'a.has-' . $slug . '-background-color  { border: 1px solid ' . $color['color'] . '; } ';
                }
            }
        }

        if (isset($pref['font_sizes'])) {
            foreach ($pref['font_sizes'] as $size) {
                if (isset($size['slug']) && isset($size['size'])) {
                    $slug = self::kebabCase($size['slug']);
                    $css .= '.fc_email_body .has-' . $slug . '-font-size  { font-size: ' . $size['size'] . 'px !important;} ';
                }
            }
        }

        // Generate CSS for theme color palette
        $themeColors = self::getThemeColorPalette();
        if (!empty($themeColors)) {
            foreach ($themeColors as $themeColor) {
                $color = $themeColor['color'];

                // Converts 'palette1' to 'palette-1'
                $slug = self::normalizeColorSlug($themeColor['slug']);

                // Stores the original slug value without modification
                $originalSlug = $themeColor['slug'];

                $css .= ".fc_email_body .has-{$originalSlug}-background-color { background-color: {$color};}";
                $css .= ".fc_email_body .has-{$originalSlug}-color { color: {$color};}";
                $css .= ".fc_email_body .has-{$slug}-background-color { background-color: {$color};}";
                $css .= ".fc_email_body .has-{$slug}-color { color: {$color};}";
            }
        }

        // Generate CSS for theme font sizes
        $themeFontSizes = self::getThemeFontSizes();
        if (!empty($themeFontSizes)) {
            foreach ($themeFontSizes as $themeFontSize) {
                $size = $themeFontSize['size'];
                $slug = $themeFontSize['slug'];
                $css .= ".fc_email_body .has-{$slug}-font-size { font-size: {$size} !important;}";
            }
        }

        $color_css = $css;
        return $color_css;
    }

    private static function normalizeColorSlug($slug)
    {
        // Normalize the slug
        $slug = strtolower($slug);

        // If the slug already follows "text-number" format, return it as is
        if (preg_match('/^(.*?)-(\d+)$/', $slug, $matches)) {
            return $slug;
        }

        // Otherwise, fix cases like "theme-palette1" -> "theme-palette-1"
        $parts = preg_split('/(\d+)/', $slug, -1, PREG_SPLIT_DELIM_CAPTURE);

        if (count($parts) > 1 && ctype_digit(trim($parts[count($parts) - 2]))) {
            return implode('-', array_filter($parts));
        }

        return $slug;
    }

    public static function kebabCase($string)
    {
        return implode('-', array_filter(preg_split('/(\d)/', strtolower(strval($string)), -1, PREG_SPLIT_DELIM_CAPTURE)));
    }

    public static function getMailHeadersFromSettings($emailSettings = [])
    {
        if (empty($emailSettings) || Arr::get($emailSettings, 'is_custom') == 'no') {
            $emailSettings = fluentcrmGetGlobalSettings('email_settings', []);
        }

        if (empty($emailSettings)) {
            return [];
        }

        $headers = [];
        if (Arr::get($emailSettings, 'from_name') && Arr::get($emailSettings, 'from_email')) {
            $headers['From'] = $emailSettings['from_name'] . ' <' . $emailSettings['from_email'] . '>';
        } else if ($fromEmail = Arr::get($emailSettings, 'from_email')) {
            $headers['From'] = $fromEmail;
        }

        if (Arr::get($emailSettings, 'reply_to_name') && Arr::get($emailSettings, 'reply_to_email')) {
            $headers['Reply-To'] = $emailSettings['reply_to_name'] . ' <' . $emailSettings['reply_to_email'] . '>';
        } else if ($replyTo = Arr::get($emailSettings, 'reply_to_email')) {
            $headers['Reply-To'] = $replyTo;
        }

        return $headers;
    }

    public static function getMailHeader($existingHeader = [])
    {
        if (!empty($existingHeader['From'])) {
            return $existingHeader;
        }

        if (!empty($existingHeader['Reply-To'])) {
            return $existingHeader;
        }

        $headers = [];
        static $globalHeaders;
        if ($globalHeaders) {
            return $globalHeaders;
        }

        $globalEmailSettings = fluentcrmGetGlobalSettings('email_settings', []);

        $fromName = Arr::get($globalEmailSettings, 'from_name');
        $fromEmail = Arr::get($globalEmailSettings, 'from_email');

        if ($fromName && $fromEmail) {
            $headers['From'] = $fromName . ' <' . $fromEmail . '>';
        } else if ($fromEmail) {
            $headers['From'] = $fromEmail;
        }

        $replyName = Arr::get($globalEmailSettings, 'reply_to_name');
        $replyEmail = Arr::get($globalEmailSettings, 'reply_to_email');

        if ($replyName && $replyEmail) {
            $headers['Reply-To'] = $replyName . ' <' . $replyEmail . '>';
        } else if ($replyEmail) {
            $headers['Reply-To'] = $replyEmail;
        }

        $globalHeaders = $headers;

        return $globalHeaders;
    }

    public static function recordCampaignRevenue($campaignId, $amount, $orderId, $currency = 'USD', $isRefunded = false)
    {
        $currency = strtolower($currency);
        $existing = fluentcrm_get_campaign_meta($campaignId, '_campaign_revenue');
        $data = ['orderIds' => []];

        if ($existing && isset($existing->value['orderIds']) && $existing->value['orderIds']) {
            $data['orderIds'] = $existing->value['orderIds'];
            $data[$currency] = $existing->value[$currency];
        } else {
            $data[$currency] = 0;
        }
        if (!in_array($orderId, $data['orderIds'])) {
            $data['orderIds'][] = $orderId;
        }

        if ($isRefunded) {
            if ($data[$currency] > $amount) {
                $data[$currency] -= $amount;
                $key = array_search($orderId, $data['orderIds']);
                if ($key !== false) {
                    unset($data['orderIds'][$key]);
                    $data['orderIds'] = array_values($data['orderIds']);
                }
            }
        } else {
            if ($existing && isset($existing->value['orderIds']) && in_array($orderId, $existing->value['orderIds'])) {
                $data[$currency] = $existing->value[$currency];
            } else {
                $data[$currency] += $amount;
            }
        }

        return fluentcrm_update_campaign_meta($campaignId, '_campaign_revenue', $data);
    }

    public static function getWPMapUserInfo($user)
    {
        if (is_numeric($user)) {
            $user = get_user_by('ID', $user);
        }

        if (!$user) {
            return [];
        }

        $subscriber = array_filter([
            'user_id'    => $user->ID,
            'first_name' => $user->first_name,
            'last_name'  => $user->last_name,
            'email'      => $user->user_email
        ]);

        if ($address1 = get_user_meta($user->ID, 'billing_address_1', true)) {
            $subscriber['address_line_1'] = $address1;
        }

        if ($address2 = get_user_meta($user->ID, 'billing_address_2', true)) {
            $subscriber['address_line_2'] = $address2;
        }

        if ($city = get_user_meta($user->ID, 'billing_city', true)) {
            $subscriber['city'] = $city;
        }

        if ($postalCode = get_user_meta($user->ID, 'billing_postcode', true)) {
            $subscriber['postal_code'] = $postalCode;
        }

        if ($country = get_user_meta($user->ID, 'billing_country', true)) {
            $subscriber['country'] = $country;
        }

        if ($state = get_user_meta($user->ID, 'billing_state', true)) {
            $subscriber['state'] = $state;
        }

        if ($phone = get_user_meta($user->ID, 'billing_phone', true)) {
            $subscriber['phone'] = $phone;
        }

        /**
         * Filter the subscriber data before it is processed.
         *
         * This filter allows you to modify the subscriber data before it is processed.
         *
         * @param array $subscriber The subscriber data.
         * @param object $user The WordPress user object.
         * @since 2.5.3
         *
         */
        $subscriber = apply_filters('fluentcrm_user_map_data', $subscriber, $user);

        $fillables = (new Subscriber)->getFillable();

        $subscriber = Arr::only($subscriber, $fillables);

        return array_filter($subscriber);
    }

    public static function isUserSyncEnabled()
    {
        static $result = null;
        if ($result === null) {
            $settings = fluentcrm_get_option('user_syncing_settings', []);
            $result = $settings && isset($settings['status']) && $settings['status'] == 'yes';
        }

        return $result;
    }

    public static function isContactDeleteOnUserDeleteEnabled()
    {
        static $result = null;
        if ($result === null) {
            $settings = fluentcrm_get_option('user_syncing_settings', []);
            $result = $settings && isset($settings['delete_contact_on_user_delete']) && $settings['delete_contact_on_user_delete'] == 'yes';
        }

        return $result;
    }

    public static function deleteContacts($contactIds)
    {
        if (!$contactIds) {
            return false;
        }
        if (!is_array($contactIds)) {
            $contactIds = (array)$contactIds;
        }

        do_action('fluentcrm_before_subscribers_deleted', $contactIds);
        Subscriber::whereIn('id', $contactIds)->delete();
        do_action('fluentcrm_after_subscribers_deleted', $contactIds);
        return true;
    }

    public static function sendDoubleOptin($contactIds)
    {
        if (!$contactIds) {
            return false;
        }
        if (!is_array($contactIds)) {
            $contactIds = (array)$contactIds;
        }

        $subscribers = Subscriber::whereIn('id', $contactIds)->where('status', 'pending')->get();
        foreach ($subscribers as $subscriber) {
            $subscriber->sendDoubleOptinEmail();
        }
        return true;
    }

    public static function hasComplianceText($text)
    {
        /**
         * Filters the compliance check string result.
         *
         * This filter allows you to modify the result of the compliance check string.
         *
         * @param mixed $result The result of the compliance check string.
         * @param string $text The text being checked for compliance.
         * @since 2.8.33
         *
         */
        $result = apply_filters('fluent_crm/disable_check_compliance_string', false, $text);

        if ($result) {
            return true; // directly return true if the filter returns true, would be better if we could return the $result of the filter
        }

        return (bool)preg_match('/##crm\.manage_subscription_url##|##crm\.unsubscribe_url##|\{\{crm\.unsubscribe_html|\{\{crm\.manage_subscription_html|\{\{crm_global_email_footer\}\}/', $text);
    }

    public static function maybeDisableEmojiOnEmail()
    {
        static $disabled;
        if ($disabled) {
            return;
        }
        /**
         * Filter to disable emoji conversion to images in FluentCRM.
         *
         * This filter allows you to disable the conversion of emojis to images.
         * By default, this filter is set to true, meaning the conversion is enabled.
         * You can use this filter to return false if you want to disable the conversion.
         *
         * @param bool Whether to disable emoji conversion to images. Default true.
         * @since 2.7.0
         *
         */
        if (apply_filters('fluent_crm/disable_emoji_to_image', true)) {
            remove_filter('wp_mail', 'wp_staticize_emoji_for_email');
        }
        $disabled = true;
    }

    /**
     * Country code from Cloudflare's CF-IPCountry header, or '' when unavailable.
     *
     * The header is only meaningful when the request actually transited Cloudflare —
     * a direct request can set it freely and pollute contact countries. By default we
     * require the companion CF-Ray marker (set by Cloudflare on every proxied
     * request); sites behind stricter or unusual proxies can override the decision
     * via the fluent_crm/trust_cf_ipcountry filter. Note: CF-Ray is itself forgeable
     * on direct-to-origin requests, so this is a best-effort heuristic, not proof of
     * Cloudflare transit — real validation would check the connecting IP against
     * Cloudflare's published ranges.
     */
    public static function getCfIpCountry()
    {
        $countryCode = strtoupper(sanitize_text_field($_SERVER['HTTP_CF_IPCOUNTRY'] ?? ''));

        if (!$countryCode || !preg_match('/^[A-Z]{2}$/', $countryCode) || $countryCode === 'XX') {
            return '';
        }

        $trusted = apply_filters('fluent_crm/trust_cf_ipcountry', !empty($_SERVER['HTTP_CF_RAY']));

        return $trusted ? $countryCode : '';
    }

    public static function getPublicLists()
    {
        $emailSettings = self::getGlobalEmailSettings();
        $lists = [];
        $preListType = Arr::get($emailSettings, 'pref_list_type', 'none');
        if ($preListType == 'filtered_only') {
            $prefListItems = Arr::get($emailSettings, 'pref_list_items', []);
            if ($prefListItems) {
                $lists = Lists::whereIn('id', $prefListItems)->get();
                if ($lists->isEmpty()) {
                    return [];
                }
            }
        } else if ($preListType == 'all') {
            $lists = Lists::get();
            if ($lists->isEmpty()) {
                return [];
            }
        }

        return $lists;
    }

    public static function getAdvancedFilterOptions()
    {
        $groups = [
            'subscriber' => [
                'label'    => __('Contact', 'fluent-crm'),
                'value'    => 'subscriber',
                'children' => [
                    [
                        'label' => __('General Properties', 'fluent-crm'),
                        'value' => 'search',
                    ],
                    [
                        'label' => __('First Name', 'fluent-crm'),
                        'value' => 'first_name',
                        'type'  => 'nullable_text',
                    ],
                    [
                        'label' => __('Last Name', 'fluent-crm'),
                        'value' => 'last_name',
                        'type'  => 'nullable_text',
                    ],
                    [
                        'label' => __('Email', 'fluent-crm'),
                        'value' => 'email',
                    ],
                    [
                        'label' => __('Address Line 1', 'fluent-crm'),
                        'value' => 'address_line_1',
                        'type'  => 'nullable_text',
                    ],
                    [
                        'label' => __('Address Line 2', 'fluent-crm'),
                        'value' => 'address_line_2',
                        'type'  => 'nullable_text',
                    ],
                    [
                        'label' => __('City', 'fluent-crm'),
                        'value' => 'city',
                        'type'  => 'nullable_text',
                    ],
                    [
                        'label' => __('State', 'fluent-crm'),
                        'value' => 'state',
                        'type'  => 'nullable_text',
                    ],
                    [
                        'label' => __('Postal Code', 'fluent-crm'),
                        'value' => 'postal_code',
                        'type'  => 'nullable_text',
                    ],
                    [
                        'label'             => __('Country', 'fluent-crm'),
                        'value'             => 'country',
                        'type'              => 'selections',
                        'component'         => 'options_selector',
                        'option_key'        => 'countries',
                        'is_multiple'       => true,
                        'is_singular_value' => true
                    ],
                    [
                        'label' => __('Phone', 'fluent-crm'),
                        'value' => 'phone',
                        'type'  => 'nullable_text',
                    ],
                    [
                        'label' => __('WP User ID', 'fluent-crm'),
                        'value' => 'user_id',
                        'type'  => 'numeric',
                    ],
                    [
                        'label'       => __('Name Prefix (Title)', 'fluent-crm'),
                        'value'       => 'prefix',
                        'type'        => 'selections',
                        'options'     => self::getContactPrefixes(true),
                        'is_multiple' => true,
                        'is_only_in'  => true
                    ],
                    [
                        'label' => __('Source', 'fluent-crm'),
                        'value' => 'source'
                    ],
                    [
                        'label' => __('Date of Birth', 'fluent-crm'),
                        'value' => 'date_of_birth',
                        'type'  => 'dates',
                    ],
                    [
                        'label' => __('Last Activity', 'fluent-crm'),
                        'value' => 'last_activity',
                        'type'  => 'dates',
                    ],
                    [
                        'label' => __('Created At', 'fluent-crm'),
                        'value' => 'created_at',
                        'type'  => 'dates',
                    ],

                ],
            ],
            'segment'    => [
                'label'    => __('Contact Segment', 'fluent-crm'),
                'value'    => 'segment',
                'children' => [
                    [
                        'label'             => __('Status', 'fluent-crm'),
                        'value'             => 'status',
                        'type'              => 'selections',
                        'component'         => 'options_selector',
                        'option_key'        => 'statuses',
                        'is_multiple'       => true,
                        'is_singular_value' => true
                    ],
                    [
                        'label'             => __('Type', 'fluent-crm'),
                        'value'             => 'contact_type',
                        'type'              => 'selections',
                        'component'         => 'options_selector',
                        'option_key'        => 'contact_types',
                        'is_multiple'       => false,
                        'is_singular_value' => true
                    ],
                    [
                        'label'       => __('Tags', 'fluent-crm'),
                        'value'       => 'tags',
                        'type'        => 'selections',
                        'component'   => 'options_selector',
                        'option_key'  => 'tags',
                        'is_multiple' => true,
                        'is_nullable' => true,
                    ],
                    [
                        'label'       => __('Lists', 'fluent-crm'),
                        'value'       => 'lists',
                        'type'        => 'selections',
                        'component'   => 'options_selector',
                        'option_key'  => 'lists',
                        'is_multiple' => true,
                        'is_nullable' => true,
                    ],
                    [
                        'label'             => __('WP User Role', 'fluent-crm'),
                        'value'             => 'user_role',
                        'type'              => 'selections',
                        'component'         => 'options_selector',
                        'option_key'        => 'user_roles_options',
                        'is_multiple'       => false,
                        'is_singular_value' => true,
                        'help'              => __('Filter by user role, please make sure your users are synced with your FluentCRM contacts', 'fluent-crm')
                    ],
                ],
            ],
            'activities' => [
                'label'    => __('Contact Activities', 'fluent-crm'),
                'value'    => 'activities',
                'children' => [
                    [
                        'label' => __('Last Email Sent', 'fluent-crm'),
                        'value' => 'email_sent',
                        'type'  => 'dates',
                    ],
                    [
                        'label' => __('Last Email Open', 'fluent-crm'),
                        'value' => 'email_opened',
                        'type'  => 'dates',
                        'help'  => __('Please note that, some email clients send false-positive for email open pixel tracking so it may not 100% correct.', 'fluent-crm')
                    ],
                    [
                        'label' => __('Last Email Clicked', 'fluent-crm'),
                        'value' => 'email_link_clicked',
                        'type'  => 'dates',
                    ],
                    [
                        'label'              => __('Campaign Email -', 'fluent-crm'),
                        'value'              => 'campaign_email_activity',
                        'type'               => 'selections',
                        'component'          => 'ajax_selector',
                        'option_key'         => 'campaigns',
                        'is_multiple'        => false,
                        'custom_operators'   => [
                            'clicked'     => 'link clicked',
                            'not_clicked' => 'did not click',
                            'open'        => 'opened',
                            'no_open'     => 'did not open yet',
                            'in'          => 'in (email sent)',
                            'not_in'      => 'not in (regardless of status)'
                        ],
                        'experimental_cache' => true,
                        'help'               => __('This will get only the contacts who got email in the selected campaign and then filter by email open/link clicked or not. <br />Please note that, some email clients send false-positive for email open pixel tracking so it may not 100% correct.', 'fluent-crm')
                    ],
                    [
                        'label'              => __('Automation Activity -', 'fluent-crm'),
                        'value'              => 'automation_activity',
                        'type'               => 'selections',
                        'component'          => 'ajax_selector',
                        'option_key'         => 'funnels',
                        'is_multiple'        => false,
                        'custom_operators'   => [
                            'completed' => 'status completed',
                            'active'    => 'status active',
                            'cancelled' => 'status cancelled',
                            'waiting'   => 'status waiting',
                            'in'        => 'in (regardless of status)',
                            'not_in'    => 'not in (regardless of status)'
                        ],
                        'experimental_cache' => true,
                        'help'               => __('You can filter your contacts based on activity in a specific automation funnel.', 'fluent-crm')
                    ],
                    [
                        'label'              => __('Email Sequence Activity -', 'fluent-crm'),
                        'value'              => 'email_sequence_activity',
                        'type'               => 'selections',
                        'component'          => 'ajax_selector',
                        'option_key'         => 'email_sequences',
                        'is_multiple'        => false,
                        'custom_operators'   => [
                            'completed' => 'status completed',
                            'active'    => 'status active',
                            'cancelled' => 'status cancelled',
                            'in'        => 'in (regardless of status)',
                            'not_in'    => 'not in (regardless of status)'
                        ],
                        'experimental_cache' => true,
                        'help'               => __('You can filter your contacts based on activity in a specific email sequences.', 'fluent-crm')
                    ]
                ]
            ]
        ];

        if (self::isCompanyEnabled()) {
            $groups['segment']['children'][] = [
                'label'              => __('Company', 'fluent-crm'),
                'value'              => 'companies',
                'type'               => 'selections',
                'component'          => 'ajax_selector',
                'option_key'         => 'companies',
                'is_multiple'        => true,
                'is_singular_value'  => true,
                'is_nullable'        => true,
                'experimental_cache' => true
            ];
            $groups['segment']['children'][] = [
                'label'              => __('Company - Industry', 'fluent-crm'),
                'value'              => 'company_industry',
                'type'               => 'selections',
                'component'          => 'ajax_selector',
                'option_key'         => 'company_industries',
                'is_multiple'        => true,
                'is_singular_value'  => true,
                'experimental_cache' => true
            ];
            $groups['segment']['children'][] = [
                'label'              => __('Company - Type', 'fluent-crm'),
                'value'              => 'company_type',
                'type'               => 'selections',
                'component'          => 'ajax_selector',
                'option_key'         => 'company_types',
                'is_multiple'        => true,
                'is_singular_value'  => true,
                'experimental_cache' => true
            ];
        }

        if ($customFields = fluentcrm_get_custom_contact_fields()) {
            // form data for custom fields in groups
            $children = [];
            foreach ($customFields as $field) {
                $item = [
                    'label' => $field['label'],
                    'value' => $field['slug'],
                    'type'  => $field['type'],
                ];

                if ($item['type'] == 'number') {
                    $item['type'] = 'numeric';
                } else if ($item['type'] == 'date') {
                    $item['type'] = 'dates';
                    $item['date_type'] = 'date';
                    $item['value_format'] = 'YYYY-MM-DD';
                } else if ($item['type'] == 'date_time') {
                    $item['type'] = 'dates';
                    $item['has_time'] = 'yes';
                    $item['date_type'] = 'datetime';
                    $item['value_format'] = 'YYYY-MM-DD HH:mm:ss';
                } else if (isset($field['options'])) {
                    $item['type'] = 'selections';
                    $options = $field['options'];
                    $formattedOptions = [];
                    foreach ($options as $option) {
                        $formattedOptions[$option] = $option;
                    }
                    $item['options'] = $formattedOptions;
                    $isMultiple = in_array($field['type'], ['checkbox', 'select-multi']);
                    $item['is_multiple'] = $isMultiple;
                    if ($isMultiple) {
                        $item['is_singular_value'] = true;
                    }

                } else {
                    $item['type'] = 'text';
                }

                $children[] = $item;

            }

            $groups['custom_fields'] = [
                'label'    => __('Custom Fields', 'fluent-crm'),
                'value'    => 'custom_fields',
                'children' => $children
            ];
        }

        if (!defined('FLUENTCAMPAIGN')) {
            $disabled = true;
            if (defined('WC_PLUGIN_FILE')) {
                $groups['woo'] = [
                    'label'    => __('WooCommerce', 'fluent-crm'),
                    'value'    => 'woo',
                    'children' => [
                        [
                            'value'    => 'total_order_count',
                            'label'    => __('Total Order Count (Pro Required)', 'fluent-crm'),
                            'type'     => 'numeric',
                            'disabled' => true
                        ],
                        [
                            'value'    => 'total_order_value',
                            'label'    => __('Total Order value (Pro Required)', 'fluent-crm'),
                            'type'     => 'numeric',
                            'disabled' => true
                        ],
                        [
                            'value'    => 'last_order_date',
                            'label'    => __('Last Order Date (Pro Required)', 'fluent-crm'),
                            'type'     => 'dates',
                            'disabled' => true
                        ],
                        [
                            'value'    => 'first_order_date',
                            'label'    => __('First Order Date (Pro Required)', 'fluent-crm'),
                            'type'     => 'dates',
                            'disabled' => true
                        ],
                        [
                            'value'       => 'purchased_items',
                            'label'       => __('Purchased Products (Pro Required)', 'fluent-crm'),
                            'type'        => 'selections',
                            'component'   => 'product_selector',
                            'is_multiple' => true,
                            'disabled'    => true
                        ],
                        [
                            'value'             => 'commerce_exist',
                            'label'             => __('Is a customer? (Pro Required)', 'fluent-crm'),
                            'type'              => 'selections',
                            'is_multiple'       => false,
                            'disable_values'    => true,
                            'value_description' => __('This filter will check if a contact has at least one shop order or not', 'fluent-crm'),
                            'custom_operators'  => [
                                'exist'     => 'Yes',
                                'not_exist' => 'No',
                            ],
                            'disabled'          => true
                        ]
                    ],
                ];
            }

            if (self::isEdd3()) {
                $groups['edd'] = [
                    'label'    => __('EDD', 'fluent-crm'),
                    'value'    => 'edd',
                    'children' => [
                        [
                            'value'    => 'total_order_count',
                            'label'    => __('Total Order Count (Pro Required)', 'fluent-crm'),
                            'type'     => 'numeric',
                            'disabled' => true
                        ],
                        [
                            'value'    => 'total_order_value',
                            'label'    => __('Total Order Value (Pro Required)', 'fluent-crm'),
                            'type'     => 'numeric',
                            'disabled' => true
                        ],
                        [
                            'value'    => 'last_order_date',
                            'label'    => __('Last Order Date (Pro Required)', 'fluent-crm'),
                            'type'     => 'dates',
                            'disabled' => true
                        ],
                        [
                            'value'    => 'first_order_date',
                            'label'    => __('First Order Date (Pro Required)', 'fluent-crm'),
                            'type'     => 'dates',
                            'disabled' => true
                        ],
                        [
                            'value'       => 'purchased_items',
                            'label'       => __('Purchased Products (Pro Required)', 'fluent-crm'),
                            'type'        => 'selections',
                            'component'   => 'product_selector',
                            'is_multiple' => true,
                            'disabled'    => true
                        ],
                    ],
                ];
            }

            if (class_exists('\Affiliate_WP')) {
                $groups['aff_wp'] = [
                    'label'    => 'AffiliateWP',
                    'value'    => 'aff_wp',
                    'children' => [
                        [
                            'value'    => 'is_affiliate',
                            'label'    => __('Is Affiliate (Pro Required)', 'fluent-crm'),
                            'type'     => 'single_assert_option',
                            'options'  => [
                                'yes' => __('Yes', 'fluent-crm'),
                                'no'  => __('No', 'fluent-crm')
                            ],
                            'disabled' => $disabled
                        ],
                        [
                            'value'    => 'affiliate_id',
                            'label'    => __('Affiliate ID (Pro Required)', 'fluent-crm'),
                            'type'     => 'numeric',
                            'disabled' => $disabled
                        ],
                        [
                            'value'    => 'referrals',
                            'label'    => __('Total Referrals (Pro Required)', 'fluent-crm'),
                            'type'     => 'numeric',
                            'disabled' => $disabled
                        ],
                        [
                            'value'    => 'status',
                            'label'    => __('Status (Pro Required)', 'fluent-crm'),
                            'type'     => 'single_assert_option',
                            'options'  => [
                                'active'   => __('Active', 'fluent-crm'),
                                'inactive' => __('Inactive', 'fluent-crm'),
                                'pending'  => __('Pending', 'fluent-crm')
                            ],
                            'disabled' => $disabled
                        ],
                        [
                            'value'    => 'earnings',
                            'label'    => __('Earnings (Pro Required)', 'fluent-crm'),
                            'type'     => 'numeric',
                            'disabled' => $disabled
                        ],
                        [
                            'value'    => 'unpaid_earnings',
                            'label'    => __('Unpaid Earnings (Pro Required)', 'fluent-crm'),
                            'type'     => 'numeric',
                            'disabled' => $disabled
                        ],
                        [
                            'value'    => 'date_registered',
                            'label'    => __('Registration Date (Pro Required)', 'fluent-crm'),
                            'type'     => 'dates',
                            'disabled' => $disabled
                        ],
                        [
                            'value'    => 'last_payment_date',
                            'label'    => __('Last Payout Date (Pro Required)', 'fluent-crm'),
                            'type'     => 'dates',
                            'disabled' => $disabled
                        ]
                    ]
                ];
            }

            if (defined('LEARNDASH_VERSION')) {
                $groups['learndash'] = [
                    'label'    => __('LearnDash', 'fluent-crm'),
                    'value'    => 'learndash',
                    'children' => [
                        [
                            'value'    => 'last_order_date',
                            'label'    => __('Last Enrollment Date (Pro Required)', 'fluent-crm'),
                            'type'     => 'dates',
                            'disabled' => $disabled
                        ],
                        [
                            'value'    => 'first_order_date',
                            'label'    => __('First Enrollment Date (Pro Required)', 'fluent-crm'),
                            'type'     => 'dates',
                            'disabled' => $disabled
                        ],
                        [
                            'value'       => 'purchased_items',
                            'label'       => __('Enrollment Courses (Pro Required)', 'fluent-crm'),
                            'type'        => 'selections',
                            'component'   => 'product_selector',
                            'is_multiple' => true,
                            'disabled'    => $disabled
                        ],
                        [
                            'value'        => 'purchased_groups',
                            'label'        => __('Enrollment Groups (Pro Required)', 'fluent-crm'),
                            'type'         => 'selections',
                            'component'    => 'product_selector',
                            'is_multiple'  => true,
                            'extended_key' => 'groups',
                            'disabled'     => $disabled,
                            'options'      => []
                        ],
                        [
                            'value'       => 'purchased_categories',
                            'label'       => __('Enrollment Categories (Pro Required)', 'fluent-crm'),
                            'type'        => 'selections',
                            'component'   => 'tax_selector',
                            'taxonomy'    => 'ld_course_category',
                            'is_multiple' => true,
                            'disabled'    => $disabled
                        ],
                        [
                            'value'       => 'purchased_tags',
                            'label'       => __('Enrollment Tags (Pro Required)', 'fluent-crm'),
                            'type'        => 'selections',
                            'component'   => 'tax_selector',
                            'taxonomy'    => 'ld_course_tag',
                            'is_multiple' => true,
                            'disabled'    => $disabled
                        ]
                    ]
                ];
            }

            if (defined('LLMS_PLUGIN_FILE')) {
                $groups['lifterlms'] = [
                    'label'    => __('LifterLMS', 'fluent-crm'),
                    'value'    => 'lifterlms',
                    'children' => [
                        [
                            'value'    => 'last_order_date',
                            'label'    => __('Last Enrollment Date (Pro Required)', 'fluent-crm'),
                            'type'     => 'dates',
                            'disabled' => $disabled
                        ],
                        [
                            'value'    => 'first_order_date',
                            'label'    => __('First Enrollment Date (Pro Required)', 'fluent-crm'),
                            'type'     => 'dates',
                            'disabled' => $disabled
                        ],
                        [
                            'value'       => 'purchased_items',
                            'label'       => __('Enrollment Courses (Pro Required)', 'fluent-crm'),
                            'type'        => 'selections',
                            'component'   => 'product_selector',
                            'is_multiple' => true,
                            'disabled'    => $disabled
                        ],
                        [
                            'value'        => 'purchased_groups',
                            'label'        => __('Enrollment Memberships (Pro Required)', 'fluent-crm'),
                            'type'         => 'selections',
                            'component'    => 'product_selector',
                            'extended_key' => 'groups',
                            'is_multiple'  => true,
                            'disabled'     => $disabled
                        ],
                        [
                            'value'       => 'purchased_categories',
                            'label'       => __('Enrollment Categories (Pro Required)', 'fluent-crm'),
                            'type'        => 'selections',
                            'component'   => 'tax_selector',
                            'taxonomy'    => 'course_cat',
                            'is_multiple' => true,
                            'disabled'    => $disabled
                        ],
                        [
                            'value'       => 'purchased_tags',
                            'label'       => __('Enrollment Tags (Pro Required)', 'fluent-crm'),
                            'type'        => 'selections',
                            'component'   => 'tax_selector',
                            'taxonomy'    => 'course_tag',
                            'is_multiple' => true,
                            'disabled'    => $disabled
                        ]
                    ],
                ];
            }
        }

        /**
         * Filter the advanced filter options for FluentCRM.
         *
         * This filter allows modification of the advanced filter options used in FluentCRM.
         *
         * @param array $groups The current filter options.
         * @since 2.5.1
         *
         */
        $groups = apply_filters('fluentcrm_advanced_filter_options', $groups);

        return array_values($groups);
    }

    public static function getComplianceSettings()
    {
        $defaults = [
            'anonymize_ip'           => 'no',
            'delete_contact_on_user' => 'no',
            'personal_data_export'   => 'yes',
            'one_click_unsubscribe'  => 'no',
            'enable_gravatar'        => 'yes',
            'gravatar_fallback'      => 'yes',
            'email_click_tracking'   => 'yes', // 'no'|'yes'|'anonymous'
            'email_open_tracking'    => 'yes', // 'no'|'yes'|'anonymous'
        ];

        $settings = get_option('_fluentcrm_compliance_settings', []);

        return wp_parse_args($settings, $defaults);
    }

    public static function getSiteUrl($path = '', $scheme = null)
    {
        return site_url($path, $scheme);
    }

    public static function isExperimentalEnabled($module)
    {
        $settings = self::getExperimentalSettings();
        return Arr::get($settings, $module) === 'yes';
    }

    public static function getExperimentalSettings()
    {
        static $settings;
        if ($settings) {
            return $settings;
        }

        $defaults = [
            'campaign_archive'        => 'no',
            'campaign_group_by_month' => 'no',
            'campaign_search'         => '',
            'campaign_max_number'     => 50,
            'campaign_ids'            => [],
            'campaign_status'         => 'archived',
            'frontend_portal'         => 'no',
            'frontend_portal_slug'    => 'fluentcrm',
            'frontend_portal_render_type' => 'standalone',
            'frontend_portal_page_id' => '',
            'classic_date_time'       => 'no',
            'company_module'          => 'no',
            'company_auto_logo'       => 'no',
            'disable_visual_ai'       => 'no',
            'multi_threading_emails'  => 'yes',
            'system_logs'             => 'no',
            'event_tracking'          => 'no',
            'abandoned_cart'          => 'no',
            'activity_log'            => 'no',
            'sms_module'              => 'no',
        ];

        $settings = get_option('_fluentcrm_experimental_settings', []);

        if (!$settings || !is_array($settings)) {
            $settings = $defaults;
        } else {
            $settings = wp_parse_args($settings, $defaults);
        }

        return apply_filters('fluent_crm/experimental_settings', $settings);
    }

    public static function willMultiThreadEmail($minPendingLimit = 300)
    {
        if (!self::isExperimentalEnabled('multi_threading_emails')) {
            return false;
        }

        // Cap the scan at the threshold — we only need "reached it or not".
        $rowcount = self::getUpcomingEmailCount($minPendingLimit);

        return $rowcount >= $minPendingLimit;
    }

    /**
     * Count sendable (pending/scheduled, due) queue rows.
     *
     * Every internal caller compares the result against a small threshold, so
     * they pass that threshold as $cap: the scan then stops after $cap index
     * entries instead of walking the entire pending slice — on multi-million
     * row queues the CLI sender used to pay a full index scan per batch just
     * to learn "more than 400". A capped count is EXACT below the cap and
     * saturates at the cap, which is precisely what threshold comparisons
     * (and the CLI's "only N left" message) need. $cap = 0 keeps the exact
     * full count for backward compatibility with external callers.
     *
     * @param int $cap Optional scan ceiling (0 = exact full count).
     * @return int
     */
    public static function getUpcomingEmailCount($cap = 0)
    {
        global $wpdb;

        $table = $wpdb->prefix . 'fc_campaign_emails';
        $cap = (int)$cap;

        if ($cap > 0) {
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
            return (int)$wpdb->get_var($wpdb->prepare(
                "SELECT COUNT(*) FROM (SELECT 1 FROM `{$table}` WHERE `status` IN ('pending', 'scheduled') AND `scheduled_at` <= %s LIMIT %d) capped_scan",
                current_time('mysql'),
                $cap
            ));
        }

        // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
        return (int)$wpdb->get_var($wpdb->prepare(
            "SELECT COUNT(*) FROM `{$table}` WHERE `status` IN ('pending', 'scheduled') AND `scheduled_at` <= %s",
            current_time('mysql')
        ));
    }

    public static function sanitizeHtml($html)
    {
        if (!$html) {
            return $html;
        }

        // Return $html if it's just a plain text
        if (!preg_match('/<[^>]*>/', $html)) {
            return $html;
        }

        $tags = wp_kses_allowed_html('post');
        $tags['style'] = [
            'types' => [],
        ];
        // iframe
        $tags['iframe'] = [
            'width'           => [],
            'height'          => [],
            'src'             => [],
            'title'           => [],
            'frameborder'     => [],
            'allow'           => [],
            'class'           => [],
            'id'              => [],
            'allowfullscreen' => [],
            'style'           => [],
        ];

        //svg
        if (empty($tags['svg'])) {
            $svg_args = [
                'svg'   => [
                    'class'           => true,
                    'aria-hidden'     => true,
                    'aria-labelledby' => true,
                    'role'            => true,
                    'xmlns'           => true,
                    'width'           => true,
                    'height'          => true,
                    'viewbox'         => true,
                ],
                'g'     => ['fill' => true],
                'title' => ['title' => true],
                'path'  => [
                    'd'         => true,
                    'fill'      => true,
                    'transform' => true,
                ],
            ];
            $tags = array_merge($tags, $svg_args);
        }

        /**
         * Filter the allowed HTML tags.
         *
         * This filter allows modification of the HTML tags that are allowed.
         *
         * @param array $tags An array of allowed HTML tags.
         * @since 2.7.0
         *
         */
        $tags = apply_filters('fluent_crm/allowed_html_tags', $tags);

        return wp_kses($html, $tags);
    }

    public static function hasConditionOnString($string)
    {
        return (bool)preg_match('/conditional-group|fcrmConditionType|conditional-content|fc-cond-blocks|fc_vis_cond/', $string);
    }

    public static function getEmailFooterContent($campaign = null)
    {
        if ($campaign && isset($campaign->settings)) {

            if (Arr::get($campaign->settings, 'is_transactional') == 'yes') {
                return '';
            }

            $customFooter = Arr::get($campaign->settings, 'footer_settings.custom_footer');
            $emailFooter = Arr::get($campaign->settings, 'footer_settings.footer_content');

            if ($customFooter === 'yes' && $emailFooter) {
                return $emailFooter;
            }
        }

        return Arr::get(self::getGlobalEmailSettings(), 'email_footer', '');
    }

    /**
     * Build the title for the `custom_email_campaign` row backing a one-off
     * "send email to this contact".
     *
     * These rows are hidden from the campaign list by the Campaign model's
     * type scope, so the title is only ever read in raw DB inspection, support
     * debugging and the MCP `list-campaigns(include_one_offs=true)` view. A
     * constant string there makes every send indistinguishable, so the
     * recipient goes in the title.
     *
     * fc_campaigns.title is VARCHAR(192), so the recipient is truncated to fit
     * rather than being silently cut off by MySQL.
     *
     * @param string $recipientEmail
     * @param string $prefix Optional caller tag (e.g. 'MCP') for provenance.
     * @return string
     */
    public static function oneOffEmailTitle($recipientEmail, $prefix = '')
    {
        $recipientEmail = sanitize_text_field((string)$recipientEmail);

        $label = $prefix
            /* translators: 1: caller tag such as "MCP", 2: recipient email address */
            ? sprintf(__('%1$s one-off email to %2$s', 'fluent-crm'), $prefix, $recipientEmail)
            /* translators: %s: recipient email address */
            : sprintf(__('Custom email to %s', 'fluent-crm'), $recipientEmail);

        // Leave headroom under the 192-char column so a long address can never
        // truncate mid-way through a multibyte character.
        if (mb_strlen($label) > 190) {
            $label = mb_substr($label, 0, 189) . '…';
        }

        return $label;
    }

    public static function getFooterConfig($campaign = null)
    {

        $defaults = [
            'disable_footer' => 'no',
            'custom_footer'  => 'no',
            'footer_content' => '',
            'font_size'      => 13,
            'font_color'     => '#202020',
            'background_color' => 'transparent',
            'footer_padding' => 20
        ];

        if ($campaign && isset($campaign->settings)) {
            if (Arr::get($campaign->settings, 'is_transactional') == 'yes') {
                return [];
            }
            $footerSettings = Arr::get($campaign->settings, 'footer_settings', []);
            $disableFooter = Arr::get($footerSettings, 'disable_footer');
            if ($disableFooter !== 'yes' && $disableFooter !== 'no') {
                $disableFooter = Arr::get($campaign->settings, 'template_config.disable_footer');
            }

            if ($disableFooter == 'yes') {
                $defaults['disable_footer'] = 'yes';
                $defaults['footer_content'] = '';
                return $defaults;
            }
            if (!empty($footerSettings['font_size'])) {
                $defaults['font_size'] = $footerSettings['font_size'];
            }

            if (!empty($footerSettings['font_color'])) {
                $defaults['font_color'] = $footerSettings['font_color'];
            }

            if (!empty($footerSettings['background_color'])) {
                $defaults['background_color'] = $footerSettings['background_color'];
            }

            $footerPadding = Arr::get($footerSettings, 'footer_padding');
            if ($footerPadding !== null && $footerPadding !== '') {
                $defaults['footer_padding'] = min(80, max(0, intval($footerPadding)));
            } else {
                $defaults['footer_padding'] = 20;
            }

            $customFooter = Arr::get($campaign->settings, 'footer_settings.custom_footer');
            $emailFooter = Arr::get($campaign->settings, 'footer_settings.footer_content');

            if ($customFooter === 'yes' && $emailFooter) {
                $defaults['footer_content'] = $emailFooter;
                return $defaults;
            }
        }

        $globalContent = Arr::get(self::getGlobalEmailSettings(), 'email_footer', '');

        $defaults['footer_content'] = $globalContent;

        return $defaults;
    }

    public static function isCompanyEnabled()
    {
        return self::isExperimentalEnabled('company_module');
    }

    public static function companyCategories()
    {
        /**
         * Filter the list of company categories.
         *
         * This filter allows modification of the company categories list.
         *
         * @param array An array of company categories.
         * @since 2.8.0
         *
         */
        return apply_filters('fluent_crm/company_categories', [
            'Accounting',
            'Airlines/Aviation',
            'Alternative Dispute Resolution',
            'Alternative Medicine',
            'Animation',
            'Apparel & Fashion',
            'Architecture & Planning',
            'Arts and Crafts',
            'Automotive',
            'Aviation & Aerospace',
            'Banking',
            'Biotechnology',
            'Broadcast Media',
            'Building Materials',
            'Business Supplies and Equipment',
            'Capital Markets',
            'Chemicals',
            'Civic & Social Organization',
            'Civil Engineering',
            'Commercial Real Estate',
            'Computer & Network Security',
            'Computer Games',
            'Computer Hardware',
            'Computer Networking',
            'Computer Software',
            'Internet',
            'Construction',
            'Consumer Electronics',
            'Consumer Goods',
            'Consumer Services',
            'Cosmetics',
            'Dairy',
            'Defense & Space',
            'Design',
            'Education Management',
            'E-Learning',
            'Electrical/Electronic Manufacturing',
            'Entertainment',
            'Environmental Services',
            'Events Services',
            'Executive Office',
            'Facilities Services',
            'Farming',
            'Financial Services',
            'Fine Art',
            'Fishery',
            'Food & Beverages',
            'Food Production',
            'Fund-Raising',
            'Furniture',
            'Gambling & Casinos',
            'Glass, Ceramics & Concrete',
            'Government Administration',
            'Government Relations',
            'Graphic Design',
            'Health, Wellness and Fitness',
            'Higher Education',
            'Hospital & Health Care',
            'Hospitality',
            'Human Resources',
            'Import and Export',
            'Individual & Family Services',
            'Industrial Automation',
            'Information Services',
            'Information Technology and Services',
            'Insurance',
            'International Affairs',
            'International Trade and Development',
            'Investment Banking',
            'Investment Management',
            'Judiciary',
            'Law Enforcement',
            'Law Practice',
            'Legal Services',
            'Legislative Office',
            'Leisure, Travel & Tourism',
            'Libraries',
            'Logistics and Supply Chain',
            'Luxury Goods & Jewelry',
            'Machinery',
            'Management Consulting',
            'Maritime',
            'Market Research',
            'Marketing and Advertising',
            'Mechanical or Industrial Engineering',
            'Media Production',
            'Medical Devices',
            'Medical Practice',
            'Mental Health Care',
            'Military',
            'Mining & Metals',
            'Motion Pictures and Film',
            'Museums and Institutions',
            'Music',
            'Nanotechnology',
            'Newspapers',
            'Non-Profit Organization Management',
            'Oil & Energy',
            'Online Media',
            'Outsourcing/Offshoring',
            'Package/Freight Delivery',
            'Packaging and Containers',
            'Paper & Forest Products',
            'Performing Arts',
            'Pharmaceuticals',
            'Philanthropy',
            'Photography',
            'Plastics',
            'Political Organization',
            'Primary/Secondary Education',
            'Printing',
            'Professional Training & Coaching',
            'Program Development',
            'Public Policy',
            'Public Relations and Communications',
            'Public Safety',
            'Publishing',
            'Railroad Manufacture',
            'Ranching',
            'Real Estate',
            'Recreational Facilities and Services',
            'Religious Institutions',
            'Renewables & Environment',
            'Research',
            'Restaurants',
            'Retail',
            'Security and Investigations',
            'Semiconductors',
            'Shipbuilding',
            'Sporting Goods',
            'Sports',
            'Staffing and Recruiting',
            'Supermarkets',
            'Telecommunications',
            'Textiles',
            'Think Tanks',
            'Tobacco',
            'Translation and Localization',
            'Transportation/Trucking/Railroad',
            'Utilities',
            'Venture Capital & Private Equity',
            'Veterinary',
            'Warehousing',
            'Wholesale',
            'Wine and Spirits',
            'Wireless',
            'Writing and Editing'
        ]);
    }

    public static function companyTypes()
    {
        /**
         * Filter the list of company types.
         *
         * This filter allows modification of the company types array.
         *
         * @param array An array of company types.
         * @since 2.8.0
         *
         */
        return apply_filters('fluent_crm/company_types', [
            'Prospect',
            'Partner',
            'Reseller',
            'Vendor',
            'Other'
        ]);
    }

    public static function getCompanyProfileSections()
    {
        $sections = [
            'overview'   => [
                'name'    => 'view_company',
                'title'   => __('Contacts', 'fluent-crm'),
                'handler' => 'route'
            ],
            'activities' => [
                'name'    => 'company_activities',
                'title'   => __('Notes & Activities', 'fluent-crm'),
                'handler' => 'route'
            ],
        ];

        /**
         * Filter the company profile sections.
         *
         * This filter allows modification of the company profile sections.
         *
         * @param array The array of company profile sections.
         * @since 2.8.0
         *
         */
        return apply_filters('fluent_crm/company_profile_sections', $sections);
    }

    public static function maybeParseAndFilterWebhookData(Webhook $webhook, $postData, $key)
    {
        $data = Arr::get($webhook->value, $key, []);
        if (!empty($postData[$key])) {
            $postedData = Arr::get($postData, $key, []);

            if (is_string($postedData)) {
                $postedData = explode(',', $postedData);
                $postedData = map_deep($postedData, 'intval');
            }

            $newData = [];
            foreach ($postedData as $item) {
                if (is_numeric($item)) {
                    $newData[] = $item;
                }
            }

            if (!empty($newData)) {
                $data = $newData;
            }

            $data = array_filter($data);
        }

        return $data;
    }

    public static function getNoteSyncFields()
    {
        $fields = array(
            'type'        => array(
                'type'    => 'input-option',
                'label'   => __('Type', 'fluent-crm'),
                'id'      => 'fc_note_type',
                'name'    => 'type',
                'options' => fluentcrm_activity_types()
            ),
            'created_at'  => array(
                'type'         => 'input-date',
                'data_type'    => 'datetime',
                'name'         => 'created_at',
                'label'        => __('Date Time', 'fluent-crm'),
                'id'           => 'fc_note_title',
                'value_format' => 'YYYY-MM-DD HH:mm:ss',
                'help'         => __('keep blank for current time', 'fluent-crm')
            ),
            'title'       => array(
                'type'        => 'input-text',
                'name'        => 'title',
                'label'       => __('Title', 'fluent-crm'),
                'id'          => 'fc_note_title',
                'placeholder' => __('Your Note Title', 'fluent-crm')
            ),
            'description' => array(
                'type'  => 'wp-editor',
                'name'  => 'description',
                'label' => __('Description', 'fluent-crm'),
                'id'    => 'fc_note_desc'
            ),
        );

        /**
         * Filter the contact note fields.
         *
         * This filter allows modification of the contact note fields.
         *
         * @param array $fields The contact note fields.
         * @since 2.8.40
         *
         */
        return apply_filters('fluent_crm/contact_note_fields', $fields);
    }

    public static function debugLog($title, $description = '', $type = 'info')
    {
        static $isEnabled = null;

        if ($isEnabled === null) {
            $isEnabled = (defined('FLUENT_CRM_DEBUG_LOG') && FLUENT_CRM_DEBUG_LOG) || self::isExperimentalEnabled('system_logs');
        }

        if (!$isEnabled) {
            return null;
        }

        if (!is_string($description)) {
            $description = json_encode($description);
        }

        return SystemLog::create([
            'title'       => sanitize_text_field($title),
            'description' => wp_kses_post($description)
        ]);
    }

    public static function getNextMinuteTaskTimeStamp()
    {
        $lastRunAt = fluentCrmGetOptionCache('_fcrm_last_scheduler');

        if ($lastRunAt) {
            $nextRun = $lastRunAt + 60;
        } else {
            $nextRun = as_next_scheduled_action('fluentcrm_scheduled_every_minute_tasks');
        }

        if ($nextRun === true || !$nextRun) {
            $nextRun = time() + 60;
        }

        return $nextRun;
    }

    public static function isWooHposEnabled()
    {
        static $enabled = null;
        if ($enabled !== null) {
            return $enabled;
        }

        $enabled = class_exists('\Automattic\WooCommerce\Utilities\OrderUtil') && \Automattic\WooCommerce\Utilities\OrderUtil::custom_orders_table_usage_is_enabled();

        return $enabled;
    }

    public static function searchWPUsers($searchQuery, $limit = 20)
    {
        $search = sanitize_text_field($searchQuery);

        // Search by user login, email, and nicename
        $args = array(
            'role__not_in' => array('Administrator'),
            'search'       => '*' . $search . '*',
            'number'       => $limit
        );

        // Get users by login, email, and nicename
        $user_query = new \WP_User_Query($args);
        $users_by_login = $user_query->get_results();
        $users = array_unique($users_by_login, SORT_REGULAR);

        return $users;
    }

    public static function latestListIdOfSubscriber($contactId)
    {
        $listId = SubscriberPivot::where('subscriber_id', $contactId)
            ->where('object_type', 'FluentCrm\App\Models\Lists')
            ->orderBy('created_at', 'DESC')
            ->orderBy('id', 'DESC')
            ->value('object_id');

        return $listId;
    }

    /**
     * Per-request memo for Tag/Lists lookups by title. Bulk CSV imports call
     * the tag/list helpers once per row, so without this the same title is
     * re-queried for every row of every chunk (rows × titles round-trips).
     * Keyed on the raw title so matching stays identical to the DB collation
     * semantics of where('title', ...); a differently-cased duplicate only
     * costs one extra query.
     */
    private static $termTitleCache = [];

    private static function findTermByTitleCached($title, $type)
    {
        $key = $type . ':' . $title;

        if (array_key_exists($key, self::$termTitleCache)) {
            return self::$termTitleCache[$key];
        }

        $term = $type === 'tag'
            ? Tag::where('title', $title)->first()
            : Lists::where('title', $title)->first();

        return self::$termTitleCache[$key] = $term;
    }

    public static function createNewTags($tagsArray)
    {
        $tags = [];
        foreach ($tagsArray as $tag) {
            $tag = sanitize_text_field($tag);
            //if that tag already exists then I need only it's id
            $sameTag = self::findTermByTitleCached($tag, 'tag');
            if ($sameTag) {
                $tags[] = $sameTag->id;
                continue;
            }

            $tagModel = Helper::createTag($tag);

            if ($tagModel) {
                $tags[] = $tagModel->id;
            }
        }

        return $tags;
    }

    public static function createNewLists($listsArray)
    {
        $lists = [];
        foreach ($listsArray as $list) {
            $list = sanitize_text_field($list);
            //if that list already exists then I need only it's id
            $sameList = self::findTermByTitleCached($list, 'list');
            if ($sameList) {
                $lists[] = $sameList->id;
                continue;
            }

            $listModel = Helper::createList($list);

            if ($listModel) {
                $lists[] = $listModel->id;
            }
        }

        return $lists;
    }

    public static function getNewAttachableLists($listsArray, $currentListIds, $ListsForAllContacts)
    {
        $listIds = [];

        foreach ($listsArray as $listTitle) {
            $listTitle = sanitize_text_field($listTitle);

            $existinglist = self::findTermByTitleCached($listTitle, 'list');
            if ($existinglist) {
                if (!in_array($existinglist->id, $currentListIds) && !in_array($existinglist->id, $ListsForAllContacts)) {
                    //if that existing list is not already in user's list and not in those lists that will be applied to all subscribers
                    $listIds[] = $existinglist->id;
                }
            } else {
                $newList = Helper::createList($listTitle);
                $listIds[] = $newList->id;
            }
        }

        return $listIds;
    }

    public static function getNewAttachableTags($tagsArray, $currentTagIds, $TagsForAllContacts)
    {
        $tagIds = [];

        foreach ($tagsArray as $tagTitle) {
            $tagTitle = sanitize_text_field($tagTitle);

            $existingTag = self::findTermByTitleCached($tagTitle, 'tag');
            if ($existingTag) {
                if (!in_array($existingTag->id, $currentTagIds) && !in_array($existingTag->id, $TagsForAllContacts)) {
                    //if that existing tag is not already in user's tag and not in those tags that will be applied to all subscribers
                    $tagIds[] = $existingTag->id;
                }
            } else {
                $newList = Helper::createTag($tagTitle);
                $tagIds[] = $newList->id;
            }
        }

        return $tagIds;
    }

    private static function createList($listTitle)
    {
        $baseSlug = Str::slug($listTitle);
        $slug = $baseSlug;
        $counter = 1;

        // Ensure unique slug
        while (Lists::where('slug', $slug)->exists()) {
            $slug = "{$baseSlug}-{$counter}";
            $counter++;
        }

        $list = Lists::create(
            [
                'title' => $listTitle,
                'slug'  => $slug
            ]
        );

        return self::$termTitleCache['list:' . $listTitle] = $list;
    }

    private static function createTag($tagTitle)
    {
        $baseSlug = Str::slug($tagTitle);
        $slug = $baseSlug;
        $counter = 1;

        // Ensure unique slug
        while (Tag::where('slug', $slug)->exists()) {
            $slug = "{$baseSlug}-{$counter}";
            $counter++;
        }

        $tag = Tag::create(
            [
                'title' => $tagTitle,
                'slug'  => $slug
            ]
        );

        return self::$termTitleCache['tag:' . $tagTitle] = $tag;
    }

    /**
     * Converts text into a URL-friendly slug, handling Latin and non-Latin scripts.
     *
     * @param string $text Input text to slugify
     * @param string $fallback Fallback slug if input is empty or invalid
     * @return string Sanitized slug
     */
    public static function slugify($text, $fallback = '')
    {
        // Normalize input: cast to string and trim whitespace
        $text = trim((string)$text);

        // Handle empty input
        if (empty($text)) {
            return sanitize_title($fallback ?: self::generateUniqueId(), $fallback);
        }

        // Process as Latin-based text
        $slug = remove_accents($text); // Convert accents (e.g., é → e)
        $slug = strtolower($slug); // Convert to lowercase
        $slug = preg_replace('/[^a-z0-9\-_]/', '-', $slug); // Replace non-alphanumeric with dashes
        $slug = preg_replace('/[\-_]{2,}/', '-', $slug); // Collapse multiple dashes/underscores
        $slug = trim($slug, '-_'); // Trim leading/trailing dashes/underscores

        // Check for empty result or non-Latin scripts
        if (empty($slug) || preg_match('/[^\p{Latin}\p{N}\-_ ]/u', $text)) {
            $slug = self::generateUniqueId();
        }

        // Final cleanup with WordPress sanitize_title
        return sanitize_title($slug, $fallback);
    }

    /**
     * Generates a unique, hyphenated identifier (~11-12 characters).
     *
     * @return string Unique ID, e.g., '6f1a2-xyz12'
     */
    public static function generateUniqueId()
    {
        return sprintf('%s-%s', substr(uniqid(), -5), wp_generate_password(5, false, false));
    }

    public static function getStatusText($text)
    {
        if (!$text) {
            return '';
        }

        $mapStatus = [
            'subscribed'     => __('Subscribed', 'fluent-crm'),
            'pending'        => __('Pending', 'fluent-crm'),
            'unsubscribed'   => __('Unsubscribed', 'fluent-crm'),
            'transactional'  => __('Transactional', 'fluent-crm'),
            'bounced'        => __('Bounced', 'fluent-crm'),
            'complained'     => __('Complained', 'fluent-crm'),
            'spammed'        => __('Spammed', 'fluent-crm'),
            'checkout-draft' => __('Checkout Draft', 'fluent-crm'),
            'completed'      => __('Completed', 'fluent-crm'),
            'complete'       => __('Complete', 'fluent-crm'),
            'on-draft'       => __('On Draft', 'fluent-crm'),
            'cancelled'      => __('Cancelled', 'fluent-crm'),
            'processing'     => __('Processing', 'fluent-crm'),
            'paid'           => __('Paid', 'fluent-crm'),
            'success'        => __('Success', 'fluent-crm')
        ];

        $mapStatus = apply_filters('fluent_crm/status_text', $mapStatus);

        return isset($mapStatus[$text]) ? $mapStatus[$text] : ucfirst($text);
    }

    public static function wasProcessedByKeyId($emailLogId)
    {
        static $sentIds = [];

        if (isset($sentIds[$emailLogId])) {
            return true;
        }

        $sentIds[$emailLogId] = true;

        return false;
    }

    public static function setInstantOption($optionKey, $value, $expire = 300)
    {
        if (wp_using_ext_object_cache()) {
            return wp_cache_set($optionKey, $value, 'fc_instant_options', $expire);
        }

        return update_option($optionKey, $value, false);
    }

    public static function getInstantOption($optionKey)
    {
        if (wp_using_ext_object_cache()) {
            return wp_cache_get($optionKey, 'fc_instant_options');
        }

        return get_option($optionKey);
    }

    /**
     * Acquire a cross-process mutex via a single atomic conditional UPDATE on
     * wp_options, keyed off a stored timestamp.
     *
     * Why DB and not wp_cache_add(): wp_cache_add() is only atomic if the active
     * object-cache drop-in implements it against the shared backend. Some do NOT
     * — notably LiteSpeed Object Cache, whose add() only checks the per-process
     * in-memory array and then unconditionally writes (no Memcached ADD / Redis
     * SET NX). Under that drop-in every concurrent worker "wins" the lock, so the
     * mailer ran multiple senders at once and overshot the provider rate limit.
     * A single-row conditional UPDATE is atomic via the InnoDB row lock on every
     * backend, mirroring the CAS used by GlobalRateLimiter.
     *
     * The UPDATE claims the lock only if it is free (empty value) or stale
     * (stored timestamp older than $ttl), so a crashed holder self-recovers after
     * the TTL.
     *
     * With a $token the stored value becomes "<timestamp>|<token>", which makes
     * refreshDbLock()/deleteDbLock() ownership-guarded: a holder that stalls past
     * the TTL and loses the lock to a successor can no longer stomp or delete the
     * successor's claim. The staleness compare still works on the combined value:
     * MySQL numerically coerces the leading digits, and SQLite's TEXT-affinity
     * comparison is lexicographic over the equal-length timestamp prefix.
     *
     * @param string $key   wp_options option_name holding the lock timestamp.
     * @param int    $ttl   Seconds before a held lock is treated as abandoned.
     * @param string $token Optional per-acquire owner token (LIKE-safe chars).
     * @return bool True if this process acquired the lock.
     */
    public static function acquireDbLock($key, $ttl, $token = '')
    {
        global $wpdb;
        $now = time();

        // Ensure the row exists so the conditional UPDATE has a row to claim.
        $wpdb->query($wpdb->prepare(
            "INSERT IGNORE INTO {$wpdb->options} (option_name, option_value, autoload) VALUES (%s, %s, %s)",
            $key, '', 'no'
        ));

        $value = $token === '' ? (string)$now : $now . '|' . $token;

        // Atomic: claim only if free or expired. Empty string casts to 0, so the
        // explicit '' check is what frees a cleanly released lock.
        $affected = $wpdb->query($wpdb->prepare(
            "UPDATE {$wpdb->options} SET option_value = %s WHERE option_name = %s AND (option_value = '' OR option_value < %d)",
            $value, $key, $now - $ttl
        ));

        if ($affected > 0) {
            wp_cache_delete($key, 'options');
            return true;
        }

        return false;
    }

    /**
     * Heartbeat a held lock: push its timestamp to now so the TTL-based stale
     * detection in acquireDbLock() cannot steal it mid-run. Caller must already
     * hold the lock.
     *
     * With a $token the refresh is ownership-guarded — it only touches a value
     * carrying this holder's token, and the return value reports whether the
     * lock is still owned, so a stalled worker whose lock was stolen can stop
     * working instead of stomping the successor's claim.
     *
     * @param string $key   wp_options option_name holding the lock timestamp.
     * @param string $token Owner token passed to acquireDbLock(), if any.
     * @return bool True while this holder still owns the lock (always true
     *              for tokenless locks — the legacy unconditional refresh).
     */
    public static function refreshDbLock($key, $token = '')
    {
        global $wpdb;

        if ($token === '') {
            $wpdb->query($wpdb->prepare(
                "UPDATE {$wpdb->options} SET option_value = %s WHERE option_name = %s",
                (string)time(), $key
            ));
            wp_cache_delete($key, 'options');
            return true;
        }

        $affected = $wpdb->query($wpdb->prepare(
            "UPDATE {$wpdb->options} SET option_value = %s WHERE option_name = %s AND option_value LIKE %s",
            time() . '|' . $token, $key, '%|' . $wpdb->esc_like($token)
        ));
        wp_cache_delete($key, 'options');

        if ($affected > 0) {
            return true;
        }

        // Zero affected rows is ambiguous: mysqli counts CHANGED rows, so a
        // second refresh inside the same epoch second writes an identical
        // value and reports 0 even though we still own the lock. Read the row
        // to tell "unchanged" apart from "stolen".
        $value = (string)$wpdb->get_var($wpdb->prepare(
            "SELECT option_value FROM {$wpdb->options} WHERE option_name = %s",
            $key
        ));

        $suffix = '|' . $token;
        return substr($value, -strlen($suffix)) === $suffix;
    }

    /**
     * Release a lock by clearing its timestamp so the next acquireDbLock() wins
     * immediately instead of waiting out the TTL. Safe to call even if this
     * process does not hold the lock (worst case frees the slot a tick early).
     *
     * @param string $key wp_options option_name holding the lock timestamp.
     * @return void
     */
    public static function releaseDbLock($key)
    {
        global $wpdb;
        $wpdb->query($wpdb->prepare(
            "UPDATE {$wpdb->options} SET option_value = '' WHERE option_name = %s",
            $key
        ));
        wp_cache_delete($key, 'options');
    }

    /**
     * Release a lock by deleting its row. Prefer this over releaseDbLock()
     * for per-object keys (one lock per campaign, funnel, etc.) so finished
     * objects leave no dead wp_options rows behind; fixed keys that are
     * reused forever should keep releaseDbLock() and avoid the
     * delete/re-insert churn. Deleting is as safe as blanking: acquireDbLock()
     * re-creates rows on demand via INSERT IGNORE, so a deleted row only
     * costs the next acquire one insert.
     *
     * With a $token the delete is ownership-guarded: a worker that stalled
     * past the TTL and lost the lock to a successor deletes nothing instead
     * of destroying the successor's live claim.
     *
     * @param string $key   wp_options option_name holding the lock timestamp.
     * @param string $token Owner token passed to acquireDbLock(), if any.
     * @return void
     */
    public static function deleteDbLock($key, $token = '')
    {
        global $wpdb;

        if ($token === '') {
            $wpdb->query($wpdb->prepare(
                "DELETE FROM {$wpdb->options} WHERE option_name = %s",
                $key
            ));
        } else {
            $wpdb->query($wpdb->prepare(
                "DELETE FROM {$wpdb->options} WHERE option_name = %s AND option_value LIKE %s",
                $key, '%|' . $wpdb->esc_like($token)
            ));
        }

        wp_cache_delete($key, 'options');
    }

    /**
     * Read the timestamp a lock was last (re)acquired with, straight from the
     * wp_options row that acquireDbLock()/refreshDbLock() write to.
     *
     * Reads via raw SQL — NOT getInstantOption() — so it returns the live lock
     * value regardless of external-object-cache mode. getInstantOption() reads
     * the `fc_instant_options` cache group when an object cache is active, but
     * the DB locks never write there, so it would always miss a held lock on
     * those sites. Mirrors GlobalRateLimiter's direct-read approach.
     *
     * @param string $key wp_options option_name holding the lock timestamp.
     * @return int Unix timestamp of the last (re)acquire, or 0 if free/absent.
     */
    public static function getDbLockTimestamp($key)
    {
        global $wpdb;

        $value = $wpdb->get_var($wpdb->prepare(
            "SELECT option_value FROM {$wpdb->options} WHERE option_name = %s",
            $key
        ));

        return (int) $value;
    }

}
