<?php
require_once __DIR__ . '/../Core.php';
require_once __DIR__ . '/../services.php';
require_once __DIR__ . '/../models/Message.php';
require_once __DIR__ . '/../models/Contact.php';
require_once __DIR__ . '/../models/Template.php';
require_once __DIR__ . '/../models/Campaign.php';
require_once __DIR__ . '/../models/Segment.php';
require_once __DIR__ . '/../models/Suppression.php';
require_once __DIR__ . '/../models/CannedReply.php';
require_once __DIR__ . '/../models/ConversationState.php';
require_once __DIR__ . '/../models/User.php';
require_once __DIR__ . '/../models/QueueJob.php';
require_once __DIR__ . '/../models/ContactList.php';
require_once __DIR__ . '/../models/MailFolder.php';
require_once __DIR__ . '/../models/MailConversation.php';
require_once __DIR__ . '/../models/MailLabel.php';
require_once __DIR__ . '/../models/MailRule.php';
require_once __DIR__ . '/../models/MailDraft.php';
require_once __DIR__ . '/../models/MailInternalNote.php';
require_once __DIR__ . '/../models/SharedMailbox.php';
require_once __DIR__ . '/../models/AutomationRule.php';
require_once __DIR__ . '/../models/ReputationSnapshot.php';
require_once __DIR__ . '/../models/EventWebhookLog.php';
require_once __DIR__ . '/../models/ConnectorConfig.php';
require_once __DIR__ . '/../models/MailSavedView.php';
require_once __DIR__ . '/../models/MailSenderProfile.php';
require_once __DIR__ . '/../models/Setting.php';

class MessagesController extends Controller
{
    public function index(): void
    {
        if (can_access('messages.campaigns')) {
            redirect('messages/campaignDashboard');
        }
        redirect('messages/inboxSms');
    }

    public function inboxSms(): void
    {
        require_permission('messages.view');
        $data = $this->buildMessagingWorkspaceData('sms');
        $data['inboxChannel'] = 'sms';
        $data['inboxTitle'] = 'SMS/MMS Inbox';
        $data['inboxDescription'] = 'Send one-to-one SMS and MMS messages, manage threaded conversations, assign workflows, and use canned replies.';
        $data['inboxRoute'] = 'messages/inboxSms';
        $this->view('messages/inbox', $data);
    }

    public function inboxEmail(): void
    {
        require_permission('messages.view');
        $data = $this->buildMessagingWorkspaceData('email');
        $data['inboxChannel'] = 'email';
        $data['inboxTitle'] = 'Email Inbox';
        $data['inboxDescription'] = 'Send one-to-one emails, work conversations in a threaded view, assign ownership, and use canned replies.';
        $data['inboxRoute'] = 'messages/inboxEmail';
        $this->view('messages/inbox', $data);
    }

    public function mailCenter(): void
    {
        require_permission('messages.view');
        $this->view('messages/mail_center', $this->buildMailCenterData());
    }

    private function buildMailCenterData(): array
    {
        $messageModel = new Message();
        $folderModel = new MailFolder();
        $labelModel = new MailLabel();
        $conversationModel = new MailConversation();
        $draftModel = new MailDraft();
        $noteModel = new MailInternalNote();
        $sharedMailboxModel = new SharedMailbox();

        $mailbox = $folderModel->normalizeKey((string)($_GET['mailbox'] ?? 'inbox')) ?: 'inbox';
        if (!$folderModel->exists($mailbox)) {
            $mailbox = 'inbox';
        }
        $page = max(1, (int)($_GET['page'] ?? 1));
        $query = trim((string)($_GET['q'] ?? ''));
        $filters = [
            'bucket' => trim((string)($_GET['bucket'] ?? 'all')) ?: 'all',
            'label_key' => trim((string)($_GET['label'] ?? 'all')) ?: 'all',
            'starred' => !empty($_GET['starred']) ? '1' : '0',
            'shared_mailbox_id' => (int)($_GET['shared_mailbox_id'] ?? 0),
        ];
        $mailboxResult = $messageModel->emailMailboxSummary($mailbox, $query, $page, 14, $filters);
        $selectedConversation = trim((string)($_GET['conversation'] ?? '')) ?: ($mailboxResult['data'][0]['conversation_key'] ?? null);
        $selectedSummary = $selectedConversation ? ($messageModel->findEmailConversation($selectedConversation, $query, $filters) ?: null) : null;
        $thread = $selectedConversation ? array_values(array_filter($messageModel->thread($selectedConversation, 250), static fn(array $row): bool => ($row['channel'] ?? '') === 'email')) : [];
        $folderCounts = $messageModel->emailMailboxCounts($query, $filters);
        foreach ($folderModel->all() as $folder) {
            $folderCounts[$folder['key']] = (int)($folderCounts[$folder['key']] ?? 0);
        }

        $selectedContact = null;
        if (!empty($selectedSummary['contact_id'])) {
            $selectedContact = (new Contact())->find((int)$selectedSummary['contact_id']);
        }
        $threadCampaigns = $selectedConversation ? $messageModel->campaignsForConversation($selectedConversation, 6) : [];
        $contactCampaigns = !empty($selectedSummary['contact_id']) ? $messageModel->recentCampaignsForContact((int)$selectedSummary['contact_id'], 6) : [];
        $conversationState = $selectedConversation ? $conversationModel->findByKey($selectedConversation) : null;
        $replyDraft = $selectedConversation ? $draftModel->findForUser((int)(current_user()['id'] ?? 0), 'reply', $selectedConversation) : null;
        $composeDraft = $draftModel->findForUser((int)(current_user()['id'] ?? 0), 'compose', null);
        $savedViews = (new MailSavedView())->allForUser((int)(current_user()['id'] ?? 0));
        $senderProfiles = (new MailSenderProfile())->active();
        $aiAssist = mailbox_ai_assist($selectedSummary, $thread, $selectedContact);

        return [
            'mailFolders' => $folderModel->all(),
            'mailLabels' => $labelModel->all(),
            'mailFolderCounts' => $folderCounts,
            'mailRules' => (new MailRule())->all(),
            'mailSavedViews' => $savedViews,
            'mailSenderProfiles' => $senderProfiles,
            'sharedMailboxes' => $sharedMailboxModel->allActive(),
            'currentMailbox' => $mailbox,
            'currentFolder' => $folderModel->byKey($mailbox) ?: ['key' => $mailbox, 'name' => ucfirst($mailbox), 'system' => true],
            'mailboxFilters' => array_merge(['q' => $query], $filters),
            'mailboxConversations' => $mailboxResult['data'],
            'mailboxMeta' => $mailboxResult['meta'],
            'selectedConversation' => $selectedConversation,
            'selectedMailboxSummary' => $selectedSummary,
            'conversationState' => $conversationState,
            'thread' => $thread,
            'contacts' => (new Contact())->all(),
            'users' => (new User())->all(),
            'emailTemplates' => (new Template())->all('email'),
            'cannedReplies' => (new CannedReply())->all('email'),
            'providerStatus' => provider_status_summary(),
            'selectedContact' => $selectedContact,
            'threadCampaigns' => $threadCampaigns,
            'contactCampaigns' => $contactCampaigns,
            'mailInternalNotes' => $selectedConversation ? $noteModel->byConversation($selectedConversation) : [],
            'composeDraft' => $composeDraft,
            'replyDraft' => $replyDraft,
            'mailAiAssist' => $aiAssist,
        ];
    }

    public function campaignDashboard(): void
    {
        require_permission('messages.campaigns');
        $this->view('messages/campaign_dashboard', $this->buildCampaignWorkspaceData());
    }

    public function campaignStudio(): void
    {
        require_permission('messages.campaigns');
        $this->view('messages/campaign_studio', $this->buildCampaignWorkspaceData());
    }

    public function campaignReports(): void
    {
        require_permission('messages.campaigns');
        $this->view('messages/campaign_reports', $this->buildCampaignWorkspaceData());
    }

    public function commercialCampaign(): void
    {
        require_permission('messages.campaigns');
        $this->view('messages/commercial_campaign', $this->buildCommercialCampaignData());
    }

