<?php
namespace BLR\Backline\Modules\Outreach;

use BLR\Backline\Core\Access;
use BLR\Backline\Core\AdminMenu;
use BLR\Backline\Core\CrudHelpers;
use BLR\Backline\Core\SettingsPage;
use BLR\Backline\Core\Sanitizer;
use BLR\Backline\Modules\Contacts\NewsletterDoubleOptIn;

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

final class Module {
    private const PROCESS_HOOK = 'blr_ba_process_outreach_campaign';
    private const HEALTH_HOOK = 'blr_ba_outreach_queue_healthcheck';
    private const CAMPAIGN_LOCK_TTL = 900;
    private const SEND_BATCH_SIZE = 20;
    private const DEFAULT_DAILY_SEND_LIMIT = 200;
    private const DEFAULT_CAMPAIGN_RECIPIENT_LIMIT = 500;
    private const OPPORTUNISTIC_RUNNER_TTL = 45;
    private const LOG_QUERY_CHUNK_SIZE = 250;
    private const STALE_SENDING_SECONDS = 1800;
    private const ROLLUP_CACHE_TTL = 300;

    public static function register_feature_only(): void {
        add_action('init', [self::class, 'register_cpts']);
        add_action('init', [self::class, 'register_meta'], 11);
        add_filter('cron_schedules', [self::class, 'register_cron_schedules']);
        add_action(self::PROCESS_HOOK, [self::class, 'handle_process_campaign_event'], 10, 1);
        add_action(self::HEALTH_HOOK, [self::class, 'handle_healthcheck']);
        add_action('wp_ajax_blr_ba_outreach_contact_search', [self::class, 'ajax_contact_search']);
        add_action('wp_ajax_blr_ba_outreach_recipient_preview', [self::class, 'ajax_recipient_preview']);
        add_action('wp', [self::class, 'maybe_process_due_campaigns']);
        add_action('save_post_blr_contact', [self::class, 'handle_contact_save'], 20, 3);
        add_action('before_delete_post', [self::class, 'handle_contact_post_change']);
        add_action('trashed_post', [self::class, 'handle_contact_post_change']);
        add_action('untrashed_post', [self::class, 'handle_contact_post_change']);
    }

    public static function register_cpts(): void {
        register_post_type('blr_outreach', array_merge([
            'labels' => [
                'name' => __('Outreach', 'blr-backline'),
                'singular_name' => __('Outreach', 'blr-backline'),
            ],
            'public' => false,
            'show_ui' => false,
            'show_in_menu' => false,
            'show_in_rest' => false,
            'supports' => ['title', 'editor', 'revisions', 'author'],
        ], Access::post_type_registration_args('blr_outreach')));

        register_post_type('blr_outreach_log', array_merge([
            'labels' => [
                'name' => __('Outreach Logs', 'blr-backline'),
                'singular_name' => __('Outreach Log', 'blr-backline'),
            ],
            'public' => false,
            'show_ui' => false,
            'show_in_menu' => false,
            'show_in_rest' => false,
            'supports' => ['title', 'author'],
        ], Access::post_type_registration_args('blr_outreach_log')));
    }

    public static function register_meta(): void {
        self::reg_meta('blr_outreach', 'blr_outreach_subject', 'string');
        self::reg_meta('blr_outreach', 'blr_outreach_from_name', 'string');
        self::reg_meta('blr_outreach', 'blr_outreach_from_email', 'string');
        self::reg_meta('blr_outreach', 'blr_outreach_reply_to', 'string');
        self::reg_meta('blr_outreach', 'blr_outreach_status', 'string');
        self::reg_meta('blr_outreach', 'blr_outreach_workflow_status', 'string');
        self::reg_meta('blr_outreach', 'blr_outreach_manual_contact_ids', 'string');
        self::reg_meta('blr_outreach', 'blr_outreach_excluded_contact_ids', 'string');
        self::reg_meta('blr_outreach', 'blr_outreach_filter_payload', 'string');
        self::reg_meta('blr_outreach', 'blr_outreach_last_sent_at', 'string');
        self::reg_meta('blr_outreach', 'blr_outreach_sent_count', 'integer');
        self::reg_meta('blr_outreach', 'blr_outreach_failed_count', 'integer');
        self::reg_meta('blr_outreach', 'blr_outreach_skipped_count', 'integer');
        self::reg_meta('blr_outreach', 'blr_outreach_queued_count', 'integer');
        self::reg_meta('blr_outreach', 'blr_outreach_sending_count', 'integer');
        self::reg_meta('blr_outreach', 'blr_outreach_total_count', 'integer');
        self::reg_meta('blr_outreach', 'blr_outreach_replied_count', 'integer');
        self::reg_meta('blr_outreach', 'blr_outreach_follow_up_count', 'integer');
        self::reg_meta('blr_outreach', 'blr_outreach_closed_count', 'integer');

        self::reg_meta('blr_outreach_log', 'blr_outreach_log_contact_id', 'integer');
        self::reg_meta('blr_outreach_log', 'blr_outreach_log_email', 'string');
        self::reg_meta('blr_outreach_log', 'blr_outreach_log_status', 'string');
        self::reg_meta('blr_outreach_log', 'blr_outreach_log_error', 'string');
        self::reg_meta('blr_outreach_log', 'blr_outreach_log_sent_at', 'string');
        self::reg_meta('blr_outreach_log', 'blr_outreach_log_note', 'string');
        self::reg_meta('blr_outreach_log', 'blr_outreach_log_last_updated', 'string');
        self::reg_meta('blr_outreach_log', 'blr_outreach_log_attempt_count', 'integer');
    }

    private static function reg_meta(string $post_type, string $key, string $type): void {
        register_post_meta($post_type, $key, [
            'type' => $type,
            'single' => true,
            'show_in_rest' => false,
            'sanitize_callback' => static function ($value) use ($key, $type) {
                if ($key === 'blr_outreach_status') {
                    return self::normalize_campaign_status((string) $value);
                }

                if ($key === 'blr_outreach_workflow_status') {
                    return self::normalize_campaign_workflow_status((string) $value);
                }

                if ($key === 'blr_outreach_log_status') {
                    return self::normalize_recipient_status((string) $value);
                }

                return Sanitizer::meta_value($key, $value, $type);
            },
            'auth_callback' => Access::rest_meta_auth_callback($post_type),
        ]);
    }

    public static function register_cron_schedules(array $schedules): array {
        if (!isset($schedules['blr_ba_every_five_minutes'])) {
            $schedules['blr_ba_every_five_minutes'] = [
                'interval' => 300,
                'display' => __('Every 5 minutes', 'blr-backline'),
            ];
        }

        return $schedules;
    }

    public static function ensure_cron_infrastructure(): void {
        if (!wp_next_scheduled(self::HEALTH_HOOK)) {
            wp_schedule_event(time() + 60, 'blr_ba_every_five_minutes', self::HEALTH_HOOK);
        }
    }

    public static function clear_scheduled_events(): void {
        wp_clear_scheduled_hook(self::PROCESS_HOOK);
        wp_clear_scheduled_hook(self::HEALTH_HOOK);
    }

    public static function handle_healthcheck(): void {
        self::ensure_cron_infrastructure();

        foreach (self::active_campaign_ids(5) as $campaign_id) {
            self::repair_stale_sending_logs((int) $campaign_id);
            $pending = self::get_pending_logs((int) $campaign_id, 1);
            if (empty($pending)) {
                self::refresh_campaign_rollup((int) $campaign_id);
                continue;
            }

            self::schedule_campaign_processing((int) $campaign_id, 1);
        }
    }

