<?php

namespace App\Services;

use App\Models\Form;
use App\Models\Franchisee;
use Illuminate\Support\Facades\Storage;

class FormEmailService
{
    public function __construct(
        protected MicrosoftGraphMailService $graphMail,
        protected FormNotificationLanguageResolver $languageResolver,
        protected RegistrationPricingHelper $pricingHelper
    ) {
    }

    public function sendFromForm(
        Form $form,
        string $franchiseeId,
        string $language,
        array $submissionData,
        array $attachment = [],
        string $eventKey = 'form_submission',
        bool $isGlobalForm = false
    ): array {
        \Log::info('[DEBUG] FormEmailService sendFromForm started', [
            'form_id' => $form->id,
            'event_key' => $eventKey,
            'franchisee_id' => $franchiseeId
        ]);

        // Step 1: Load ALL form notifications WITH their translations
        // (subject/message live on FormNotificationTranslation, not FormNotification).
        $form->load('notifications.translations');

        if ($form->notifications->isEmpty()) {
            \Log::warning('FormEmailService: No notifications configured for form', [
                'form_id' => $form->id,
            ]);
            return ['sent' => false, 'reason' => 'No notifications configured for this form'];
        }

        // Filter notifications by event_key
        $relevantNotifications = $form->notifications->filter(function ($notification) use ($eventKey) {
            return $notification->event_key === $eventKey;
        });

        if ($relevantNotifications->isEmpty()) {
            \Log::warning('[DEBUG] FormEmailService: No notifications configured for event type', [
                'form_id' => $form->id,
                'event_key' => $eventKey,
            ]);
            return ['sent' => false, 'reason' => "No notifications configured for event: {$eventKey}"];
        }

        // Step 2: Build payload and flatten data for variable substitution
        $payloadData = $this->buildPayloadForTemplate($submissionData, $form, $franchiseeId, $language);
        \Log::info('[DEBUG] FormEmailService Payload prepared', ['keys' => array_keys($payloadData)]);
        $results = [];

        // Step 3: 🔥 LOOP THROUGH RELEVANT NOTIFICATIONS (filtered by event_key)
        $franchisee = !empty($franchiseeId) ? Franchisee::find($franchiseeId) : null;

        foreach ($relevantNotifications as $notification) {

            \Log::info('[DEBUG] FormEmailService: Processing notification', [
                'notification_id' => $notification->id,
                'name' => $notification->notification_name,
                'to_raw' => $notification->to_email,
                'event_key' => $notification->event_key
            ]);

            // 1. Determine TO email dynamically (translates {email} or {franchise_email} into actual emails)
            $rawToEmail = $notification->to_email ?: '{email}';
            $toEmail = $this->processMessageVariables($rawToEmail, $payloadData);

            // Fallback: If template variables weren't resolved, extract from payload
            if ($toEmail === '{email}' || $toEmail === '{parent_email}' || empty($toEmail)) {
                $toEmail = $this->extractEmailFromPayload($payloadData);
            }

            // Skip this specific notification if the To email is missing or invalid
            if (!$toEmail || !filter_var($toEmail, FILTER_VALIDATE_EMAIL)) {
                \Log::warning('FormEmailService: Invalid recipient email for notification', [
                    'notification_id' => $notification->id,
                    'toEmail' => $toEmail,
                ]);

                $results[] = [
                    'notification_id' => $notification->id,
                    'sent' => false,
                    'reason' => 'Recipient email missing or invalid',
                ];
                continue; // Move to the next notification
            }

            // 2. Resolve subject + body from the translation matching `$language`,
            //    with fallback to English, then to any translation we have.
            $recipientLang = $this->isParentRecipient($rawToEmail)
                ? $this->languageResolver->resolveParentLanguage($payloadData, $language)
                : $language;

            $translation = $this->languageResolver->resolveNotificationTranslation(
                $notification,
                $recipientLang,
                $isGlobalForm
            );

            if (!$translation) {
                \Log::warning('FormEmailService: No translation found for notification', [
                    'notification_id' => $notification->id,
                    'language' => $language,
                ]);
                $results[] = [
                    'notification_id' => $notification->id,
                    'sent' => false,
                    'reason' => 'No translation found for notification',
                ];
                continue;
            }

            // 3. Process subject and body for {variables}
            $subjectTemplate = $this->resolveSubmissionSubject(
                $translation->subject ?? '',
                $rawToEmail,
                $eventKey,
                $payloadData
            );
            $subject = $this->processMessageVariables($subjectTemplate, $payloadData);
            $messageContent = $this->processMessageVariables(
                $translation->message ?? '',
                $payloadData
            );

            // 3. Wrap in professional layout — parent-facing recipients get a
            //    warmer, congratulatory template; everyone else gets the
            //    standard admin/franchisee notification layout.
            $isParentRecipient = $this->isParentRecipient($rawToEmail);
            $isPaymentEvent = str_starts_with($eventKey, 'payment_');
            $layoutView = $isParentRecipient ? 'emails.parent_layout' : 'emails.layout';
            // Body source by recipient/event:
            // Honor the configured message body. Whatever variables the
            // franchisee inserts ({name}, {school_type}, {all_fields}, …) are
            // rendered exactly as written — so only the fields they add show up.
            // We only fall back to the auto all-fields card when a parent
            // non-payment body is effectively empty, so legacy/blank templates
            // still show the submission.
            $layoutBody = $messageContent;
            if ($isParentRecipient && !$isPaymentEvent && trim(strip_tags($messageContent)) === '') {
                $layoutBody = $this->buildAllFieldsHtml($submissionData, $payloadData);
            }

            $parentName = '';
            if (!empty($submissionData)) {
                $parentName = $submissionData['name'] ?? $submissionData['first_name'] ?? '';
                if (empty($parentName)) {
                    foreach ($submissionData as $key => $value) {
                        if (is_array($value)) {
                            $parentName = $value['name'] ?? $value['first_name'] ?? '';
                            if (!empty($parentName)) break;
                        }
                    }
                }
            }
            if (empty($parentName)) {
                $parentName = $payloadData['name'] ?? $payloadData['first_name'] ?? '';
            }

            try {
                $bodyHtml = view($layoutView, [
                    'bodyHtml' => $layoutBody,
                    'franchisee' => $franchisee,
                    'parentName' => $parentName,
                    'isPaymentEmail' => $isPaymentEvent && $isParentRecipient,
                    'submittedOn' => $this->resolveSubmittedAtDisplay($payloadData),
                ])->render();
            } catch (\Throwable $e) {
                \Log::error('FormEmailService: Failed to render email layout', [
                    'layout' => $layoutView,
                    'error' => $e->getMessage()
                ]);
                $bodyHtml = $layoutBody; // Fallback to raw content
            }


            $mailSender = $this->resolveMailSender($notification, $franchisee, $rawToEmail, $payloadData);

            // 5. Parse CC and BCC safely
            $cc = !empty($notification->cc) ? array_filter(array_map('trim', explode(',', $notification->cc))) : [];
            $bcc = !empty($notification->bcc) ? array_filter(array_map('trim', explode(',', $notification->bcc))) : [];

            // 6. Send via Microsoft Graph
            try {

                $graphPayload = $this->graphMail->buildPayload(
                    toEmail: $toEmail,
                    subject: $subject,
                    htmlBody: $bodyHtml,
                    replyTo: $mailSender['reply_to'],
                    fromDisplayName: $mailSender['from_name'] ?: null,
                    fromDisplayEmail: $mailSender['from_email'] ?: null,
                    cc: $cc,
                    bcc: $bcc,
                    attachments: $attachment
                );

                $this->graphMail->sendMail($graphPayload, $mailSender['sender_upn']);

                $results[] = [
                    'notification_id' => $notification->id,
                    'sent' => true,
                    'to' => $toEmail,
                    'sender' => $mailSender['sender_upn'],
                    'from_name' => $mailSender['from_name'] ?? null,
                    'from_email' => $mailSender['from_email'] ?? null,
                    'event_key' => $eventKey,
                    'recipient_role' => $this->resolveRecipientRole($rawToEmail),
                ];

                \Log::info("FormEmailService: Successfully sent email to {$toEmail}");

            } catch (\Throwable $e) {
                \Log::error('FormEmailService sendMail failed', [
                    'notification_id' => $notification->id,
                    'to' => $toEmail,
                    'exception' => get_class($e),
                    'message' => $e->getMessage(),
                ]);

                $results[] = [
                    'notification_id' => $notification->id,
                    'sent' => false,
                    'to' => $toEmail,
                    'reason' => 'Graph sendMail failed',
                    'error' => $e->getMessage(),
                    'event_key' => $eventKey,
                    'recipient_role' => $this->resolveRecipientRole($rawToEmail),
                ];
            }
        }

        \Log::info('[DEBUG] FormEmailService finished', ['results' => $results]);
        return $results; // Returns the status of ALL emails sent
    }