    private function buildCommercialCampaignData(): array
    {
        $workspace = $this->buildCampaignWorkspaceData();
        $settingModel = new Setting();
        $reputationModel = new ReputationSnapshot();
        $automationModel = new AutomationRule();
        $eventModel = new EventWebhookLog();
        $connectorModel = new ConnectorConfig();
        $senderProfiles = (new MailSenderProfile())->all();
        $providerStatus = $workspace['providerStatus'] ?? provider_status_summary();
        $reputationSummary = $reputationModel->summary();
        return array_merge($workspace, [
            'deliverabilitySettings' => [
                'domain' => (string)$settingModel->get('deliverability_domain', ''),
                'dkim_selector' => (string)$settingModel->get('deliverability_dkim_selector', 's1'),
                'spf_include' => (string)$settingModel->get('deliverability_spf_include', 'include:sendgrid.net'),
                'return_path_domain' => (string)$settingModel->get('deliverability_return_path_domain', ''),
                'tracking_domain' => (string)$settingModel->get('deliverability_tracking_domain', ''),
                'dmarc_policy' => (string)$settingModel->get('deliverability_dmarc_policy', 'quarantine'),
                'bimi_svg_url' => (string)$settingModel->get('deliverability_bimi_svg_url', ''),
                'warmup_profile' => (string)$settingModel->get('deliverability_warmup_profile', ''),
                'one_click_unsubscribe' => (string)$settingModel->get('deliverability_one_click_unsubscribe', '1'),
                'alerts_enabled' => (string)$settingModel->get('deliverability_alerts_enabled', '1'),
                'safe_launch_batch_size' => (string)$settingModel->get('campaign_safe_launch_batch_size', '50'),
                'holdback_percent' => (string)$settingModel->get('campaign_holdback_percent', '5'),
                'seed_list_json' => (string)$settingModel->get('campaign_seed_list_json', ''),
                'domain_throttle_json' => (string)$settingModel->get('campaign_domain_throttle_json', '{"gmail.com":50,"yahoo.com":30}'),
                'provider_failover_transport' => (string)$settingModel->get('campaign_provider_failover_transport', 'sendgrid_api'),
            ],
            'dnsRecords' => deliverability_dns_records(),
            'complianceSnapshot' => deliverability_compliance_snapshot($providerStatus, $workspace['healthSummary'] ?? [], $reputationSummary),
            'automationRules' => $automationModel->all(),
            'automationStats' => $automationModel->stats(),
            'reputationSnapshots' => $reputationModel->recent(12),
            'reputationSummary' => $reputationSummary,
            'eventWebhookLogs' => $eventModel->recent(20),
            'eventWebhookSummary' => $eventModel->summary(),
            'connectors' => $connectorModel->all(),
            'connectorCatalog' => connector_catalog(),
            'senderProfiles' => $senderProfiles,
            'users' => (new User())->all(),
            'mailFolders' => (new MailFolder())->all(),
            'mailLabels' => (new MailLabel())->all(),
            'sharedMailboxes' => (new SharedMailbox())->allActive(),
        ]);
    }

    private function buildMessagingWorkspaceData(string $channel): array
    {
        $messageModel = new Message();
        $filters = [
            'q' => trim($_GET['q'] ?? ''),
            'channel' => $channel,
            'direction' => $_GET['direction'] ?? 'all',
            'status' => $_GET['status'] ?? 'all',
        ];
        $page = max(1, (int)($_GET['page'] ?? 1));
        $conversationResult = $messageModel->conversationSummary($filters, $page, 15);
        $selectedConversation = $_GET['conversation'] ?? ($conversationResult['data'][0]['conversation_key'] ?? null);
        $thread = $selectedConversation ? $messageModel->thread($selectedConversation) : [];
        $lastMessage = $thread ? end($thread) : null;

        return [
            'smsTemplates' => (new Template())->all('sms'),
            'emailTemplates' => (new Template())->all('email'),
            'filters' => $filters,
            'contacts' => (new Contact())->all(),
            'conversations' => $conversationResult['data'],
            'conversationMeta' => $conversationResult['meta'],
            'selectedConversation' => $selectedConversation,
            'thread' => $thread,
            'lastMessage' => $lastMessage,
            'cannedReplies' => (new CannedReply())->all($channel),
            'conversationState' => $selectedConversation ? (new ConversationState())->findByKey($selectedConversation) : null,
            'users' => (new User())->all(),
        ];
    }

    private function buildCampaignWorkspaceData(): array
    {
        $messageModel = new Message();
        $campaignModel = new Campaign();
        $queueModel = new QueueJob();
        $suppressionModel = new Suppression();
        $providerStatus = provider_status_summary();
        $templates = (new Template())->all();
        $analytics = $messageModel->analyticsDaily(21);
        $healthSummary = $messageModel->healthSummary(30);
        $suppressionSummary = $suppressionModel->summary(30);
        $queueStats = $queueModel->stats();
        $campaigns = $campaignModel->all();

        return [
            'templates' => $templates,
            'campaigns' => $campaigns,
            'segments' => (new Segment())->all(),
            'contactLists' => (new ContactList())->all(),
            'templateJson' => json_encode($templates),
            'campaignContextJson' => json_encode([
                'providerStatus' => $providerStatus,
                'systemHealth' => [
                    'bounceRate' => (float)($healthSummary['bounce_rate'] ?? 0),
                    'queuePressure' => (float)($healthSummary['queue_pressure'] ?? 0),
                ],
                'timezone' => config('app.timezone', 'America/Chicago'),
            ]),
            'analytics' => $analytics,
            'healthSummary' => $healthSummary,
            'providerStatus' => $providerStatus,
            'suppressionSummary' => $suppressionSummary,
            'suppressionRecent' => $suppressionModel->recent(8),
            'queueStats' => $queueStats,
            'failedQueueJobs' => $queueModel->failed(8),
            'deliverabilityAlerts' => $this->buildDeliverabilityAlerts($healthSummary, $queueStats, $suppressionSummary, $providerStatus),
            'campaignComparison' => $campaignModel->compareRecent(8),
            'templatePerformance' => $campaignModel->templatePerformance(8),
            'segmentPerformance' => $campaignModel->segmentPerformance(8),
        ];
    }

    private function redirectToMailCenter(string $mailbox = 'inbox', ?string $conversationKey = null): void
    {
        $params = ['route=messages/mailCenter', 'mailbox=' . urlencode($mailbox)];
        if ($conversationKey) {
            $params[] = 'conversation=' . urlencode($conversationKey);
        }
        redirect_url('index.php?' . implode('&', $params));
    }

    private function redirectToInboxRoute(string $channel, ?string $conversationKey = null): void
    {
        $returnRoute = trim((string)($_POST['return_route'] ?? ''));
        if ($channel === 'email' && $returnRoute === 'messages/mailCenter') {
            $mailbox = trim((string)($_POST['mailbox'] ?? '')) ?: 'inbox';
            $this->redirectToMailCenter($mailbox, $conversationKey);
        }
        $route = $channel === 'email' ? 'messages/inboxEmail' : 'messages/inboxSms';
        if ($conversationKey) {
            redirect_url('index.php?route=' . $route . '&conversation=' . urlencode($conversationKey));
        }
        redirect($route);
    }

    public function sendSms(): void
    {
        require_permission('messages.send');
        verify_csrf();
        $contactId = (int)($_POST['contact_id'] ?? 0);
        $contact = $contactId ? (new Contact())->find($contactId) : null;
        $to = $contact['phone'] ?? trim($_POST['to_address'] ?? '');
        $body = apply_sms_preferences($_POST['body'] ?? '', (int)(current_user()['id'] ?? 0));
        $mediaUrl = trim($_POST['media_url'] ?? '') ?: null;
        try {
            if ((new Suppression())->isSuppressed('sms', $to)) {
                throw new RuntimeException('This SMS recipient is on the suppression list.');
            }
            $timezone = $contact['timezone'] ?? quiet_hours_settings()['timezone'];
            if (is_within_quiet_hours($timezone)) {
                throw new RuntimeException(quiet_hours_notice($timezone));
            }
            $twilio = new TwilioService();
            $result = $twilio->sendSms($to, $body, $mediaUrl);
            $messageId = (new Message())->create([
                'contact_id' => $contactId ?: null,
                'channel' => 'sms',
                'direction' => 'outbound',
                'body' => $body,
                'from_address' => effective_twilio_config((int)(current_user()['id'] ?? 0))['from_number'],
                'to_address' => $to,
                'status' => $result['status'] ?? 'sent',
                'external_id' => $result['sid'] ?? null,
                'media_url' => $mediaUrl,
                'metadata_json' => json_encode($result),
            ]);
            audit_log('message.sent', 'message', $messageId, ['channel' => 'sms']);
            flash('success', 'SMS/MMS sent successfully.');
        } catch (Throwable $e) {
            flash('error', $e->getMessage());
        }
        $this->redirectToInboxRoute('sms');
    }