    public static function ajax_contact_search(): void {
        if (!is_user_logged_in() || !Access::user_can_access()) {
            wp_send_json_error(['message' => __('Forbidden', 'blr-backline')], 403);
        }

        if (!Access::module_is_accessible('contacts') && !Access::module_is_accessible('outreach')) {
            wp_send_json_error(['message' => __('Forbidden', 'blr-backline')], 403);
        }

        check_ajax_referer('blr_ba_outreach_contact_search', 'nonce');
        $query = isset($_GET['q']) ? sanitize_text_field((string) wp_unslash($_GET['q'])) : '';
        $ids = self::search_contact_ids_for_picker($query, 30);
        $results = [];

        foreach ($ids as $contact_id) {
            $contact = get_post((int) $contact_id);
            if (!$contact || $contact->post_type !== 'blr_contact') {
                continue;
            }

            $results[] = [
                'id' => (int) $contact->ID,
                'text' => self::contact_option_label((int) $contact->ID),
            ];
        }

        wp_send_json_success(['results' => $results]);
    }


    public static function ajax_recipient_preview(): void {
        if (!is_user_logged_in() || !Access::user_can_access()) {
            wp_send_json_error(['message' => __('Forbidden', 'blr-backline')], 403);
        }

        if (!Access::module_is_accessible('outreach')) {
            wp_send_json_error(['message' => __('Forbidden', 'blr-backline')], 403);
        }

        check_ajax_referer('blr_ba_outreach_recipient_preview', 'nonce');

        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Sanitized immediately by helper methods below.
        $manual_ids = self::sanitize_id_list(wp_unslash($_POST['manual_contact_ids'] ?? []));
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Sanitized immediately by helper methods below.
        $excluded_ids = self::sanitize_id_list(wp_unslash($_POST['excluded_contact_ids'] ?? []));
        $filters = self::filters_from_request(wp_unslash($_POST));

        wp_send_json_success(self::preview_payload($manual_ids, $filters, $excluded_ids));
    }

    private static function active_campaign_ids(int $limit = 5): array {
        // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Small scoped queue health check for active campaigns only.
        $campaigns = get_posts([
            'post_type' => 'blr_outreach',
            'post_status' => ['publish', 'draft'],
            'numberposts' => $limit,
            'orderby' => 'modified',
            'order' => 'ASC',
            'fields' => 'ids',
            'no_found_rows' => true,
            'meta_query' => [
                [
                    'key' => 'blr_outreach_status',
                    'value' => ['Queued', 'Sending'],
                    'compare' => 'IN',
                ],
            ],
        ]);

        return array_values(array_map('intval', is_array($campaigns) ? $campaigns : []));
    }

    public static function maybe_process_due_campaigns(): void {
        if (wp_doing_cron() || wp_doing_ajax() || (defined('REST_REQUEST') && REST_REQUEST)) {
            return;
        }

        if (!is_user_logged_in() || !Access::user_can_access()) {
            return;
        }

        if ((string) get_query_var('blr_band_admin') !== '1') {
            return;
        }

        self::ensure_cron_infrastructure();

        if (get_transient('blr_ba_outreach_runner_recent')) {
            return;
        }

        set_transient('blr_ba_outreach_runner_recent', 1, self::OPPORTUNISTIC_RUNNER_TTL);

        foreach (self::active_campaign_ids(5) as $campaign_id) {
            self::repair_stale_sending_logs((int) $campaign_id);
            $pending = self::get_pending_logs((int) $campaign_id, 1);
            if (empty($pending)) {
                self::refresh_campaign_rollup((int) $campaign_id);
                continue;
            }

            self::schedule_campaign_processing((int) $campaign_id, 1);
            if (function_exists('spawn_cron')) {
                spawn_cron(time());
            }
            break;
        }
    }

    public static function default_from_name(): string {
        return SettingsPage::default_from_name();
    }

    public static function default_from_email(): string {
        return SettingsPage::default_from_email();
    }

    public static function default_reply_to(): string {
        return SettingsPage::default_reply_to();
    }

    public static function campaign_delivery_statuses(): array {
        return ['Draft', 'Queued', 'Sending', 'Sent', 'Partial', 'Failed'];
    }

    public static function campaign_workflow_statuses(): array {
        return ['Replied', 'Follow-up Needed', 'Closed'];
    }

    public static function campaign_statuses(): array {
        return array_merge(self::campaign_delivery_statuses(), self::campaign_workflow_statuses());
    }

    public static function campaign_editable_statuses(): array {
        return self::campaign_workflow_statuses();
    }

    public static function recipient_statuses(): array {
        return ['Queued', 'Sending', 'Sent', 'Failed', 'Replied', 'Follow-up Needed', 'Closed'];
    }

    public static function retryable_recipient_statuses(): array {
        return ['Queued', 'Sending', 'Failed'];
    }

    public static function is_valid_campaign_delivery_status(string $status): bool {
        return in_array($status, self::campaign_delivery_statuses(), true);
    }

    public static function is_valid_campaign_workflow_status(string $status): bool {
        return in_array($status, self::campaign_workflow_statuses(), true);
    }

    public static function is_retryable_recipient_status(string $status): bool {
        return in_array($status, self::retryable_recipient_statuses(), true);
    }

    public static function normalize_campaign_status(string $value): string {
        return self::normalize_status_from_options($value, self::campaign_statuses(), [
            'follow up needed' => 'Follow-up Needed',
            'followup needed' => 'Follow-up Needed',
        ]);
    }

    public static function normalize_campaign_workflow_status(string $value): string {
        return self::normalize_status_from_options($value, self::campaign_workflow_statuses(), [
            'follow up needed' => 'Follow-up Needed',
            'followup needed' => 'Follow-up Needed',
        ]);
    }

    public static function normalize_recipient_status(string $value): string {
        return self::normalize_status_from_options($value, self::recipient_statuses(), [
            'follow up needed' => 'Follow-up Needed',
            'followup needed' => 'Follow-up Needed',
        ]);
    }

    private static function normalize_status_from_options(string $value, array $options, array $aliases = []): string {
        $value = trim(sanitize_text_field($value));
        if ($value === '') {
            return '';
        }

        foreach ($options as $option) {
            if (strcasecmp($option, $value) === 0) {
                return $option;
            }
        }

        $normalized = strtolower(preg_replace('/\s+/', ' ', str_replace('-', ' ', $value)));
        return $aliases[$normalized] ?? '';
    }

    public static function derive_campaign_delivery_status(int $campaign_id, array $stats = []): string {
        $campaign_id = absint($campaign_id);
        if ($campaign_id <= 0) {
            return 'Draft';
        }

        $stats = array_merge([
            'total' => 0,
            'queued' => 0,
            'sending' => 0,
            'sent' => 0,
            'failed' => 0,
            'replied' => 0,
            'follow_up' => 0,
            'closed' => 0,
        ], $stats);

        if ((int) $stats['sending'] > 0) {
            return 'Sending';
        }

        if ((int) $stats['queued'] > 0) {
            return 'Queued';
        }

        $delivered_count = (int) $stats['sent'] + (int) $stats['replied'] + (int) $stats['follow_up'] + (int) $stats['closed'];
        $failed_count = (int) $stats['failed'];

        if ($delivered_count > 0 && $failed_count > 0) {
            return 'Partial';
        }

        if ($delivered_count > 0) {
            return 'Sent';
        }

        if ($failed_count > 0) {
            return 'Failed';
        }

        $campaign = get_post($campaign_id);
        if ($campaign instanceof \WP_Post && $campaign->post_type === 'blr_outreach' && $campaign->post_status !== 'publish') {
            return 'Draft';
        }

        return ((int) $stats['total'] > 0) ? 'Failed' : 'Draft';
    }

    public static function resolved_campaign_status(int $campaign_id, array $stats = []): string {
        $campaign_id = absint($campaign_id);
        if ($campaign_id <= 0) {
            return 'Draft';
        }

        $delivery_status = (string) get_post_meta($campaign_id, 'blr_outreach_status', true);
        $workflow_status = (string) get_post_meta($campaign_id, 'blr_outreach_workflow_status', true);

        if (self::is_valid_campaign_workflow_status($delivery_status) && !self::is_valid_campaign_workflow_status($workflow_status)) {
            $workflow_status = $delivery_status;
        }

        if (!self::is_valid_campaign_delivery_status($delivery_status)) {
            $delivery_status = self::derive_campaign_delivery_status($campaign_id, $stats);
        }

        if (in_array($delivery_status, ['Queued', 'Sending'], true)) {
            return $delivery_status;
        }

        if (self::is_valid_campaign_workflow_status($workflow_status)) {
            return $workflow_status;
        }

        return $delivery_status;
    }