    /**
     * Detect whether a notification is being sent to the submitting parent
     * (vs. an admin / franchisee). We key off the raw `to_email` template
     * because it expresses *intent* ({parent_email}/{email} = parent) even
     * after variables have been substituted into a real address.
     */
    private function isParentRecipient(string $rawToEmail): bool
    {
        $needle = strtolower($rawToEmail);
        return str_contains($needle, '{parent_email}') || str_contains($needle, '{email}');
    }

    /**
     * @param  \App\Models\FormNotification|array<string, mixed>  $notificationOrTemplate
     * @param  array<string, mixed>  $payloadData
     * @return array{sender_upn: string, from_name: string, from_email: string, reply_to: ?string}
     */
    private function resolveMailSender(
        object|array $notificationOrTemplate,
        ?Franchisee $franchisee,
        string $rawToEmail,
        array $payloadData
    ): array {
        $graphDefault = (string) config('services.microsoft_graph.sender_upn');
        $isParent = $this->isParentRecipient($rawToEmail);

        $configuredFromName = trim((string) (
            is_array($notificationOrTemplate)
                ? ($notificationOrTemplate['from_name'] ?? '')
                : ($notificationOrTemplate->from_name ?? '')
        ));
        $configuredFromEmail = trim((string) (
            is_array($notificationOrTemplate)
                ? ($notificationOrTemplate['from_email'] ?? '')
                : ($notificationOrTemplate->from_email ?? '')
        ));

        if ($configuredFromName !== '') {
            $configuredFromName = $this->processMessageVariables($configuredFromName, $payloadData);
        }

        $franchiseName = trim((string) ($franchisee?->name ?? $payloadData['franchise_name'] ?? ''));
        $franchiseEmail = trim((string) ($franchisee?->email ?? $payloadData['franchise_email'] ?? ''));

        // IMPORTANT: Microsoft Graph will return ErrorSendAsDenied if we attempt to set
        // a "from" address that the sending mailbox doesn't have SendAs permissions for.
        // So we always send via the configured Graph mailbox, and use Reply-To to route
        // responses back to the franchise address.
        $safeReplyTo = ($franchiseEmail !== '' && filter_var($franchiseEmail, FILTER_VALIDATE_EMAIL))
            ? $franchiseEmail
            : null;

        if ($isParent) {
            return [
                'sender_upn' => $graphDefault,
                'from_name' => $configuredFromName !== '' ? $configuredFromName : $franchiseName,
                // Don't set Graph "from" unless it's the same as the sender mailbox.
                'from_email' => '',
                'reply_to' => $safeReplyTo,
            ];
        }

        // Admin/franchise notifications also always go out from the Graph sender mailbox
        // unless we later explicitly support multiple mailboxes with SendAs permissions.
        return [
            'sender_upn' => $graphDefault,
            'from_name' => $configuredFromName !== '' ? $configuredFromName : $franchiseName,
            'from_email' => '',
            'reply_to' => null,
        ];
    }

    /**
     * @param  array<int, array<string, mixed>>  $results
     * @param  array<string, mixed>  $payload
     * @return list<string>  parent|franchise
     */
    public function missingRecipientTypes(array $results, array $payload): array
    {
        $payload = $this->enrichPayloadWithContactFields($payload);
        $sentTo = [];
        foreach ($results as $row) {
            if (!empty($row['sent']) && !empty($row['to'])) {
                $sentTo[] = strtolower((string) $row['to']);
            }
        }

        $missing = [];

        $parentEmail = strtolower((string) ($payload['parent_email'] ?? $payload['email'] ?? ''));
        if ($parentEmail !== '' && filter_var($parentEmail, FILTER_VALIDATE_EMAIL) && !in_array($parentEmail, $sentTo, true)) {
            $missing[] = 'parent';
        }

        $franchiseEmail = strtolower((string) ($payload['franchise_email'] ?? ''));
        if ($franchiseEmail !== '' && filter_var($franchiseEmail, FILTER_VALIDATE_EMAIL) && !in_array($franchiseEmail, $sentTo, true)) {
            $missing[] = 'franchise';
        }

        return $missing;
    }

    /**
     * Payment emails are tracked by recipient role + event, not just address
     * (parent and franchise may share the same inbox during testing).
     *
     * @param  array<int, array<string, mixed>>  $results
     * @param  array<string, mixed>  $payload
     * @return list<string>  parent|franchise
     */
    public function missingPaymentRecipientTypes(array $results, array $payload, string $eventKey): array
    {
        $payload = $this->enrichPayloadWithContactFields($payload);
        $missing = [];

        $parentEmail = strtolower((string) ($payload['parent_email'] ?? $payload['email'] ?? ''));
        if ($parentEmail !== '' && filter_var($parentEmail, FILTER_VALIDATE_EMAIL)
            && !$this->wasPaymentEmailSentTo($results, $parentEmail, $eventKey)) {
            $missing[] = 'parent';
        }

        $franchiseEmail = strtolower((string) ($payload['franchise_email'] ?? ''));
        if ($franchiseEmail !== '' && filter_var($franchiseEmail, FILTER_VALIDATE_EMAIL)
            && !$this->wasPaymentEmailSentTo($results, $franchiseEmail, $eventKey)) {
            $missing[] = 'franchise';
        }

        return $missing;
    }

    /**
     * Whether a payment event email was actually delivered to a specific address.
     *
     * @param  array<int, array<string, mixed>>  $results
     */
    public function wasPaymentEmailSentTo(array $results, string $email, string $eventKey): bool
    {
        $email = strtolower(trim($email));
        if ($email === '') {
            return false;
        }

        foreach ($results as $row) {
            if (empty($row['sent'])) {
                continue;
            }
            if (($row['event_key'] ?? '') !== $eventKey) {
                continue;
            }
            if (strtolower((string) ($row['to'] ?? '')) === $email) {
                return true;
            }
        }

        return false;
    }

    /**
     * Ensure email, parent_email, franchise_email, and franchise_name are populated.
     *
     * @param  array<string, mixed>  $payload
     * @return array<string, mixed>
     */
    public function enrichPayloadWithContactFields(array $payload, ?string $franchiseeId = null): array
    {
        $extractedEmail = $this->extractEmailFromPayload($payload);
        if ($extractedEmail) {
            $payload['email'] = $payload['email'] ?? $extractedEmail;
            if (empty($payload['parent_email'])) {
                $payload['parent_email'] = $extractedEmail;
            }
        }

        $franchiseeId = $franchiseeId ?? (string) ($payload['franchisee_id'] ?? '');
        if ($franchiseeId !== '') {
            $franchise = Franchisee::find($franchiseeId);
            if ($franchise) {
                if (empty($payload['franchise_email'])) {
                    $payload['franchise_email'] = $franchise->email ?? '';
                }
                if (empty($payload['franchise_name'])) {
                    $payload['franchise_name'] = $franchise->name ?? '';
                }
            }
        }

        return $payload;
    }

    /**
     * @param  array<int, array<string, mixed>>  $results
     */
    private function wasRecipientRoleNotified(array $results, string $role, string $eventKey): bool
    {
        foreach ($results as $row) {
            if (empty($row['sent'])) {
                continue;
            }
            if (($row['event_key'] ?? '') !== $eventKey) {
                continue;
            }
            if (($row['recipient_role'] ?? '') === $role) {
                return true;
            }
        }

        return false;
    }