    public function sendEmail(): void
    {
        require_permission('messages.send');
        verify_csrf();
        $contactId = (int)($_POST['contact_id'] ?? 0);
        $contact = $contactId ? (new Contact())->find($contactId) : null;
        $to = $contact['email'] ?? trim($_POST['to_address'] ?? '');
        $subject = $_POST['subject'] ?? '';
        $body = apply_email_preferences($_POST['body'] ?? '', (int)(current_user()['id'] ?? 0));
        $attachments = [];
        $messageId = null;
        try {
            if ((new Suppression())->isSuppressed('email', $to)) {
                throw new RuntimeException('This email recipient is on the suppression list.');
            }
            $timezone = $contact['timezone'] ?? quiet_hours_settings()['timezone'];
            if (is_within_quiet_hours($timezone)) {
                throw new RuntimeException(quiet_hours_notice($timezone));
            }
            if (!empty($_FILES['attachment']['name'])) {
                $saved = upload_file($_FILES['attachment'], ['pdf', 'doc', 'docx', 'jpg', 'jpeg', 'png', 'gif']);
                if ($saved) {
                    $attachments[] = uploaded_file_path($saved);
                }
            }
            $messageModel = new Message();
            $messageId = $messageModel->create([
                'contact_id' => $contactId ?: null,
                'channel' => 'email',
                'direction' => 'outbound',
                'subject' => $subject,
                'body' => $body,
                'from_address' => effective_mail_config((int)(current_user()['id'] ?? 0))['from_email'],
                'to_address' => $to,
                'status' => 'queued',
                'attachment_path' => isset($saved) ? $saved : null,
            ]);
            $trackedHtml = instrument_email_html($body, $messageId);
            $externalId = '<msg-' . $messageId . '@' . parse_url(config('app.base_url'), PHP_URL_HOST) . '>';
            $mailConfig = effective_mail_config((int)(current_user()['id'] ?? 0));
            $ok = (new EmailService())->sendEmail($to, $subject, $trackedHtml, strip_tags($body), $attachments, ['Message-ID: ' . $externalId, 'Reply-To: ' . $mailConfig['reply_to_email']]);
            if (!$ok) {
                throw new RuntimeException('Email send failed. Check SendGrid, SMTP, or server mail settings.');
            }
            $messageModel->updateExternalAndStatus($messageId, $externalId, 'sent', null);
            if (trim((string)($_POST['return_route'] ?? '')) === 'messages/mailCenter') {
                $_POST['mailbox'] = 'sent';
            }
            audit_log('message.sent', 'message', $messageId, ['channel' => 'email']);
            flash('success', 'Email sent successfully.');
        } catch (Throwable $e) {
            if (!empty($messageId)) {
                $payload = ['error' => $e->getMessage(), 'failed_at' => date('c')];
                (new Message())->markFailedIfPending((int)$messageId, json_encode($payload));
            }
            if (trim((string)($_POST['return_route'] ?? '')) === 'messages/mailCenter') {
                $_POST['mailbox'] = 'sent';
            }
            flash('error', $e->getMessage());
        }
        $this->redirectToInboxRoute('email');
    }

    public function reply(): void
    {
        require_permission('messages.reply');
        verify_csrf();
        $channel = $_POST['channel'] ?? 'sms';
        $conversationKey = $_POST['conversation_key'] ?? '';
        $thread = (new Message())->thread($conversationKey);
        $last = $thread ? end($thread) : null;
        if (!$last) {
            flash('error', 'Conversation not found.');
            $this->redirectToInboxRoute($channel);
        }
        $messageId = null;
        try {
            if ($channel === 'sms') {
                $to = $last['direction'] === 'inbound' ? $last['from_address'] : $last['to_address'];
                if ((new Suppression())->isSuppressed('sms', $to)) {
                    throw new RuntimeException('This SMS recipient is on the suppression list.');
                }
                $contact = !empty($last['contact_id']) ? (new Contact())->find((int)$last['contact_id']) : null;
                $timezone = $contact['timezone'] ?? quiet_hours_settings()['timezone'];
                if (is_within_quiet_hours($timezone)) {
                    throw new RuntimeException(quiet_hours_notice($timezone));
                }
                $body = apply_sms_preferences($_POST['body'] ?? '', (int)(current_user()['id'] ?? 0));
                $result = (new TwilioService())->sendSms($to, $body, null);
                $messageId = (new Message())->create([
                    'contact_id' => $last['contact_id'] ?: null,
                    'channel' => 'sms',
                    'direction' => 'outbound',
                    'body' => $body,
                    'from_address' => effective_twilio_config((int)(current_user()['id'] ?? 0))['from_number'],
                    'to_address' => $to,
                    'status' => $result['status'] ?? 'sent',
                    'external_id' => $result['sid'] ?? null,
                    'metadata_json' => json_encode($result),
                    'thread_key' => $conversationKey,
                ]);
            } else {
                $to = normalize_address($last['direction'] === 'inbound' ? $last['from_address'] : $last['to_address']);
                if ((new Suppression())->isSuppressed('email', $to)) {
                    throw new RuntimeException('This email recipient is on the suppression list.');
                }
                $contact = !empty($last['contact_id']) ? (new Contact())->find((int)$last['contact_id']) : null;
                $timezone = $contact['timezone'] ?? quiet_hours_settings()['timezone'];
                if (is_within_quiet_hours($timezone)) {
                    throw new RuntimeException(quiet_hours_notice($timezone));
                }
                $subject = trim($_POST['subject'] ?? ('Re: ' . ($last['subject'] ?? 'Conversation')));
                $body = apply_email_preferences($_POST['body'] ?? '', (int)(current_user()['id'] ?? 0));
                $messageModel = new Message();
                $messageId = $messageModel->create([
                    'contact_id' => $last['contact_id'] ?: null,
                    'channel' => 'email',
                    'direction' => 'outbound',
                    'subject' => $subject,
                    'body' => $body,
                    'from_address' => effective_mail_config((int)(current_user()['id'] ?? 0))['from_email'],
                    'to_address' => $to,
                    'status' => 'queued',
                    'thread_key' => $conversationKey,
                    'in_reply_to_external_id' => $last['external_id'] ?? null,
                    'references_header' => trim(($last['references_header'] ?? '') . ' ' . ($last['external_id'] ?? '')),
                ]);
                $trackedHtml = instrument_email_html($body, $messageId);
                $externalId = '<msg-' . $messageId . '@' . parse_url(config('app.base_url'), PHP_URL_HOST) . '>';
                $mailConfig = effective_mail_config((int)(current_user()['id'] ?? 0));
                $headers = ['Message-ID: ' . $externalId, 'Reply-To: ' . $mailConfig['reply_to_email']];
                if (!empty($last['external_id'])) {
                    $headers[] = 'In-Reply-To: ' . $last['external_id'];
                    $headers[] = 'References: ' . trim(($last['references_header'] ?? '') . ' ' . $last['external_id']);
                }
                $ok = (new EmailService())->sendEmail($to, $subject, $trackedHtml, strip_tags($body), [], $headers);
                if (!$ok) {
                    throw new RuntimeException('Email send failed. Check SendGrid, SMTP, or server mail settings.');
                }
                $messageModel->updateExternalAndStatus($messageId, $externalId, 'sent', null);
            }
            audit_log('message.replied', 'message', $messageId ?? null, ['channel' => $channel, 'conversation_key' => $conversationKey]);
            flash('success', 'Reply sent.');
        } catch (Throwable $e) {
            if ($channel === 'email' && !empty($messageId)) {
                $payload = ['error' => $e->getMessage(), 'failed_at' => date('c')];
                (new Message())->markFailedIfPending((int)$messageId, json_encode($payload));
            }
            flash('error', $e->getMessage());
        }
        $this->redirectToInboxRoute($channel, $conversationKey);
    }

    public function saveMailFolder(): void
    {
        require_permission('messages.view');
        verify_csrf();
        try {
            $folder = (new MailFolder())->create((string)($_POST['folder_name'] ?? ''), trim((string)($_POST['parent_key'] ?? '')) ?: null);
            audit_log('mail_folder.created', 'mail_folder', null, ['key' => $folder['key'], 'name' => $folder['name'], 'parent_key' => $folder['parent_key'] ?? null]);
            flash('success', 'Folder created: ' . ($folder['name'] ?? 'New Folder'));
        } catch (Throwable $e) {
            flash('error', $e->getMessage());
        }
        $returnMailbox = trim((string)($_POST['return_mailbox'] ?? '')) ?: 'inbox';
        $this->redirectToMailCenter($returnMailbox);
    }