    public static function status_label(string $status): string {
        switch ($status) {
            case 'Draft': return __('Draft', 'blr-backline');
            case 'Queued': return __('Queued', 'blr-backline');
            case 'Sending': return __('Sending', 'blr-backline');
            case 'Sent': return __('Sent', 'blr-backline');
            case 'Partial': return __('Completed with Failures', 'blr-backline');
            case 'Failed': return __('Failed', 'blr-backline');
            case 'Replied': return __('Replied', 'blr-backline');
            case 'Follow-up Needed': return __('Follow-up Needed', 'blr-backline');
            case 'Closed': return __('Closed', 'blr-backline');
            default: return $status;
        }
    }

    public static function badge(string $status): string {
        $map = [
            'Draft' => 'secondary',
            'Queued' => 'warning',
            'Sending' => 'info',
            'Sent' => 'success',
            'Partial' => 'warning',
            'Failed' => 'danger',
            'Replied' => 'primary',
            'Follow-up Needed' => 'warning',
            'Closed' => 'dark',
        ];
        $class = $map[$status] ?? 'secondary';
        $label = $status !== '' ? self::status_label($status) : __('—', 'blr-backline');
        return '<span class="badge bg-' . esc_attr($class) . '">' . esc_html($label) . '</span>';
    }

    public static function sanitize_id_list($value): array {
        $arr = is_array($value) ? $value : [$value];
        $out = [];
        foreach ($arr as $v) {
            $id = absint($v);
            if ($id > 0) {
                $out[] = $id;
            }
        }
        return array_values(array_unique($out));
    }

    public static function csv_from_ids(array $ids): string {
        return implode(',', self::sanitize_id_list($ids));
    }

    public static function ids_from_csv($csv): array {
        if (!is_string($csv) || trim($csv) === '') {
            return [];
        }
        return self::sanitize_id_list(array_map('trim', explode(',', $csv)));
    }

    private static function sanitize_string_list($value): array {
        $arr = is_array($value) ? $value : [$value];
        $out = [];
        foreach ($arr as $v) {
            $v = trim(sanitize_text_field((string) $v));
            if ($v !== '') {
                $out[] = $v;
            }
        }
        $out = array_values(array_unique($out));
        natcasesort($out);
        return array_values($out);
    }

    public static function handle_contact_save(int $post_id, \WP_Post $post, bool $update): void {
        unset($post, $update);

        if ($post_id <= 0 || wp_is_post_revision($post_id)) {
            return;
        }

        self::flush_contact_meta_option_caches();
    }

    public static function handle_contact_post_change(int $post_id): void {
        $post = get_post($post_id);
        if (!$post instanceof \WP_Post || $post->post_type !== 'blr_contact') {
            return;
        }

        self::flush_contact_meta_option_caches();
    }

    public static function flush_contact_meta_option_caches(): void {
        global $wpdb;

        $prefixes = [
            '_transient_blr_ba_contact_meta_options_',
            '_transient_timeout_blr_ba_contact_meta_options_',
        ];

        foreach ($prefixes as $option_prefix) {
            $like = $wpdb->esc_like($option_prefix) . '%';
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Cache invalidation for transient-backed outreach contact filter options.
            $matching_options = $wpdb->get_col($wpdb->prepare("SELECT option_name FROM {$wpdb->options} WHERE option_name LIKE %s", $like));
            foreach ((array) $matching_options as $option_name) {
                delete_option((string) $option_name);
            }
        }
    }

    public static function meta_options(string $key): array {
        $cache_key = 'blr_ba_contact_meta_options_' . md5($key);
        $cached = get_transient($cache_key);
        if (is_array($cached)) {
            return $cached;
        }

        $out = [];
        $page = 1;

        do {
            $query = new \WP_Query([
                'post_type' => 'blr_contact',
                'post_status' => 'publish',
                'posts_per_page' => self::LOG_QUERY_CHUNK_SIZE,
                'paged' => $page,
                'fields' => 'ids',
                'orderby' => 'ID',
                'order' => 'ASC',
                'no_found_rows' => false,
            ]);

            foreach ((array) $query->posts as $id) {
                $val = trim((string) get_post_meta((int) $id, $key, true));
                if ($val !== '') {
                    $out[] = $val;
                }
            }

            $page++;
        } while ($page <= max(1, (int) $query->max_num_pages));

        $out = array_values(array_unique($out));
        natcasesort($out);
        $out = array_values($out);
        set_transient($cache_key, $out, HOUR_IN_SECONDS);
        return $out;
    }

    public static function contact_type_options(): array {
        $defaults = ['Venue', 'Promoter', 'Label', 'Blog', 'Playlist', 'Sync', 'Press', 'Fan', 'Other'];
        $found = self::meta_options('blr_contact_type');
        $merged = array_values(array_unique(array_merge($defaults, $found)));
        natcasesort($merged);
        return array_values($merged);
    }

    public static function state_options(): array {
        return self::meta_options('blr_contact_state');
    }

    public static function company_options(): array {
        return self::meta_options('blr_contact_company');
    }

    public static function role_options(): array {
        return self::meta_options('blr_contact_role');
    }

    public static function default_filters(): array {
        return [
            'contact_type' => [],
            'state' => [],
            'company' => [],
            'role' => [],
            'newsletter_only' => false,
            'has_email' => true,
        ];
    }

    public static function filters_from_request(array $src): array {
        $defaults = self::default_filters();
        return [
            'contact_type' => self::sanitize_string_list($src['filter_contact_type'] ?? $defaults['contact_type']),
            'state' => self::sanitize_string_list($src['filter_state'] ?? $defaults['state']),
            'company' => self::sanitize_string_list($src['filter_company'] ?? $defaults['company']),
            'role' => self::sanitize_string_list($src['filter_role'] ?? $defaults['role']),
            'newsletter_only' => !empty($src['filter_newsletter_only']),
            'has_email' => !isset($src['filter_has_email']) || !empty($src['filter_has_email']),
        ];
    }

    public static function filters_from_json($json): array {
        $defaults = self::default_filters();
        if (!is_string($json) || trim($json) === '') {
            return $defaults;
        }
        $decoded = json_decode($json, true);
        if (!is_array($decoded)) {
            return $defaults;
        }
        return [
            'contact_type' => self::sanitize_string_list($decoded['contact_type'] ?? $defaults['contact_type']),
            'state' => self::sanitize_string_list($decoded['state'] ?? $defaults['state']),
            'company' => self::sanitize_string_list($decoded['company'] ?? $defaults['company']),
            'role' => self::sanitize_string_list($decoded['role'] ?? $defaults['role']),
            'newsletter_only' => !empty($decoded['newsletter_only']),
            'has_email' => !isset($decoded['has_email']) || !empty($decoded['has_email']),
        ];
    }

    public static function filters_to_json(array $filters): string {
        return wp_json_encode([
            'contact_type' => self::sanitize_string_list($filters['contact_type'] ?? []),
            'state' => self::sanitize_string_list($filters['state'] ?? []),
            'company' => self::sanitize_string_list($filters['company'] ?? []),
            'role' => self::sanitize_string_list($filters['role'] ?? []),
            'newsletter_only' => !empty($filters['newsletter_only']),
            'has_email' => !isset($filters['has_email']) || !empty($filters['has_email']),
        ]);
    }

    public static function preview_payload(array $manual_ids, array $filters, array $excluded_ids = []): array {
        $preview = self::resolve_contact_preview($manual_ids, $filters, $excluded_ids);

        return [
            'included' => array_values($preview['included']),
            'excluded' => array_values($preview['excluded']),
            'counts' => [
                'included' => count($preview['included']),
                'excluded' => count($preview['excluded']),
                'manual' => count(array_filter($preview['included'], static function (array $recipient): bool {
                    return in_array((string) ($recipient['source'] ?? ''), ['manual', 'manual_filter'], true);
                })),
                'filtered' => count(array_filter($preview['included'], static function (array $recipient): bool {
                    return in_array((string) ($recipient['source'] ?? ''), ['filter', 'manual_filter'], true);
                })),
            ],
        ];
    }