    private function resolveRecipientRole(string $rawToEmail): string
    {
        $needle = strtolower($rawToEmail);
        if ($this->isParentRecipient($rawToEmail)) {
            return 'parent';
        }
        if (str_contains($needle, '{franchise_email}') || str_contains($needle, 'franchise')) {
            return 'franchise';
        }

        return 'other';
    }

    /**
     * @return array<int, array<string, mixed>>
     */
    public function normalizeEmailResults(mixed $results): array
    {
        if (!is_array($results)) {
            return [];
        }

        if (isset($results['sent']) || isset($results['reason'])) {
            return [$results];
        }

        return array_values(array_filter($results, 'is_array'));
    }

  /**
     * Pick a customer- or admin-appropriate subject for form submission emails.
     *
     * @param  array<string, mixed>  $payloadData
     */
    private function resolveSubmissionSubject(
        string $configuredSubject,
        string $rawToEmail,
        string $eventKey,
        array $payloadData
    ): string {
        if ($eventKey !== 'form_submission') {
            return $configuredSubject !== '' ? $configuredSubject : 'Notification';
        }

        $isParent = $this->isParentRecipient($rawToEmail);

        if ($isParent) {
            if ($configuredSubject !== '' && !$this->isGenericAdminSubmissionSubject($configuredSubject)) {
                return $configuredSubject;
            }

            return $this->defaultParentSubmissionSubject($payloadData);
        }

        if ($configuredSubject !== '') {
            return $configuredSubject;
        }

        $name = trim((string) ($payloadData['name'] ?? 'New submission'));

        return "New form submission — {$name}";
    }

    private function isGenericAdminSubmissionSubject(string $subject): bool
    {
        $normalized = strtolower(trim($subject));

        return $normalized === ''
            || $normalized === 'new form submission'
            || str_contains($normalized, 'a new form submission has been received');
    }

    /**
     * @param  array<string, mixed>  $payloadData
     */
    private function defaultParentSubmissionSubject(array $payloadData): string
    {
        $program = trim((string) ($payloadData['program_name'] ?? ''));
        $formName = trim((string) ($payloadData['form_name'] ?? ''));

        if ($program !== '' && $program !== 'your program') {
            return "Thank you — we received your {$program} submission";
        }

        if ($formName !== '') {
            return "Thank you — we received your {$formName} submission";
        }

        return 'Thank you — we received your submission';
    }

    /** @deprecated Use sendFromNotificationTemplates */
    public function sendDefaultPaymentNotifications(
        string $franchiseeId,
        string $language,
        array $submissionPayload,
        string $eventKey,
        array $templates,
        array $attachments = []
    ): array {
        return $this->sendFromNotificationTemplates(
            $franchiseeId,
            $language,
            $submissionPayload,
            $eventKey,
            $templates,
            $attachments,
            null
        );
    }

    /**
     * Helper to find the email address in a potentially stepped/nested form submission
     */
    private function extractEmailFromPayload(array $payloadData): ?string
    {
        if (!empty($payloadData['email']) && filter_var($payloadData['email'], FILTER_VALIDATE_EMAIL)) {
            return $payloadData['email'];
        }

        foreach ($payloadData as $key => $val) {
            if (is_string($val) && preg_match('/(^|\.)email$/i', $key)) {
                if (filter_var($val, FILTER_VALIDATE_EMAIL)) {
                    return $val;
                }
            }
        }

        return null;
    }

    /**
     * Dynamically replaces {variables} with actual data from the form submission.
     */
    private function processMessageVariables(string $text, array $payloadData): string
    {
        if (str_contains($text, '{all_fields}')) {
            $allFieldsHtml = $this->buildAllFieldsHtml($payloadData['raw'] ?? [], $payloadData);
            $text = str_replace('{all_fields}', $allFieldsHtml, $text);
        }

        // {fields:a,b,c} → the SAME boxed-card design as {all_fields}, but
        // limited to the listed attribute keys. Lets franchisees show only
        // selected fields without losing the styled layout.
        if (str_contains($text, '{fields:')) {
            $text = preg_replace_callback('/\{fields:\s*([a-zA-Z0-9_.\- ,]+)\}/', function ($matches) use ($payloadData) {
                $keys = array_values(array_filter(
                    array_map('trim', explode(',', $matches[1])),
                    static fn ($k) => $k !== ''
                ));

                return $this->buildSelectedFieldsHtml($payloadData['raw'] ?? [], $payloadData, $keys);
            }, $text);
        }

        if (str_contains($text, '{submission_date}')) {
            $submittedAt = $payloadData['submission_date'] ?? $payloadData['paid_at'] ?? now()->format('Y-m-d H:i:s');
            $text = str_replace('{submission_date}', (string) $submittedAt, $text);
        }

        if (str_contains($text, '{payment_details}')) {
            $text = str_replace('{payment_details}', $this->buildPaymentDetailsHtml($payloadData), $text);
        }

        if (str_contains($text, '{payment_confirmation_body}')) {
            $enriched = $this->pricingHelper->enrich($payloadData);
            $text = str_replace('{payment_confirmation_body}', $this->buildEnterprisePaymentConfirmationHtml($enriched), $text);
        }

        return preg_replace_callback('/\{([a-zA-Z0-9_.-]+)\}/', function ($matches) use ($payloadData) {
            $key = $matches[1];
            $val = $this->getByPath($payloadData, $key);

            if ($val === null || is_array($val) || is_object($val)) {
                return '';
            }

            return (string) $val;
        }, $text);
    }

    /**
     * Builds a polished, professional HTML "submission card" for the {all_fields} variable.
     *
     * Output is fully inline-styled and uses <table>-based layout for maximum
     * email-client compatibility (Outlook, Gmail, Apple Mail, etc.).
     */
    private function buildAllFieldsHtml(array $submissionData, array $context = []): string
    {
        $rows = $this->collectDisplayableFields($submissionData);
        $rows = $this->pricingHelper->deduplicateDisplayFields($rows);

        if (empty($rows)) {
            return '';
        }

        $submittedAt = $this->resolveSubmittedAtDisplay($context);
        $flat = $this->flatten(is_array($submissionData) ? $submissionData : []);
        $enriched = $this->pricingHelper->enrich(array_merge($flat, $context));
        $isPaid = str_starts_with((string) ($context['payment_event'] ?? ''), 'payment_');

        // Check if this submission has booking/class registration details
        $isBooking = false;
        foreach (array_keys($rows) as $key) {
            $lower = strtolower($key);
            if (
                str_contains($lower, 'program') || 
                str_contains($lower, 'group') || 
                str_contains($lower, 'instructor') || 
                str_contains($lower, 'child') ||
                str_contains($lower, 'student')
            ) {
                $isBooking = true;
                break;
            }
        }

        if ($isBooking) {
            $rows = $this->pricingHelper->mergeCanonicalPaymentFields($rows, $enriched, $isPaid);

            return $this->buildBookingSummaryHtml($rows, $submittedAt);
        }

        return $this->buildStandardGridHtml($rows, $submittedAt);
    }