    public function moveEmailConversation(): void
    {
        require_permission('messages.view');
        verify_csrf();
        $conversationKey = trim((string)($_POST['conversation_key'] ?? ''));
        $targetFolder = (new MailFolder())->normalizeKey((string)($_POST['target_folder'] ?? '')) ?: 'inbox';
        $folderModel = new MailFolder();
        if ($conversationKey === '') {
            flash('error', 'Choose a conversation before moving it.');
            $this->redirectToMailCenter(trim((string)($_POST['mailbox'] ?? '')) ?: 'inbox');
        }
        if (!$folderModel->exists($targetFolder)) {
            flash('error', 'That folder does not exist.');
            $this->redirectToMailCenter(trim((string)($_POST['mailbox'] ?? '')) ?: 'inbox', $conversationKey);
        }
        $messageModel = new Message();
        if ($messageModel->isComputedMailboxFolder($targetFolder)) {
            flash('error', ucfirst($targetFolder) . ' is managed automatically based on email send status. Choose Inbox, Archive, Junk, Trash, Snoozed, or a custom folder instead.');
            $this->redirectToMailCenter(trim((string)($_POST['mailbox'] ?? '')) ?: 'inbox', $conversationKey);
        }
        $updated = $messageModel->moveConversationToFolder($conversationKey, $targetFolder);
        $folder = $folderModel->byKey($targetFolder) ?: ['name' => ucfirst($targetFolder)];
        flash('success', $updated > 0 ? 'Conversation moved to ' . ($folder['name'] ?? ucfirst($targetFolder)) . '.' : 'No email messages were moved.');
        audit_log('message.conversation_moved', 'conversation', null, ['conversation_key' => $conversationKey, 'target_folder' => $targetFolder]);
        $this->redirectToMailCenter($targetFolder, $conversationKey);
    }

    public function saveMailLabel(): void
    {
        require_permission('messages.view');
        verify_csrf();
        try {
            $label = (new MailLabel())->create((string)($_POST['label_name'] ?? ''), (string)($_POST['color_token'] ?? 'blue'));
            audit_log('mail_label.created', 'mail_label', null, ['key' => $label['label_key'] ?? null, 'name' => $label['name'] ?? null]);
            flash('success', 'Label created: ' . ($label['name'] ?? 'Label'));
        } catch (Throwable $e) {
            flash('error', $e->getMessage());
        }
        $this->redirectToMailCenter(trim((string)($_POST['return_mailbox'] ?? '')) ?: 'inbox', trim((string)($_POST['conversation_key'] ?? '')) ?: null);
    }

    public function saveMailSavedView(): void
    {
        require_permission('messages.view');
        verify_csrf();
        try {
            (new MailSavedView())->save([
                'name' => (string)($_POST['name'] ?? ''),
                'filter_json' => [
                    'mailbox' => (string)($_POST['mailbox'] ?? 'inbox'),
                    'q' => (string)($_POST['q'] ?? ''),
                    'bucket' => (string)($_POST['bucket'] ?? 'all'),
                    'label_key' => (string)($_POST['label_key'] ?? 'all'),
                    'starred' => !empty($_POST['starred']) ? '1' : '0',
                    'shared_mailbox_id' => (int)($_POST['shared_mailbox_id'] ?? 0),
                ],
                'is_shared' => !empty($_POST['is_shared']) ? 1 : 0,
            ]);
            flash('success', 'Saved view added to Mail Center.');
        } catch (Throwable $e) {
            flash('error', $e->getMessage());
        }
        $this->redirectToMailCenter(trim((string)($_POST['mailbox'] ?? '')) ?: 'inbox', trim((string)($_POST['conversation_key'] ?? '')) ?: null);
    }

    public function toggleMailPinned(): void
    {
        require_permission('messages.view');
        verify_csrf();
        $conversationKey = trim((string)($_POST['conversation_key'] ?? ''));
        if ($conversationKey === '') {
            flash('error', 'Choose a conversation before pinning it.');
            $this->redirectToMailCenter(trim((string)($_POST['mailbox'] ?? '')) ?: 'inbox');
        }
        $pinned = (new MailConversation())->togglePinned($conversationKey);
        flash('success', $pinned ? 'Conversation pinned.' : 'Conversation unpinned.');
        audit_log('mail_conversation.pinned', 'conversation', null, ['conversation_key' => $conversationKey, 'is_pinned' => $pinned]);
        $this->redirectToMailCenter(trim((string)($_POST['mailbox'] ?? '')) ?: 'inbox', $conversationKey);
    }

    public function toggleMailMuted(): void
    {
        require_permission('messages.view');
        verify_csrf();
        $conversationKey = trim((string)($_POST['conversation_key'] ?? ''));
        if ($conversationKey === '') {
            flash('error', 'Choose a conversation before muting it.');
            $this->redirectToMailCenter(trim((string)($_POST['mailbox'] ?? '')) ?: 'inbox');
        }
        $muted = (new MailConversation())->toggleMuted($conversationKey);
        flash('success', $muted ? 'Conversation muted.' : 'Conversation unmuted.');
        audit_log('mail_conversation.muted', 'conversation', null, ['conversation_key' => $conversationKey, 'is_muted' => $muted]);
        $this->redirectToMailCenter(trim((string)($_POST['mailbox'] ?? '')) ?: 'inbox', $conversationKey);
    }

    public function claimMailConversation(): void
    {
        require_permission('messages.view');
        verify_csrf();
        $conversationKey = trim((string)($_POST['conversation_key'] ?? ''));
        if ($conversationKey === '') {
            flash('error', 'Choose a conversation first.');
            $this->redirectToMailCenter(trim((string)($_POST['mailbox'] ?? '')) ?: 'inbox');
        }
        $userId = !empty($_POST['release_claim']) ? null : ((int)(current_user()['id'] ?? 0) ?: null);
        (new MailConversation())->claim($conversationKey, $userId);
        flash('success', $userId ? 'Conversation claimed for active work.' : 'Conversation claim released.');
        $this->redirectToMailCenter(trim((string)($_POST['mailbox'] ?? '')) ?: 'inbox', $conversationKey);
    }

    public function applyConversationLabels(): void
    {
        require_permission('messages.view');
        verify_csrf();
        $conversationKey = trim((string)($_POST['conversation_key'] ?? ''));
        if ($conversationKey === '') {
            flash('error', 'Choose a conversation before applying labels.');
            $this->redirectToMailCenter(trim((string)($_POST['mailbox'] ?? '')) ?: 'inbox');
        }
        (new MailLabel())->replaceConversationLabels($conversationKey, (array)($_POST['label_keys'] ?? []));
        flash('success', 'Conversation labels updated.');
        audit_log('mail_labels.updated', 'conversation', null, ['conversation_key' => $conversationKey]);
        $this->redirectToMailCenter(trim((string)($_POST['mailbox'] ?? '')) ?: 'inbox', $conversationKey);
    }

    public function toggleMailStar(): void
    {
        require_permission('messages.view');
        verify_csrf();
        $conversationKey = trim((string)($_POST['conversation_key'] ?? ''));
        if ($conversationKey === '') {
            flash('error', 'Choose a conversation before starring it.');
            $this->redirectToMailCenter(trim((string)($_POST['mailbox'] ?? '')) ?: 'inbox');
        }
        $starred = (new MailConversation())->toggleStar($conversationKey);
        flash('success', $starred ? 'Conversation starred.' : 'Conversation unstarred.');
        audit_log('mail_conversation.starred', 'conversation', null, ['conversation_key' => $conversationKey, 'is_starred' => $starred]);
        $this->redirectToMailCenter(trim((string)($_POST['mailbox'] ?? '')) ?: 'inbox', $conversationKey);
    }

    public function archiveMailConversation(): void
    {
        require_permission('messages.view');
        verify_csrf();
        $conversationKey = trim((string)($_POST['conversation_key'] ?? ''));
        if ($conversationKey === '') {
            flash('error', 'Choose a conversation before archiving it.');
            $this->redirectToMailCenter(trim((string)($_POST['mailbox'] ?? '')) ?: 'inbox');
        }
        $mode = trim((string)($_POST['mode'] ?? 'archive'));
        (new MailConversation())->archive($conversationKey, $mode !== 'unarchive');
        flash('success', $mode === 'unarchive' ? 'Conversation returned to the mailbox.' : 'Conversation archived.');
        audit_log('mail_conversation.archive', 'conversation', null, ['conversation_key' => $conversationKey, 'mode' => $mode]);
        $target = $mode === 'unarchive' ? 'inbox' : 'archive';
        $this->redirectToMailCenter($target, $conversationKey);
    }