    public static function resolve_contacts(array $manual_ids, array $filters, array $excluded_ids = []): array {
        $preview = self::resolve_contact_preview($manual_ids, $filters, $excluded_ids);
        return array_values($preview['included']);
    }

    private static function resolve_contact_preview(array $manual_ids, array $filters, array $excluded_ids = []): array {
        $manual_ids = self::sanitize_id_list($manual_ids);
        $excluded_ids = self::sanitize_id_list($excluded_ids);
        $filters = array_merge(self::default_filters(), $filters);

        $query_args = [
            'post_type' => 'blr_contact',
            'post_status' => 'publish',
            'posts_per_page' => self::LOG_QUERY_CHUNK_SIZE,
            'fields' => 'ids',
            'orderby' => 'title',
            'order' => 'ASC',
            'no_found_rows' => false,
        ];

        $meta_query = ['relation' => 'AND'];
        if (!empty($filters['has_email'])) {
            $meta_query[] = ['key' => 'blr_contact_email', 'value' => '', 'compare' => '!='];
        }
        if (!empty($filters['newsletter_only'])) {
            $meta_query[] = NewsletterDoubleOptIn::subscribed_meta_query();
        }
        if (!empty($filters['contact_type'])) {
            $meta_query[] = [
                'key' => 'blr_contact_type',
                'value' => self::sanitize_string_list($filters['contact_type']),
                'compare' => 'IN',
            ];
        }
        if (!empty($filters['state'])) {
            $meta_query[] = [
                'key' => 'blr_contact_state',
                'value' => self::sanitize_string_list($filters['state']),
                'compare' => 'IN',
            ];
        }
        if (!empty($filters['company'])) {
            $meta_query[] = [
                'key' => 'blr_contact_company',
                'value' => self::sanitize_string_list($filters['company']),
                'compare' => 'IN',
            ];
        }
        if (!empty($filters['role'])) {
            $meta_query[] = [
                'key' => 'blr_contact_role',
                'value' => self::sanitize_string_list($filters['role']),
                'compare' => 'IN',
            ];
        }

        $has_targeting_filters =
            !empty($filters['newsletter_only']) ||
            !empty($filters['contact_type']) ||
            !empty($filters['state']) ||
            !empty($filters['company']) ||
            !empty($filters['role']);

        $auto_ids = [];
        if ($has_targeting_filters) {
            if (count($meta_query) > 1) {
                $query_args['meta_query'] = $meta_query; // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
            }

            $auto_ids = self::query_contact_ids($query_args);
        }

        $ids = array_values(array_unique(array_merge($manual_ids, array_map('intval', $auto_ids))));
        sort($ids);

        if (!empty($ids)) {
            _prime_post_caches($ids, false, true);
        }

        $included = [];
        $excluded = [];
        foreach ($ids as $id) {
            $contact = get_post($id);
            if (!$contact || $contact->post_type !== 'blr_contact') {
                continue;
            }

            $email = sanitize_email((string) get_post_meta($id, 'blr_contact_email', true));
            if (!is_email($email)) {
                continue;
            }

            $is_manual = in_array($id, $manual_ids, true);
            $is_filter = in_array($id, $auto_ids, true);
            $recipient = [
                'contact_id' => (int) $id,
                'name' => (string) get_the_title($contact),
                'email' => $email,
                'first_name' => (string) get_post_meta($id, 'blr_contact_first_name', true),
                'company' => (string) get_post_meta($id, 'blr_contact_company', true),
                'role' => (string) get_post_meta($id, 'blr_contact_role', true),
                'contact_type' => (string) get_post_meta($id, 'blr_contact_type', true),
                'city' => (string) get_post_meta($id, 'blr_contact_city', true),
                'state' => (string) get_post_meta($id, 'blr_contact_state', true),
                'source' => self::recipient_source_key($is_manual, $is_filter),
                'source_label' => self::recipient_source_label($is_manual, $is_filter),
            ];

            if (in_array($id, $excluded_ids, true)) {
                $excluded[] = $recipient;
            } else {
                $included[] = $recipient;
            }
        }

        usort($included, [self::class, 'sort_recipient_rows']);
        usort($excluded, [self::class, 'sort_recipient_rows']);

        return [
            'included' => $included,
            'excluded' => $excluded,
        ];
    }

    private static function recipient_source_key(bool $is_manual, bool $is_filter): string {
        if ($is_manual && $is_filter) {
            return 'manual_filter';
        }

        if ($is_manual) {
            return 'manual';
        }

        return 'filter';
    }

    private static function recipient_source_label(bool $is_manual, bool $is_filter): string {
        if ($is_manual && $is_filter) {
            return __('Manual + filter', 'blr-backline');
        }

        if ($is_manual) {
            return __('Manual', 'blr-backline');
        }

        return __('Filter', 'blr-backline');
    }

    private static function sort_recipient_rows(array $left, array $right): int {
        $left_name = strtolower(trim((string) ($left['name'] ?? '')));
        $right_name = strtolower(trim((string) ($right['name'] ?? '')));

        if ($left_name === $right_name) {
            return strcmp((string) ($left['email'] ?? ''), (string) ($right['email'] ?? ''));
        }

        return strcmp($left_name, $right_name);
    }

    private static function query_contact_ids(array $query_args): array {
        $page = 1;
        $ids = [];

        do {
            $query_args['paged'] = $page;
            $query = new \WP_Query($query_args);
            foreach ((array) $query->posts as $post_id) {
                $ids[] = (int) $post_id;
            }
            $page++;
        } while ($page <= max(1, (int) $query->max_num_pages));

        return array_values(array_unique(array_map('intval', $ids)));
    }

    public static function contact_option_label(int $contact_id): string {
        $contact = get_post($contact_id);
        if (!$contact || $contact->post_type !== 'blr_contact') {
            return '';
        }

        $email = (string) get_post_meta($contact->ID, 'blr_contact_email', true);
        $company = (string) get_post_meta($contact->ID, 'blr_contact_company', true);
        $label = get_the_title($contact);
        if ($company !== '') {
            $label .= ' — ' . $company;
        }
        if ($email !== '') {
            $label .= ' <' . $email . '>';
        }

        return $label;
    }

    public static function manual_contact_options(array $selected_ids = []): array {
        $selected_ids = self::sanitize_id_list($selected_ids);
        if (empty($selected_ids)) {
            return [];
        }

        $contacts = get_posts([
            'post_type' => 'blr_contact',
            'post_status' => 'publish',
            'post__in' => $selected_ids,
            'numberposts' => count($selected_ids),
            'orderby' => 'title',
            'order' => 'ASC',
            'no_found_rows' => true,
        ]);

        return is_array($contacts) ? $contacts : [];
    }

    public static function search_contact_ids_for_picker(string $query = '', int $limit = 30): array {
        $args = [
            'post_type' => 'blr_contact',
            'post_status' => 'publish',
            'posts_per_page' => max(1, min(100, $limit)),
            'orderby' => 'title',
            'order' => 'ASC',
            'fields' => 'ids',
            'no_found_rows' => true,
        ];

        $query = trim($query);
        if ($query !== '') {
            $args['s'] = $query;
        }

        return array_values(array_map('intval', get_posts($args)));
    }

    public static function merge_tags(): array {
        return ['{{first_name}}', '{{name}}', '{{email}}', '{{company}}', '{{city}}', '{{state}}', '{{role}}', '{{contact_type}}'];
    }