    /**
     * Render the SAME styled card as {all_fields}, but limited to a specific set
     * of attribute keys. Values and labels are pulled from the same source as
     * {all_fields} so the design (Contact / Student / Booking boxes) is identical
     * — only the chosen fields are shown. Powers the {fields:a,b,c} variable for
     * both form-submission and payment emails.
     *
     * @param  array<int|string, mixed>  $submissionData  raw submission (for flattening)
     * @param  array<string, mixed>  $context  enriched payload (program_name, amount, …)
     * @param  list<string>  $keys  attribute keys to include
     */
    private function buildSelectedFieldsHtml(array $submissionData, array $context, array $keys): string
    {
        if ($keys === []) {
            return '';
        }

        // Same clean field map {all_fields} uses, so labels/values match exactly.
        $allRows = $this->pricingHelper->deduplicateDisplayFields(
            $this->collectDisplayableFields($submissionData)
        );

        // Fallback lookup for derived fields not in the raw submission
        // (program_name, amount, payment_method, …).
        $flat = $this->flatten(is_array($submissionData) ? $submissionData : []);
        $lookup = array_merge($flat, $context);

        $rows = [];
        foreach ($keys as $key) {
            $shortKey = preg_replace('/^\d+\./', '', $key);
            $label = ucwords(str_replace(['.', '_'], ' ', $shortKey));
            if (stripos($label, 'Class Details ') === 0) {
                $label = substr($label, 14);
            }

            // Prefer the enriched payload (formatted money like "$100", derived
            // fields like program_name, contact fields), then fall back to the
            // cleaned {all_fields} value.
            $value = null;
            $resolved = $this->getByPath($lookup, $key);
            if ($resolved === null) {
                $resolved = $lookup[$key] ?? null;
            }
            if ($resolved !== null && !is_array($resolved)) {
                $candidate = is_string($resolved) ? strip_tags($resolved) : (string) $resolved;
                if (trim($candidate) !== '') {
                    $value = $candidate;
                }
            }

            if ($value === null) {
                foreach ($allRows as $rowLabel => $rowValue) {
                    if (strcasecmp($rowLabel, $label) === 0) {
                        $value = $rowValue;
                        break;
                    }
                }
            }

            if ($value === null || trim((string) $value) === '') {
                continue;
            }

            $rows[$label] = $value;
        }

        if ($rows === []) {
            return '';
        }

        $submittedAt = $this->resolveSubmittedAtDisplay($context);

        // Mirror {all_fields}: boxed Contact/Student/Booking cards when the
        // selection includes program/child/student fields, else the grid.
        $isBooking = false;
        foreach (array_keys($rows) as $label) {
            $lower = strtolower($label);
            if (
                str_contains($lower, 'program') ||
                str_contains($lower, 'group') ||
                str_contains($lower, 'instructor') ||
                str_contains($lower, 'child') ||
                str_contains($lower, 'student')
            ) {
                $isBooking = true;
                break;
            }
        }

        return $isBooking
            ? $this->buildBookingSummaryHtml($rows, $submittedAt)
            : $this->buildStandardGridHtml($rows, $submittedAt);
    }

    /**
     * @param  array<string, mixed>  $payload
     */
    private function resolveSubmittedAtDisplay(array $payload): string
    {
        $raw = $payload['payment_date']
            ?? $payload['paid_at']
            ?? $payload['submission_date']
            ?? null;

        if ($raw) {
            try {
                return \Carbon\Carbon::parse((string) $raw)->format('M j, Y · g:i A');
            } catch (\Throwable) {
                return (string) $raw;
            }
        }

        return now()->format('M j, Y · g:i A');
    }