    public function snoozeMailConversation(): void
    {
        require_permission('messages.view');
        verify_csrf();
        $conversationKey = trim((string)($_POST['conversation_key'] ?? ''));
        if ($conversationKey === '') {
            flash('error', 'Choose a conversation before snoozing it.');
            $this->redirectToMailCenter(trim((string)($_POST['mailbox'] ?? '')) ?: 'inbox');
        }
        $preset = trim((string)($_POST['snooze_preset'] ?? 'tomorrow'));
        $until = match ($preset) {
            'two_days' => date('Y-m-d 08:00:00', strtotime('+2 days')),
            'next_week' => date('Y-m-d 08:00:00', strtotime('next monday')),
            'clear' => null,
            default => date('Y-m-d 08:00:00', strtotime('+1 day')),
        };
        (new MailConversation())->snooze($conversationKey, $until);
        flash('success', $until ? 'Conversation snoozed until ' . $until . '.' : 'Conversation snooze cleared.');
        audit_log('mail_conversation.snoozed', 'conversation', null, ['conversation_key' => $conversationKey, 'until' => $until]);
        $target = $until ? 'snoozed' : 'inbox';
        $this->redirectToMailCenter($target, $conversationKey);
    }

    public function updateMailConversation(): void
    {
        require_permission('messages.view');
        verify_csrf();
        $conversationKey = trim((string)($_POST['conversation_key'] ?? ''));
        if ($conversationKey === '') {
            flash('error', 'Choose a conversation first.');
            $this->redirectToMailCenter(trim((string)($_POST['mailbox'] ?? '')) ?: 'inbox');
        }
        $approvalStatus = trim((string)($_POST['approval_status'] ?? 'not_required')) ?: 'not_required';
        $payload = [
            'assigned_user_id' => (int)($_POST['assigned_user_id'] ?? 0) ?: null,
            'shared_mailbox_id' => (int)($_POST['shared_mailbox_id'] ?? 0) ?: null,
            'focus_bucket' => trim((string)($_POST['focus_bucket'] ?? 'focused')) ?: 'focused',
            'sla_status' => trim((string)($_POST['sla_status'] ?? 'normal')) ?: 'normal',
            'approval_status' => $approvalStatus,
            'approval_requested_user_id' => (int)($_POST['approval_requested_user_id'] ?? 0) ?: null,
            'sensitivity_level' => trim((string)($_POST['sensitivity_level'] ?? 'standard')) ?: 'standard',
        ];
        if (in_array($approvalStatus, ['approved', 'rejected'], true)) {
            $payload['approval_decided_user_id'] = (int)(current_user()['id'] ?? 0) ?: null;
            $payload['approval_decided_at'] = date('Y-m-d H:i:s');
        }
        (new MailConversation())->upsert($conversationKey, $payload);
        flash('success', 'Conversation workflow updated.');
        audit_log('mail_conversation.updated', 'conversation', null, ['conversation_key' => $conversationKey] + $payload);
        $this->redirectToMailCenter(trim((string)($_POST['mailbox'] ?? '')) ?: 'inbox', $conversationKey);
    }

    public function addMailInternalNote(): void
    {
        require_permission('messages.view');
        verify_csrf();
        $conversationKey = trim((string)($_POST['conversation_key'] ?? ''));
        if ($conversationKey === '') {
            flash('error', 'Choose a conversation before adding a note.');
            $this->redirectToMailCenter(trim((string)($_POST['mailbox'] ?? '')) ?: 'inbox');
        }
        try {
            (new MailInternalNote())->create($conversationKey, (string)($_POST['note_body'] ?? ''), (int)(current_user()['id'] ?? 0) ?: null);
            flash('success', 'Internal note added.');
            audit_log('mail_note.created', 'conversation', null, ['conversation_key' => $conversationKey]);
        } catch (Throwable $e) {
            flash('error', $e->getMessage());
        }
        $this->redirectToMailCenter(trim((string)($_POST['mailbox'] ?? '')) ?: 'inbox', $conversationKey);
    }

    public function saveMailRule(): void
    {
        require_permission('messages.view');
        verify_csrf();
        try {
            $ruleData = $_POST;
            if ((new Message())->isComputedMailboxFolder((string)($ruleData['target_folder_key'] ?? ''))) {
                $ruleData['target_folder_key'] = '';
            }
            (new MailRule())->create($ruleData);
            flash('success', 'Mailbox rule created.');
            audit_log('mail_rule.created', 'mail_rule', null, ['name' => (string)($_POST['name'] ?? '')]);
        } catch (Throwable $e) {
            flash('error', $e->getMessage());
        }
        $this->redirectToMailCenter(trim((string)($_POST['return_mailbox'] ?? '')) ?: 'inbox', trim((string)($_POST['conversation_key'] ?? '')) ?: null);
    }

    public function createSharedMailbox(): void
    {
        require_permission('messages.view');
        verify_csrf();
        try {
            $mailbox = (new SharedMailbox())->create($_POST);
            flash('success', 'Shared mailbox created: ' . ($mailbox['name'] ?? 'Mailbox'));
            audit_log('shared_mailbox.created', 'shared_mailbox', $mailbox['id'] ?? null, ['email_address' => $mailbox['email_address'] ?? null]);
        } catch (Throwable $e) {
            flash('error', $e->getMessage());
        }
        $this->redirectToMailCenter(trim((string)($_POST['return_mailbox'] ?? '')) ?: 'inbox', trim((string)($_POST['conversation_key'] ?? '')) ?: null);
    }

    public function saveMailDraft(): void
    {
        require_permission('messages.view');
        verify_csrf();
        $draftType = trim((string)($_POST['draft_type'] ?? 'compose')) ?: 'compose';
        $conversationKey = trim((string)($_POST['conversation_key'] ?? '')) ?: null;
        $draftId = (new MailDraft())->saveForUser((int)(current_user()['id'] ?? 0), $draftType, $conversationKey, $_POST);
        $this->json(['ok' => true, 'draft_id' => $draftId, 'saved_at' => date('Y-m-d H:i:s')]);
    }

    public function deleteMailDraft(): void
    {
        require_permission('messages.view');
        verify_csrf();
        (new MailDraft())->deleteForUser((int)(current_user()['id'] ?? 0), (int)($_POST['draft_id'] ?? 0));
        $this->json(['ok' => true]);
    }

    public function createCampaign(): void
    {
        require_permission('messages.campaigns');
        verify_csrf();
        try {
            $result = (new CampaignService())->queueCampaign($_POST);
            $message = 'Campaign saved. Campaign ID: ' . (int)$result['id'];
            if (!empty($result['preflight']['warnings'])) {
                $message .= ' Preflight notes: ' . implode(' ', array_slice($result['preflight']['warnings'], 0, 2));
            }
            flash('success', $message);
        } catch (Throwable $e) {
            flash('error', $e->getMessage());
        }
        redirect('messages/campaignStudio');
    }

    public function approveCampaign(): void
    {
        require_permission('messages.campaigns');
        $id = (int)($_GET['id'] ?? 0);
        $campaign = (new Campaign())->find($id);
        if (!$campaign) {
            flash('error', 'Campaign not found.');
            redirect('messages/campaignReports');
        }
        (new Campaign())->approve($id, (int)(current_user()['id'] ?? 0));
        (new QueueJob())->createCampaignProcess($id, $campaign['scheduled_at'] ?? null);
        audit_log('campaign.approved', 'campaign', $id);
        flash('success', 'Campaign approved and queued for processing.');
        redirect_url('index.php?route=messages/report&id=' . $id);
    }