    public static function apply_merge_tags(string $content, int $contact_id, string $email = '', string $context = 'text'): string {
        $name = (string) get_the_title($contact_id);
        $first_name = trim((string) get_post_meta($contact_id, 'blr_contact_first_name', true));
        if ($first_name === '') {
            $parts = preg_split('/\s+/', trim($name));
            $first_name = is_array($parts) && !empty($parts[0]) ? (string) $parts[0] : $name;
        }

        $replacements = [
            '{{first_name}}' => $first_name,
            '{{name}}' => $name,
            '{{email}}' => $email,
            '{{company}}' => (string) get_post_meta($contact_id, 'blr_contact_company', true),
            '{{city}}' => (string) get_post_meta($contact_id, 'blr_contact_city', true),
            '{{state}}' => (string) get_post_meta($contact_id, 'blr_contact_state', true),
            '{{role}}' => (string) get_post_meta($contact_id, 'blr_contact_role', true),
            '{{contact_type}}' => (string) get_post_meta($contact_id, 'blr_contact_type', true),
        ];

        if ($context === 'html') {
            $replacements = array_map('esc_html', $replacements);
        }

        return strtr($content, $replacements);
    }

    private static function render_email_body(string $body, int $contact_id, string $email = '', string $reply_to = ''): string {
        $html = nl2br(self::apply_merge_tags($body, $contact_id, $email, 'html'));
        $reply_to = sanitize_email($reply_to !== '' ? $reply_to : self::default_reply_to());

        if ($reply_to !== '') {
            $unsubscribe_href = 'mailto:' . $reply_to . '?subject=' . rawurlencode(__('Unsubscribe', 'blr-backline'));
            $html .= '<hr><p style="font-size:12px;color:#666;">' . sprintf(
                /* translators: %s: mailto unsubscribe link. */
                wp_kses_post(__('To unsubscribe from future outreach emails, <a href="%s">reply with unsubscribe</a>.', 'blr-backline')),
                esc_url($unsubscribe_href)
            ) . '</p>';
        }

        return $html;
    }

    public static function remove_deprecated_campaign_attachment_data(int $campaign_id): void {
        $campaign_id = absint($campaign_id);
        if ($campaign_id <= 0) {
            return;
        }

        $attachment_ids = self::ids_from_csv((string) get_post_meta($campaign_id, 'blr_outreach_attachment_ids', true));
        foreach (array_values(array_unique(array_map('absint', $attachment_ids))) as $attachment_id) {
            if ($attachment_id > 0) {
                CrudHelpers::delete_attachment($attachment_id);
            }
        }

        delete_post_meta($campaign_id, 'blr_outreach_attachment_ids');
    }

    public static function cleanup_campaign_related_data(int $campaign_id): void {
        $campaign_id = absint($campaign_id);
        if ($campaign_id <= 0) {
            return;
        }

        $log_ids = get_posts([
            'post_type' => 'blr_outreach_log',
            'post_status' => ['publish', 'draft', 'trash'],
            'numberposts' => -1,
            'fields' => 'ids',
            'no_found_rows' => true,
            'post_parent' => $campaign_id,
        ]);

        foreach (array_values(array_unique(array_map('absint', is_array($log_ids) ? $log_ids : []))) as $log_id) {
            if ($log_id > 0) {
                wp_delete_post($log_id, true);
            }
        }

        self::remove_deprecated_campaign_attachment_data($campaign_id);

        wp_clear_scheduled_hook(self::PROCESS_HOOK, [$campaign_id]);
        delete_option(self::lock_option_key($campaign_id));
        delete_transient('blr_ba_outreach_stats_' . $campaign_id);
    }

    public static function delete_campaign_and_related_data(int $campaign_id): bool {
        $campaign_id = absint($campaign_id);
        if ($campaign_id <= 0) {
            return false;
        }

        $campaign = get_post($campaign_id);
        if (!$campaign || $campaign->post_type !== 'blr_outreach') {
            return false;
        }

        self::cleanup_campaign_related_data($campaign_id);

        $deleted = wp_delete_post($campaign_id, true);
        return (bool) $deleted;
    }


    public static function build_headers(array $campaign): array {
        $from_name = Sanitizer::header_text((string) ($campaign['from_name'] ?? ''));
        $from_email = sanitize_email((string) ($campaign['from_email'] ?? ''));
        $reply_to = sanitize_email((string) ($campaign['reply_to'] ?? ''));

        if ($from_name === '') {
            $from_name = Sanitizer::header_text(self::default_from_name());
        }
        if ($from_email === '') {
            $from_email = sanitize_email(self::default_from_email());
        }
        if ($reply_to === '') {
            $reply_to = sanitize_email(self::default_reply_to());
        }

        $headers = ['Content-Type: text/html; charset=UTF-8'];
        if ($from_email !== '') {
            if ($from_name !== '') {
                if (function_exists('mb_encode_mimeheader')) {
                    $encoded_name = mb_encode_mimeheader($from_name, 'UTF-8', 'B', '');
                    $headers[] = 'From: ' . $encoded_name . ' <' . $from_email . '>';
                } else {
                    $needs_quoting = preg_match('/[(),.:;<>@\[\]\\"]/',$from_name) === 1;
                    $safe_name = $needs_quoting
                        ? '"' . str_replace('"', '\\"', $from_name) . '"'
                        : $from_name;
                    $headers[] = 'From: ' . $safe_name . ' <' . $from_email . '>';
                }
            } else {
                $headers[] = 'From: <' . $from_email . '>';
            }
        }
        if ($reply_to !== '') {
            $headers[] = 'Reply-To: ' . $reply_to;
        }

        $unsubscribe_mailbox = $reply_to !== '' ? $reply_to : $from_email;
        if ($unsubscribe_mailbox !== '') {
            $headers[] = 'List-Unsubscribe: <mailto:' . $unsubscribe_mailbox . '?subject=' . rawurlencode(__('Unsubscribe', 'blr-backline')) . '>';
        }

        return $headers;
    }

    public static function find_existing_log(int $campaign_id, int $contact_id, string $email): ?\WP_Post {
        $posts = get_posts([
            'post_type' => 'blr_outreach_log',
            'post_status' => ['publish', 'draft'],
            'numberposts' => 1,
            'post_parent' => $campaign_id,
            'meta_query' => [
                'relation' => 'AND',
                ['key' => 'blr_outreach_log_contact_id', 'value' => $contact_id, 'compare' => '='],
                ['key' => 'blr_outreach_log_email', 'value' => $email, 'compare' => '='],
            ],
        ]); // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
        return !empty($posts[0]) ? $posts[0] : null;
    }

    public static function already_sent(int $campaign_id, int $contact_id, string $email): bool {
        $existing = self::find_existing_log($campaign_id, $contact_id, $email);
        if (!$existing) {
            return false;
        }
        $sent_at = (string) get_post_meta($existing->ID, 'blr_outreach_log_sent_at', true);
        $status = (string) get_post_meta($existing->ID, 'blr_outreach_log_status', true);
        return trim($sent_at) !== '' || $status === 'Sent';
    }

    public static function upsert_log(int $campaign_id, int $contact_id, string $email, array $args = []): int {
        $existing = self::find_existing_log($campaign_id, $contact_id, $email);
        $title = (string) get_the_title($contact_id);
        if ($title === '') {
            $title = $email;
        }

        $postarr = [
            'post_type' => 'blr_outreach_log',
            'post_status' => 'publish',
            'post_parent' => $campaign_id,
            'post_title' => $title . ' — ' . $email,
        ];

        if ($existing) {
            $postarr['ID'] = (int) $existing->ID;
            $log_id = wp_update_post(wp_slash($postarr), true);
        } else {
            $log_id = wp_insert_post(wp_slash($postarr), true);
        }

        if (is_wp_error($log_id) || !$log_id) {
            return 0;
        }

        return self::update_log((int) $log_id, $contact_id, $email, $args);
    }


    public static function sanitize_log_email(string $email): string {
        $email = sanitize_email($email);
        return is_email($email) ? $email : '';
    }