    /**
     * Builds a structured 3-card layout (Contact, Student, Booking/Payment)
     */
    private function buildBookingSummaryHtml(array $rows, string $submittedAt): string
    {
        // Categorize fields
        $contactFields = [];
        $studentFields = [];
        $classFields = [];
        $paymentFields = [];

        foreach ($rows as $label => $value) {
            $lowerLabel = strtolower($label);

            if (
                str_contains($lowerLabel, 'email') || 
                str_contains($lowerLabel, 'phone') || 
                str_contains($lowerLabel, 'mobile') || 
                str_contains($lowerLabel, 'contact') || 
                $lowerLabel === 'name' || 
                $lowerLabel === 'parent name' || 
                $lowerLabel === 'parent\'s name'
            ) {
                $contactFields[$label] = $value;
            } elseif (
                str_contains($lowerLabel, 'child') || 
                str_contains($lowerLabel, 'student') || 
                str_contains($lowerLabel, 'age') || 
                str_contains($lowerLabel, 'school') || 
                str_contains($lowerLabel, 'grade')
            ) {
                $studentFields[$label] = $value;
            } elseif (
                str_contains($lowerLabel, 'price') || 
                str_contains($lowerLabel, 'discount') || 
                str_contains($lowerLabel, 'payment') || 
                str_contains($lowerLabel, 'amount') || 
                str_contains($lowerLabel, 'fee')
            ) {
                $paymentFields[$label] = $value;
            } else {
                $classFields[$label] = $value;
            }
        }

        $html = '';

        // ---- CARD 1: CONTACT INFORMATION ----
        if (!empty($contactFields)) {
            $html .= '<table role="presentation" cellpadding="0" cellspacing="0" border="0" width="100%" style="background-color:#ffffff;border-radius:12px;border:1px solid #cbd5e1;box-shadow:0 4px 6px -1px rgba(0,0,0,0.05);margin-bottom:20px;overflow:hidden;font-family:Inter,Arial,sans-serif;">';
            $html .= '<tr><td style="background-color:#f8fafc;padding:12px 20px;border-bottom:1px solid #e2e8f0;font-size:12px;font-weight:700;color:#1e293b;letter-spacing:0.05em;text-transform:uppercase;">👤 Contact Information</td></tr>';
            $html .= '<tr><td style="padding:16px 20px;"><table role="presentation" cellpadding="0" cellspacing="0" border="0" width="100%">';
            
            $chunks = array_chunk($contactFields, 3, true);
            foreach ($chunks as $chunk) {
                $html .= '<tr>';
                $colIndex = 0;
                $numCols = count($chunk);
                foreach ($chunk as $label => $value) {
                    $isName = (strtolower($label) === 'name' || strtolower($label) === 'parent name');
                    $isEmail = str_contains(strtolower($label), 'email');
                    
                    if ($numCols === 3) {
                        $width = $isEmail ? '44%' : '28%';
                    } else {
                        $width = round(100 / $numCols, 2) . '%';
                    }
                    
                    $paddingRight = $colIndex < 2 ? 'padding-right:24px;' : '';
                    $html .= '<td class="card-col-stack" width="' . $width . '" valign="top" style="' . $paddingRight . 'padding-bottom:8px;">';
                    $html .= '<div style="font-size:11px;font-weight:700;color:#64748b;letter-spacing:0.05em;text-transform:uppercase;margin-bottom:4px;">' . htmlspecialchars($label) . '</div>';
                    
                    $html .= '<table role="presentation" cellpadding="0" cellspacing="0" border="0" width="100%"><tr>';
                    $html .= '<td valign="middle" style="font-size:14px;font-weight:400;color:#0f172a;line-height:1.4;word-break:break-all;">' . $this->formatFieldValue($label, $value) . '</td>';
                    if ($isName) {
                        $html .= '<td width="24" valign="middle" align="right" style="width:24px;padding-left:8px;">';
                        $html .= '<div style="width:20px;height:20px;border-radius:50%;background:#e2e8f0;text-align:center;line-height:20px;font-size:10px;color:#475569;">👤</div>';
                        $html .= '</td>';
                    }
                    $html .= '</tr></table>';
                    
                    $html .= '</td>';
                    $colIndex++;
                }
                for ($pad = $colIndex; $pad < 3; $pad++) {
                    $html .= '<td class="card-col-stack" width="28%">&nbsp;</td>';
                }
                $html .= '</tr>';
            }
            $html .= '</table></td></tr></table>';
        }

        // ---- CARD 2: STUDENT DETAILS ----
        if (!empty($studentFields)) {
            $html .= '<table role="presentation" cellpadding="0" cellspacing="0" border="0" width="100%" style="background-color:#ffffff;border-radius:12px;border:1px solid #cbd5e1;box-shadow:0 4px 6px -1px rgba(0,0,0,0.05);margin-bottom:20px;overflow:hidden;font-family:Inter,Arial,sans-serif;">';
            $html .= '<tr><td style="background-color:#f8fafc;padding:12px 20px;border-bottom:1px solid #e2e8f0;font-size:12px;font-weight:700;color:#1e293b;letter-spacing:0.05em;text-transform:uppercase;">🎓 Student Details</td></tr>';
            $html .= '<tr><td style="padding:16px 20px;"><table role="presentation" cellpadding="0" cellspacing="0" border="0" width="100%">';
            
            $chunks = array_chunk($studentFields, 3, true);
            foreach ($chunks as $chunk) {
                $html .= '<tr>';
                $colIndex = 0;
                foreach ($chunk as $label => $value) {
                    $paddingRight = $colIndex < 2 ? 'padding-right:24px;' : '';
                    $html .= '<td class="card-col-stack" width="33.33%" valign="top" style="' . $paddingRight . 'padding-bottom:8px;">';
                    $html .= '<div style="font-size:11px;font-weight:700;color:#64748b;letter-spacing:0.05em;text-transform:uppercase;margin-bottom:4px;">' . htmlspecialchars($label) . '</div>';
                    $html .= '<div style="font-size:14px;font-weight:400;color:#0f172a;line-height:1.4;word-break:break-all;">' . $this->formatFieldValue($label, $value) . '</div>';
                    $html .= '</td>';
                    $colIndex++;
                }
                for ($pad = $colIndex; $pad < 3; $pad++) {
                    $html .= '<td class="card-col-stack" width="33.33%">&nbsp;</td>';
                }
                $html .= '</tr>';
            }
            $html .= '</table></td></tr></table>';
        }

        // ---- CARD 3: BOOKING & PROGRAM ----
        if (!empty($classFields) || !empty($paymentFields)) {
            $html .= '<table role="presentation" cellpadding="0" cellspacing="0" border="0" width="100%" style="background-color:#ffffff;border-radius:12px;border:1px solid #cbd5e1;box-shadow:0 4px 6px -1px rgba(0,0,0,0.05);margin-bottom:20px;overflow:hidden;font-family:Inter,Arial,sans-serif;">';
            $html .= '<tr><td style="background:linear-gradient(135deg, #0ea5e9, #0284c7);padding:14px 20px;font-size:13px;font-weight:700;color:#ffffff;letter-spacing:0.05em;text-transform:uppercase;">🤖 Booking &amp; Program Details</td></tr>';
            $html .= '<tr><td style="padding:20px;">';

            if (!empty($classFields)) {
                $html .= '<div style="font-size:12px;font-weight:700;color:#0ea5e9;border-bottom:1px solid #f1f5f9;padding-bottom:4px;margin-bottom:14px;text-transform:uppercase;letter-spacing:0.04em;">Class Details</div>';
                $html .= '<table role="presentation" cellpadding="0" cellspacing="0" border="0" width="100%" style="margin-bottom:10px;">';
                
                $startTime = null;
                $endTime = null;
                $days = null;
                $classFieldsCleaned = [];
                foreach ($classFields as $label => $value) {
                    $l = strtolower($label);
                    if ($l === 'start time') {
                        $startTime = $value;
                    } elseif ($l === 'end time') {
                        $endTime = $value;
                    } elseif ($l === 'days' || $l === 'day 0') {
                        $days = $value;
                    } else {
                        $classFieldsCleaned[$label] = $value;
                    }
                }

                if ($days || $startTime || $endTime) {
                    $timeVal = '';
                    if ($startTime && $endTime) {
                        $timeVal = $startTime . ' - ' . $endTime;
                    } else {
                        $timeVal = $startTime ?: $endTime ?: '';
                    }

                    $daysAndTimes = '';
                    if ($days && $timeVal) {
                        $daysAndTimes = $days . ' · ' . $timeVal;
                    } else {
                        $daysAndTimes = $days ?: $timeVal;
                    }

                    $classFieldsCleaned['Days & Times'] = $daysAndTimes;
                }

                $chunks = array_chunk($classFieldsCleaned, 3, true);
                foreach ($chunks as $chunk) {
                    $html .= '<tr>';
                    $colIndex = 0;
                    foreach ($chunk as $label => $value) {
                        $paddingRight = $colIndex < 2 ? 'padding-right:24px;' : '';
                        $html .= '<td class="card-col-stack" width="33.33%" valign="top" style="' . $paddingRight . 'padding-bottom:12px;">';
                        $html .= '<div style="font-size:10px;font-weight:700;color:#64748b;letter-spacing:0.04em;text-transform:uppercase;margin-bottom:4px;">' . htmlspecialchars($label) . '</div>';
                        $html .= '<div style="font-size:13px;font-weight:400;color:#0f172a;line-height:1.4;">' . $this->formatFieldValue($label, $value) . '</div>';
                        $html .= '</td>';
                        $colIndex++;
                    }
                    for ($pad = $colIndex; $pad < 3; $pad++) {
                        $html .= '<td class="card-col-stack" width="33.33%">&nbsp;</td>';
                    }
                    $html .= '</tr>';
                }
                $html .= '</table>';
            }

            if (!empty($paymentFields)) {
                $html .= '<div style="font-size:12px;font-weight:700;color:#0ea5e9;border-bottom:1px solid #f1f5f9;padding-bottom:4px;margin-top:10px;margin-bottom:14px;text-transform:uppercase;letter-spacing:0.04em;">Payment Summary</div>';
                $html .= '<table role="presentation" cellpadding="0" cellspacing="0" border="0" width="100%">';
                
                $chunks = array_chunk($paymentFields, 3, true);
                foreach ($chunks as $chunk) {
                    $html .= '<tr>';
                    $colIndex = 0;
                    foreach ($chunk as $label => $value) {
                        $isFinalPrice = (str_contains(strtolower($label), 'final') || strtolower($label) === 'price');
                        $paddingRight = $colIndex < 2 ? 'padding-right:24px;' : '';
                        $html .= '<td class="card-col-stack" width="33.33%" valign="top" style="' . $paddingRight . 'padding-bottom:8px;">';
                        
                        if ($isFinalPrice) {
                            $html .= '<div style="font-size:10px;font-weight:700;color:#0ea5e9;letter-spacing:0.04em;text-transform:uppercase;margin-bottom:4px;">' . htmlspecialchars($label) . '</div>';
                            $html .= '<div style="font-size:16px;font-weight:400;color:#0284c7;line-height:1.4;">' . $this->formatFieldValue($label, $value) . '</div>';
                        } else {
                            $html .= '<div style="font-size:10px;font-weight:700;color:#64748b;letter-spacing:0.04em;text-transform:uppercase;margin-bottom:4px;">' . htmlspecialchars($label) . '</div>';
                            $html .= '<div style="font-size:13px;font-weight:400;color:#0f172a;line-height:1.4;">' . $this->formatFieldValue($label, $value) . '</div>';
                        }
                        
                        $html .= '</td>';
                        $colIndex++;
                    }
                    for ($pad = $colIndex; $pad < 3; $pad++) {
                        $html .= '<td class="card-col-stack" width="33.33%">&nbsp;</td>';
                    }
                    $html .= '</tr>';
                }
                $html .= '</table>';
            }

            $html .= '</td></tr></table>';
        }

        return $html;
    }

