<?php
/**
 * Standalone apCampaign application surface.
 *
 * @package apCampaign
 */

declare(strict_types=1);

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

if (!class_exists('APCampaign_App')) {
    final class APCampaign_App
    {
        private const QUERY_VAR = 'apcampaign_app';
        private const ACTION_VAR = 'apcampaign_action';
        private const CAMPAIGN_VAR = 'apcampaign_id';
        private const NONCE_ACTION = 'apcampaign_app';

        public static function boot(): void
        {
            add_action('init', [self::class, 'register_routes']);
            add_filter('query_vars', [self::class, 'register_query_vars']);
            add_action('template_redirect', [self::class, 'maybe_render']);
            add_action('wp_enqueue_scripts', [self::class, 'enqueue_assets']);
            add_action('wp_ajax_apcampaign_app_save_campaign', [self::class, 'ajax_save_campaign']);
            add_action('wp_ajax_apcampaign_app_run_preflight', [self::class, 'ajax_run_preflight']);
            add_action('wp_ajax_apcampaign_app_test_send', [self::class, 'ajax_test_send']);
            add_action('wp_ajax_apcampaign_app_templates', [self::class, 'ajax_templates']);
            add_action('wp_ajax_apcampaign_app_save_template', [self::class, 'ajax_save_template']);
            add_action('wp_ajax_apcampaign_app_delete_template', [self::class, 'ajax_delete_template']);
            add_action('wp_ajax_apcampaign_app_retarget_preview', [self::class, 'ajax_retarget_preview']);
            add_action('wp_ajax_apcampaign_app_retarget', [self::class, 'ajax_retarget']);
            add_action('wp_ajax_apcampaign_app_archive', [self::class, 'ajax_archive']);
            add_action('wp_ajax_apcampaign_app_settings', [self::class, 'ajax_settings']);
            add_action('wp_ajax_apcampaign_app_save_settings', [self::class, 'ajax_save_settings']);
            add_action('wp_ajax_apcampaign_app_schedule', [self::class, 'ajax_schedule']);
            add_action('wp_ajax_apcampaign_app_send', [self::class, 'ajax_send']);
            add_action('wp_ajax_apcampaign_app_import_docx', [self::class, 'ajax_import_docx']);
        }

        public static function register_routes(): void
        {
            add_rewrite_rule('^apcampaign/?$', 'index.php?' . self::QUERY_VAR . '=1&' . self::ACTION_VAR . '=dashboard', 'top');
            add_rewrite_rule('^apcampaign/create/?$', 'index.php?' . self::QUERY_VAR . '=1&' . self::ACTION_VAR . '=create', 'top');
            add_rewrite_rule('^apcampaign/create-simple/?$', 'index.php?' . self::QUERY_VAR . '=1&' . self::ACTION_VAR . '=create-simple', 'top');
            add_rewrite_rule('^apcampaign/create-raw/?$', 'index.php?' . self::QUERY_VAR . '=1&' . self::ACTION_VAR . '=create-raw', 'top');
            add_rewrite_rule('^apcampaign/document-guide/?$', 'index.php?' . self::QUERY_VAR . '=1&' . self::ACTION_VAR . '=document-guide', 'top');
            add_rewrite_rule('^apcampaign/lists/?$', 'index.php?' . self::QUERY_VAR . '=1&' . self::ACTION_VAR . '=lists', 'top');
            add_rewrite_rule('^apcampaign/mailer/?$', 'index.php?' . self::QUERY_VAR . '=1&' . self::ACTION_VAR . '=mailer', 'top');
            add_rewrite_rule('^apcampaign/archived/?$', 'index.php?' . self::QUERY_VAR . '=1&' . self::ACTION_VAR . '=archived', 'top');
            add_rewrite_rule('^apcampaign/edit/([0-9]+)/?$', 'index.php?' . self::QUERY_VAR . '=1&' . self::ACTION_VAR . '=edit&' . self::CAMPAIGN_VAR . '=$matches[1]', 'top');
            add_rewrite_rule('^apcampaign/view/([0-9]+)/?$', 'index.php?' . self::QUERY_VAR . '=1&' . self::ACTION_VAR . '=view&' . self::CAMPAIGN_VAR . '=$matches[1]', 'top');
        }

        public static function register_query_vars(array $vars): array
        {
            $vars[] = self::QUERY_VAR;
            $vars[] = self::ACTION_VAR;
            $vars[] = self::CAMPAIGN_VAR;
            return $vars;
        }

        public static function url(string $path = ''): string
        {
            $path = trim($path, '/');
            if ((string) get_option('permalink_structure', '') !== '') {
                return home_url('/apcampaign/' . ($path !== '' ? $path . '/' : ''));
            }

            $action = $path !== '' ? $path : 'dashboard';
            $campaign_id = 0;
            if (preg_match('#^(edit|view)/(\d+)$#', $path, $matches)) {
                $action = $matches[1];
                $campaign_id = (int) $matches[2];
            }

            $args = [
                self::QUERY_VAR => '1',
                self::ACTION_VAR => $action,
            ];
            if ($campaign_id > 0) {
                $args[self::CAMPAIGN_VAR] = $campaign_id;
            }
            return add_query_arg($args, home_url('/'));
        }

        public static function enqueue_assets(): void
        {
            if (!self::is_app_request()) {
                return;
            }
            wp_enqueue_media();
            wp_enqueue_style('apcampaign-grapesjs', APCAMPAIGN_PLUGIN_URL . 'assets/vendor/grapesjs/grapes.min.css', [], '0.23.2');
            $app_css_version = APCAMPAIGN_VERSION . '-' . (string) filemtime(APCAMPAIGN_PLUGIN_DIR . 'assets/app.css');
            $app_js_version = APCAMPAIGN_VERSION . '-' . (string) filemtime(APCAMPAIGN_PLUGIN_DIR . 'assets/app.js');
            wp_enqueue_style('apcampaign-app', APCAMPAIGN_PLUGIN_URL . 'assets/app.css', ['apcampaign-grapesjs'], $app_css_version);
            wp_enqueue_script('apcampaign-grapesjs', APCAMPAIGN_PLUGIN_URL . 'assets/vendor/grapesjs/grapes.min.js', [], '0.23.2', true);
            wp_enqueue_script('apcampaign-app', APCAMPAIGN_PLUGIN_URL . 'assets/app.js', ['apcampaign-grapesjs'], $app_js_version, true);
            wp_localize_script('apcampaign-app', 'apCampaignApp', [
                'ajaxUrl' => admin_url('admin-ajax.php'),
                'nonce' => wp_create_nonce(self::NONCE_ACTION),
                'mediaEnabled' => self::can_upload(),
                'wordTemplateUrl' => (string) apply_filters(
                    'apcampaign_document_template_url',
                    APCAMPAIGN_PLUGIN_URL . 'assets/templates/apcampaign-email-document-template.docx'
                ),
            ]);
        }

        public static function maybe_render(): void
        {
            if (!self::is_app_request()) {
                return;
            }
            if (!defined('APCAMPAIGN_DEMO_MODE') && !is_user_logged_in()) {
                auth_redirect();
            }
            if (!self::can_access()) {
                wp_die(esc_html__('You do not have access to apCampaign.', 'apcampaign'), 403);
            }

            $action = sanitize_key((string) get_query_var(self::ACTION_VAR));
            status_header(200);
            nocache_headers();
            echo '<!doctype html><html ' . get_language_attributes() . '><head>';
            echo '<meta charset="' . esc_attr(get_bloginfo('charset')) . '">';
            echo '<meta name="viewport" content="width=device-width, initial-scale=1">';
            $app_title = (string) apply_filters('apcampaign_app_title', __('apCampaign', 'apcampaign'));
            echo '<title>' . esc_html($app_title) . '</title>';
            wp_head();
            $body_classes = (array) apply_filters('apcampaign_app_body_classes', ['apcampaign-body', 'apcampaign-theme-dark']);
            $body_classes = array_values(array_filter(array_map('sanitize_html_class', $body_classes)));
            echo '</head><body class="' . esc_attr(implode(' ', $body_classes)) . '">';
            if (defined('APCAMPAIGN_DEMO_MODE')) {
                echo '<div class="apcampaign-demo-notice" role="status">Live product demo — uploads, saving, testing, scheduling, and sending are disabled.</div>';
            }
            echo '<main class="apcampaign-app" data-apcampaign-app>';
            echo apply_filters('apcampaign_app_sidebar_html', self::render_sidebar());
            echo '<section class="apcampaign-main">';
            echo self::render_utility_nav();
            if ($action === 'create') {
                echo self::render_create_choice();
            } elseif ($action === 'create-simple') {
                echo self::render_composer(0, 'block');
            } elseif ($action === 'create-raw') {
                echo self::render_composer(0, 'raw_html');
            } elseif ($action === 'document-guide') {
                echo self::render_document_guide();
            } elseif ($action === 'edit') {
                echo self::render_composer((int) get_query_var(self::CAMPAIGN_VAR));
            } elseif ($action === 'view') {
                echo self::render_sent_view((int) get_query_var(self::CAMPAIGN_VAR));
            } elseif ($action === 'lists') {
                echo self::render_lists_screen();
            } elseif ($action === 'mailer') {
                echo self::render_mailer_screen();
            } elseif ($action === 'archived') {
                echo self::render_archived_campaigns();
            } else {
                echo self::render_dashboard();
            }
            echo '</section></main>';
            wp_footer();
            echo '</body></html>';
            exit;
        }

        public static function ajax_save_campaign(): void
        {
            self::ajax_guard();
            $campaign_id = absint($_POST['campaign_id'] ?? 0);
            $mode = sanitize_key((string) wp_unslash($_POST['composer_mode'] ?? 'block'));
            $design = json_decode((string) wp_unslash($_POST['design_json'] ?? '{"blocks":[]}'), true);
            if (!is_array($design)) {
                $design = ['blocks' => []];
            }
            $raw_html = (string) wp_unslash($_POST['raw_html'] ?? '');
            $rendered_html = (string) wp_unslash($_POST['rendered_html'] ?? '');
            $subject = sanitize_text_field(wp_unslash($_POST['subject_line'] ?? ''));
            $preheader = sanitize_text_field(wp_unslash($_POST['preheader_text'] ?? ''));
            $from_name = sanitize_text_field(wp_unslash($_POST['from_name'] ?? ''));
            $from_email = sanitize_email(wp_unslash($_POST['from_email'] ?? ''));
            $name = sanitize_text_field(wp_unslash($_POST['name'] ?? 'Untitled Campaign'));
            $audience_key = sanitize_key(wp_unslash($_POST['audience_key'] ?? ''));

            if ($campaign_id < 1) {
                $created = APCampaign_Lifecycle::create_campaign([
                    'name' => $name,
                    'status' => 'draft',
                    'composer_mode' => $mode,
                    'audience_key' => $audience_key,
                    'subject_line' => $subject,
                    'preheader_text' => $preheader,
                    'from_name' => $from_name,
                    'from_email' => $from_email,
                    'reply_to_email' => $from_email,
                ]);
                $campaign_id = (int) $created['campaign_id'];
            } else {
                self::update_campaign($campaign_id, [
                    'name' => $name,
                    'status' => 'draft',
                    'composer_mode' => $mode,
                    'audience_key' => $audience_key,
                    'subject_line' => $subject,
                    'preheader_text' => $preheader,
                    'from_name' => $from_name,
                    'from_email' => $from_email,
                    'reply_to_email' => $from_email,
                ]);
            }

            $snapshot = APCampaign_Lifecycle::save_content_snapshot($campaign_id, [
                'snapshot_type' => 'draft',
                'composer_mode' => $mode,
                'design' => $design,
                'raw_html' => $raw_html,
                'rendered_html' => $rendered_html,
            ]);
            wp_send_json_success([
                'campaign_id' => $campaign_id,
                'snapshot' => $snapshot,
                'edit_url' => self::url('edit/' . $campaign_id),
            ]);
        }

        public static function ajax_run_preflight(): void
        {
            self::ajax_guard();
            $campaign_id = absint($_POST['campaign_id'] ?? 0);
            $snapshot_id = self::current_snapshot_id($campaign_id);
            if ($campaign_id < 1 || $snapshot_id < 1) {
                wp_send_json_error(['message' => 'Save the campaign before running preflight.'], 400);
            }
            $preflight = APCampaign_Lifecycle::run_preflight($campaign_id, $snapshot_id);
            $intel = APCampaign_Lifecycle::run_intelligence($campaign_id, $snapshot_id);
            wp_send_json_success(['preflight' => $preflight, 'intelligence' => $intel]);
        }

        public static function ajax_test_send(): void
        {
            self::ajax_guard();
            $campaign_id = absint($_POST['campaign_id'] ?? 0);
            $to = sanitize_email(wp_unslash($_POST['to'] ?? ''));
            if ($campaign_id < 1 || !is_email($to)) {
                wp_send_json_error(['message' => 'Campaign and valid test recipient are required.'], 400);
            }
            $campaign = self::campaign($campaign_id);
            $snapshot = self::current_snapshot($campaign_id);
            if (!$campaign || !$snapshot) {
                wp_send_json_error(['message' => 'Save the campaign before sending a test.'], 400);
            }
            $html = APCampaign_Renderer::wrap_document((string) $snapshot['rendered_html'], (string) $campaign['subject_line']);
            $sent = wp_mail($to, (string) $campaign['subject_line'], $html, [
                'Content-Type: text/html; charset=UTF-8',
                'From: ' . (string) $campaign['from_name'] . ' <' . (string) $campaign['from_email'] . '>',
            ]);
            $has_apmailer = class_exists('APMailer_Lifecycle');
            $smtp_profile = $has_apmailer ? APMailer_Lifecycle::default_smtp_profile() : null;
            $has_smtp_route = $has_apmailer && is_array($smtp_profile) && APMailer_Lifecycle::smtp_route_ready($smtp_profile);
            wp_send_json_success([
                'sent' => (bool) $sent,
                'delivery_status' => $sent ? 'accepted_by_wordpress' : 'rejected_by_wordpress',
                'transport' => $has_smtp_route ? 'apMailer generic SMTP route' : ($has_apmailer ? 'apMailer WordPress bridge' : 'WordPress mail'),
                'provider_configured' => $has_smtp_route,
                'provider_delivery_confirmed' => false,
            ]);
        }

        public static function ajax_templates(): void
        {
            self::ajax_guard();
            global $wpdb;
            $t = APCampaign_Schema::tables();
            $requested_mode = sanitize_key((string) wp_unslash($_POST['composer_mode'] ?? 'block'));
            $requested_mode = $requested_mode === 'raw_html' ? 'raw_html' : 'block';
            $rows = $wpdb->get_results(
                $wpdb->prepare(
                    "SELECT id, template_key, name, description, composer_mode, design_json, raw_html, category, status, updated_at FROM {$t['templates']} WHERE status = 'active' AND composer_mode = %s ORDER BY updated_at DESC LIMIT 50",
                    $requested_mode
                ),
                ARRAY_A
            ) ?: [];
            $templates = array_map(static function (array $row): array {
                $design = json_decode((string) ($row['design_json'] ?? '{"blocks":[]}'), true);
                if (!is_array($design)) {
                    $design = ['blocks' => []];
                }
                return [
                    'source' => 'template',
                    'id' => (int) $row['id'],
                    'template_key' => (string) $row['template_key'],
                    'name' => (string) $row['name'],
                    'description' => (string) $row['description'],
                    'composer_mode' => (string) $row['composer_mode'],
                    'design' => $design,
                    'raw_html' => (string) ($row['raw_html'] ?? ''),
                    'category' => (string) $row['category'],
                    'updated_at' => (string) $row['updated_at'],
                ];
            }, $rows);

            $sent_rows = $wpdb->get_results(
                $wpdb->prepare(
                    "SELECT c.id AS campaign_id, c.name, c.subject_line, c.status, c.composer_mode, c.updated_at, s.design_json, s.raw_html
                FROM {$t['campaigns']} c
                INNER JOIN {$t['content_snapshots']} s ON s.campaign_id = c.id AND s.is_current = 1
                WHERE c.composer_mode = %s AND c.status IN ('sent', 'completed', 'send_complete')
                ORDER BY c.updated_at DESC
                LIMIT 30",
                    $requested_mode
                ),
                ARRAY_A
            ) ?: [];
            $sent_designs = array_map(static function (array $row): array {
                $design = json_decode((string) ($row['design_json'] ?? '{"blocks":[]}'), true);
                if (!is_array($design)) {
                    $design = ['blocks' => []];
                }
                return [
                    'source' => 'sent_design',
                    'id' => (int) $row['campaign_id'],
                    'template_key' => 'sent_' . (int) $row['campaign_id'],
                    'name' => (string) ($row['name'] ?: $row['subject_line'] ?: __('Sent Campaign', 'apcampaign')),
                    'description' => (string) ($row['subject_line'] ?? ''),
                    'composer_mode' => $requested_mode,
                    'design' => $design,
                    'raw_html' => (string) ($row['raw_html'] ?? ''),
                    'category' => 'sent',
                    'updated_at' => (string) $row['updated_at'],
                    'status' => (string) $row['status'],
                ];
            }, $sent_rows);

            wp_send_json_success(['templates' => $templates, 'sent_designs' => $sent_designs]);
        }

        public static function ajax_save_template(): void
        {
            self::ajax_guard();
            $mode = sanitize_key((string) wp_unslash($_POST['composer_mode'] ?? 'block'));
            $design = json_decode((string) wp_unslash($_POST['design_json'] ?? '{"blocks":[]}'), true);
            if (!is_array($design)) {
                $design = ['blocks' => []];
            }
            $raw_html = (string) wp_unslash($_POST['raw_html'] ?? '');
            $rendered_html = (string) wp_unslash($_POST['rendered_html'] ?? '');
            $name = sanitize_text_field(wp_unslash($_POST['template_name'] ?? ''));
            if ($name === '') {
                $name = sanitize_text_field(wp_unslash($_POST['name'] ?? 'Campaign Template'));
            }

            $template = APCampaign_Lifecycle::create_template([
                'name' => $name,
                'description' => sanitize_textarea_field(wp_unslash($_POST['description'] ?? 'Saved from apCampaign composer.')),
                'composer_mode' => $mode,
                'design' => $design,
                'raw_html' => $raw_html,
                'rendered_html' => $rendered_html,
                'category' => 'user',
                'status' => 'active',
            ]);
            wp_send_json_success($template);
        }

        public static function ajax_delete_template(): void
        {
            self::ajax_guard();
            global $wpdb;
            $t = APCampaign_Schema::tables();
            $template_key = sanitize_text_field(wp_unslash($_POST['template_key'] ?? ''));
            if ($template_key === '') {
                wp_send_json_error(['message' => 'Template key is required.'], 400);
            }
            $updated = $wpdb->update(
                $t['templates'],
                ['status' => 'deleted', 'updated_at' => current_time('mysql')],
                ['template_key' => $template_key],
                ['%s', '%s'],
                ['%s']
            );
            if ($updated === false) {
                wp_send_json_error(['message' => 'Template could not be deleted.'], 500);
            }
            wp_send_json_success(['deleted' => true, 'template_key' => $template_key]);
        }

        public static function ajax_retarget(): void
        {
            self::ajax_guard();
            $campaign_id = absint($_POST['campaign_id'] ?? 0);
            $behavior = sanitize_key((string) wp_unslash($_POST['behavior'] ?? 'not_opened'));
            if (!in_array($behavior, ['all', 'opened', 'clicked', 'not_opened'], true)) {
                $behavior = 'not_opened';
            }
            $campaign = self::campaign($campaign_id);
            if (!$campaign) {
                wp_send_json_error(['message' => 'Campaign not found.'], 404);
            }
            $restrict = (bool) get_option('apcampaign_retarget_owner_only', false);
            if ($restrict && !current_user_can('manage_options') && (int) ($campaign['created_by_user_id'] ?? 0) !== get_current_user_id()) {
                wp_send_json_error(['message' => 'Retargeting is restricted to the campaign owner on this site.'], 403);
            }
            $snapshot = self::current_snapshot($campaign_id);
            if (!$snapshot) {
                wp_send_json_error(['message' => 'Campaign needs a saved snapshot before retargeting.'], 400);
            }
            $recipients = self::retarget_recipients($campaign_id, $behavior);
            if (!$recipients) {
                wp_send_json_error(['message' => 'No matching recipients found for this retargeting audience.'], 400);
            }
            $subject_override = sanitize_text_field(wp_unslash($_POST['subject_line'] ?? ''));
            $labels = [
                'all' => 'All prior recipients',
                'opened' => 'Opened',
                'clicked' => 'Clicked',
                'not_opened' => 'Did not open',
            ];
            $created = APCampaign_Lifecycle::create_campaign([
                'name' => 'Retarget: ' . (string) $campaign['name'] . ' - ' . $labels[$behavior],
                'status' => 'draft',
                'composer_mode' => (string) $campaign['composer_mode'],
                'audience_key' => 'retarget_' . $campaign_id . '_' . $behavior,
                'subject_line' => $subject_override !== '' ? $subject_override : (string) $campaign['subject_line'],
                'preheader_text' => (string) $campaign['preheader_text'],
                'from_name' => (string) $campaign['from_name'],
                'from_email' => (string) $campaign['from_email'],
                'reply_to_email' => (string) $campaign['reply_to_email'],
                'sender_identity_ref' => (string) $campaign['sender_identity_ref'],
                'settings' => [
                    'retargeting' => [
                        'source_campaign_id' => $campaign_id,
                        'behavior' => $behavior,
                    ],
                ],
                'source_plugin' => 'apcampaign',
                'source_record_id' => 'retarget:' . $campaign_id,
            ]);
            $new_campaign_id = (int) $created['campaign_id'];
            $design = json_decode((string) ($snapshot['design_json'] ?? '{"blocks":[]}'), true);
            if (!is_array($design)) {
                $design = ['blocks' => []];
            }
            APCampaign_Lifecycle::save_content_snapshot($new_campaign_id, [
                'snapshot_type' => 'draft',
                'composer_mode' => (string) $campaign['composer_mode'],
                'design' => $design,
                'raw_html' => (string) ($snapshot['raw_html'] ?? ''),
                'rendered_html' => (string) ($snapshot['rendered_html'] ?? ''),
            ]);
            APCampaign_Lifecycle::create_audience_snapshot($new_campaign_id, 'retarget_' . $campaign_id . '_' . $behavior, $recipients, [
                'source_system' => 'apcampaign_retarget',
                'source_ref' => (string) $campaign_id,
                'behavior' => $behavior,
            ]);
            wp_send_json_success([
                'campaign_id' => $new_campaign_id,
                'recipient_count' => count($recipients),
                'edit_url' => self::url('edit/' . $new_campaign_id),
                'message' => 'Retargeting draft created for ' . count($recipients) . ' recipients.',
            ]);
        }

        public static function ajax_retarget_preview(): void
        {
            self::ajax_guard();
            $campaign_id = absint($_POST['campaign_id'] ?? 0);
            $campaign = self::campaign($campaign_id);
            if (!$campaign) {
                wp_send_json_error(['message' => 'Campaign not found.'], 404);
            }
            $restrict = (bool) get_option('apcampaign_retarget_owner_only', false);
            if ($restrict && !current_user_can('manage_options') && (int) ($campaign['created_by_user_id'] ?? 0) !== get_current_user_id()) {
                wp_send_json_error(['message' => 'Retargeting is restricted to the campaign owner on this site.'], 403);
            }
            $counts = [];
            foreach (['not_opened', 'opened', 'clicked', 'all'] as $behavior) {
                $counts[$behavior] = count(self::retarget_recipients($campaign_id, $behavior));
            }
            wp_send_json_success([
                'campaign' => [
                    'id' => $campaign_id,
                    'name' => (string) $campaign['name'],
                    'subject_line' => (string) $campaign['subject_line'],
                    'status' => (string) $campaign['status'],
                    'updated_at' => (string) $campaign['updated_at'],
                ],
                'counts' => $counts,
            ]);
        }

        public static function ajax_archive(): void
        {
            self::ajax_guard();
            $campaign_id = absint($_POST['campaign_id'] ?? 0);
            $campaign = self::campaign($campaign_id);
            if (!$campaign) {
                wp_send_json_error(['message' => 'Campaign not found.'], 404);
            }
            self::update_campaign($campaign_id, ['status' => 'archived']);
            wp_send_json_success([
                'archived' => true,
                'campaign_id' => $campaign_id,
                'archive_url' => self::url('archived'),
            ]);
        }

        public static function ajax_settings(): void
        {
            self::ajax_guard();
            wp_send_json_success(['settings' => self::composer_settings()]);
        }

        public static function ajax_save_settings(): void
        {
            self::ajax_guard();
            update_option('apcampaign_default_from_name', sanitize_text_field(wp_unslash($_POST['default_from_name'] ?? '')));
            update_option('apcampaign_default_from_email', sanitize_email(wp_unslash($_POST['default_from_email'] ?? '')));
            update_option('apcampaign_spam_pass_score', max(0, min(100, absint($_POST['spam_pass_score'] ?? 70))));
            update_option('apcampaign_preflight_link_limit', max(1, min(200, absint($_POST['preflight_link_limit'] ?? 25))));
            update_option('apcampaign_retarget_owner_only', !empty($_POST['retarget_owner_only']) && (string) wp_unslash($_POST['retarget_owner_only']) !== '0' ? '1' : '0');
            update_option('apcampaign_physical_address', sanitize_textarea_field(wp_unslash($_POST['physical_address'] ?? '')));
            wp_send_json_success(['settings' => self::composer_settings()]);
        }

        public static function ajax_schedule(): void
        {
            self::ajax_guard();
            $campaign_id = absint($_POST['campaign_id'] ?? 0);
            if ($campaign_id < 1 || !self::current_snapshot($campaign_id)) {
                wp_send_json_error(['message' => 'Save the campaign before scheduling.'], 400);
            }
            $raw_time = sanitize_text_field((string) wp_unslash($_POST['scheduled_at'] ?? ''));
            $scheduled = date_create_immutable($raw_time, wp_timezone());
            if (!$scheduled || $scheduled->getTimestamp() <= time() + 60) {
                wp_send_json_error(['message' => 'Choose a valid future date and time.'], 400);
            }
            $prepared = self::prepare_send_job($campaign_id);
            if (empty($prepared['success'])) {
                wp_send_json_error(['message' => (string) ($prepared['error'] ?? 'The campaign is not ready to schedule.'), 'details' => $prepared], 400);
            }
            global $wpdb;
            $t = APCampaign_Schema::tables();
            $local_time = wp_date('Y-m-d H:i:s', $scheduled->getTimestamp(), wp_timezone());
            $wpdb->update($t['send_jobs'], [
                'status' => 'scheduled',
                'scheduled_at' => $local_time,
                'updated_at' => current_time('mysql'),
            ], ['id' => (int) $prepared['send_job_id']]);
            $wpdb->update($t['campaigns'], [
                'status' => 'scheduled',
                'scheduled_at' => $local_time,
                'updated_at' => current_time('mysql'),
            ], ['id' => $campaign_id]);
            if (!APCampaign_Delivery::schedule((int) $prepared['send_job_id'], $scheduled->getTimestamp())) {
                wp_send_json_error(['message' => 'WordPress could not schedule the send. No message was sent.'], 500);
            }
            wp_send_json_success([
                'scheduled' => true,
                'send_job_id' => (int) $prepared['send_job_id'],
                'scheduled_at' => $local_time,
                'message' => 'Campaign scheduled for ' . $local_time . '.',
            ]);
        }

        public static function ajax_send(): void
        {
            self::ajax_guard();
            $campaign_id = absint($_POST['campaign_id'] ?? 0);
            if ($campaign_id < 1 || !self::current_snapshot($campaign_id)) {
                wp_send_json_error(['message' => 'Save the campaign before sending.'], 400);
            }
            $prepared = self::prepare_send_job($campaign_id);
            if (empty($prepared['success'])) {
                wp_send_json_error(['message' => (string) ($prepared['error'] ?? 'The campaign is not ready to send.'), 'details' => $prepared], 400);
            }
            $result = APCampaign_Delivery::process_job((int) $prepared['send_job_id']);
            if (empty($result['success'])) {
                wp_send_json_error(['message' => (string) ($result['error'] ?? 'The send job failed safely.'), 'details' => $result], 500);
            }
            wp_send_json_success([
                'sent' => true,
                'send_job_id' => (int) $prepared['send_job_id'],
                'result' => $result,
                'message' => !empty($result['complete']) ? 'Campaign send completed.' : 'Campaign send started; remaining recipients are queued.',
            ]);
        }

        public static function ajax_import_docx(): void
        {
            self::ajax_guard();
            if (!self::can_upload()) {
                wp_send_json_error(['message' => 'You do not have permission to upload files.'], 403);
            }
            if (empty($_FILES['docx_file']) || !empty($_FILES['docx_file']['error'])) {
                wp_send_json_error(['message' => 'Choose a Word .docx file to import.'], 400);
            }
            $file = $_FILES['docx_file'];
            $name = sanitize_file_name((string) ($file['name'] ?? ''));
            if (strtolower(pathinfo($name, PATHINFO_EXTENSION)) !== 'docx' || (int) ($file['size'] ?? 0) > 20971520) {
                wp_send_json_error(['message' => 'Only Word .docx files up to 20 MB are accepted.'], 400);
            }
            if (!class_exists('ZipArchive')) {
                wp_send_json_error(['message' => 'This server is missing the PHP ZIP extension needed to read Word documents.'], 500);
            }
            $type = wp_check_filetype_and_ext((string) $file['tmp_name'], $name);
            if (($type['ext'] ?? '') !== 'docx') {
                wp_send_json_error(['message' => 'This file is not a valid Word .docx document.'], 400);
            }

            require_once ABSPATH . 'wp-admin/includes/file.php';
            require_once ABSPATH . 'wp-admin/includes/media.php';
            require_once ABSPATH . 'wp-admin/includes/image.php';

            $parsed = self::parse_docx_for_raw_email((string) $file['tmp_name']);
            if (is_wp_error($parsed)) {
                wp_send_json_error(['message' => $parsed->get_error_message()], 400);
            }

            $uploaded = [];
            foreach ($parsed['images'] as $rid => $image) {
                $bits = wp_upload_bits($image['name'], null, $image['bytes']);
                if (!empty($bits['error'])) {
                    continue;
                }
                $attachment_id = wp_insert_attachment([
                    'post_mime_type' => $image['mime'],
                    'post_title' => sanitize_text_field(pathinfo($image['name'], PATHINFO_FILENAME)),
                    'post_status' => 'inherit',
                ], $bits['file']);
                if ($attachment_id && !is_wp_error($attachment_id)) {
                    wp_update_attachment_metadata($attachment_id, wp_generate_attachment_metadata($attachment_id, $bits['file']));
                    $uploaded[$rid] = ['id' => $attachment_id, 'url' => $bits['url']];
                }
            }

            $warnings = [];
            $blocks = self::classify_docx_email_blocks($parsed['paragraphs'], $uploaded, $warnings);
            $html = self::render_docx_email_html($blocks);
            if (!$html) {
                wp_send_json_error(['message' => 'No usable campaign content was found in the document.'], 400);
            }
            $cta_count = count(array_filter($blocks, static fn($block) => $block['type'] === 'cta'));
            if (!$cta_count) {
                $warnings[] = 'No linked CTA button was found.';
            }
            if (empty($parsed['meta']['subject'])) {
                $warnings[] = 'Subject Line was not found; enter it before saving.';
            }
            if (empty($parsed['meta']['preview'])) {
                $warnings[] = 'Preview Text was not found; enter it before saving.';
            }

            wp_send_json_success([
                'html' => $html,
                'campaign_name' => $parsed['meta']['campaign_name'] ?? '',
                'subject' => $parsed['meta']['subject'] ?? '',
                'preview' => $parsed['meta']['preview'] ?? '',
                'image_count' => count($uploaded),
                'cta_count' => $cta_count,
                'warnings' => array_values(array_unique($warnings)),
            ]);
        }

        private static function render_sidebar(): string
        {
            ob_start();
            ?>
            <aside class="apcampaign-sidebar">
                <a class="apcampaign-brand" href="<?php echo esc_url(self::url()); ?>">
                    <strong><?php echo esc_html__('apCampaign', 'apcampaign'); ?></strong>
                    <span><?php echo esc_html__('Campaign Studio', 'apcampaign'); ?></span>
                </a>
                <nav>
                    <a href="<?php echo esc_url(self::url()); ?>"><?php echo esc_html__('Dashboard', 'apcampaign'); ?></a>
                    <a href="<?php echo esc_url(self::url('create-simple')); ?>"><?php echo esc_html__('Drag & Drop', 'apcampaign'); ?></a>
                    <a href="<?php echo esc_url(self::url('create-raw')); ?>"><?php echo esc_html__('Raw HTML', 'apcampaign'); ?></a>
                    <a href="<?php echo esc_url(self::url('lists')); ?>"><?php echo esc_html__('Campaign Lists', 'apcampaign'); ?></a>
                    <a href="<?php echo esc_url(self::url('mailer')); ?>"><?php echo esc_html__('Mailer Settings', 'apcampaign'); ?></a>
                    <a href="<?php echo esc_url(self::url('archived')); ?>"><?php echo esc_html__('Archived Campaigns', 'apcampaign'); ?></a>
                </nav>
            </aside>
            <?php
            return (string) ob_get_clean();
        }

        private static function render_lists_screen(): string
        {
            $lists = self::contact_lists();
            ob_start();
            ?>
            <header class="apcampaign-header">
                <div>
                    <p><?php echo esc_html__('Unlimited native audiences', 'apcampaign'); ?></p>
                    <h1><?php echo esc_html__('Campaign Lists', 'apcampaign'); ?></h1>
                </div>
                <div class="apcampaign-header-actions">
                    <a class="apcampaign-button primary" href="<?php echo esc_url(self::url('create-simple')); ?>"><?php echo esc_html__('Create Campaign', 'apcampaign'); ?></a>
                </div>
            </header>
            <section class="apcampaign-panel">
                <div class="apcampaign-panel-head">
                    <div>
                        <p><?php echo esc_html__('Composer compatibility', 'apcampaign'); ?></p>
                        <h2><?php echo esc_html__('Current selectable lists', 'apcampaign'); ?></h2>
                    </div>
                </div>
                <?php if (!$lists) : ?>
                    <div class="apcampaign-empty"><?php echo esc_html__('No lists yet. Import or add contacts to create your first native apCampaign audience.', 'apcampaign'); ?></div>
                <?php else : ?>
                    <table class="apcampaign-table">
                        <thead>
                            <tr>
                                <th><?php echo esc_html__('List', 'apcampaign'); ?></th>
                                <th><?php echo esc_html__('Active Contacts', 'apcampaign'); ?></th>
                                <th><?php echo esc_html__('Status', 'apcampaign'); ?></th>
                            </tr>
                        </thead>
                        <tbody>
                            <?php foreach ($lists as $list) : ?>
                                <tr>
                                    <td><?php echo esc_html((string) ($list['name'] ?? 'Untitled list')); ?></td>
                                    <td><?php echo esc_html(number_format_i18n((int) ($list['active_count'] ?? 0))); ?></td>
                                    <td><?php echo esc_html((string) ($list['status'] ?? 'active')); ?></td>
                                </tr>
                            <?php endforeach; ?>
                        </tbody>
                    </table>
                <?php endif; ?>
            </section>
            <?php
            return (string) ob_get_clean();
        }

        private static function render_mailer_screen(): string
        {
            $status = self::dependency_status('apmailer');
            $settings = self::composer_settings();
            ob_start();
            ?>
            <header class="apcampaign-header">
                <div>
                    <p><?php echo esc_html__('Delivery companion', 'apcampaign'); ?></p>
                    <h1><?php echo esc_html__('Mailer Settings', 'apcampaign'); ?></h1>
                </div>
                <div class="apcampaign-header-actions">
                    <a class="apcampaign-button" href="<?php echo esc_url(self::url('create-raw')); ?>"><?php echo esc_html__('Raw HTML', 'apcampaign'); ?></a>
                    <a class="apcampaign-button primary" href="<?php echo esc_url(self::url('create-simple')); ?>"><?php echo esc_html__('Drag & Drop', 'apcampaign'); ?></a>
                </div>
            </header>
            <section class="apcampaign-panel apcampaign-companion-panel">
                <div class="apcampaign-companion-head">
                    <div>
                        <h2><?php echo esc_html__('Mailer status', 'apcampaign'); ?></h2>
                    </div>
                    <?php echo self::render_dependency_badge($status); ?>
                </div>
                <?php echo self::render_dependency_guidance($status, 'apMailer'); ?>
            </section>
            <section class="apcampaign-panel">
                <div class="apcampaign-panel-head">
                    <div>
                        <p><?php echo esc_html__('Composer defaults', 'apcampaign'); ?></p>
                        <h2><?php echo esc_html__('Current sender fallback', 'apcampaign'); ?></h2>
                    </div>
                </div>
                <div class="apcampaign-companion-grid">
                    <article>
                        <span><?php echo esc_html__('Default From Name', 'apcampaign'); ?></span>
                        <strong><?php echo esc_html((string) $settings['default_from_name']); ?></strong>
                    </article>
                    <article>
                        <span><?php echo esc_html__('Default From Email', 'apcampaign'); ?></span>
                        <strong><?php echo esc_html((string) $settings['default_from_email']); ?></strong>
                    </article>
                    <article>
                        <span><?php echo esc_html__('Live Send Gate', 'apcampaign'); ?></span>
                        <strong><?php echo esc_html__('Requires list + mail route', 'apcampaign'); ?></strong>
                    </article>
                </div>
            </section>
            <?php
            return (string) ob_get_clean();
        }

        private static function render_utility_nav(): string
        {
            $home_url = (string) apply_filters('apcampaign_app_home_url', 'https://artistprowp.com/apcampaign/');
            $home_label = (string) apply_filters('apcampaign_app_home_label', __('Home', 'apcampaign'));
            ob_start();
            ?>
            <nav class="apcampaign-utility-nav" aria-label="<?php echo esc_attr__('Campaign navigation', 'apcampaign'); ?>">
                <a href="<?php echo esc_url($home_url); ?>"><?php echo esc_html($home_label); ?></a>
                <a href="<?php echo esc_url(self::url()); ?>"><?php echo esc_html__('Campaigns', 'apcampaign'); ?></a>
                <a href="<?php echo esc_url(self::url('create')); ?>"><?php echo esc_html__('New Campaign', 'apcampaign'); ?></a>
                <button type="button" class="apcampaign-theme-toggle" data-apcampaign-theme-toggle aria-pressed="false" aria-label="<?php echo esc_attr__('Toggle light and dark mode', 'apcampaign'); ?>"></button>
            </nav>
            <?php
            return (string) ob_get_clean();
        }

        private static function render_dashboard(): string
        {
            global $wpdb;
            $t = APCampaign_Schema::tables();
            $campaigns = $wpdb->get_results("SELECT * FROM {$t['campaigns']} WHERE status <> 'archived' ORDER BY updated_at DESC LIMIT 50", ARRAY_A);
            $counts = APCampaign_Lifecycle::counts();
            $summary = self::campaign_performance_summary();
            ob_start();
            ?>
            <header class="apcampaign-header">
                <div>
                    <p><?php echo esc_html((string) apply_filters(
                        'apcampaign_app_dashboard_eyebrow',
                        __('Standalone WordPress campaign system', 'apcampaign')
                    )); ?></p>
                    <h1><?php echo esc_html__('Campaign Dashboard', 'apcampaign'); ?></h1>
                </div>
                <div class="apcampaign-header-actions">
                    <a class="apcampaign-button" href="<?php echo esc_url(self::url('create-raw')); ?>"><?php echo esc_html__('Raw HTML', 'apcampaign'); ?></a>
                    <a class="apcampaign-button primary" href="<?php echo esc_url(self::url('create-simple')); ?>"><?php echo esc_html__('Drag & Drop Email', 'apcampaign'); ?></a>
                </div>
            </header>
            <section class="apcampaign-kpis">
                <?php foreach (['campaigns' => 'Total Campaigns', 'send_jobs' => 'Sends', 'contact_lists' => 'Lists', 'contacts' => 'Contacts', 'preflight_reports' => 'Preflights'] as $key => $label) : ?>
                    <article><span><?php echo esc_html($label); ?></span><strong><?php echo esc_html((string) ($counts[$key] ?? 0)); ?></strong></article>
                <?php endforeach; ?>
            </section>
            <section class="apcampaign-panel apcampaign-performance-panel">
                <div class="apcampaign-panel-head">
                    <div>
                        <p><?php echo esc_html__('Campaign performance', 'apcampaign'); ?></p>
                        <h2><?php echo esc_html__('Sent Campaign Summary', 'apcampaign'); ?></h2>
                    </div>
                    <a href="<?php echo esc_url(self::url('archived')); ?>"><?php echo esc_html__('Archived Campaigns', 'apcampaign'); ?></a>
                </div>
                <div class="apcampaign-performance-grid">
                    <article><span><?php echo esc_html__('Sent Campaigns', 'apcampaign'); ?></span><strong><?php echo esc_html(number_format_i18n((int) $summary['sent_campaigns'])); ?></strong></article>
                    <article><span><?php echo esc_html__('Emails Sent', 'apcampaign'); ?></span><strong><?php echo esc_html(number_format_i18n((int) $summary['emails_sent'])); ?></strong></article>
                    <article><span><?php echo esc_html__('Audience Total', 'apcampaign'); ?></span><strong><?php echo esc_html(number_format_i18n((int) $summary['recipients_total'])); ?></strong></article>
                    <article><span><?php echo esc_html__('Open Rate', 'apcampaign'); ?></span><strong><?php echo esc_html(self::format_rate((float) $summary['open_rate'])); ?></strong></article>
                    <article><span><?php echo esc_html__('Click Rate', 'apcampaign'); ?></span><strong><?php echo esc_html(self::format_rate((float) $summary['click_rate'])); ?></strong></article>
                    <article><span><?php echo esc_html__('Failures', 'apcampaign'); ?></span><strong><?php echo esc_html(number_format_i18n((int) $summary['failures'])); ?></strong></article>
                </div>
            </section>
            <section class="apcampaign-panel">
                <h2><?php echo esc_html__('Recent Campaigns', 'apcampaign'); ?></h2>
                <div class="apcampaign-campaign-list">
                    <?php if (!$campaigns) : ?>
                        <article class="apcampaign-campaign-row"><p><?php echo esc_html__('No campaigns yet.', 'apcampaign'); ?></p></article>
                    <?php endif; ?>
                    <?php foreach ($campaigns as $campaign) : ?>
                        <?php echo self::render_campaign_row($campaign, false); ?>
                    <?php endforeach; ?>
                </div>
            </section>
            <?php
            return (string) ob_get_clean();
        }

        private static function render_archived_campaigns(): string
        {
            global $wpdb;
            $t = APCampaign_Schema::tables();
            $campaigns = $wpdb->get_results("SELECT * FROM {$t['campaigns']} WHERE status = 'archived' ORDER BY updated_at DESC LIMIT 200", ARRAY_A);
            ob_start();
            ?>
            <header class="apcampaign-header">
                <div>
                    <p><?php echo esc_html__('Campaign archive', 'apcampaign'); ?></p>
                    <h1><?php echo esc_html__('Archived Campaigns', 'apcampaign'); ?></h1>
                </div>
                <div class="apcampaign-header-actions">
                    <a class="apcampaign-button" href="<?php echo esc_url(self::url()); ?>"><?php echo esc_html__('Dashboard', 'apcampaign'); ?></a>
                </div>
            </header>
            <section class="apcampaign-panel">
                <h2><?php echo esc_html__('Previously archived campaigns', 'apcampaign'); ?></h2>
                <div class="apcampaign-campaign-list">
                    <?php if (!$campaigns) : ?>
                        <article class="apcampaign-campaign-row"><p><?php echo esc_html__('No archived campaigns yet.', 'apcampaign'); ?></p></article>
                    <?php endif; ?>
                    <?php foreach ($campaigns as $campaign) : ?>
                        <?php echo self::render_campaign_row($campaign, true); ?>
                    <?php endforeach; ?>
                </div>
            </section>
            <?php
            return (string) ob_get_clean();
        }

        /**
         * @param array<string,mixed> $campaign
         */
        private static function render_campaign_row(array $campaign, bool $archived): string
        {
            $campaign_id = (int) $campaign['id'];
            $stats = self::campaign_detail_stats($campaign_id);
            ob_start();
            ?>
            <article class="apcampaign-campaign-row" data-apcampaign-row="<?php echo esc_attr((string) $campaign_id); ?>">
                <div>
                    <h3><?php echo esc_html((string) $campaign['name']); ?></h3>
                    <p><?php echo esc_html((string) ($campaign['subject_line'] ?: __('No subject yet', 'apcampaign'))); ?></p>
                    <div class="apcampaign-row-metrics">
                        <span><?php echo esc_html(ucwords(str_replace('_', ' ', (string) $campaign['status']))); ?></span>
                        <span><?php echo esc_html((string) $campaign['composer_mode']); ?></span>
                    </div>
                </div>
                <div class="apcampaign-row-actions">
                    <a class="apcampaign-button" href="<?php echo esc_url(self::url('view/' . $campaign_id)); ?>"><?php echo esc_html__('View', 'apcampaign'); ?></a>
                    <div class="apcampaign-row-stats" aria-label="<?php echo esc_attr__('Campaign stats', 'apcampaign'); ?>">
                        <span><b><?php echo esc_html(number_format_i18n((int) $stats['sent'])); ?></b> <?php echo esc_html__('sent', 'apcampaign'); ?></span>
                        <span><b><?php echo esc_html(number_format_i18n((int) $stats['opened'])); ?></b> <?php echo esc_html__('opened', 'apcampaign'); ?></span>
                        <span><b><?php echo esc_html(number_format_i18n((int) $stats['clicked'])); ?></b> <?php echo esc_html__('clicked', 'apcampaign'); ?></span>
                        <span><b><?php echo esc_html(self::format_rate((float) $stats['open_rate'])); ?></b> <?php echo esc_html__('open', 'apcampaign'); ?></span>
                    </div>
                    <span class="apcampaign-row-updated"><?php echo esc_html__('Updated ', 'apcampaign') . esc_html((string) $campaign['updated_at']); ?></span>
                    <a class="apcampaign-button" href="<?php echo esc_url(self::url('edit/' . $campaign_id)); ?>"><?php echo esc_html__('Edit', 'apcampaign'); ?></a>
                    <?php if (!$archived) : ?>
                        <button type="button" class="apcampaign-button" data-apcampaign-retarget="<?php echo esc_attr((string) $campaign_id); ?>"><?php echo esc_html__('Retarget', 'apcampaign'); ?></button>
                        <button type="button" class="apcampaign-button quiet" data-apcampaign-archive="<?php echo esc_attr((string) $campaign_id); ?>"><?php echo esc_html__('Archive', 'apcampaign'); ?></button>
                    <?php endif; ?>
                </div>
            </article>
            <?php
            return (string) ob_get_clean();
        }


        private static function render_document_guide(): string
        {
            ob_start();
            ?>
            <div class="apcampaign-panel" style="max-width:920px;margin:24px auto;padding:32px;">
                <a href="<?php echo esc_url(self::url('create-raw')); ?>">&larr; Back to Raw HTML Campaigns</a>
                <h1><?php echo esc_html__('Prepare an eBlast Document', 'apcampaign'); ?></h1>
                <p><?php echo esc_html__('Use the downloadable template to prepare campaign copy, images and button links in Microsoft Word or Google Docs. The importer converts the document into email-safe HTML and places it in a draft campaign for review.', 'apcampaign'); ?></p>
                <p><?php echo esc_html__('The template includes sample images and example copy, so you can upload it as-is first to see how the conversion works. Then replace the sample content with the final campaign content and upload again.', 'apcampaign'); ?></p>
                <p><strong><?php echo esc_html__('Important:', 'apcampaign'); ?></strong> <?php echo esc_html__('text inside square brackets is guidance for the person preparing the Word document. The brackets mark labels and instructions the importer knows how to ignore. Do not delete the bracketed labels or instruction structure. Replace the sample campaign words, links and images around those labels with your real content, then upload again. Bracketed instruction text is skipped by the importer and is not meant to appear in the generated HTML.', 'apcampaign'); ?></p>
                <p><a class="apcampaign-button" href="<?php echo esc_url(APCAMPAIGN_PLUGIN_URL . 'assets/templates/apcampaign-email-document-template.docx'); ?>" download><?php echo esc_html__('Download the Word template', 'apcampaign'); ?></a></p>
                <h2><?php echo esc_html__('Required document fields', 'apcampaign'); ?></h2>
                <ol>
                    <li><?php echo wp_kses_post(__('Complete <strong>Campaign Name</strong>, <strong>Subject Line</strong> and <strong>Preview Text</strong>.', 'apcampaign')); ?></li>
                    <li><?php echo esc_html__('Keep the square-bracket instruction labels in the template. They are there to guide the importer and the person preparing the document; they will not be published in the HTML.', 'apcampaign'); ?></li>
                    <li><?php echo wp_kses_post(__('Replace each sample image with the final full-quality image. In Word, select the sample image and use <strong>Picture Format &rarr; Change Picture</strong>, or delete it and choose <strong>Insert &rarr; Pictures</strong>.', 'apcampaign')); ?></li>
                    <li><?php echo esc_html__('Write headings and body copy in normal paragraphs.', 'apcampaign'); ?></li>
                    <li><?php echo esc_html__('Apply the destination hyperlink directly to each short CTA button line.', 'apcampaign'); ?></li>
                    <li><?php echo esc_html__('Add another image, heading, copy and CTA group when another section is needed.', 'apcampaign'); ?></li>
                </ol>
                <h2><?php echo esc_html__('Google Docs', 'apcampaign'); ?></h2>
                <p><?php echo wp_kses_post(__('Open the Google Doc and choose <strong>File &rarr; Download &rarr; Microsoft Word (.docx)</strong>, then import that downloaded file.', 'apcampaign')); ?></p>
                <h2><?php echo esc_html__('Making changes', 'apcampaign'); ?></h2>
                <p><?php echo esc_html__('Use the Word document as the source for the campaign. If copy, images, button text or links need to change, update the Word document and upload it again. The importer will regenerate the Raw HTML from the latest document.', 'apcampaign'); ?></p>
                <p><?php echo esc_html__('Manual edits in the Raw HTML box are for staff cleanup or emergency fixes only. Reuploading the Word document replaces the generated Raw HTML, including any manual HTML changes made after the previous upload.', 'apcampaign'); ?></p>
                <h2><?php echo esc_html__('More sections and custom designs', 'apcampaign'); ?></h2>
                <p><?php echo esc_html__('To add another section, copy the same simple pattern: image, section heading, section copy, button copy and button link. Keep sections stacked vertically for the best email results.', 'apcampaign'); ?></p>
                <p><?php echo esc_html__('For more elaborate layouts, use the campaign block designer or have a designer create the HTML. This Word importer is meant for clean, fast eBlasts, not complex custom design layouts.', 'apcampaign'); ?></p>
                <h2><?php echo esc_html__('Avoid these Word features', 'apcampaign'); ?></h2>
                <p><?php echo esc_html__('Do not use floating text boxes, columns, headers, footers or overlapping images. They do not translate reliably to email.', 'apcampaign'); ?></p>
                <h2><?php echo esc_html__('Before sending', 'apcampaign'); ?></h2>
                <p><?php echo esc_html__('Review the conversion report, check desktop and mobile previews, save the campaign as a draft and approve a test send. Importing a document never sends a campaign.', 'apcampaign'); ?></p>
            </div>
            <?php
            return (string) ob_get_clean();
        }

        private static function render_create_choice(): string
        {
            ob_start();
            ?>
            <header class="apcampaign-header">
                <div>
                    <p><?php echo esc_html__('Choose a composer', 'apcampaign'); ?></p>
                    <h1><?php echo esc_html__('Create Campaign', 'apcampaign'); ?></h1>
                </div>
            </header>
            <section class="apcampaign-choice-grid">
                <a class="apcampaign-choice-card" href="<?php echo esc_url(self::url('create-simple')); ?>">
                    <strong><?php echo esc_html__('Drag & Drop', 'apcampaign'); ?></strong>
                    <span><?php echo esc_html__('Visual block builder with preview, spam check, AI prediction, test, schedule, and send controls.', 'apcampaign'); ?></span>
                </a>
                <a class="apcampaign-choice-card" href="<?php echo esc_url(self::url('create-raw')); ?>">
                    <strong><?php echo esc_html__('Raw HTML', 'apcampaign'); ?></strong>
                    <span><?php echo esc_html__('Paste complete email HTML, inspect live preview, run preflight, and send through the same lifecycle.', 'apcampaign'); ?></span>
                </a>
            </section>
            <?php
            return (string) ob_get_clean();
        }

        private static function render_composer(int $campaign_id = 0, ?string $forced_mode = null): string
        {
            $campaign = $campaign_id > 0 ? self::campaign($campaign_id) : null;
            $snapshot = $campaign_id > 0 ? self::current_snapshot($campaign_id) : null;
            $mode = $forced_mode ?: (string) ($campaign['composer_mode'] ?? 'block');
            $design = $snapshot ? json_decode((string) ($snapshot['design_json'] ?? '{"blocks":[]}'), true) : ['blocks' => []];
            if (!is_array($design)) {
                $design = ['blocks' => []];
            }
            $raw_html = (string) ($snapshot['raw_html'] ?? '');
            $lists = self::contact_lists();
            $is_raw = $mode === 'raw_html';
            $settings = self::composer_settings();
            ob_start();
            ?>
            <?php if (!$is_raw) : ?>
                <section class="apcampaign-mobile-block-notice">
                    <strong><?php echo esc_html__('Use the block editor on an iPad or computer', 'apcampaign'); ?></strong>
                    <p><?php echo esc_html__('The visual canvas needs a wider screen. On this phone, use Raw HTML for an urgent copy-and-paste edit.', 'apcampaign'); ?></p>
                    <a class="apcampaign-button primary" href="<?php echo esc_url(self::url('create-raw')); ?>"><?php echo esc_html__('Open Raw HTML', 'apcampaign'); ?></a>
                </section>
            <?php endif; ?>
            <form class="apcampaign-composer <?php echo $is_raw ? 'is-raw' : 'is-simple'; ?>" data-apcampaign-composer data-campaign-id="<?php echo esc_attr((string) $campaign_id); ?>">
                <header class="apcampaign-composer-toolbar">
                    <div class="apcampaign-toolbar-row apcampaign-toolbar-primary">
                        <label class="apcampaign-field apcampaign-field-name"><span><?php echo esc_html__('Campaign Name', 'apcampaign'); ?></span><input name="name" value="<?php echo esc_attr((string) ($campaign['name'] ?? '')); ?>" placeholder="<?php echo esc_attr__('Internal campaign name...', 'apcampaign'); ?>"></label>
                        <label class="apcampaign-field apcampaign-field-subject"><span><?php echo esc_html__('Subject Line', 'apcampaign'); ?> <em><?php echo esc_html__('best under 60 characters', 'apcampaign'); ?></em></span><input name="subject_line" maxlength="120" value="<?php echo esc_attr((string) ($campaign['subject_line'] ?? '')); ?>" placeholder="<?php echo esc_attr__('Clear, specific subject...', 'apcampaign'); ?>"></label>
                        <label class="apcampaign-field apcampaign-field-preview"><span><?php echo esc_html__('Preview Text', 'apcampaign'); ?> <em><?php echo esc_html__('best under 90 characters', 'apcampaign'); ?></em></span><input name="preheader_text" maxlength="160" value="<?php echo esc_attr((string) ($campaign['preheader_text'] ?? '')); ?>" placeholder="<?php echo esc_attr__('Short inbox preview...', 'apcampaign'); ?>"></label>
                    </div>
                    <div class="apcampaign-toolbar-row apcampaign-toolbar-secondary">
                        <label><?php echo esc_html__('From:', 'apcampaign'); ?><input name="from_name" value="<?php echo esc_attr((string) ($campaign['from_name'] ?? $settings['default_from_name'])); ?>" placeholder="<?php echo esc_attr__('Name', 'apcampaign'); ?>"></label>
                        <input name="from_email" value="<?php echo esc_attr((string) ($campaign['from_email'] ?? $settings['default_from_email'])); ?>" placeholder="<?php echo esc_attr__('From email', 'apcampaign'); ?>">
                        <label><?php echo esc_html__('Send To:', 'apcampaign'); ?><select name="send_to_type"><option value="own_list"><?php echo esc_html__('Your List', 'apcampaign'); ?></option></select></label>
                        <label><?php echo esc_html__('List:', 'apcampaign'); ?><select name="audience_key">
                            <option value=""><?php echo esc_html__('-- Select --', 'apcampaign'); ?></option>
                            <?php foreach ($lists as $list) : ?>
                                <option value="<?php echo esc_attr('list_' . (int) $list['id']); ?>" <?php selected((string) ($campaign['audience_key'] ?? ''), 'list_' . (int) $list['id']); ?>><?php echo esc_html((string) $list['name'] . ' (' . (int) $list['active_count'] . ')'); ?></option>
                            <?php endforeach; ?>
                        </select></label>
                    </div>
                    <div class="apcampaign-actions">
                        <a class="apcampaign-action-link" href="<?php echo esc_url(self::url($is_raw ? 'create-raw' : 'create-simple')); ?>"><?php echo esc_html__('New', 'apcampaign'); ?></a>
                        <button type="button" data-apcampaign-templates><?php echo esc_html__('Templates', 'apcampaign'); ?></button>
                        <button type="button" data-apcampaign-save-template><?php echo esc_html__('Save Template', 'apcampaign'); ?></button>
                        <button type="button" data-apcampaign-download><?php echo esc_html__('Download', 'apcampaign'); ?></button>
                        <button type="button" data-apcampaign-preview><?php echo esc_html__('Preview', 'apcampaign'); ?></button>
                        <button type="button" data-apcampaign-spam><?php echo esc_html__('Spam', 'apcampaign'); ?></button>
                        <button type="button" data-apcampaign-preflight><?php echo esc_html__('Predict', 'apcampaign'); ?></button>
                        <button type="submit"><?php echo esc_html__('Save Draft', 'apcampaign'); ?></button>
                        <button type="button" data-apcampaign-test><?php echo esc_html__('Test', 'apcampaign'); ?></button>
                        <button type="button" data-apcampaign-schedule class="blue"><?php echo esc_html__('Schedule', 'apcampaign'); ?></button>
                        <button type="button" data-apcampaign-send class="primary"><?php echo esc_html__('Send', 'apcampaign'); ?></button>
                    </div>
                </header>
                <input type="hidden" name="composer_mode" value="<?php echo esc_attr($mode); ?>">
                <textarea name="design_json" hidden><?php echo esc_textarea(wp_json_encode($design)); ?></textarea>
                <textarea name="rendered_html" hidden><?php echo esc_textarea((string) ($snapshot['rendered_html'] ?? '')); ?></textarea>
                <section class="apcampaign-studio">
                    <aside class="apcampaign-blocks <?php echo $is_raw ? 'hidden' : ''; ?>" data-apcampaign-blocks>
                        <h2><?php echo esc_html__('Content Blocks', 'apcampaign'); ?></h2>
                        <div class="apcampaign-tip compact"><strong><?php echo esc_html__('Blocks:', 'apcampaign'); ?></strong> <?php echo esc_html__('Drag or click into the editor.', 'apcampaign'); ?></div>
                        <h3><?php echo esc_html__('Blocks', 'apcampaign'); ?></h3>
                        <?php foreach (self::block_catalog() as $type => $block) : ?>
                            <div class="apcampaign-block-tile" role="button" tabindex="0" draggable="true" data-apcampaign-add-block="<?php echo esc_attr($type); ?>"><?php echo esc_html($block['label']); ?></div>
                        <?php endforeach; ?>
                    </aside>
                    <section class="apcampaign-canvas-wrap">
                        <?php if (!$is_raw) : ?>
                            <div class="apcampaign-onboarding compact" data-apcampaign-onboarding><strong><?php echo esc_html__('Composer ready', 'apcampaign'); ?></strong><span><?php echo esc_html__('Preview and Predict before scheduling.', 'apcampaign'); ?></span><button type="button" data-apcampaign-dismiss-tip>×</button></div>
                        <?php endif; ?>
                        <?php if ($is_raw) : ?>
                            <div class="apcampaign-raw-head"><span><?php echo esc_html__('HTML Editor', 'apcampaign'); ?></span><em data-apcampaign-char-count>0 chars</em><button type="button" data-apcampaign-docx-trigger><?php echo esc_html__('Import Word eBlast', 'apcampaign'); ?></button><a href="<?php echo esc_url(self::url('document-guide')); ?>" target="_blank" rel="noopener"><?php echo esc_html__('Template & instructions', 'apcampaign'); ?></a><button type="button" data-apcampaign-clear><?php echo esc_html__('Clear', 'apcampaign'); ?></button></div>
                            <input type="file" accept=".docx,application/vnd.openxmlformats-officedocument.wordprocessingml.document" data-apcampaign-docx-file hidden>
                            <div class="apcampaign-word-import-status" data-apcampaign-docx-status></div>
                        <?php endif; ?>
                        <div class="apcampaign-canvas <?php echo $mode === 'raw_html' ? 'hidden' : ''; ?>" data-apcampaign-canvas></div>
                        <textarea class="apcampaign-raw <?php echo $mode === 'block' ? 'hidden' : ''; ?>" name="raw_html" data-apcampaign-raw placeholder="<?php echo esc_attr__('Paste your full HTML email here. Include your complete HTML document when available. Use {unsubscribe_url} for the required unsubscribe link.', 'apcampaign'); ?>"><?php echo esc_textarea($raw_html); ?></textarea>
                    </section>
                    <aside class="apcampaign-inspector">
                        <h2><?php echo esc_html($is_raw ? __('Live Preview', 'apcampaign') : __('Block Settings', 'apcampaign')); ?></h2>
                        <div data-apcampaign-results>
                            <p><?php echo esc_html($is_raw ? __('Preview, Spam, or Predict when ready.', 'apcampaign') : __('Select content in the editor or run Predict when ready.', 'apcampaign')); ?></p>
                        </div>
                    </aside>
                </section>
                <section class="apcampaign-preview" data-apcampaign-preview-panel hidden>
                    <iframe title="<?php echo esc_attr__('Campaign preview', 'apcampaign'); ?>"></iframe>
                </section>
            </form>
            <?php
            return (string) ob_get_clean();
        }

        private static function render_sent_view(int $campaign_id): string
        {
            $campaign = self::campaign($campaign_id);
            $snapshot = self::current_snapshot($campaign_id);
            if (!$campaign || !$snapshot) {
                return '<section class="apcampaign-panel"><p>' . esc_html__('Campaign not found.', 'apcampaign') . '</p></section>';
            }
            $stats = self::campaign_detail_stats($campaign_id);
            $recipients = self::campaign_recipient_activity($campaign_id);
            ob_start();
            ?>
            <header class="apcampaign-header">
                <div>
                    <p><?php echo esc_html__('Campaign report', 'apcampaign'); ?></p>
                    <h1><?php echo esc_html((string) ($campaign['subject_line'] ?: $campaign['name'])); ?></h1>
                </div>
                <div class="apcampaign-header-actions">
                    <a class="apcampaign-button" href="<?php echo esc_url(self::url()); ?>"><?php echo esc_html__('Dashboard', 'apcampaign'); ?></a>
                    <a class="apcampaign-button" href="<?php echo esc_url(self::url('edit/' . $campaign_id)); ?>"><?php echo esc_html__('Edit', 'apcampaign'); ?></a>
                </div>
            </header>
            <section class="apcampaign-panel apcampaign-report">
                <div class="apcampaign-row-metrics">
                    <span><?php echo esc_html(ucwords(str_replace('_', ' ', (string) $campaign['status']))); ?></span>
                    <span><?php echo esc_html((string) $campaign['composer_mode']); ?></span>
                    <span><?php echo esc_html__('Updated ', 'apcampaign') . esc_html((string) $campaign['updated_at']); ?></span>
                </div>
                <div class="apcampaign-performance-grid apcampaign-report-grid">
                    <article><span><?php echo esc_html__('Recipients', 'apcampaign'); ?></span><strong><?php echo esc_html(number_format_i18n((int) $stats['recipients'])); ?></strong></article>
                    <article><span><?php echo esc_html__('Sent', 'apcampaign'); ?></span><strong><?php echo esc_html(number_format_i18n((int) $stats['sent'])); ?></strong></article>
                    <article><span><?php echo esc_html__('Opened', 'apcampaign'); ?></span><strong><?php echo esc_html(number_format_i18n((int) $stats['opened'])); ?></strong></article>
                    <article><span><?php echo esc_html__('Clicked', 'apcampaign'); ?></span><strong><?php echo esc_html(number_format_i18n((int) $stats['clicked'])); ?></strong></article>
                    <article><span><?php echo esc_html__('Open Rate', 'apcampaign'); ?></span><strong><?php echo esc_html(self::format_rate((float) $stats['open_rate'])); ?></strong></article>
                    <article><span><?php echo esc_html__('Click Rate', 'apcampaign'); ?></span><strong><?php echo esc_html(self::format_rate((float) $stats['click_rate'])); ?></strong></article>
                </div>
            </section>
            <section class="apcampaign-panel apcampaign-report">
                <h2><?php echo esc_html__('Recipient Activity', 'apcampaign'); ?></h2>
                <div class="apcampaign-report-table-wrap">
                    <table class="apcampaign-table apcampaign-report-table">
                        <thead><tr><th><?php echo esc_html__('Recipient', 'apcampaign'); ?></th><th><?php echo esc_html__('Status', 'apcampaign'); ?></th><th><?php echo esc_html__('Opens', 'apcampaign'); ?></th><th><?php echo esc_html__('Clicks', 'apcampaign'); ?></th><th><?php echo esc_html__('Last Activity', 'apcampaign'); ?></th></tr></thead>
                        <tbody>
                            <?php if (!$recipients) : ?>
                                <tr><td colspan="5"><?php echo esc_html__('No recipient activity has been recorded for this campaign yet.', 'apcampaign'); ?></td></tr>
                            <?php endif; ?>
                            <?php foreach ($recipients as $recipient) : ?>
                                <tr>
                                    <td><?php echo esc_html((string) $recipient['email']); ?></td>
                                    <td><?php echo esc_html(ucwords(str_replace('_', ' ', (string) $recipient['status']))); ?></td>
                                    <td><?php echo esc_html(number_format_i18n((int) $recipient['opens'])); ?></td>
                                    <td><?php echo esc_html(number_format_i18n((int) $recipient['clicks'])); ?></td>
                                    <td><?php echo esc_html((string) ($recipient['last_activity'] ?: __('No activity yet', 'apcampaign'))); ?></td>
                                </tr>
                            <?php endforeach; ?>
                        </tbody>
                    </table>
                </div>
            </section>
            <section class="apcampaign-panel apcampaign-sent-view">
                <h2><?php echo esc_html__('Email Preview', 'apcampaign'); ?></h2>
                <iframe srcdoc="<?php echo esc_attr(APCampaign_Renderer::wrap_document((string) $snapshot['rendered_html'], (string) $campaign['subject_line'])); ?>"></iframe>
            </section>
            <?php
            return (string) ob_get_clean();
        }

        /**
         * @return array<string,array<string,mixed>>
         */
        private static function block_catalog(): array
        {
            return [
                'hero_image' => ['label' => 'Hero + Image', 'options' => []],
                'hero' => ['label' => 'Hero', 'options' => []],
                'heading' => ['label' => 'Heading', 'options' => ['text' => 'Email headline', 'level' => 1, 'color' => '#111827']],
                'image' => ['label' => 'Image', 'options' => ['src' => '', 'alt' => '', 'width' => 600]],
                'text' => ['label' => 'Text', 'options' => ['text' => 'Write your message here.', 'color' => '#374151']],
                'button' => ['label' => 'Button', 'options' => ['text' => 'Learn More', 'url' => 'https://example.com', 'background' => '#2563eb', 'color' => '#ffffff']],
                'teaser' => ['label' => 'Article Teaser', 'options' => []],
                'two_column' => ['label' => 'Two Columns', 'options' => []],
                'card' => ['label' => 'Card', 'options' => []],
                'quote' => ['label' => 'Quote', 'options' => []],
                'list' => ['label' => 'List', 'options' => []],
                'social' => ['label' => 'Social Links', 'options' => []],
                'divider' => ['label' => 'Divider', 'options' => []],
                'spacer' => ['label' => 'Spacer', 'options' => ['height' => 24]],
            ];
        }

        private static function campaign(int $campaign_id): ?array
        {
            global $wpdb;
            $t = APCampaign_Schema::tables();
            $row = $wpdb->get_row($wpdb->prepare("SELECT * FROM {$t['campaigns']} WHERE id = %d", $campaign_id), ARRAY_A);
            return is_array($row) ? $row : null;
        }

        private static function current_snapshot(int $campaign_id): ?array
        {
            global $wpdb;
            $t = APCampaign_Schema::tables();
            $row = $wpdb->get_row($wpdb->prepare("SELECT * FROM {$t['content_snapshots']} WHERE campaign_id = %d AND is_current = 1 ORDER BY id DESC LIMIT 1", $campaign_id), ARRAY_A);
            return is_array($row) ? $row : null;
        }

        private static function current_snapshot_id(int $campaign_id): int
        {
            $snapshot = self::current_snapshot($campaign_id);
            return $snapshot ? (int) $snapshot['id'] : 0;
        }

        /**
         * @return array<string,int|float>
         */
        private static function campaign_detail_stats(int $campaign_id): array
        {
            global $wpdb;
            $t = APCampaign_Schema::tables();
            $job = $wpdb->get_row($wpdb->prepare(
                "SELECT COALESCE(SUM(total_recipients),0) AS recipients, COALESCE(SUM(sent_count),0) AS sent, COALESCE(SUM(failed_count),0) AS failed FROM {$t['send_jobs']} WHERE campaign_id = %d",
                $campaign_id
            ), ARRAY_A) ?: [];
            $events = $wpdb->get_row($wpdb->prepare(
                "SELECT
                    COUNT(DISTINCT CASE WHEN event_type IN ('open','opened') THEN IF(recipient_id > 0, CONCAT('r:', recipient_id), CONCAT('e:', email)) END) AS opened,
                    COUNT(DISTINCT CASE WHEN event_type IN ('click','clicked') THEN IF(recipient_id > 0, CONCAT('r:', recipient_id), CONCAT('e:', email)) END) AS clicked
                FROM {$t['tracking_events']}
                WHERE campaign_id = %d",
                $campaign_id
            ), ARRAY_A) ?: [];
            $recipients = max((int) ($job['recipients'] ?? 0), (int) ($job['sent'] ?? 0));
            $opened = (int) ($events['opened'] ?? 0);
            $clicked = (int) ($events['clicked'] ?? 0);

            return [
                'recipients' => $recipients,
                'sent' => (int) ($job['sent'] ?? 0),
                'failed' => (int) ($job['failed'] ?? 0),
                'opened' => $opened,
                'clicked' => $clicked,
                'open_rate' => $recipients > 0 ? ($opened / $recipients) * 100 : 0.0,
                'click_rate' => $recipients > 0 ? ($clicked / $recipients) * 100 : 0.0,
            ];
        }

        /**
         * @return array<int,array<string,mixed>>
         */
        private static function campaign_recipient_activity(int $campaign_id): array
        {
            global $wpdb;
            $t = APCampaign_Schema::tables();
            return $wpdb->get_results($wpdb->prepare(
                "SELECT r.email, r.status,
                    SUM(CASE WHEN e.event_type IN ('open','opened') THEN 1 ELSE 0 END) AS opens,
                    SUM(CASE WHEN e.event_type IN ('click','clicked') THEN 1 ELSE 0 END) AS clicks,
                    MAX(e.occurred_at) AS last_activity
                FROM {$t['send_job_recipients']} r
                LEFT JOIN {$t['tracking_events']} e ON e.recipient_id = r.id OR (e.tracking_token <> '' AND e.tracking_token = r.tracking_token)
                WHERE r.campaign_id = %d
                GROUP BY r.id, r.email, r.status
                ORDER BY last_activity DESC, r.email ASC
                LIMIT 500",
                $campaign_id
            ), ARRAY_A) ?: [];
        }

        /**
         * @return array<int,array<string,mixed>>
         */
        private static function retarget_recipients(int $campaign_id, string $behavior): array
        {
            global $wpdb;
            $t = APCampaign_Schema::tables();
            $rows = $wpdb->get_results($wpdb->prepare(
                "SELECT r.email, r.contact_ref, r.recipient_data_json,
                    SUM(CASE WHEN e.event_type IN ('open', 'opened') THEN 1 ELSE 0 END) AS opens,
                    SUM(CASE WHEN e.event_type IN ('click', 'clicked') THEN 1 ELSE 0 END) AS clicks
                FROM {$t['send_job_recipients']} r
                LEFT JOIN {$t['tracking_events']} e ON e.recipient_id = r.id OR (e.tracking_token <> '' AND e.tracking_token = r.tracking_token)
                WHERE r.campaign_id = %d AND r.email <> ''
                GROUP BY r.email, r.contact_ref, r.recipient_data_json
                ORDER BY r.email ASC",
                $campaign_id
            ), ARRAY_A) ?: [];

            $out = [];
            foreach ($rows as $row) {
                $opens = (int) ($row['opens'] ?? 0);
                $clicks = (int) ($row['clicks'] ?? 0);
                if ($behavior === 'opened' && $opens < 1) {
                    continue;
                }
                if ($behavior === 'clicked' && $clicks < 1) {
                    continue;
                }
                if ($behavior === 'not_opened' && $opens > 0) {
                    continue;
                }
                $data = json_decode((string) ($row['recipient_data_json'] ?? '{}'), true);
                if (!is_array($data)) {
                    $data = [];
                }
                $out[] = [
                    'email' => sanitize_email((string) $row['email']),
                    'contact_ref' => sanitize_text_field((string) ($row['contact_ref'] ?? '')),
                    'merge' => (array) ($data['merge'] ?? []),
                ];
            }
            return $out;
        }

        /**
         * @return array<string,int|float>
         */
        private static function campaign_performance_summary(): array
        {
            global $wpdb;
            $t = APCampaign_Schema::tables();
            $campaign_count = (int) $wpdb->get_var("SELECT COUNT(*) FROM {$t['campaigns']} WHERE status IN ('sent','completed','send_complete')");
            $jobs = $wpdb->get_row("SELECT
                    COALESCE(SUM(total_recipients),0) AS recipients_total,
                    COALESCE(SUM(sent_count),0) AS emails_sent,
                    COALESCE(SUM(failed_count),0) AS failures
                FROM {$t['send_jobs']}", ARRAY_A) ?: [];
            $events = $wpdb->get_row("SELECT
                    COUNT(DISTINCT CASE WHEN event_type IN ('open','opened') THEN IF(recipient_id > 0, CONCAT('r:', recipient_id), CONCAT('e:', email)) END) AS unique_opens,
                    COUNT(DISTINCT CASE WHEN event_type IN ('click','clicked') THEN IF(recipient_id > 0, CONCAT('r:', recipient_id), CONCAT('e:', email)) END) AS unique_clicks
                FROM {$t['tracking_events']}", ARRAY_A) ?: [];
            $history = $wpdb->get_row("SELECT
                    COUNT(*) AS imported_campaigns,
                    COALESCE(SUM(sent_count),0) AS imported_sent,
                    COALESCE(SUM(sent_count * open_rate),0) AS weighted_open,
                    COALESCE(SUM(sent_count * click_rate),0) AS weighted_click
                FROM {$t['history_metrics']}", ARRAY_A) ?: [];

            $job_sent = (int) ($jobs['emails_sent'] ?? 0);
            $job_recipients = (int) ($jobs['recipients_total'] ?? 0);
            $imported_sent = (int) ($history['imported_sent'] ?? 0);
            $emails_sent = $job_sent + $imported_sent;
            $recipients_total = max($job_recipients, $job_sent) + $imported_sent;
            $history_open_rate = $imported_sent > 0 ? (float) ($history['weighted_open'] ?? 0) / $imported_sent : 0.0;
            $history_click_rate = $imported_sent > 0 ? (float) ($history['weighted_click'] ?? 0) / $imported_sent : 0.0;
            $open_rate = $emails_sent > 0 ? (((int) ($events['unique_opens'] ?? 0) + ($imported_sent * ($history_open_rate / 100))) / $emails_sent) * 100 : 0.0;
            $click_rate = $emails_sent > 0 ? (((int) ($events['unique_clicks'] ?? 0) + ($imported_sent * ($history_click_rate / 100))) / $emails_sent) * 100 : 0.0;

            return [
                'sent_campaigns' => $campaign_count + (int) ($history['imported_campaigns'] ?? 0),
                'emails_sent' => $emails_sent,
                'recipients_total' => $recipients_total,
                'open_rate' => $open_rate,
                'click_rate' => $click_rate,
                'failures' => (int) ($jobs['failures'] ?? 0),
            ];
        }

        private static function format_rate(float $rate): string
        {
            return number_format_i18n(max(0.0, $rate), 1) . '%';
        }

        private static function update_campaign(int $campaign_id, array $data): void
        {
            global $wpdb;
            $t = APCampaign_Schema::tables();
            $data['updated_at'] = current_time('mysql');
            $wpdb->update($t['campaigns'], $data, ['id' => $campaign_id]);
        }

        private static function contact_lists(): array
        {
            global $wpdb;
            $t = APCampaign_Schema::tables();
            return $wpdb->get_results("SELECT * FROM {$t['contact_lists']} WHERE status = 'active' ORDER BY name ASC LIMIT 200", ARRAY_A) ?: [];
        }

        private static function composer_settings(): array
        {
            return [
                'default_from_name' => (string) get_option('apcampaign_default_from_name', get_bloginfo('name')),
                'default_from_email' => (string) get_option('apcampaign_default_from_email', get_option('admin_email')),
                'spam_pass_score' => (int) get_option('apcampaign_spam_pass_score', 70),
                'preflight_link_limit' => (int) get_option('apcampaign_preflight_link_limit', 25),
                'retarget_owner_only' => (bool) get_option('apcampaign_retarget_owner_only', false),
                'physical_address' => (string) get_option('apcampaign_physical_address', ''),
            ];
        }

        /**
         * @return array<string,mixed>
         */
        private static function prepare_send_job(int $campaign_id): array
        {
            if (!APCampaign_Delivery::compliance_ready()) {
                return ['success' => false, 'error' => 'Add the organization physical mailing address in Mailer Settings before sending.'];
            }
            $campaign = self::campaign($campaign_id);
            $snapshot_id = self::current_snapshot_id($campaign_id);
            if (!$campaign || $snapshot_id < 1) {
                return ['success' => false, 'error' => 'Campaign content is missing.'];
            }
            if (!preg_match('/^list_(\d+)$/', (string) ($campaign['audience_key'] ?? ''), $matches)) {
                return ['success' => false, 'error' => 'Select a campaign list before sending.'];
            }
            $audience = APCampaign_Lifecycle::create_audience_snapshot_from_list($campaign_id, (int) $matches[1], ['behavior' => 'all']);
            if (empty($audience['audience_snapshot_id']) || empty($audience['recipient_count'])) {
                return ['success' => false, 'error' => 'The selected list has no active, unsuppressed recipients.'];
            }
            return APCampaign_Lifecycle::queue_send_job(
                $campaign_id,
                $snapshot_id,
                (int) $audience['audience_snapshot_id']
            );
        }

        /**
         * @return array{state:string,label:string,detail:string,url:string}
         */
        private static function dependency_status(string $dependency): array
        {
            if (self::suite_context_active()) {
                return [
                    'state' => 'suite',
                    'label' => __('Suite managed', 'apcampaign'),
                    'detail' => __('This setting is managed by PubMotor Suite.', 'apcampaign'),
                    'url' => home_url('/crm/'),
                ];
            }

            if ($dependency === 'apmailer' && (defined('APMAILER_VERSION') || class_exists('APMailer_Contract'))) {
                return [
                    'state' => 'active',
                    'label' => __('apMailer active', 'apcampaign'),
                    'detail' => __('apMailer is installed and active.', 'apcampaign'),
                    'url' => admin_url('plugins.php'),
                ];
            }

            if ($dependency === 'aplists' && (defined('APLISTS_VERSION') || class_exists('APLists_Contract'))) {
                return [
                    'state' => 'active',
                    'label' => __('apLists active', 'apcampaign'),
                    'detail' => __('apLists is installed and active.', 'apcampaign'),
                    'url' => admin_url('plugins.php'),
                ];
            }

            return [
                'state' => 'missing',
                'label' => $dependency === 'apmailer' ? __('apMailer needed', 'apcampaign') : __('apLists needed', 'apcampaign'),
                'detail' => $dependency === 'apmailer'
                    ? __('Install and activate apMailer with standalone apCampaign before live sending.', 'apcampaign')
                    : __('Install and activate apLists with standalone apCampaign before building live audiences.', 'apcampaign'),
                'url' => admin_url('plugins.php'),
            ];
        }

        private static function suite_context_active(): bool
        {
            return defined('AP2_CRM_VERSION') || class_exists('AP2_CRM_Module_Registry');
        }

        /**
         * @param array{state:string,label:string,detail:string,url:string} $status
         */
        private static function render_dependency_badge(array $status): string
        {
            $state = sanitize_html_class($status['state']);
            return '<span class="apcampaign-status-badge is-' . esc_attr($state) . '">' . esc_html($status['label']) . '</span>';
        }

        /**
         * @param array{state:string,label:string,detail:string,url:string} $status
         */
        private static function render_dependency_guidance(array $status, string $name): string
        {
            ob_start();
            ?>
            <div class="apcampaign-companion-guidance">
                <p><?php echo esc_html($status['detail']); ?></p>
                <?php if ($status['state'] === 'suite') : ?>
                    <a class="apcampaign-button" href="<?php echo esc_url($status['url']); ?>"><?php echo esc_html__('Open PubMotor Suite', 'apcampaign'); ?></a>
                <?php elseif ($status['state'] === 'active') : ?>
                    <a class="apcampaign-button" href="<?php echo esc_url($status['url']); ?>"><?php echo esc_html(sprintf(__('%s plugin active', 'apcampaign'), $name)); ?></a>
                <?php else : ?>
                    <a class="apcampaign-button primary" href="<?php echo esc_url($status['url']); ?>"><?php echo esc_html(sprintf(__('Activate %s', 'apcampaign'), $name)); ?></a>
                <?php endif; ?>
            </div>
            <?php
            return (string) ob_get_clean();
        }

        private static function parse_docx_for_raw_email(string $path)
        {
            $zip = new ZipArchive();
            if ($zip->open($path) !== true) {
                return new WP_Error('bad_docx', 'The Word document could not be opened.');
            }
            $total = 0;
            for ($i = 0; $i < $zip->numFiles; $i++) {
                $stat = $zip->statIndex($i);
                $total += (int) ($stat['size'] ?? 0);
                if ($total > 52428800) {
                    $zip->close();
                    return new WP_Error('large_docx', 'The expanded document is too large.');
                }
            }
            $doc_xml = $zip->getFromName('word/document.xml');
            $rels_xml = $zip->getFromName('word/_rels/document.xml.rels');
            if (!$doc_xml || !$rels_xml) {
                $zip->close();
                return new WP_Error('bad_docx', 'The document is missing required Word data.');
            }

            $rels = [];
            $rd = new DOMDocument();
            if (@$rd->loadXML($rels_xml)) {
                foreach ($rd->getElementsByTagName('Relationship') as $rel) {
                    $rels[$rel->getAttribute('Id')] = [
                        'target' => $rel->getAttribute('Target'),
                        'type' => $rel->getAttribute('Type'),
                        'external' => $rel->getAttribute('TargetMode') === 'External',
                    ];
                }
            }

            $images = [];
            foreach ($rels as $rid => $rel) {
                if (strpos($rel['type'], '/image') === false || $rel['external']) {
                    continue;
                }
                $target = preg_replace('#^\.\./#', '', $rel['target']);
                $entry = 'word/' . ltrim((string) $target, '/');
                $bytes = $zip->getFromName($entry);
                if ($bytes === false) {
                    continue;
                }
                $ext = strtolower(pathinfo($entry, PATHINFO_EXTENSION));
                $mime = $ext === 'png' ? 'image/png' : (($ext === 'gif') ? 'image/gif' : 'image/jpeg');
                $images[$rid] = [
                    'bytes' => $bytes,
                    'name' => 'apcampaign-eblast-' . wp_generate_password(8, false, false) . '.' . ($ext ?: 'jpg'),
                    'mime' => $mime,
                ];
            }

            $dom = new DOMDocument();
            if (!@$dom->loadXML($doc_xml)) {
                $zip->close();
                return new WP_Error('bad_docx', 'The Word document XML is invalid.');
            }
            $xp = new DOMXPath($dom);
            $xp->registerNamespace('w', 'http://schemas.openxmlformats.org/wordprocessingml/2006/main');
            $xp->registerNamespace('r', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships');
            $xp->registerNamespace('a', 'http://schemas.openxmlformats.org/drawingml/2006/main');
            $xp->registerNamespace('v', 'urn:schemas-microsoft-com:vml');

            $paragraphs = [];
            foreach ($xp->query('//w:body//w:p') as $p) {
                $text_nodes = iterator_to_array($xp->query('.//w:t', $p));
                $text = trim(preg_replace('/\s+/u', ' ', implode('', array_map(static fn($node) => $node->nodeValue, $text_nodes))));
                $style_node = $xp->query('./w:pPr/w:pStyle', $p)->item(0);
                $style = $style_node ? $style_node->getAttributeNS('http://schemas.openxmlformats.org/wordprocessingml/2006/main', 'val') : '';
                $bold_nodes = $xp->query('.//w:r[w:rPr/w:b]/w:t', $p);
                $all_text_nodes = $xp->query('.//w:t', $p);
                $bold = $all_text_nodes->length > 0 && $bold_nodes->length === $all_text_nodes->length;
                $links = [];
                foreach ($xp->query('.//w:hyperlink[@r:id]', $p) as $h) {
                    $rid = $h->getAttributeNS('http://schemas.openxmlformats.org/officeDocument/2006/relationships', 'id');
                    if (!empty($rels[$rid]['external'])) {
                        $links[] = $rels[$rid]['target'];
                    }
                }
                foreach ($xp->query('.//w:instrText', $p) as $it) {
                    if (preg_match('/HYPERLINK\s+"([^"]+)"/i', $it->nodeValue, $m)) {
                        $links[] = $m[1];
                    }
                }
                $image_ids = [];
                foreach ($xp->query('.//a:blip[@r:embed]', $p) as $blip) {
                    $image_ids[] = $blip->getAttributeNS('http://schemas.openxmlformats.org/officeDocument/2006/relationships', 'embed');
                }
                foreach ($xp->query('.//v:imagedata[@r:id]', $p) as $image) {
                    $image_ids[] = $image->getAttributeNS('http://schemas.openxmlformats.org/officeDocument/2006/relationships', 'id');
                }
                if ($text !== '' || $image_ids) {
                    $paragraphs[] = ['text' => $text, 'style' => $style, 'bold' => $bold, 'links' => array_values(array_unique($links)), 'images' => $image_ids];
                }
            }
            $zip->close();

            $meta = [];
            $kept = [];
            $pending_meta = '';
            foreach ($paragraphs as $index => $p) {
                if ($pending_meta && $p['text'] !== '') {
                    $meta[$pending_meta] = $p['text'];
                    $pending_meta = '';
                    continue;
                }
                if (preg_match('/^(Campaign Name|Subject(?: Line)?|Preview(?: Text)?|Preheader)\s*:\s*(.+)$/i', $p['text'], $m)) {
                    $key = strtolower($m[1]);
                    if (strpos($key, 'campaign') === 0) {
                        $meta['campaign_name'] = $m[2];
                    } elseif (strpos($key, 'subject') === 0) {
                        $meta['subject'] = $m[2];
                    } else {
                        $meta['preview'] = $m[2];
                    }
                    continue;
                }
                if (preg_match('/^(Campaign Name|Subject(?: Line)?|Email Preview Line|Preview(?: Text)?|Preheader)\s*:?$/i', $p['text'], $m)) {
                    $key = strtolower($m[1]);
                    $pending_meta = strpos($key, 'campaign') === 0 ? 'campaign_name' : (strpos($key, 'subject') === 0 ? 'subject' : 'preview');
                    continue;
                }
                if ($index === 0 && preg_match('/\beblast\b/i', $p['text'])) {
                    $meta['campaign_name'] = $p['text'];
                    continue;
                }
                $kept[] = $p;
            }
            return ['meta' => $meta, 'paragraphs' => $kept, 'images' => $images];
        }

        private static function classify_docx_email_blocks(array $paragraphs, array $uploaded, array &$warnings): array
        {
            $blocks = [];
            $pending = '';
            $cta_text = '';
            foreach ($paragraphs as $p) {
                if (preg_match('/^(Checklist before upload|Using Google Docs)\s*:?$/i', $p['text'])) {
                    break;
                }
                foreach ($p['images'] as $rid) {
                    if (isset($uploaded[$rid])) {
                        $blocks[] = ['type' => 'image', 'url' => $uploaded[$rid]['url'], 'alt' => $p['text'] ?: 'Campaign image'];
                    } else {
                        $warnings[] = 'An embedded image could not be uploaded.';
                    }
                }
                if ($p['text'] === '') {
                    continue;
                }
                if (preg_match('/^\[[^\]]+\]$/u', $p['text'])) {
                    continue;
                }
                if (preg_match('/^(?:Optional\s+)?Style Notes?\s*:/i', $p['text'])) {
                    continue;
                }
                if (preg_match('/^(Important|Changing images|Image instructions|Template instructions|Do not delete the brackets|Items inside (?:of )?those brackets|Replace the sample image)\s*:/i', $p['text'])) {
                    continue;
                }
                if (preg_match('/^Square-bracket labels and instructions\b/i', $p['text'])) {
                    continue;
                }
                if (preg_match('/^(Campaign Setup|Content Section\s+\d+)\s*:?$/i', $p['text'])) {
                    continue;
                }
                if (preg_match('/^This is a sample opening paragraph for a test campaign\./i', $p['text'])) {
                    continue;
                }
                if (preg_match('/^(How this works|Fill in this template)\b/i', $p['text'])) {
                    continue;
                }
                if (preg_match('/^(?:(?:Section\s+\d+|Header|Second|2nd)\s+)?Image\s*:?$/i', $p['text'])) {
                    continue;
                }
                if (preg_match('/^(?:Section(?:\s+\d+)?\s+)?Copy\s*:?$/i', $p['text'])) {
                    continue;
                }
                if (preg_match('/^(H2|Heading|Section Heading)\s*:?$/i', $p['text'])) {
                    $pending = 'heading';
                    continue;
                }
                if (preg_match('/^(Button Copy|CTA(?: Button)? Text)\s*:?$/i', $p['text'])) {
                    $pending = 'cta_text';
                    continue;
                }
                if (preg_match('/^(Button Link|CTA(?: Destination)? Link)\s*:?$/i', $p['text'])) {
                    $pending = 'cta_link';
                    continue;
                }
                $url = $p['links'][0] ?? '';
                if ($pending === 'heading') {
                    $blocks[] = ['type' => 'heading', 'text' => $p['text']];
                    $pending = '';
                    continue;
                }
                if ($pending === 'cta_text') {
                    $cta_text = $p['text'];
                    $pending = '';
                    continue;
                }
                if ($pending === 'cta_link') {
                    $destination = $url ?: (filter_var($p['text'], FILTER_VALIDATE_URL) ? $p['text'] : '');
                    if ($destination && $cta_text) {
                        $blocks[] = ['type' => 'cta', 'text' => $cta_text, 'url' => esc_url_raw($destination)];
                    } else {
                        $warnings[] = 'A CTA button is missing its text or destination link.';
                    }
                    $cta_text = '';
                    $pending = '';
                    continue;
                }
                $is_heading = preg_match('/heading|title/i', $p['style']) || ($p['bold'] && strlen($p['text']) < 180);
                if ($url && strlen($p['text']) < 100) {
                    $blocks[] = ['type' => 'cta', 'text' => $p['text'], 'url' => esc_url_raw($url)];
                } elseif ($is_heading) {
                    $blocks[] = ['type' => 'heading', 'text' => $p['text']];
                } else {
                    $blocks[] = ['type' => 'text', 'text' => $p['text'], 'url' => $url ? esc_url_raw($url) : ''];
                }
            }
            return $blocks;
        }

        private static function render_docx_email_html(array $blocks): string
        {
            if (!$blocks) {
                return '';
            }
            $html = '<style>@media only screen and (max-width:620px){.apcampaign-docx-wrap{width:100%!important;max-width:100%!important}.apcampaign-docx-pad{padding-left:18px!important;padding-right:18px!important}.apcampaign-docx-img{width:100%!important;max-width:100%!important;height:auto!important}.apcampaign-docx-spacer{height:22px!important;line-height:22px!important}}</style>';
            $html .= '<table class="apcampaign-docx-wrap" role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" align="center" bgcolor="#ffffff" style="width:100%;max-width:600px;background-color:#ffffff;border-collapse:collapse;margin:0 auto;">';
            foreach ($blocks as $i => $block) {
                if ($block['type'] === 'image') {
                    $link = '';
                    for ($j = $i + 1; $j < count($blocks) && $blocks[$j]['type'] !== 'image'; $j++) {
                        if ($blocks[$j]['type'] === 'cta') {
                            $link = $blocks[$j]['url'];
                            break;
                        }
                    }
                    $img = '<img class="apcampaign-docx-img" src="' . esc_url($block['url']) . '" width="600" alt="' . esc_attr($block['alt']) . '" border="0" style="display:block;width:100%;max-width:600px;height:auto;border:0;border-radius:16px;">';
                    $html .= '<tr><td align="center">' . ($link ? '<a href="' . esc_url($link) . '" target="_blank">' . $img . '</a>' : $img) . '</td></tr><tr><td class="apcampaign-docx-spacer" height="30" style="font-size:1px;line-height:30px;">&nbsp;</td></tr>';
                } elseif ($block['type'] === 'heading') {
                    $html .= '<tr><td class="apcampaign-docx-pad" style="padding:0 34px 16px;font-family:Arial,Helvetica,sans-serif;font-size:22px;line-height:29px;color:#004f9f;font-weight:bold;">' . esc_html($block['text']) . '</td></tr>';
                } elseif ($block['type'] === 'text') {
                    $content = esc_html($block['text']);
                    if (!empty($block['url'])) {
                        $content = '<a href="' . esc_url($block['url']) . '" target="_blank" style="color:#1976d2;">' . $content . '</a>';
                    }
                    $html .= '<tr><td class="apcampaign-docx-pad" style="padding:0 34px 22px;font-family:Arial,Helvetica,sans-serif;font-size:16px;line-height:25px;color:#1f2933;">' . $content . '</td></tr>';
                } elseif ($block['type'] === 'cta') {
                    $html .= '<tr><td class="apcampaign-docx-pad" align="center" style="padding:6px 34px 44px;"><table role="presentation" cellpadding="0" cellspacing="0" border="0" bgcolor="#1976d2" style="border-radius:16px;"><tr><td align="center" bgcolor="#1976d2" style="padding:13px 26px;border-radius:16px;"><a href="' . esc_url($block['url']) . '" target="_blank" style="font-family:Arial,Helvetica,sans-serif;font-size:16px;line-height:21px;color:#ffffff;text-decoration:none;font-weight:bold;">' . esc_html($block['text']) . '</a></td></tr></table></td></tr>';
                }
            }
            return $html . '<tr><td class="apcampaign-docx-spacer" height="30" style="font-size:1px;line-height:30px;">&nbsp;</td></tr></table>';
        }

        private static function can_access(): bool
        {
            if (defined('APCAMPAIGN_DEMO_MODE')) {
                return true;
            }
            return (bool) apply_filters(
                'apcampaign_current_user_can_access',
                current_user_can('edit_posts'),
                get_current_user_id()
            );
        }

        private static function can_upload(): bool
        {
            if (defined('APCAMPAIGN_DEMO_MODE')) {
                return false;
            }
            return (bool) apply_filters(
                'apcampaign_current_user_can_upload',
                current_user_can('upload_files'),
                get_current_user_id()
            );
        }

        private static function ajax_guard(): void
        {
            if (defined('APCAMPAIGN_DEMO_MODE')) {
                wp_send_json_error(['message' => 'This action is disabled in the public demo.'], 403);
            }
            if (!self::can_access()) {
                wp_send_json_error(['message' => 'Permission denied'], 403);
            }
            if (!wp_verify_nonce((string) ($_POST['nonce'] ?? ''), self::NONCE_ACTION)) {
                wp_send_json_error(['message' => 'Security check failed'], 403);
            }
        }

        private static function is_app_request(): bool
        {
            return (string) get_query_var(self::QUERY_VAR) === '1';
        }
    }
}