    public static function validated_contact_id(int $contact_id): int {
        $contact_id = absint($contact_id);
        if ($contact_id <= 0) {
            return 0;
        }

        $contact = get_post($contact_id);
        if (!$contact || $contact->post_type !== 'blr_contact') {
            return 0;
        }

        return $contact_id;
    }

    public static function can_current_user_edit_contact(int $contact_id): bool {
        $contact_id = self::validated_contact_id($contact_id);
        return $contact_id > 0 && Access::can_edit_post($contact_id, 'blr_contact');
    }

    public static function queue_diagnostics(int $campaign_id = 0): array {
        $campaign_id = absint($campaign_id);
        $next_healthcheck = wp_next_scheduled(self::HEALTH_HOOK);
        $stats = $campaign_id > 0 ? self::get_campaign_stats($campaign_id) : [];

        return [
            'cron_ready' => $next_healthcheck !== false,
            'disable_wp_cron' => defined('DISABLE_WP_CRON') && DISABLE_WP_CRON === true,
            'next_healthcheck' => $next_healthcheck ? (int) $next_healthcheck : 0,
            'campaign_locked' => $campaign_id > 0 && (int) get_option(self::lock_option_key($campaign_id), 0) > 0,
            'pending' => (int) ($stats['queued'] ?? 0) + (int) ($stats['sending'] ?? 0),
            'queued' => (int) ($stats['queued'] ?? 0),
            'sending' => (int) ($stats['sending'] ?? 0),
            'failed' => (int) ($stats['failed'] ?? 0),
        ];
    }

    public static function manual_run_queue(int $campaign_id): array {
        $campaign_id = absint($campaign_id);
        self::ensure_cron_infrastructure();

        $report = self::process_campaign_queue($campaign_id);
        $report['cron_ready'] = wp_next_scheduled(self::HEALTH_HOOK) !== false;

        if (($report['remaining'] ?? 0) > 0) {
            self::schedule_campaign_processing($campaign_id, 20);
        }

        if (function_exists('spawn_cron')) {
            spawn_cron(time());
        }

        return $report;
    }

    public static function update_log(int $log_id, int $contact_id, string $email, array $args = []): int {
        if ($log_id <= 0) {
            return 0;
        }

        $contact_id = self::validated_contact_id($contact_id);
        $email = self::sanitize_log_email($email);

        update_post_meta($log_id, 'blr_outreach_log_contact_id', $contact_id);
        update_post_meta($log_id, 'blr_outreach_log_email', $email);
        if (array_key_exists('status', $args)) {
            update_post_meta($log_id, 'blr_outreach_log_status', sanitize_text_field((string) $args['status']));
        }
        if (array_key_exists('error', $args)) {
            update_post_meta($log_id, 'blr_outreach_log_error', sanitize_text_field((string) $args['error']));
        }
        if (array_key_exists('sent_at', $args)) {
            $sent_at = trim((string) $args['sent_at']);
            if ($sent_at !== '') {
                update_post_meta($log_id, 'blr_outreach_log_sent_at', sanitize_text_field($sent_at));
            } else {
                delete_post_meta($log_id, 'blr_outreach_log_sent_at');
            }
        }
        if (array_key_exists('note', $args)) {
            update_post_meta($log_id, 'blr_outreach_log_note', wp_kses_post((string) $args['note']));
        }
        if (array_key_exists('attempt_count', $args)) {
            update_post_meta($log_id, 'blr_outreach_log_attempt_count', absint($args['attempt_count']));
        }
        update_post_meta($log_id, 'blr_outreach_log_last_updated', current_time('mysql'));
        return $log_id;
    }

    private static function campaign_recipient_limit(): int {
        $limit = (int) apply_filters('blr_backline_outreach_campaign_recipient_limit', self::DEFAULT_CAMPAIGN_RECIPIENT_LIMIT);
        return max(1, $limit);
    }

    private static function daily_send_limit(): int {
        $limit = (int) apply_filters('blr_backline_outreach_daily_send_limit', self::DEFAULT_DAILY_SEND_LIMIT);
        return max(1, $limit);
    }

    private static function sent_count_today(): int {
        $start = current_time('Y-m-d 00:00:00');
        $query = new \WP_Query([
            'post_type' => 'blr_outreach_log',
            'post_status' => ['publish', 'draft'],
            'fields' => 'ids',
            'posts_per_page' => 1,
            'no_found_rows' => false,
            'meta_query' => [
                'relation' => 'AND',
                [
                    'key' => 'blr_outreach_log_status',
                    'value' => 'Sent',
                    'compare' => '=',
                ],
                [
                    'key' => 'blr_outreach_log_sent_at',
                    'value' => $start,
                    'compare' => '>=',
                    'type' => 'DATETIME',
                ],
            ],
        ]); // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query

        return (int) $query->found_posts;
    }

    private static function contact_is_suppressed(int $contact_id): bool {
        $contact_id = self::validated_contact_id($contact_id);
        if ($contact_id <= 0) {
            return true;
        }

        $suppressed = NewsletterDoubleOptIn::get_status($contact_id) === NewsletterDoubleOptIn::STATUS_UNSUBSCRIBED;

        /**
         * Filters whether an outreach contact should be suppressed from campaign delivery.
         *
         * @param bool $suppressed Whether the contact is suppressed.
         * @param int  $contact_id Contact post ID.
         */
        return (bool) apply_filters('blr_backline_outreach_contact_is_suppressed', $suppressed, $contact_id);
    }

    private static function lock_option_key(int $campaign_id): string {
        return 'blr_ba_outreach_lock_' . $campaign_id;
    }

    private static function acquire_campaign_lock(int $campaign_id): bool {
        $key = self::lock_option_key($campaign_id);
        $now = time();

        if (add_option($key, $now, '', false)) {
            return true;
        }

        $existing = (int) get_option($key, 0);
        if ($existing > 0 && ($existing + self::CAMPAIGN_LOCK_TTL) < $now) {
            delete_option($key);
            return add_option($key, $now, '', false);
        }

        return false;
    }

    private static function release_campaign_lock(int $campaign_id): void {
        delete_option(self::lock_option_key($campaign_id));
    }

    public static function schedule_campaign_processing(int $campaign_id, int $delay = 5): void {
        $campaign_id = absint($campaign_id);
        if ($campaign_id <= 0) {
            return;
        }

        self::ensure_cron_infrastructure();

        $scheduled = wp_next_scheduled(self::PROCESS_HOOK, [$campaign_id]);
        if (!$scheduled) {
            wp_schedule_single_event(time() + max(1, $delay), self::PROCESS_HOOK, [$campaign_id]);
        }

        if (function_exists('spawn_cron')) {
            spawn_cron(time());
        }
    }

    public static function queue_campaign(int $campaign_id, array $recipients, array &$report = []): bool {
        $campaign = get_post($campaign_id);
        if (!$campaign || $campaign->post_type !== 'blr_outreach') {
            return false;
        }

        $report = [
            'total' => 0,
            'queued' => 0,
            'skipped' => 0,
            'suppressed' => 0,
            'invalid' => 0,
            'limited' => 0,
        ];

        $recipient_limit = self::campaign_recipient_limit();
        $seen = [];
        foreach ($recipients as $recipient) {
            $contact_id = absint($recipient['contact_id'] ?? 0);
            $email = sanitize_email((string) ($recipient['email'] ?? ''));
            if ($contact_id <= 0 || !is_email($email)) {
                $report['invalid']++;
                continue;
            }

            $unique_key = $contact_id . '|' . strtolower($email);
            if (isset($seen[$unique_key])) {
                continue;
            }
            $seen[$unique_key] = true;

            if ($report['total'] >= $recipient_limit) {
                $report['limited']++;
                continue;
            }

            $report['total']++;

            if (self::contact_is_suppressed($contact_id)) {
                $report['suppressed']++;
                continue;
            }

            if (self::already_sent($campaign_id, $contact_id, $email)) {
                $report['skipped']++;
                continue;
            }

            $existing = self::find_existing_log($campaign_id, $contact_id, $email);
            $attempts = $existing ? (int) get_post_meta((int) $existing->ID, 'blr_outreach_log_attempt_count', true) : 0;
            $log_id = self::upsert_log($campaign_id, $contact_id, $email, [
                'status' => 'Queued',
                'error' => '',
                'sent_at' => '',
                'attempt_count' => $attempts,
            ]);
            if ($log_id > 0) {
                $report['queued']++;
            } else {
                $report['invalid']++;
            }
        }

        update_post_meta($campaign_id, 'blr_outreach_status', 'Queued');
        update_post_meta($campaign_id, 'blr_outreach_skipped_count', (int) $report['skipped']);
        self::refresh_campaign_rollup($campaign_id);
        self::schedule_campaign_processing($campaign_id, 5);

        return $report['queued'] > 0;
    }