    /**
     * Builds the standard grid layout (fallback for non-booking emails)
     */
    private function buildStandardGridHtml(array $rows, string $submittedAt): string
    {
        // ---- Section title (text-only, no nested card chrome) --------------
        // A simple typographic header instead of a gradient strip — keeps the
        // outer email hero as the only strong brand element.
        $header = ''
            . '<tr>'
            . '<td style="padding:4px 0 14px 0;border-bottom:2px solid #0ea5e9;font-family:Inter,Arial,sans-serif;">'
            . '<table role="presentation" cellpadding="0" cellspacing="0" border="0" width="100%">'
            . '<tr>'
            . '<td style="vertical-align:middle;">'
            . '<div style="font-size:11px;font-weight:700;color:#0284c7;letter-spacing:0.14em;text-transform:uppercase;line-height:1.4;">Submission Details</div>'
            . '</td>'
            . '<td align="right" style="vertical-align:middle;font-size:12px;font-weight:500;color:#64748b;white-space:nowrap;">'
            . htmlspecialchars($submittedAt)
            . '</td>'
            . '</tr></table>'
            . '</td>'
            . '</tr>';

        // ---- Body rows: 3-column grid + full-width rows for long fields ----
        [$narrow, $wide] = $this->partitionFieldsByWidth($rows);

        $gridRowsHtml = '';
        $narrowChunks = array_chunk($narrow, 3, true);
        $totalNarrowRows = count($narrowChunks);
        $wideCount = count($wide);

        foreach ($narrowChunks as $rowIndex => $chunk) {
            // Suppress the bottom border on the final narrow row only if there
            // are no wide rows after it.
            $isLastNarrowRow = ($rowIndex + 1 === $totalNarrowRows);
            $bottomBorder = ($isLastNarrowRow && $wideCount === 0)
                ? ''
                : 'border-bottom:1px solid #e2e8f0;';

            $cellsHtml = '';
            $colIndex = 0;
            foreach ($chunk as $label => $value) {
                $rightBorder = $colIndex < 2 ? 'border-right:1px solid #e2e8f0;' : '';
                $cellsHtml .= ''
                    . '<td width="33%" valign="top" style="width:33.33%;padding:18px 20px;background:#ffffff;'
                    . $rightBorder . $bottomBorder
                    . 'font-family:Inter,Arial,sans-serif;">'
                    . '<div style="font-size:11px;font-weight:700;color:#475569;letter-spacing:0.05em;text-transform:uppercase;line-height:1.4;margin-bottom:6px;">'
                    . htmlspecialchars($label)
                    . '</div>'
                    . '<div style="font-size:14px;font-weight:400;color:#0f172a;line-height:1.5;word-break:break-word;">'
                    . $this->formatFieldValue($label, $value)
                    . '</div>'
                    . '</td>';
                $colIndex++;
            }

            // Pad incomplete final row with empty cells so columns stay aligned.
            for ($pad = $colIndex; $pad < 3; $pad++) {
                $rightBorder = $pad < 2 ? 'border-right:1px solid #e2e8f0;' : '';
                $cellsHtml .= '<td width="33%" style="width:33.33%;padding:18px 20px;background:#ffffff;'
                    . $rightBorder . $bottomBorder . '">&nbsp;</td>';
            }

            $gridRowsHtml .= '<tr>' . $cellsHtml . '</tr>';
        }

        // Long-text fields stretch across all 3 columns on their own row, with
        // an accented left border so they stand out as the "rich" content.
        $wideRowsHtml = '';
        $wideIndex = 0;
        foreach ($wide as $label => $value) {
            $wideIndex++;
            $isLastWide = $wideIndex === $wideCount;
            $bottomBorder = $isLastWide ? '' : 'border-bottom:1px solid #e2e8f0;';

            $wideRowsHtml .= ''
                . '<tr>'
                . '<td colspan="3" style="padding:18px 22px 20px;background:#f8fafc;'
                . $bottomBorder
                . 'border-left:3px solid #0ea5e9;font-family:Inter,Arial,sans-serif;">'
                . '<div style="font-size:11px;font-weight:700;color:#475569;letter-spacing:0.05em;text-transform:uppercase;line-height:1.4;margin-bottom:6px;">'
                . htmlspecialchars($label)
                . '</div>'
                . '<div style="font-size:14px;font-weight:400;color:#0f172a;line-height:1.6;word-break:break-word;white-space:pre-wrap;">'
                . $this->formatFieldValue($label, $value)
                . '</div>'
                . '</td>'
                . '</tr>';
        }

        $body = ''
            . '<tr>'
            . '<td style="padding:0;">'
            . '<table role="presentation" cellpadding="0" cellspacing="0" border="0" width="100%" '
            . 'style="border-collapse:collapse;table-layout:fixed;border-left:1px solid #e2e8f0;border-right:1px solid #e2e8f0;border-bottom:1px solid #e2e8f0;">'
            . $gridRowsHtml
            . $wideRowsHtml
            . '</table>'
            . '</td>'
            . '</tr>';

        // No outer card chrome — the section title + grid sit directly on
        // the email body, so we don't get a "card inside a card" effect.
        return ''
            . '<table role="presentation" cellpadding="0" cellspacing="0" border="0" width="100%" '
            . 'style="margin:24px 0 8px 0;border-collapse:separate;border-spacing:0;">'
            . $header
            . $body
            . '</table>';
    }

    /**
     * Reduce the flattened submission payload to a clean label => value map,
     * stripping internal fields, IDs, tokens, empty values, etc.
     */
    private function collectDisplayableFields(array $submissionData): array
    {
        $flatData = $this->flatten($submissionData);
        $cleanData = [];

        foreach ($flatData as $k => $v) {
            $lowerKey = strtolower($k);
            $shortKey = preg_replace('/^\d+\./', '', $lowerKey);

            if (
                $shortKey === 'step' ||
                str_starts_with($shortKey, 'step') ||
                str_ends_with($shortKey, '_raw') ||
                str_contains($shortKey, 'zoho') ||
                str_contains($shortKey, 'token') ||
                str_contains($shortKey, 'uuid') ||
                str_contains($shortKey, 'response') ||
                str_contains($shortKey, 'url') ||
                preg_match('/(_id|id)$/i', $shortKey) ||
                $v === null || $v === '' ||
                is_array($v)
            ) {
                continue;
            }

            $cleanKey = ucwords(str_replace(['.', '_'], ' ', $shortKey));

            // Remove "Class Details " prefix case-insensitively if present
            if (stripos($cleanKey, 'Class Details ') === 0) {
                $cleanKey = substr($cleanKey, 14);
            }

            // Rename Day 0 or other day fields to "Days"
            if (preg_match('/^Day\s*\d*$/i', trim($cleanKey))) {
                $cleanKey = 'Days';
            }

            $cleanValue = is_string($v) ? strip_tags($v) : (string) $v;

            // Format "per_lesson" values to "Per lesson"
            if (strtolower(trim($cleanValue)) === 'per_lesson') {
                $cleanValue = 'Per lesson';
            }

            if ($cleanKey === '' || $cleanValue === '') {
                continue;
            }

            // Last write wins — keeps the de-prefixed key (e.g. "Email" over "0.email")
            $cleanData[$cleanKey] = $cleanValue;
        }

        return $cleanData;
    }

    /**
     * Split fields into "narrow" (rendered in a 3-column grid) and "wide"
     * (full-width row) buckets so long values don't squash the grid.
     *
     * @param array<string,string> $fields
     * @return array{0: array<string,string>, 1: array<string,string>}
     */
    private function partitionFieldsByWidth(array $fields): array
    {
        $wideLabelKeywords = [
            'message',
            'comment',
            'description',
            'address',
            'note',
            'feedback',
            'enquiry',
            'inquiry',
            'reason',
            'details',
            'remark',
        ];

        $narrow = [];
        $wide = [];

        foreach ($fields as $label => $value) {
            $lowerLabel = strtolower($label);

            $isWide = false;
            foreach ($wideLabelKeywords as $kw) {
                if (str_contains($lowerLabel, $kw)) {
                    $isWide = true;
                    break;
                }
            }

            // Auto-wide if the value itself is long enough that it would
            // visually wrap into multiple lines in a 33% column.
            if (!$isWide && strlen((string) $value) > 60) {
                $isWide = true;
            }

            if ($isWide) {
                $wide[$label] = $value;
            } else {
                $narrow[$label] = $value;
            }
        }

        return [$narrow, $wide];
    }

    /**
     * Format a single value for display: turn emails into mailto: links,
     * phones into tel: links, and URLs into clickable links.
     */
    private function formatFieldValue(string $label, string $value): string
    {
        $lower = strtolower($label);
        $escaped = htmlspecialchars($value, ENT_QUOTES, 'UTF-8');
        $linkStyle = 'color:#0284c7;text-decoration:none;font-weight:400;';

        if (str_contains($lower, 'email') && filter_var($value, FILTER_VALIDATE_EMAIL)) {
            return '<a href="mailto:' . $escaped . '" style="' . $linkStyle . '">' . $escaped . '</a>';
        }

        if (str_contains($lower, 'phone') || str_contains($lower, 'mobile') || str_contains($lower, 'contact number')) {
            $tel = preg_replace('/[^0-9+]/', '', $value);
            if (strlen($tel) >= 6) {
                return '<a href="tel:' . htmlspecialchars($tel, ENT_QUOTES, 'UTF-8') . '" style="' . $linkStyle . '">' . $escaped . '</a>';
            }
        }

        if (filter_var($value, FILTER_VALIDATE_URL)) {
            return '<a href="' . $escaped . '" target="_blank" rel="noopener" style="' . $linkStyle . '">' . $escaped . '</a>';
        }

        return $escaped;
    }

    /**
     * Send default payment notification templates (called by PaymentNotificationService).
     *
     * @param  array<int, array<string, mixed>>  $templates
     * @param  array<int, array<string, mixed>>  $attachments
     */
    /**
     * @param  array<int|string, mixed>  $submissionData
     * @return array<string, mixed>
     */
    public function buildSubmissionPayload(
        array $submissionData,
        Form $form,
        string $franchiseeId,
        string $language
    ): array {
        return $this->buildPayloadForTemplate($submissionData, $form, $franchiseeId, $language);
    }