    public function startCampaign(): void
    {
        require_permission('messages.campaigns');
        verify_csrf();
        $campaignModel = new Campaign();
        $queueModel = new QueueJob();
        $campaignId = (int)($_POST['campaign_id'] ?? 0);
        $campaign = array_merge($campaignModel->find($campaignId) ?: [], $campaignModel->summary($campaignId) ?: []);
        if (!$campaign) {
            flash('error', 'Campaign not found.');
            $this->redirectToCampaignReturn($campaignId);
        }
        if (($campaign['approval_status'] ?? 'not_required') === 'pending') {
            flash('error', 'Approve the campaign before starting it.');
            $this->redirectToCampaignReturn($campaignId);
        }
        if ((int)($campaign['queued_recipients'] ?? 0) <= 0 && (int)($campaign['failed_recipients'] ?? 0) > 0) {
            flash('error', 'This campaign has failed recipients waiting. Use Restart to recover them before starting again.');
            $this->redirectToCampaignReturn($campaignId);
        }
        if ((int)($campaign['queued_recipients'] ?? 0) <= 0) {
            flash('error', 'This campaign has no queued recipients ready to start.');
            $this->redirectToCampaignReturn($campaignId);
        }

        $campaignModel->setControlState($campaignId, 'running');
        if (($campaign['status'] ?? 'draft') !== 'sent') {
            $campaignModel->setStatus($campaignId, 'queued');
        }
        $availableAt = (!empty($campaign['scheduled_at']) && strtotime((string)$campaign['scheduled_at']) > time())
            ? (string)$campaign['scheduled_at']
            : null;
        $queueModel->createCampaignProcess($campaignId, $availableAt);
        audit_log('campaign.started', 'campaign', $campaignId, ['available_at' => $availableAt]);
        flash('success', $availableAt ? 'Campaign started and kept on its scheduled queue.' : 'Campaign started and queued for processing.');
        $this->redirectToCampaignReturn($campaignId);
    }

    public function pauseCampaign(): void
    {
        require_permission('messages.campaigns');
        verify_csrf();
        $campaignModel = new Campaign();
        $queueModel = new QueueJob();
        $campaignId = (int)($_POST['campaign_id'] ?? 0);
        $campaign = $campaignModel->find($campaignId);
        if (!$campaign) {
            flash('error', 'Campaign not found.');
            $this->redirectToCampaignReturn($campaignId);
        }

        $campaignModel->setControlState($campaignId, 'paused');
        if (($campaign['status'] ?? 'draft') !== 'sent') {
            $campaignModel->setStatus($campaignId, 'queued');
        }
        $queueModel->dedupeCampaignProcessJobs($campaignId, null);
        audit_log('campaign.paused', 'campaign', $campaignId);
        flash('success', 'Campaign paused. Unsent recipients remain queued until you start it again.');
        $this->redirectToCampaignReturn($campaignId);
    }

    public function stopCampaign(): void
    {
        require_permission('messages.campaigns');
        verify_csrf();
        $campaignModel = new Campaign();
        $queueModel = new QueueJob();
        $campaignId = (int)($_POST['campaign_id'] ?? 0);
        $campaign = $campaignModel->find($campaignId);
        if (!$campaign) {
            flash('error', 'Campaign not found.');
            $this->redirectToCampaignReturn($campaignId);
        }

        $campaignModel->setControlState($campaignId, 'stopped');
        if (($campaign['status'] ?? 'draft') !== 'sent') {
            $campaignModel->setStatus($campaignId, 'queued');
        }
        $queueModel->dedupeCampaignProcessJobs($campaignId, null);
        audit_log('campaign.stopped', 'campaign', $campaignId);
        flash('success', 'Campaign stopped. Use Restart or Force Queue when you are ready to send again.');
        $this->redirectToCampaignReturn($campaignId);
    }

    public function restartCampaign(): void
    {
        require_permission('messages.campaigns');
        verify_csrf();
        $campaignModel = new Campaign();
        $queueModel = new QueueJob();
        $campaignId = (int)($_POST['campaign_id'] ?? 0);
        $campaign = array_merge($campaignModel->find($campaignId) ?: [], $campaignModel->summary($campaignId) ?: []);
        if (!$campaign) {
            flash('error', 'Campaign not found.');
            $this->redirectToCampaignReturn($campaignId);
        }

        $requeued = $campaignModel->requeueFailedRecipients($campaignId);
        if ($requeued <= 0 && (int)($campaign['queued_recipients'] ?? 0) <= 0) {
            flash('error', 'There are no failed or queued recipients available to restart.');
            $this->redirectToCampaignReturn($campaignId);
        }
        $campaignModel->setControlState($campaignId, 'running');
        if (($campaign['status'] ?? 'draft') !== 'sent' || $requeued > 0 || (int)($campaign['queued_recipients'] ?? 0) > 0) {
            $campaignModel->setStatus($campaignId, 'queued');
        }
        $queueModel->forceCampaignProcess($campaignId);
        audit_log('campaign.restarted', 'campaign', $campaignId, ['requeued_failed_recipients' => $requeued]);
        $message = $requeued > 0
            ? 'Campaign restarted and ' . $requeued . ' failed recipient(s) were returned to the queue.'
            : 'Campaign restarted and queued for processing.';
        flash('success', $message);
        $this->redirectToCampaignReturn($campaignId);
    }

    public function forceQueueCampaign(): void
    {
        require_permission('messages.campaigns');
        verify_csrf();
        $campaignModel = new Campaign();
        $queueModel = new QueueJob();
        $campaignId = (int)($_POST['campaign_id'] ?? 0);
        $campaign = array_merge($campaignModel->find($campaignId) ?: [], $campaignModel->summary($campaignId) ?: []);
        if (!$campaign) {
            flash('error', 'Campaign not found.');
            $this->redirectToCampaignReturn($campaignId);
        }
        if (($campaign['approval_status'] ?? 'not_required') === 'pending') {
            flash('error', 'Approve the campaign before force-queuing it.');
            $this->redirectToCampaignReturn($campaignId);
        }

        $requeued = 0;
        if ((int)($campaign['queued_recipients'] ?? 0) <= 0 && (int)($campaign['failed_recipients'] ?? 0) > 0) {
            $requeued = $campaignModel->requeueFailedRecipients($campaignId);
        }
        $fresh = array_merge($campaignModel->find($campaignId) ?: [], $campaignModel->summary($campaignId) ?: []);
        if ((int)($fresh['queued_recipients'] ?? 0) <= 0 && $requeued <= 0) {
            flash('error', 'There are no queued recipients to force back into processing.');
            $this->redirectToCampaignReturn($campaignId);
        }

        $campaignModel->setControlState($campaignId, 'running');
        if (($fresh['status'] ?? 'draft') !== 'sent' || (int)($fresh['queued_recipients'] ?? 0) > 0) {
            $campaignModel->setStatus($campaignId, 'queued');
        }
        $queueModel->forceCampaignProcess($campaignId);
        audit_log('campaign.force_queued', 'campaign', $campaignId, ['requeued_failed_recipients' => $requeued]);
        $message = $requeued > 0
            ? 'Force Queue created an immediate run and recovered ' . $requeued . ' failed recipient(s).'
            : 'Campaign force-queued for immediate processing.';
        flash('success', $message);
        $this->redirectToCampaignReturn($campaignId);
    }

    public function deleteCampaign(): void
    {
        require_permission('messages.campaigns');
        verify_csrf();
        $campaignModel = new Campaign();
        $campaignId = (int)($_POST['campaign_id'] ?? 0);
        $campaign = array_merge($campaignModel->find($campaignId) ?: [], $campaignModel->summary($campaignId) ?: []);
        if (!$campaign) {
            flash('error', 'Campaign not found.');
            $this->redirectToCampaignReturn($campaignId);
        }

        if (($campaign['status'] ?? 'draft') === 'processing') {
            flash('error', 'Pause or stop the campaign before deleting it.');
            $this->redirectToCampaignReturn($campaignId);
        }

        if ((int)($campaign['sent_recipients'] ?? 0) > 0
            || (int)($campaign['delivered_count'] ?? 0) > 0
            || (int)($campaign['open_count'] ?? 0) > 0
            || (int)($campaign['click_count'] ?? 0) > 0) {
            flash('error', 'Delete is only available for unsent campaigns so reporting history is not lost.');
            $this->redirectToCampaignReturn($campaignId);
        }

        $campaignName = (string)($campaign['name'] ?? 'Campaign');
        $campaignModel->deleteCascade($campaignId);
        audit_log('campaign.deleted', 'campaign', $campaignId, ['name' => $campaignName]);
        flash('success', 'Campaign deleted: ' . $campaignName);
        $this->redirectToCampaignReturn(0);
    }