    private static function get_pending_logs(int $campaign_id, int $limit = self::SEND_BATCH_SIZE): array {
        return get_posts([
            'post_type' => 'blr_outreach_log',
            'post_status' => ['publish', 'draft'],
            'numberposts' => $limit,
            'post_parent' => $campaign_id,
            'orderby' => 'ID',
            'order' => 'ASC',
            'no_found_rows' => true,
            'meta_query' => [
                [
                    'key' => 'blr_outreach_log_status',
                    'value' => ['Queued', 'Sending'],
                    'compare' => 'IN',
                ],
            ],
        ]); // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
    }

    public static function get_campaign_stats(int $campaign_id): array {
        $campaign_id = absint($campaign_id);
        $stats = [
            'total' => 0,
            'queued' => 0,
            'sending' => 0,
            'sent' => 0,
            'failed' => 0,
            'replied' => 0,
            'follow_up' => 0,
            'closed' => 0,
        ];

        if ($campaign_id <= 0) {
            return $stats;
        }

        $cache_key = 'blr_ba_outreach_stats_' . $campaign_id;
        $cached = get_transient($cache_key);
        if (is_array($cached)) {
            return array_merge($stats, $cached);
        }

        global $wpdb;
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Aggregated rollup query over outreach log postmeta; surrounding method caches the result in a transient.
        $status_rows = $wpdb->get_results(
            $wpdb->prepare(
                "SELECT pm.meta_value AS status_value, COUNT(1) AS log_count
                 FROM {$wpdb->posts} p
                 INNER JOIN {$wpdb->postmeta} pm ON pm.post_id = p.ID AND pm.meta_key = %s
                 WHERE p.post_type = %s
                   AND p.post_parent = %d
                   AND p.post_status IN ('publish', 'draft')
                 GROUP BY pm.meta_value",
                'blr_outreach_log_status',
                'blr_outreach_log',
                $campaign_id
            ),
            ARRAY_A
        );

        foreach ((array) $status_rows as $row) {
            $status = (string) ($row['status_value'] ?? '');
            $count = (int) ($row['log_count'] ?? 0);
            $stats['total'] += $count;
            if ($status === 'Queued') {
                $stats['queued'] = $count;
            } elseif ($status === 'Sending') {
                $stats['sending'] = $count;
            } elseif ($status === 'Sent') {
                $stats['sent'] = $count;
            } elseif ($status === 'Failed') {
                $stats['failed'] = $count;
            } elseif ($status === 'Replied') {
                $stats['replied'] = $count;
            } elseif ($status === 'Follow-up Needed') {
                $stats['follow_up'] = $count;
            } elseif ($status === 'Closed') {
                $stats['closed'] = $count;
            }
        }

        set_transient($cache_key, $stats, self::ROLLUP_CACHE_TTL);
        return $stats;
    }

    private static function refresh_campaign_rollup(int $campaign_id): array {
        $campaign_id = absint($campaign_id);
        if ($campaign_id <= 0) {
            return self::get_campaign_stats(0);
        }

        delete_transient('blr_ba_outreach_stats_' . $campaign_id);
        $stats = self::get_campaign_stats($campaign_id);

        update_post_meta($campaign_id, 'blr_outreach_total_count', (int) $stats['total']);
        update_post_meta($campaign_id, 'blr_outreach_sent_count', (int) $stats['sent']);
        update_post_meta($campaign_id, 'blr_outreach_failed_count', (int) $stats['failed']);
        update_post_meta($campaign_id, 'blr_outreach_skipped_count', max(0, (int) get_post_meta($campaign_id, 'blr_outreach_skipped_count', true)));
        update_post_meta($campaign_id, 'blr_outreach_queued_count', (int) $stats['queued']);
        update_post_meta($campaign_id, 'blr_outreach_sending_count', (int) $stats['sending']);
        update_post_meta($campaign_id, 'blr_outreach_replied_count', (int) $stats['replied']);
        update_post_meta($campaign_id, 'blr_outreach_follow_up_count', (int) $stats['follow_up']);
        update_post_meta($campaign_id, 'blr_outreach_closed_count', (int) $stats['closed']);

        $delivery_status = self::derive_campaign_delivery_status($campaign_id, $stats);

        update_post_meta($campaign_id, 'blr_outreach_status', $delivery_status);

        $last_sent = self::get_campaign_last_sent_at_from_logs($campaign_id);
        if ($last_sent !== '') {
            update_post_meta($campaign_id, 'blr_outreach_last_sent_at', $last_sent);
        } else {
            delete_post_meta($campaign_id, 'blr_outreach_last_sent_at');
        }

        return $stats;
    }

    public static function refresh_campaign_rollup_now(int $campaign_id): array {
        return self::refresh_campaign_rollup($campaign_id);
    }

    private static function get_campaign_last_sent_at_from_logs(int $campaign_id): string {
        global $wpdb;
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- One-shot aggregate used during explicit rollup refresh.
        $sent_at = (string) $wpdb->get_var(
            $wpdb->prepare(
                "SELECT MAX(pm_sent.meta_value)
                 FROM {$wpdb->posts} p
                 INNER JOIN {$wpdb->postmeta} pm_sent ON pm_sent.post_id = p.ID AND pm_sent.meta_key = %s
                 WHERE p.post_type = %s
                   AND p.post_parent = %d
                   AND p.post_status IN ('publish', 'draft')
                   AND pm_sent.meta_value <> ''",
                'blr_outreach_log_sent_at',
                'blr_outreach_log',
                $campaign_id
            )
        );
        return sanitize_text_field($sent_at);
    }

    private static function maybe_update_campaign_last_sent_at(int $campaign_id, string $sent_at): void {
        $campaign_id = absint($campaign_id);
        $sent_at = trim($sent_at);
        if ($campaign_id <= 0 || $sent_at === '') {
            return;
        }

        $existing = (string) get_post_meta($campaign_id, 'blr_outreach_last_sent_at', true);
        if ($existing === '' || strcmp($sent_at, $existing) > 0) {
            update_post_meta($campaign_id, 'blr_outreach_last_sent_at', sanitize_text_field($sent_at));
        }
    }

    public static function sync_contact_status(int $contact_id, string $status, bool $increment_sent = false, bool $require_edit_access = false): void {
        $contact_id = self::validated_contact_id($contact_id);
        if ($contact_id <= 0) {
            return;
        }

        if ($require_edit_access && !self::can_current_user_edit_contact($contact_id)) {
            return;
        }

        update_post_meta($contact_id, 'blr_contact_outreach_status', sanitize_text_field($status));

        if ($increment_sent) {
            update_post_meta($contact_id, 'blr_contact_last_contacted', current_time('mysql'));
            $count = (int) get_post_meta($contact_id, 'blr_contact_total_emails_sent', true);
            update_post_meta($contact_id, 'blr_contact_total_emails_sent', $count + 1);
        }

        if ($status === 'Replied') {
            update_post_meta($contact_id, 'blr_contact_last_reply', current_time('mysql'));
        }
    }