    /**
     * @param  array<int, array<string, mixed>>  $templates
     * @param  array<int, array<string, mixed>>  $attachments
     */
    /**
     * @param  array<string, mixed>  $submissionPayload
     * @param  array<int, array<string, mixed>>  $templates
     * @param  array<int, array<string, mixed>>  $attachments
     */
    public function sendFromNotificationTemplates(
        string $franchiseeId,
        string $franchiseLanguage,
        array $submissionPayload,
        string $eventKey,
        array $templates,
        array $attachments = [],
        ?string $parentLanguage = null
    ): array {
        if ($templates === []) {
            return ['sent' => false, 'reason' => "No default templates for event: {$eventKey}"];
        }

        $virtualForm = new Form([
            'id' => 'default-payment',
            'name' => 'Payment Notifications',
        ]);

        $parentLanguage ??= $this->languageResolver->resolveParentLanguage(
            $submissionPayload,
            $franchiseLanguage
        );

        $payloadData = $this->buildPayloadForTemplate(
            $submissionPayload,
            $virtualForm,
            $franchiseeId,
            $parentLanguage
        );
        $franchisee = !empty($franchiseeId) ? Franchisee::find($franchiseeId) : null;
        $defaultFrom = config('services.microsoft_graph.sender_upn');

        $results = [];

        foreach ($templates as $index => $template) {
            if (!is_array($template)) {
                continue;
            }

            $rawToEmail = (string) ($template['to_email'] ?? '{parent_email}');
            $isParentRecipient = $this->isParentRecipient($rawToEmail);
            $language = $isParentRecipient ? $parentLanguage : $franchiseLanguage;

            $payloadData = $this->buildPayloadForTemplate(
                $submissionPayload,
                $virtualForm,
                $franchiseeId,
                $language
            );

            $toEmail = $this->processMessageVariables($rawToEmail, $payloadData);

            if ($toEmail === '{email}' || $toEmail === '{parent_email}' || empty($toEmail)) {
                $toEmail = $this->extractEmailFromPayload($payloadData);
            }

            if (!$toEmail || !filter_var($toEmail, FILTER_VALIDATE_EMAIL)) {
                $results[] = [
                    'notification_id' => "default-{$index}",
                    'sent' => false,
                    'reason' => 'Recipient email missing or invalid',
                ];
                continue;
            }

            $subjectTemplate = $this->resolveSubmissionSubject(
                (string) ($template['subject'] ?? ''),
                $rawToEmail,
                $eventKey,
                $payloadData
            );
            $subject = $this->processMessageVariables($subjectTemplate, $payloadData);
            $messageContent = $this->processMessageVariables(
                (string) ($template['message'] ?? ''),
                $payloadData
            );

            $isPaymentEvent = str_starts_with($eventKey, 'payment_');
            $layoutView = $isParentRecipient ? 'emails.parent_layout' : 'emails.layout';
            // Honor the configured message body (see sendFromForm) — only the
            // variables present in the body are rendered. We fall back to the
            // auto all-fields card only when a parent non-payment body is empty.
            $layoutBody = $messageContent;
            if ($isParentRecipient && !$isPaymentEvent && trim(strip_tags($messageContent)) === '') {
                $layoutBody = $this->buildAllFieldsHtml($submissionPayload['raw'] ?? $submissionPayload, $payloadData);
            }

            $parentName = $payloadData['name'] ?? $payloadData['first_name'] ?? '';

            try {
                $bodyHtml = view($layoutView, [
                    'bodyHtml' => $layoutBody,
                    'franchisee' => $franchisee,
                    'parentName' => $parentName,
                    'isPaymentEmail' => $isPaymentEvent && $isParentRecipient,
                    'submittedOn' => $this->resolveSubmittedAtDisplay($payloadData),
                ])->render();
            } catch (\Throwable $e) {
                $bodyHtml = $layoutBody;
            }

            $mailSender = $this->resolveMailSender($template, $franchisee, $rawToEmail, $payloadData);
            // Send the invoice attachment to all recipients (parent + franchise admin).
            $templateAttachments = $attachments;

            try {
                $graphPayload = $this->graphMail->buildPayload(
                    toEmail: $toEmail,
                    subject: $subject,
                    htmlBody: $bodyHtml,
                    replyTo: $mailSender['reply_to'],
                    fromDisplayName: $mailSender['from_name'] ?: null,
                    fromDisplayEmail: $mailSender['from_email'] ?: null,
                    cc: [],
                    bcc: [],
                    attachments: $templateAttachments
                );

                $this->graphMail->sendMail($graphPayload, $mailSender['sender_upn']);

                \Log::info('FormEmailService: default template sent', [
                    'event_key' => $eventKey,
                    'to' => $toEmail,
                    'role' => $this->resolveRecipientRole($rawToEmail),
                    'language' => $language,
                ]);

                $results[] = [
                    'notification_id' => "default-{$index}",
                    'sent' => true,
                    'to' => $toEmail,
                    'sender' => $mailSender['sender_upn'],
                    'from_name' => $mailSender['from_name'] ?? null,
                    'from_email' => $mailSender['from_email'] ?? null,
                    'source' => 'default_template',
                    'event_key' => $eventKey,
                    'recipient_role' => $this->resolveRecipientRole($rawToEmail),
                ];
            } catch (\Throwable $e) {
                \Log::error('FormEmailService: default template send failed', [
                    'event_key' => $eventKey,
                    'to' => $toEmail,
                    'role' => $this->resolveRecipientRole($rawToEmail),
                    'error' => $e->getMessage(),
                ]);

                $results[] = [
                    'notification_id' => "default-{$index}",
                    'sent' => false,
                    'to' => $toEmail,
                    'reason' => 'Graph sendMail failed',
                    'error' => $e->getMessage(),
                    'event_key' => $eventKey,
                    'recipient_role' => $this->resolveRecipientRole($rawToEmail),
                ];
            }
        }

        return $results;
    }

    /**
     * HTML summary of payment fields for {payment_details} and payment emails.
     *
     * @param  array<string, mixed>  $payload
     */
    public function buildPaymentDetailsHtml(array $payload): string
    {
        $payload = $this->pricingHelper->enrich($payload);

        return $this->buildEnterprisePaymentConfirmationHtml($payload);
    }