    public function sendCampaignTest(): void
    {
        require_permission('messages.campaigns');
        verify_csrf();
        $campaignId = (int)($_POST['campaign_id'] ?? 0);
        $campaign = (new Campaign())->find($campaignId);
        if (!$campaign) {
            flash('error', 'Campaign not found.');
            redirect('messages/campaignReports');
        }
        $template = !empty($campaign['template_id']) ? (new Template())->find((int)$campaign['template_id']) : null;
        $variant = $_POST['variant'] ?? 'default';
        $sample = [
            'id' => 0,
            'first_name' => 'Avery',
            'last_name' => 'Jordan',
            'company' => 'Northwind Health',
            'email' => trim($_POST['to_address'] ?? ''),
            'phone' => trim($_POST['to_address'] ?? ''),
            'timezone' => config('app.timezone', 'America/Chicago'),
        ];
        $messageId = null;
        try {
            if ($campaign['channel'] === 'sms') {
                $to = trim($_POST['to_address'] ?? '');
                $baseBody = $this->campaignBodyForVariant($campaign, $template, $variant);
                $body = apply_sms_preferences(render_template_content($baseBody, $sample), (int)($campaign['created_by'] ?? 0));
                $result = (new TwilioService())->sendSms($to, $body, null);
                $messageId = (new Message())->create([
                    'campaign_id' => $campaignId,
                    'channel' => 'sms',
                    'direction' => 'outbound',
                    'body' => $body,
                    'from_address' => effective_twilio_config((int)($campaign['created_by'] ?? 0))['from_number'],
                    'to_address' => $to,
                    'status' => $result['status'] ?? 'sent',
                    'external_id' => $result['sid'] ?? null,
                    'metadata_json' => json_encode(['mode' => 'test_send', 'variant' => $variant, 'provider' => $result]),
                ]);
            } else {
                $to = trim($_POST['to_address'] ?? '');
                $subjectBase = $this->campaignSubjectForVariant($campaign, $template, $variant);
                $bodyBase = $this->campaignBodyForVariant($campaign, $template, $variant);
                $subject = render_template_content($subjectBase ?: ($campaign['name'] ?? 'Campaign Test'), $sample);
                $body = apply_email_preferences(render_template_content($bodyBase, $sample), (int)($campaign['created_by'] ?? 0));
                $messageModel = new Message();
                $messageId = $messageModel->create([
                    'campaign_id' => $campaignId,
                    'channel' => 'email',
                    'direction' => 'outbound',
                    'subject' => $subject,
                    'body' => $body,
                    'from_address' => effective_mail_config((int)($campaign['created_by'] ?? 0))['from_email'],
                    'to_address' => $to,
                    'status' => 'queued',
                    'metadata_json' => json_encode(['mode' => 'test_send', 'variant' => $variant]),
                ]);
                $trackedHtml = instrument_email_html($body, $messageId);
                $externalId = '<msg-' . $messageId . '@' . parse_url(config('app.base_url'), PHP_URL_HOST) . '>';
                $ok = (new EmailService())->sendEmail($to, $subject, $trackedHtml, strip_tags($body), [], ['Message-ID: ' . $externalId]);
                if (!$ok) {
                    throw new RuntimeException('Email send failed. Check SendGrid, SMTP, or server mail settings.');
                }
                $messageModel->updateExternalAndStatus($messageId, $externalId, 'sent', null);
            }
            audit_log('campaign.test_sent', 'campaign', $campaignId, ['to' => $_POST['to_address'] ?? '', 'variant' => $variant]);
            flash('success', 'Test send completed.');
        } catch (Throwable $e) {
            if ($campaign['channel'] === 'email' && !empty($messageId)) {
                $payload = ['error' => $e->getMessage(), 'failed_at' => date('c'), 'mode' => 'test_send'];
                (new Message())->markFailedIfPending((int)$messageId, json_encode($payload));
            }
            flash('error', $e->getMessage());
        }
        redirect_url('index.php?route=messages/report&id=' . $campaignId);
    }

    public function report(): void
    {
        require_permission('messages.campaigns');
        $id = (int)($_GET['id'] ?? 0);
        $campaignModel = new Campaign();
        $campaign = array_merge($campaignModel->find($id) ?: [], $campaignModel->summary($id) ?: []);
        if (!$campaign) {
            flash('error', 'Campaign not found.');
            redirect('messages/campaignReports');
        }
        $campaignHealth = [
            'delivery_rate' => round(((int)($campaign['delivered_count'] ?? 0) / max(1, (int)($campaign['total_recipients'] ?? 0))) * 100, 1),
            'bounce_rate' => round(((int)($campaign['bounced_count'] ?? 0) / max(1, (int)($campaign['total_recipients'] ?? 0))) * 100, 1),
            'open_rate' => round(((int)($campaign['unique_open_count'] ?? 0) / max(1, (int)($campaign['total_recipients'] ?? 0))) * 100, 1),
            'click_rate' => round(((int)($campaign['unique_click_count'] ?? 0) / max(1, (int)($campaign['total_recipients'] ?? 0))) * 100, 1),
        ];
        $perPageOptions = [15, 25, 50];
        $recipientPerPage = (int)($_GET['rb_per_page'] ?? 15);
        if (!in_array($recipientPerPage, $perPageOptions, true)) {
            $recipientPerPage = 15;
        }
        $openPerPage = (int)($_GET['open_per_page'] ?? 15);
        if (!in_array($openPerPage, $perPageOptions, true)) {
            $openPerPage = 15;
        }
        $clickPerPage = (int)($_GET['click_per_page'] ?? 15);
        if (!in_array($clickPerPage, $perPageOptions, true)) {
            $clickPerPage = 15;
        }
        $recipientPager = paginate_meta(max(1, (int)($_GET['rb_page'] ?? 1)), $recipientPerPage, $campaignModel->recipientBreakdownCount($id));
        $openPager = paginate_meta(max(1, (int)($_GET['open_page'] ?? 1)), $openPerPage, $campaignModel->openRecipientsCount($id));
        $clickPager = paginate_meta(max(1, (int)($_GET['click_page'] ?? 1)), $clickPerPage, $campaignModel->clickRecipientsCount($id));
        $this->view('messages/report', [
            'campaign' => $campaign,
            'campaignHealth' => $campaignHealth,
            'preflight' => (new CampaignService())->preflightForCampaign($campaign),
            'variantPerformance' => $campaignModel->variantPerformance($id),
            'providerStatus' => provider_status_summary(),
            'queueStats' => (new QueueJob())->stats(),
            'trend' => $campaignModel->messageTrend($id),
            'recipients' => $campaignModel->recipientBreakdown($id, $recipientPager['per_page'], $recipientPager['offset']),
            'recipientPager' => $recipientPager,
            'openRecipients' => $campaignModel->openRecipients($id, $openPager['per_page'], $openPager['offset']),
            'openPager' => $openPager,
            'clickRecipients' => $campaignModel->clickRecipients($id, $clickPager['per_page'], $clickPager['offset']),
            'clickPager' => $clickPager,
            'reportPerPageOptions' => $perPageOptions,
            'comparison' => $campaignModel->compareRecent(8),
            'templatePerformance' => $campaignModel->templatePerformance(8),
            'segmentPerformance' => $campaignModel->segmentPerformance(8),
        ]);
    }

    private function redirectToCampaignReturn(int $campaignId): void
    {
        $returnTo = trim((string)($_POST['return_to'] ?? ''));
        if ($returnTo === 'report' && $campaignId > 0) {
            redirect_url('index.php?route=messages/report&id=' . $campaignId);
        }
        if ($returnTo === 'studio') {
            redirect('messages/campaignStudio');
        }
        if ($returnTo === 'dashboard') {
            redirect('messages/campaignDashboard');
        }
        redirect('messages/campaignReports');
    }

    private function campaignSubjectForVariant(array $campaign, ?array $template, string $variant): string
    {
        if ($variant === 'b' && !empty($campaign['variant_b_subject'])) {
            return (string)$campaign['variant_b_subject'];
        }
        if ($variant === 'a' && !empty($campaign['variant_a_subject'])) {
            return (string)$campaign['variant_a_subject'];
        }
        return (string)($campaign['subject_override'] ?? ($template['subject'] ?? $campaign['name'] ?? 'Campaign'));
    }