    private static function repair_stale_sending_logs(int $campaign_id): int {
        $campaign_id = absint($campaign_id);
        if ($campaign_id <= 0) {
            return 0;
        }

        $cutoff = gmdate('Y-m-d H:i:s', time() - self::STALE_SENDING_SECONDS);
        $logs = get_posts([
            'post_type' => 'blr_outreach_log',
            'post_status' => ['publish', 'draft'],
            'numberposts' => self::LOG_QUERY_CHUNK_SIZE,
            'post_parent' => $campaign_id,
            'orderby' => 'ID',
            'order' => 'ASC',
            'meta_query' => [
                'relation' => 'AND',
                [
                    'key' => 'blr_outreach_log_status',
                    'value' => 'Sending',
                    'compare' => '=',
                ],
                [
                    'key' => 'blr_outreach_log_last_updated',
                    'value' => $cutoff,
                    'compare' => '<=',
                    'type' => 'CHAR',
                ],
            ],
        ]); // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query

        $count = 0;
        foreach ((array) $logs as $log) {
            $log_id = (int) $log->ID;
            $contact_id = (int) get_post_meta($log_id, 'blr_outreach_log_contact_id', true);
            $email = sanitize_email((string) get_post_meta($log_id, 'blr_outreach_log_email', true));
            self::update_log($log_id, $contact_id, $email, [
                'status' => 'Queued',
                'error' => '',
                'sent_at' => '',
            ]);
            $count++;
        }

        if ($count > 0) {
            self::refresh_campaign_rollup($campaign_id);
        }

        return $count;
    }

    public static function requeue_failed_logs(int $campaign_id): int {
        $count = 0;
        $page = 1;

        do {
            $query = new \WP_Query([
                'post_type' => 'blr_outreach_log',
                'post_status' => ['publish', 'draft'],
                'posts_per_page' => self::LOG_QUERY_CHUNK_SIZE,
                'paged' => $page,
                'post_parent' => $campaign_id,
                'orderby' => 'ID',
                'order' => 'ASC',
                'meta_query' => [
                    [
                        'key' => 'blr_outreach_log_status',
                        'value' => 'Failed',
                        'compare' => '=',
                    ],
                ],
            ]); // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query

            foreach ((array) $query->posts as $log) {
                $log_id = (int) $log->ID;
                $contact_id = (int) get_post_meta($log_id, 'blr_outreach_log_contact_id', true);
                $email = sanitize_email((string) get_post_meta($log_id, 'blr_outreach_log_email', true));
                if ($contact_id <= 0 || !is_email($email)) {
                    continue;
                }
                self::update_log($log_id, $contact_id, $email, [
                    'status' => 'Queued',
                    'error' => '',
                    'sent_at' => '',
                ]);
                $count++;
            }

            $page++;
        } while ($page <= max(1, (int) $query->max_num_pages));

        if ($count > 0) {
            update_post_meta($campaign_id, 'blr_outreach_status', 'Queued');
            self::refresh_campaign_rollup($campaign_id);
        }

        return $count;
    }

    public static function process_campaign_queue(int $campaign_id, int $limit = self::SEND_BATCH_SIZE): array {
        $report = [
            'processed' => 0,
            'sent' => 0,
            'failed' => 0,
            'skipped' => 0,
            'remaining' => 0,
            'locked' => false,
        ];

        $campaign = get_post($campaign_id);
        if (!$campaign || $campaign->post_type !== 'blr_outreach') {
            return $report;
        }

        if (!self::acquire_campaign_lock($campaign_id)) {
            $report['locked'] = true;
            return $report;
        }

        self::repair_stale_sending_logs($campaign_id);

        $subject = (string) get_post_meta($campaign_id, 'blr_outreach_subject', true);
        $body = (string) $campaign->post_content;
        $from_name = (string) get_post_meta($campaign_id, 'blr_outreach_from_name', true);
        $from_email = (string) get_post_meta($campaign_id, 'blr_outreach_from_email', true);
        $reply_to = (string) get_post_meta($campaign_id, 'blr_outreach_reply_to', true);
        $headers = self::build_headers([
            'from_name' => $from_name,
            'from_email' => $from_email,
            'reply_to' => $reply_to,
        ]);

        update_post_meta($campaign_id, 'blr_outreach_status', 'Sending');

        $daily_send_limit = self::daily_send_limit();
        $sent_today = self::sent_count_today();

        try {
            $logs = self::get_pending_logs($campaign_id, $limit);
            foreach ($logs as $log) {
                $log_id = (int) $log->ID;
                $contact_id = (int) get_post_meta($log_id, 'blr_outreach_log_contact_id', true);
                $email = sanitize_email((string) get_post_meta($log_id, 'blr_outreach_log_email', true));
                $current_status = (string) get_post_meta($log_id, 'blr_outreach_log_status', true);
                $existing_sent_at = (string) get_post_meta($log_id, 'blr_outreach_log_sent_at', true);
                if ($contact_id <= 0 || !is_email($email)) {
                    self::update_log($log_id, $contact_id, $email, [
                        'status' => 'Failed',
                        'error' => __('Recipient record is missing a valid email address.', 'blr-backline'),
                    ]);
                    $report['failed']++;
                    $report['processed']++;
                    continue;
                }

                if (trim($existing_sent_at) !== '') {
                    if (self::is_retryable_recipient_status($current_status)) {
                        self::update_log($log_id, $contact_id, $email, [
                            'sent_at' => '',
                        ]);
                    } else {
                        self::update_log($log_id, $contact_id, $email, [
                            'status' => 'Sent',
                        ]);
                        $report['skipped']++;
                        continue;
                    }
                }

                if (self::contact_is_suppressed($contact_id)) {
                    self::update_log($log_id, $contact_id, $email, [
                        'status' => 'Closed',
                        'note' => __('Skipped because the contact is unsubscribed.', 'blr-backline'),
                        'error' => '',
                    ]);
                    $report['skipped']++;
                    $report['processed']++;
                    continue;
                }

                if ($sent_today >= $daily_send_limit) {
                    break;
                }

                $attempt_count = (int) get_post_meta($log_id, 'blr_outreach_log_attempt_count', true);
                self::update_log($log_id, $contact_id, $email, [
                    'status' => 'Sending',
                    'error' => '',
                    'attempt_count' => $attempt_count + 1,
                ]);

                $personal_subject = self::apply_merge_tags($subject, $contact_id, $email);
                $personal_body = self::render_email_body($body, $contact_id, $email, $reply_to);

                $last_error = '';
                $listener = static function ($wp_error) use (&$last_error) {
                    if (is_wp_error($wp_error)) {
                        $last_error = (string) $wp_error->get_error_message();
                    }
                };
                add_action('wp_mail_failed', $listener, 10, 1);
                $sent = wp_mail($email, $personal_subject, $personal_body, $headers);
                remove_action('wp_mail_failed', $listener, 10);

                if ($sent) {
                    $sent_at = current_time('mysql');
                    self::update_log($log_id, $contact_id, $email, [
                        'status' => 'Sent',
                        'error' => '',
                        'sent_at' => $sent_at,
                    ]);
                    self::maybe_update_campaign_last_sent_at($campaign_id, $sent_at);
                    self::sync_contact_status($contact_id, 'Sent', true);
                    $sent_today++;
                    $report['sent']++;
                } else {
                    self::update_log($log_id, $contact_id, $email, [
                        'status' => 'Failed',
                        'error' => $last_error !== '' ? $last_error : __('wp_mail() returned false.', 'blr-backline'),
                    ]);
                    self::sync_contact_status($contact_id, 'Failed', false);
                    $report['failed']++;
                }

                $report['processed']++;
            }

            $stats = self::refresh_campaign_rollup($campaign_id);
            $report['remaining'] = (int) $stats['queued'] + (int) $stats['sending'];
            if ($report['remaining'] > 0) {
                self::schedule_campaign_processing($campaign_id, 20);
            }
        } finally {
            self::release_campaign_lock($campaign_id);
        }

        return $report;
    }

    public static function handle_process_campaign_event(int $campaign_id): void {
        self::process_campaign_queue(absint($campaign_id));
    }

    public static function send_campaign_now(int $campaign_id, array $recipients, array &$report = []): bool {
        return self::queue_campaign($campaign_id, $recipients, $report);
    }
}