    /**
     * Enterprise-style payment confirmation body for parent emails.
     *
     * @param  array<string, mixed>  $payload
     */
    public function buildEnterprisePaymentConfirmationHtml(array $payload): string
    {
        $payload = $this->pricingHelper->enrich($payload);

        $e = static fn (string $value): string => htmlspecialchars(strip_tags((string) $value), ENT_QUOTES, 'UTF-8');

        $amountPaid = $e($payload['amount'] ?? $payload['amount_due_now'] ?? '');
        $totalClass = $e($payload['total_class_value'] ?? '');
        $pricePerLesson = $e($payload['price_per_lesson_display'] ?? $payload['price_per_lesson'] ?? '');
        $frequency = $e($payload['payment_frequency_label'] ?? '');
        $program = $e($payload['program_name'] ?? '');
        $group = $e($payload['group_name'] ?? '');
        $child = $e($payload['childs_name'] ?? '');
        $parentName = $e($payload['name'] ?? '');
        $paymentDate = $e($payload['payment_date'] ?? $payload['paid_at'] ?? '');
        $paymentMethod = $e($payload['payment_method'] ?? 'Card');
        $registrationId = $e($payload['registration_id'] ?? '');
        $installment = $e($payload['installment_label'] ?? '');
        $lessons = $e($payload['number_of_lessons'] ?? '');

        $installmentBadge = $installment !== ''
            ? '<span style="display:inline-block;margin-top:8px;padding:4px 12px;background:#e0f2fe;color:#0369a1;font-size:11px;font-weight:600;border-radius:999px;letter-spacing:0.04em;text-transform:uppercase;">'.$installment.'</span>'
            : '';

        return <<<HTML
<div style="font-family:'Poppins','Inter',Arial,sans-serif;color:#334155;">
  <div style="background:linear-gradient(135deg,#0ea5e9 0%,#0284c7 100%);border-radius:12px;padding:24px 28px;margin-bottom:24px;text-align:center;">
    <p style="margin:0 0 6px 0;font-size:12px;font-weight:600;color:rgba(255,255,255,0.85);letter-spacing:0.12em;text-transform:uppercase;">Amount paid today</p>
    <p style="margin:0;font-size:32px;font-weight:700;color:#ffffff;line-height:1.2;">{$amountPaid}</p>
    {$installmentBadge}
  </div>

  <table role="presentation" width="100%" cellspacing="0" cellpadding="0" style="margin-bottom:20px;border:1px solid #e2e8f0;border-radius:12px;overflow:hidden;">
    <tr>
      <td colspan="2" style="background:#f8fafc;padding:14px 20px;border-bottom:1px solid #e2e8f0;">
        <span style="font-size:13px;font-weight:700;color:#0f172a;letter-spacing:0.06em;text-transform:uppercase;">Payment summary</span>
      </td>
    </tr>
    <tr><td style="padding:12px 20px;color:#64748b;font-size:13px;border-bottom:1px solid #f1f5f9;">Total value of the class</td><td style="padding:12px 20px;font-size:14px;font-weight:600;color:#0f172a;border-bottom:1px solid #f1f5f9;">{$totalClass}</td></tr>
    <tr><td style="padding:12px 20px;color:#64748b;font-size:13px;border-bottom:1px solid #f1f5f9;">Price</td><td style="padding:12px 20px;font-size:14px;font-weight:600;color:#0f172a;border-bottom:1px solid #f1f5f9;">{$pricePerLesson}</td></tr>
    <tr><td style="padding:12px 20px;color:#64748b;font-size:13px;border-bottom:1px solid #f1f5f9;">Payment frequency</td><td style="padding:12px 20px;font-size:14px;font-weight:600;color:#0f172a;border-bottom:1px solid #f1f5f9;">{$frequency}</td></tr>
    <tr><td style="padding:12px 20px;color:#64748b;font-size:13px;border-bottom:1px solid #f1f5f9;">Payment date</td><td style="padding:12px 20px;font-size:14px;color:#0f172a;border-bottom:1px solid #f1f5f9;">{$paymentDate}</td></tr>
    <tr><td style="padding:12px 20px;color:#64748b;font-size:13px;">Payment method</td><td style="padding:12px 20px;font-size:14px;color:#0f172a;">{$paymentMethod}</td></tr>
  </table>

  <table role="presentation" width="100%" cellspacing="0" cellpadding="0" style="margin-bottom:20px;border:1px solid #e2e8f0;border-radius:12px;overflow:hidden;">
    <tr>
      <td colspan="2" style="background:#f8fafc;padding:14px 20px;border-bottom:1px solid #e2e8f0;">
        <span style="font-size:13px;font-weight:700;color:#0f172a;letter-spacing:0.06em;text-transform:uppercase;">Registration details</span>
      </td>
    </tr>
    <tr><td style="padding:12px 20px;color:#64748b;font-size:13px;width:42%;border-bottom:1px solid #f1f5f9;">Program</td><td style="padding:12px 20px;font-size:14px;font-weight:600;color:#0f172a;border-bottom:1px solid #f1f5f9;">{$program}</td></tr>
    <tr><td style="padding:12px 20px;color:#64748b;font-size:13px;border-bottom:1px solid #f1f5f9;">Class / group</td><td style="padding:12px 20px;font-size:14px;color:#0f172a;border-bottom:1px solid #f1f5f9;">{$group}</td></tr>
    <tr><td style="padding:12px 20px;color:#64748b;font-size:13px;border-bottom:1px solid #f1f5f9;">Student</td><td style="padding:12px 20px;font-size:14px;color:#0f172a;border-bottom:1px solid #f1f5f9;">{$child}</td></tr>
    <tr><td style="padding:12px 20px;color:#64748b;font-size:13px;border-bottom:1px solid #f1f5f9;">Number of lessons</td><td style="padding:12px 20px;font-size:14px;color:#0f172a;border-bottom:1px solid #f1f5f9;">{$lessons}</td></tr>
    <tr><td style="padding:12px 20px;color:#64748b;font-size:13px;">Registration ID</td><td style="padding:12px 20px;font-size:12px;color:#64748b;font-family:monospace;">{$registrationId}</td></tr>
  </table>

  <p style="margin:0;font-size:13px;line-height:1.6;color:#64748b;text-align:center;">
    A PDF receipt is attached to this email for your records.
  </p>
</div>
HTML;
    }

    private function buildPayloadForTemplate(
        array $submissionData,
        Form $form,
        string $franchiseeId,
        string $language
    ): array {
        $rawSubmission = $submissionData['raw'] ?? $submissionData;

        $formName = $form->name ?? null;
        if (!$formName && $form->relationLoaded('translations')) {
            $formName = $form->translations->firstWhere('language', $language)?->title
                ?? $form->translations->firstWhere('language', 'en')?->title;
        }
        if (!$formName) {
            $form->loadMissing('translations');
            $formName = $form->translations->firstWhere('language', $language)?->title
                ?? $form->translations->firstWhere('language', 'en')?->title;
        }

        $payload = [
            'raw' => $rawSubmission,
            'form_id' => $form->id ?? null,
            'form_name' => $formName,
            'franchisee_id' => $franchiseeId,
            'language' => $language,
        ];

        $flat = $this->flatten(is_array($rawSubmission) ? $rawSubmission : []);
        $payload = array_merge($payload, $flat, $submissionData);

        // Map '0.childs_name' to just 'childs_name'
        foreach ($flat as $key => $value) {
            if (preg_match('/^\d+\.(.+)$/', $key, $matches)) {
                $shortKey = $matches[1];
                $payload[$shortKey] = $payload[$shortKey] ?? $value;
            }
        }

        foreach (['program_name', 'group_name', 'childs_name'] as $field) {
            if (!empty($payload[$field])) {
                continue;
            }
            foreach ($flat as $key => $value) {
                if (!is_string($value) || $value === '') {
                    continue;
                }
                if ($key === $field || str_ends_with((string) $key, '.'.$field)) {
                    $payload[$field] = strip_tags(trim($value));
                    break;
                }
            }
        }

        // 🔥 Ensure `{email}` is always natively mapped in the payload
        if (!isset($payload['email'])) {
            $payload['email'] = $this->extractEmailFromPayload($payload);
        }

        $existingParentEmail = trim((string) ($submissionData['parent_email'] ?? ''));
        $resolvedEmail = trim((string) ($payload['email'] ?? $this->extractEmailFromPayload($payload) ?? ''));
        if ($resolvedEmail !== '') {
            $payload['email'] = $resolvedEmail;
        }
        $payload['parent_email'] = ($existingParentEmail !== '' && filter_var($existingParentEmail, FILTER_VALIDATE_EMAIL))
            ? $existingParentEmail
            : ($resolvedEmail !== '' ? $resolvedEmail : ($payload['parent_email'] ?? ''));

        $payload['franchise_email'] = '';
        $payload['franchise_name'] = '';
        if (!empty($franchiseeId)) {
            $franchise = Franchisee::find($franchiseeId);
            if ($franchise) {
                $payload['franchise_email'] = $franchise->email ?? '';
                $payload['franchise_name'] = $franchise->name ?? '';
            }
        }

        return $payload;
    }

    private function flatten(array $data, string $prefix = ''): array
    {
        $out = [];
        foreach ($data as $k => $v) {
            $key = $prefix === '' ? (string) $k : $prefix . '.' . $k;
            if (is_array($v)) {
                $out += $this->flatten($v, $key);
            } else {
                $out[$key] = $v;
            }
        }
        return $out;
    }

    private function getByPath(array $data, string $path)
    {
        if (array_key_exists($path, $data)) {
            return $data[$path];
        }

        $parts = explode('.', $path);
        $current = $data;

        foreach ($parts as $part) {
            if (!is_array($current))
                return null;

            if (ctype_digit($part)) {
                $idx = (int) $part;
                if (!array_key_exists($idx, $current))
                    return null;
                $current = $current[$idx];
            } else {
                if (!array_key_exists($part, $current))
                    return null;
                $current = $current[$part];
            }
        }

        return $current;
    }

}