    private function campaignBaseBody(array $campaign, ?array $template): string
    {
        $body = trim((string)($campaign['body_override'] ?? ''));
        if ($body !== '') {
            return $body;
        }
        $blocks = json_decode((string)($campaign['builder_json'] ?? ''), true);
        if (is_array($blocks) && $blocks) {
            $html = [];
            foreach ($blocks as $block) {
                if (is_array($block) && !empty($block['html'])) {
                    $html[] = (string)$block['html'];
                }
            }
            if ($html) {
                return implode("

", $html);
            }
        }
        return (string)($template['body'] ?? '');
    }

    private function campaignBodyForVariant(array $campaign, ?array $template, string $variant): string
    {
        if ($variant === 'b' && !empty($campaign['variant_b_body'])) {
            return (string)$campaign['variant_b_body'];
        }
        if ($variant === 'a' && !empty($campaign['variant_a_body'])) {
            return (string)$campaign['variant_a_body'];
        }
        return $this->campaignBaseBody($campaign, $template);
    }


    public function saveCommercialCampaignSettings(): void
    {
        require_permission('messages.campaigns');
        verify_csrf();
        $model = new Setting();
        $fields = [
            'deliverability_domain','deliverability_dkim_selector','deliverability_spf_include','deliverability_return_path_domain','deliverability_tracking_domain','deliverability_dmarc_policy','deliverability_bimi_svg_url','deliverability_warmup_profile','deliverability_one_click_unsubscribe','deliverability_alerts_enabled',
            'campaign_safe_launch_batch_size','campaign_holdback_percent','campaign_seed_list_json','campaign_domain_throttle_json','campaign_provider_failover_transport',
            'privacy_mask_pii','privacy_default_sensitivity','privacy_export_requires_admin','marketplace_webhook_secret'
        ];
        foreach ($fields as $field) {
            if (array_key_exists($field, $_POST)) {
                $model->set($field, (string)$_POST[$field]);
            }
        }
        audit_log('commercial_campaign.settings.saved', 'settings', null, ['scope' => 'v16.4']);
        flash('success', 'Commercial campaign settings updated.');
        redirect('messages/commercialCampaign');
    }

    public function saveReputationSnapshot(): void
    {
        require_permission('messages.campaigns');
        verify_csrf();
        try {
            $id = (new ReputationSnapshot())->save($_POST);
            audit_log('deliverability.snapshot.saved', 'reputation_snapshot', $id, ['domain' => $_POST['domain_name'] ?? null]);
            flash('success', 'Reputation snapshot saved.');
        } catch (Throwable $e) {
            flash('error', $e->getMessage());
        }
        redirect('messages/commercialCampaign');
    }

    public function saveAutomationRule(): void
    {
        require_permission('messages.campaigns');
        verify_csrf();
        try {
            $condition = [
                'channel' => trim((string)($_POST['condition_channel'] ?? '')),
                'contains' => trim((string)($_POST['condition_contains'] ?? '')),
                'from_domain' => trim((string)($_POST['condition_from_domain'] ?? '')),
            ];
            $action = [
                'move_folder' => trim((string)($_POST['action_move_folder'] ?? '')),
                'apply_label' => trim((string)($_POST['action_apply_label'] ?? '')),
                'assign_user_id' => (int)($_POST['action_assign_user_id'] ?? 0),
                'shared_mailbox_id' => (int)($_POST['action_shared_mailbox_id'] ?? 0),
                'focus_bucket' => trim((string)($_POST['action_focus_bucket'] ?? '')),
                'snooze_hours' => (int)($_POST['action_snooze_hours'] ?? 0),
                'pause_campaign' => !empty($_POST['action_pause_campaign']) ? 1 : 0,
                'force_queue' => !empty($_POST['action_force_queue']) ? 1 : 0,
                'set_mail_transport' => trim((string)($_POST['action_set_mail_transport'] ?? '')),
                'create_followup_task' => !empty($_POST['action_create_followup_task']) ? 1 : 0,
                'followup_title' => trim((string)($_POST['action_followup_title'] ?? '')),
                'sensitivity_level' => trim((string)($_POST['action_sensitivity_level'] ?? '')),
            ];
            $id = (new AutomationRule())->save([
                'name' => (string)($_POST['name'] ?? ''),
                'trigger_event' => (string)($_POST['trigger_event'] ?? 'message_received'),
                'condition_json' => $condition,
                'action_json' => $action,
                'is_active' => !empty($_POST['is_active']) ? 1 : 0,
                'sort_order' => (int)($_POST['sort_order'] ?? 100),
            ]);
            audit_log('automation_rule.saved', 'automation_rule', $id, ['trigger_event' => $_POST['trigger_event'] ?? null]);
            flash('success', 'Automation rule saved.');
        } catch (Throwable $e) {
            flash('error', $e->getMessage());
        }
        redirect('messages/commercialCampaign');
    }

    public function toggleAutomationRule(): void
    {
        require_permission('messages.campaigns');
        $id = (int)($_GET['id'] ?? 0);
        if ($id > 0) {
            (new AutomationRule())->toggle($id);
            flash('success', 'Automation rule updated.');
        }
        redirect('messages/commercialCampaign');
    }

    private function buildDeliverabilityAlerts(array $healthSummary, array $queueStats, array $suppressionSummary, array $providerStatus): array
    {
        $alerts = [];
        if (empty($providerStatus['smtp']['configured'])) {
            $alerts[] = ['severity' => 'error', 'title' => 'Mail delivery not configured', 'detail' => 'Email campaigns cannot reliably send until SendGrid API, SMTP, or mail-from settings are completed.'];
        }
        if (empty($providerStatus['twilio']['configured'])) {
            $alerts[] = ['severity' => 'warn', 'title' => 'Twilio not configured', 'detail' => 'SMS campaigns and test sends will be blocked until Twilio credentials are added.'];
        }
        if ((float)($healthSummary['bounce_rate'] ?? 0) >= 3.0) {
            $alerts[] = ['severity' => 'warn', 'title' => 'Bounce rate elevated', 'detail' => 'Recent bounce rate is ' . number_format((float)$healthSummary['bounce_rate'], 1) . '%. Review imported lists and suppress stale addresses.'];
        }
        if ((int)($queueStats['failed_jobs'] ?? 0) > 0) {
            $alerts[] = ['severity' => 'error', 'title' => 'Queue failures need attention', 'detail' => (int)$queueStats['failed_jobs'] . ' queue jobs are currently failed and should be retried.'];
        }
        if ((int)($suppressionSummary['complaint_total'] ?? 0) > 0) {
            $alerts[] = ['severity' => 'warn', 'title' => 'Complaint suppressions present', 'detail' => (int)$suppressionSummary['complaint_total'] . ' addresses are suppressed due to complaints.'];
        }
        if (!$alerts) {
            $alerts[] = ['severity' => 'ok', 'title' => 'System health is stable', 'detail' => 'Providers, queue, and recent deliverability signals look healthy.'];
        }
        return $alerts;
    }

    public function exportCsv(): void
    {
        require_permission('messages.export');
        $filters = [
            'q' => trim($_GET['q'] ?? ''),
            'channel' => $_GET['channel'] ?? 'all',
            'direction' => $_GET['direction'] ?? 'all',
            'status' => $_GET['status'] ?? 'all',
        ];
        $rows = (new Message())->exportRows($filters);
        header('Content-Type: text/csv');
        header('Content-Disposition: attachment; filename="messages_export.csv"');
        $out = fopen('php://output', 'w');
        fputcsv($out, ['ID','Thread','Channel','Direction','Subject','Body','From','To','Status','Created At']);
        foreach ($rows as $row) {
            fputcsv($out, [$row['id'], $row['thread_key'], $row['channel'], $row['direction'], $row['subject'], strip_tags((string)$row['body']), $row['from_address'], $row['to_address'], $row['status'], $row['created_at']]);
        }
        fclose($out);
        exit;
    }

    public function saveConversationState(): void
    {
        require_permission('messages.reply');
        verify_csrf();
        (new ConversationState())->save($_POST);
        audit_log('conversation.state.updated', 'conversation', null, ['conversation_key' => $_POST['conversation_key'] ?? null, 'status' => $_POST['status'] ?? 'open']);
        flash('success', 'Conversation workflow updated.');
        $this->redirectToInboxRoute((string)($_POST['channel'] ?? 'sms'), (string)($_POST['conversation_key'] ?? ''));
    }

    public function providerStatus(): void
    {
        require_permission('settings.view');
        $this->view('settings/provider_status', [
            'providerStatus' => provider_status_summary(),
            'testResults' => flash('provider_test_results') ?: [],
        ]);
    }

    public function unsubscribe(): void
    {
        $channel = $_GET['channel'] ?? 'email';
        $address = trim($_GET['address'] ?? '');
        if ($address) {
            (new Contact())->setUnsubscribed($channel, $address, true);
            (new Suppression())->add($channel, $address, 'unsubscribe');
        }
        echo '<!doctype html><html><head><meta charset="utf-8"><title>Unsubscribed</title><style>body{font-family:Arial,sans-serif;padding:40px;background:#f3f6fa;color:#1b2940}.card{max-width:640px;margin:50px auto;background:#fff;padding:32px;border-radius:14px;box-shadow:0 8px 30px rgba(0,0,0,.08)}</style></head><body><div class="card"><h1>Preference updated</h1><p>You have been unsubscribed from ' . e(strtoupper($channel)) . ' communications for <strong>' . e($address) . '</strong>.</p></div></body></html>';
        exit;
    }
}
