<?php
declare(strict_types=1);

namespace Help4BuilderSuite\Modules\Forms;

use Help4BuilderSuite\Contracts\ModuleContract;
use Help4BuilderSuite\Support\AdminRequest;
use Help4BuilderSuite\Support\IpTools;
use WP_REST_Request;
use WP_REST_Response;

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

// Forms use dedicated submission tables; admin filters are read-only.
// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching

final class Module implements ModuleContract
{
    private ?Captcha $captchaService = null;

    public function key(): string
    {
        return 'forms';
    }

    public function boot(): void
    {
        (new PrivacyWorkspace())->boot();
        add_action('init', [$this, 'ensureFormsCapabilities'], 5);
        add_action('init', [$this, 'ensureDefaultForms'], 25);
        add_action('wp_enqueue_scripts', [$this, 'enqueueAssets']);

        add_action('admin_menu', [$this, 'registerAdminPages']);
        add_action('admin_enqueue_scripts', [$this, 'enqueueAdminAssets']);
        add_action('admin_post_h4bs_save_form', [$this, 'handleSaveForm']);
        add_action('admin_post_h4bs_delete_form', [$this, 'handleDeleteForm']);
        add_action('admin_post_h4bs_restore_form', [$this, 'handleRestoreForm']);
        add_action('admin_post_h4bs_newsletter_add', [$this, 'handleNewsletterAdd']);
        add_action('admin_post_h4bs_newsletter_status', [$this, 'handleNewsletterStatus']);
        add_action('admin_post_h4bs_save_captcha_settings', [$this, 'handleSaveCaptchaSettings']);
        add_action('admin_post_h4bs_export_form_entries', [$this, 'handleExportEntries']);
        add_action('admin_post_h4bs_update_form_entry', [$this, 'handleUpdateEntry']);
        add_action('admin_post_h4bs_bulk_update_form_entries', [$this, 'handleBulkUpdateEntries']);

        add_action('admin_post_h4bs_form_submit', [$this, 'handleFormSubmit']);
        add_action('admin_post_nopriv_h4bs_form_submit', [$this, 'handleFormSubmit']);
        add_action('wp_ajax_h4bs_form_submit', [$this, 'handleAjaxFormSubmit']);
        add_action('wp_ajax_nopriv_h4bs_form_submit', [$this, 'handleAjaxFormSubmit']);

        add_action('rest_api_init', [$this, 'registerRestRoutes']);

        add_shortcode('h4_form', [$this, 'renderFormShortcode']);
        add_shortcode('h4_contact_form', [$this, 'renderContactShortcode']);
        add_shortcode('h4_newsletter_form', [$this, 'renderNewsletterShortcode']);
        add_shortcode('h4_quote_request', [$this, 'renderQuoteShortcode']);
        add_shortcode('h4_callback_request', [$this, 'renderCallbackShortcode']);
        add_shortcode('h4_booking', [$this, 'renderBookingShortcode']);
    }

    public function enqueueAdminAssets(string $hook): void
    {
        if (! str_contains($hook, 'h4bs-forms') && ! str_contains($hook, 'h4bs-form-entries') && ! str_contains($hook, 'h4bs-form-settings') && ! str_contains($hook, 'h4bs-form-privacy')) {
            return;
        }
        wp_enqueue_style('h4bs-forms-admin', H4BS_URL . 'assets/css/forms-admin.css', [], H4BS_VERSION);
        wp_enqueue_script('h4bs-forms-admin', H4BS_URL . 'assets/js/forms-admin.js', [], H4BS_VERSION, true);
    }

    public function enqueueAssets(): void
    {
        if (! $this->viewHasManagedForm()) {
            return;
        }

        wp_enqueue_style('dashicons');
        wp_enqueue_style(
            'h4bs-frontend',
            H4BS_URL . 'assets/css/frontend.css',
            ['dashicons'],
            H4BS_VERSION
        );
        wp_enqueue_script(
            'h4bs-forms',
            H4BS_URL . 'assets/js/forms.js',
            [],
            H4BS_VERSION,
            true
        );
        wp_register_script(
            'h4bs-turnstile',
            // phpcs:ignore PluginCheck.CodeAnalysis.EnqueuedResourceOffloading.OffloadedContent -- Turnstile is an optional configured external service documented in readme.txt.
            'https://challenges.cloudflare.com/turnstile/v0/api.js',
            [],
            H4BS_VERSION,
            [
                'strategy' => 'async',
                'in_footer' => true,
            ]
        );
        wp_localize_script(
            'h4bs-forms',
            'h4bsFormsData',
            [
                'ajaxUrl' => admin_url('admin-ajax.php'),
                'nonce'   => wp_create_nonce('h4bs_form_ajax'),
            ]
        );
    }

    public function registerRestRoutes(): void
    {
        register_rest_route(
            'h4bs/v1',
            '/forms/submit',
            [
                'methods'             => 'POST',
                'callback'            => [$this, 'restSubmit'],
                'permission_callback' => '__return_true',
            ]
        );
    }

    /** Give administrators every Forms capability by default. */
    public function ensureFormsCapabilities(): void
    {
        $role = get_role('administrator');
        if ($role) {
            foreach (['manage_help4_forms', 'view_help4_form_entries', 'manage_help4_form_entries', 'export_help4_form_entries', 'manage_help4_form_settings', 'access_help4_form_privacy', 'manage_help4_form_privacy', 'manage_help4_form_privacy_holds', 'export_help4_form_privacy'] as $capability) {
                if (! $role->has_cap($capability)) {
                    $role->add_cap($capability);
                }
            }
        }
    }

    public function restSubmit(WP_REST_Request $request): WP_REST_Response
    {
        $payload = $request->get_json_params();
        if (! is_array($payload)) {
            $payload = $request->get_params();
        }
        $result = $this->processSubmission($payload);
        return new WP_REST_Response($result, ! empty($result['ok']) ? 200 : $this->submissionStatus($result));
    }

    public function registerAdminPages(): void
    {
        add_submenu_page(
            'h4bs-settings',
            __('Forms', 'help4-builder-suite'),
            __('Forms', 'help4-builder-suite'),
            'manage_help4_forms',
            'h4bs-forms',
            [$this, 'renderFormsPage']
        );

        add_submenu_page(
            'h4bs-settings',
            __('Form Entries', 'help4-builder-suite'),
            __('Form Entries', 'help4-builder-suite'),
            'view_help4_form_entries',
            'h4bs-form-entries',
            [$this, 'renderEntriesPage']
        );

        add_submenu_page(
            'h4bs-settings',
            __('Newsletter', 'help4-builder-suite'),
            __('Newsletter', 'help4-builder-suite'),
            'manage_help4_form_settings',
            'h4bs-newsletter',
            [$this, 'renderNewsletterPage']
        );

        add_submenu_page(
            'h4bs-settings',
            __('Form Security', 'help4-builder-suite'),
            __('Form Security', 'help4-builder-suite'),
            'manage_help4_form_settings',
            'h4bs-form-settings',
            [$this, 'renderFormSettingsPage']
        );
    }

    public function renderFormsPage(): void
    {
        if (! $this->canManageForms()) {
            return;
        }

        global $wpdb;
        $showArchived = AdminRequest::queryKey('view') === 'archived';
        $forms = $wpdb->get_results($wpdb->prepare("SELECT * FROM %i WHERE status " . ($showArchived ? "= 'archived'" : "<> 'archived'") . ' ORDER BY updated_at DESC', $this->formsTable()), ARRAY_A); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- branch is a fixed internal SQL fragment.
        if (! is_array($forms)) {
            $forms = [];
        }
        $inventorySearch = sanitize_text_field((string) wp_unslash($_GET['forms_s'] ?? '')); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only staff filter.
        $inventoryStatus = sanitize_key((string) wp_unslash($_GET['forms_status'] ?? 'all')); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only staff filter.
        if (! in_array($inventoryStatus, ['all', 'active', 'inactive', 'archived'], true)) {
            $inventoryStatus = 'all';
        }
        $forms = array_values(array_filter($forms, static function (array $form) use ($inventorySearch, $inventoryStatus): bool {
            if ($inventoryStatus !== 'all' && (string) ($form['status'] ?? '') !== $inventoryStatus) {
                return false;
            }
            if ($inventorySearch === '') {
                return true;
            }
            $haystack = strtolower(implode(' ', [(string) ($form['name'] ?? ''), (string) ($form['type'] ?? ''), (string) ($form['status'] ?? ''), (string) ($form['slug'] ?? '')]));
            return str_contains($haystack, strtolower($inventorySearch));
        }));

        $entryCounts = [];
        $counts = $wpdb->get_results($wpdb->prepare('SELECT form_id, COUNT(*) AS cnt FROM %i GROUP BY form_id', $this->entriesTable()), ARRAY_A);
        if (is_array($counts)) {
            foreach ($counts as $row) {
                $entryCounts[(int) ($row['form_id'] ?? 0)] = (int) ($row['cnt'] ?? 0);
            }
        }

        $view = AdminRequest::queryKey('view');
        $isEditor = $view === 'editor';
        $editId = AdminRequest::queryAbsInt('edit');
        $editRow = null;
        if ($editId > 0) {
            $editRow = $wpdb->get_row($wpdb->prepare('SELECT * FROM %i WHERE id = %d LIMIT 1', $this->formsTable(), $editId), ARRAY_A);
        }
        ?>
        <div class="wrap h4bs-admin-wrap">
            <h1><?php echo $isEditor ? esc_html($editId > 0 ? __('Edit form', 'help4-builder-suite') : __('Create a form', 'help4-builder-suite')) : esc_html__('Forms', 'help4-builder-suite'); ?></h1>
            <p><?php echo $isEditor ? esc_html__('Build what visitors see, preview it, then save once.', 'help4-builder-suite') : esc_html__('Create forms, review messages, and check delivery without editing code.', 'help4-builder-suite'); ?></p>
            <?php $this->renderAdminNotice(); ?>
            <?php if ($isEditor) : ?>
                <p><a class="button" href="<?php echo esc_url(admin_url('admin.php?page=h4bs-forms')); ?>"><?php esc_html_e('Back to forms', 'help4-builder-suite'); ?></a></p>
                <?php if ($editId > 0 && ! is_array($editRow)) : ?>
                    <div class="notice notice-error inline" role="alert"><p><strong><?php esc_html_e('That form could not be found.', 'help4-builder-suite'); ?></strong> <?php esc_html_e('Nothing was created or changed. Return to Forms and choose an existing form.', 'help4-builder-suite'); ?></p></div>
                <?php else : ?>
                    <?php $this->renderFormEditor(is_array($editRow) ? $editRow : []); ?>
                <?php endif; ?>
            <?php else : ?>
            <p>
                <a class="button button-primary" href="<?php echo esc_url(admin_url('admin.php?page=h4bs-forms&view=editor')); ?>"><?php esc_html_e('Create a form', 'help4-builder-suite'); ?></a>
                <?php if ($this->canViewEntries()) : ?><a class="button" href="<?php echo esc_url(admin_url('admin.php?page=h4bs-form-entries')); ?>"><?php esc_html_e('View Submissions', 'help4-builder-suite'); ?></a><?php endif; ?>
                <?php if ($this->canManageFormSettings()) : ?><a class="button" href="<?php echo esc_url(admin_url('admin.php?page=h4bs-newsletter')); ?>"><?php esc_html_e('Newsletter', 'help4-builder-suite'); ?></a><?php endif; ?>
                <?php if ($this->canManageFormSettings()) : ?><a class="button" href="<?php echo esc_url(admin_url('admin.php?page=h4bs-form-settings')); ?>"><?php esc_html_e('Form Security', 'help4-builder-suite'); ?></a><?php endif; ?>
                <a class="button" href="<?php echo esc_url(admin_url('admin.php?page=h4bs-forms&view=' . ($showArchived ? 'list' : 'archived'))); ?>"><?php echo $showArchived ? esc_html__('Back to current forms', 'help4-builder-suite') : esc_html__('Archived forms', 'help4-builder-suite'); ?></a>
            </p>

            <form method="get" class="h4bs-form-inventory-filters"><input type="hidden" name="page" value="h4bs-forms"><input type="hidden" name="view" value="<?php echo $showArchived ? 'archived' : 'list'; ?>"><label><?php esc_html_e('Find a form', 'help4-builder-suite'); ?><input type="search" name="forms_s" value="<?php echo esc_attr($inventorySearch); ?>" placeholder="<?php esc_attr_e('Name or purpose', 'help4-builder-suite'); ?>"></label><label><?php esc_html_e('Availability', 'help4-builder-suite'); ?><select name="forms_status"><option value="all"><?php esc_html_e('All in this view', 'help4-builder-suite'); ?></option><option value="active" <?php selected($inventoryStatus, 'active'); ?>><?php esc_html_e('Open', 'help4-builder-suite'); ?></option><option value="inactive" <?php selected($inventoryStatus, 'inactive'); ?>><?php esc_html_e('Hidden', 'help4-builder-suite'); ?></option><option value="archived" <?php selected($inventoryStatus, 'archived'); ?>><?php esc_html_e('Archived', 'help4-builder-suite'); ?></option></select></label><button class="button" type="submit"><?php esc_html_e('Search forms', 'help4-builder-suite'); ?></button><?php if ($inventorySearch !== '' || $inventoryStatus !== 'all') : ?><a class="button" href="<?php echo esc_url(admin_url('admin.php?page=h4bs-forms&view=' . ($showArchived ? 'archived' : 'list'))); ?>"><?php esc_html_e('Clear', 'help4-builder-suite'); ?></a><?php endif; ?></form>

            <style>@media(min-width:783px){body .h4bs-admin-wrap table.h4bs-forms-table{display:table!important}body .h4bs-admin-wrap table.h4bs-forms-table>thead{display:table-header-group!important}body .h4bs-admin-wrap table.h4bs-forms-table>tbody{display:table-row-group!important}body .h4bs-admin-wrap table.h4bs-forms-table>thead>tr,body .h4bs-admin-wrap table.h4bs-forms-table>tbody>tr:not([hidden]){display:table-row!important}body .h4bs-admin-wrap table.h4bs-forms-table>thead>tr>th,body .h4bs-admin-wrap table.h4bs-forms-table>tbody>tr:not([hidden])>td{display:table-cell!important;width:auto!important;min-height:0!important}body .h4bs-admin-wrap table.h4bs-forms-table>tbody>tr:not([hidden])>td:before{display:none!important;content:none!important}}</style><table class="widefat striped h4bs-forms-table">
                <thead style="display:table-header-group!important">
                    <tr>
                        <th><?php esc_html_e('Form', 'help4-builder-suite'); ?></th>
                        <th><?php esc_html_e('Visitor availability', 'help4-builder-suite'); ?></th>
                        <th><?php esc_html_e('Submissions', 'help4-builder-suite'); ?></th>
                        <th><?php esc_html_e('Last updated', 'help4-builder-suite'); ?></th>
                        <th><?php esc_html_e('Actions', 'help4-builder-suite'); ?></th>
                    </tr>
                </thead>
                <tbody>
                    <?php if (! $forms) : ?>
                        <tr><td colspan="5"><?php esc_html_e('No forms found.', 'help4-builder-suite'); ?></td></tr>
                    <?php endif; ?>
                    <?php foreach ($forms as $form) : ?>
                        <?php
                        $formId = (int) ($form['id'] ?? 0);
                        /* translators: %s: Human form purpose, such as Contact. */
                        $formPurpose = sprintf(__('%s form', 'help4-builder-suite'), ucfirst((string) ($form['type'] ?? 'contact')));
                        /* translators: %d: Internal numeric form ID. */
                        $formIdText = sprintf(__('Internal form ID: %d', 'help4-builder-suite'), $formId);
                        $usageCount = $this->formUsageCount($formId, (string) ($form['slug'] ?? ''));
                        /* translators: %d: Count of known pages or Builder layouts containing this form. */
                        $usageText = sprintf(_n('%d known page or Builder reference will show the form unavailable. Stored entries remain attached and the form can be restored.', '%d known page or Builder references will show the form unavailable. Stored entries remain attached and the form can be restored.', $usageCount, 'help4-builder-suite'), $usageCount);
                        ?>
                        <tr>
                            <td><strong><?php echo esc_html((string) ($form['name'] ?? '')); ?></strong><br><span class="description"><?php echo esc_html($formPurpose); ?></span></td>
                            <td data-label="<?php esc_attr_e('Availability', 'help4-builder-suite'); ?>"><?php echo (string) ($form['status'] ?? 'active') === 'active' ? esc_html__('Open and accepting messages', 'help4-builder-suite') : ((string) ($form['status'] ?? '') === 'archived' ? esc_html__('Archived and unavailable', 'help4-builder-suite') : esc_html__('Hidden / not accepting messages', 'help4-builder-suite')); ?></td>
                            <td data-label="<?php esc_attr_e('Submissions', 'help4-builder-suite'); ?>"><?php $formEntryCount = (int) ($entryCounts[$formId] ?? 0); ?><?php if ($formEntryCount > 0 && $this->canViewEntries()) : ?><a href="<?php echo esc_url(admin_url('admin.php?page=h4bs-form-entries&form_id=' . $formId)); ?>"><?php /* translators: %d: Stored submission count. */ echo esc_html(sprintf(_n('%d submission', '%d submissions', $formEntryCount, 'help4-builder-suite'), $formEntryCount)); ?></a><?php elseif ($formEntryCount > 0) : ?><?php /* translators: %d: Stored submission count. */ echo esc_html(sprintf(_n('%d submission — ask an administrator to review it', '%d submissions — ask an administrator to review them', $formEntryCount, 'help4-builder-suite'), $formEntryCount)); ?><?php else : ?><?php esc_html_e('No submissions', 'help4-builder-suite'); ?><?php endif; ?></td>
                            <td data-label="<?php esc_attr_e('Updated', 'help4-builder-suite'); ?>"><?php echo esc_html(mysql2date(get_option('date_format') . ' ' . get_option('time_format'), (string) ($form['updated_at'] ?? ''))); ?></td>
                            <td data-label="<?php esc_attr_e('Actions', 'help4-builder-suite'); ?>">
                                <?php if ($showArchived) : ?><form method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>"><input type="hidden" name="action" value="h4bs_restore_form"><input type="hidden" name="form_id" value="<?php echo esc_attr((string) $formId); ?>"><?php wp_nonce_field('h4bs_restore_form_' . $formId); ?><button class="button" type="submit"><?php esc_html_e('Restore form', 'help4-builder-suite'); ?></button></form><?php else : ?><a class="button button-primary" href="<?php echo esc_url(admin_url('admin.php?page=h4bs-forms&view=editor&edit=' . $formId)); ?>"><?php esc_html_e('Edit form', 'help4-builder-suite'); ?></a><details class="h4bs-form-more"><summary><?php esc_html_e('More actions', 'help4-builder-suite'); ?></summary><div class="h4bs-form-more-panel"><div><strong><?php esc_html_e('Add this form to a page', 'help4-builder-suite'); ?></strong><p><code>[h4_form id="<?php echo esc_attr((string) $formId); ?>"]</code></p><button class="button" type="button" data-h4bs-copy-shortcode data-shortcode="[h4_form id=&quot;<?php echo esc_attr((string) $formId); ?>&quot;]"><?php esc_html_e('Copy embed code', 'help4-builder-suite'); ?></button><p class="description"><?php echo esc_html($formIdText); ?></p></div><hr><form method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>" data-h4bs-delete-form data-form-name="<?php echo esc_attr((string) ($form['name'] ?? '')); ?>"><input type="hidden" name="action" value="h4bs_delete_form"><input type="hidden" name="form_id" value="<?php echo esc_attr((string) $formId); ?>"><?php wp_nonce_field('h4bs_delete_form_' . $formId); ?><label><?php esc_html_e('Type the exact form name to archive', 'help4-builder-suite'); ?><input type="text" name="confirm_name" autocomplete="off" required></label><p class="description"><?php echo esc_html($usageText); ?></p><button class="button button-link-delete" type="submit"><?php esc_html_e('Archive form', 'help4-builder-suite'); ?></button></form></div></details><?php endif; ?>
                            </td>
                        </tr>
                    <?php endforeach; ?>
                </tbody>
            </table>

            <?php endif; ?>
        </div>
        <?php
    }

    public function renderEntriesPage(): void
    {
        if (! $this->canViewEntries()) {
            return;
        }

        global $wpdb;
        $formFilter = AdminRequest::queryAbsInt('form_id');
        $search = sanitize_text_field((string) wp_unslash($_GET['s'] ?? '')); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin filter.
        $dateFrom = $this->sanitizeEntryDate(sanitize_text_field((string) wp_unslash($_GET['date_from'] ?? ''))); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only staff filter.
        $dateTo = $this->sanitizeEntryDate(sanitize_text_field((string) wp_unslash($_GET['date_to'] ?? ''))); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only staff filter.
        $dateRangeError = $dateFrom !== '' && $dateTo !== '' && $dateFrom > $dateTo;
        $entryView = sanitize_key((string) wp_unslash($_GET['entry_view'] ?? 'current')); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only staff filter.
        $entryView = $entryView === 'archived' ? 'archived' : 'current';
        $statusFilter = sanitize_key((string) wp_unslash($_GET['entry_status'] ?? 'all')); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only staff filter.
        $statusFilter = in_array($statusFilter, ['all', 'new', 'in_progress', 'waiting', 'resolved'], true) ? $statusFilter : 'all';
        $assigneeRaw = sanitize_key((string) wp_unslash($_GET['assigned_user_id'] ?? 'all')); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only staff filter.
        $assigneeFilter = $assigneeRaw === 'unassigned' ? -1 : ($assigneeRaw === 'mine' ? -2 : absint($assigneeRaw));
        $perPage = 25;
        $requestedPage = max(1, absint(wp_unslash($_GET['paged'] ?? 1))); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only staff pagination.
        $matchingTotal = $dateRangeError ? 0 : $this->countEntries($formFilter, $search, $dateFrom, $dateTo, $entryView, $statusFilter, $assigneeFilter);
        $totalPages = max(1, (int) ceil($matchingTotal / $perPage));
        $currentPage = min($requestedPage, $totalPages);
        $entries = $dateRangeError ? [] : $this->queryEntries($formFilter, $search, $dateFrom, $dateTo, $perPage, $entryView, $statusFilter, $assigneeFilter, ($currentPage - 1) * $perPage);
        $queueCounts = $this->entryQueueCounts();
        $canManageEntries = $this->canManageEntries();
        $assigneeValue = $assigneeFilter === -1 ? 'unassigned' : ($assigneeFilter === -2 ? 'mine' : (string) $assigneeFilter);
        $filterArgs = array_filter([
            'page' => 'h4bs-form-entries',
            'entry_view' => $entryView,
            's' => $search,
            'form_id' => $formFilter,
            'date_from' => $dateFrom,
            'date_to' => $dateTo,
            'entry_status' => $statusFilter,
            'assigned_user_id' => $assigneeValue,
        ], static fn ($value): bool => $value !== '' && $value !== 0 && $value !== 'all');
        $paginationBase = str_replace('999999999', '%#%', add_query_arg(array_merge($filterArgs, ['paged' => 999999999]), admin_url('admin.php')));
        $firstVisible = $matchingTotal > 0 ? (($currentPage - 1) * $perPage) + 1 : 0;
        $lastVisible = min($matchingTotal, $currentPage * $perPage);
        /* translators: 1: first visible result, 2: last visible result, 3: total matching entries, 4: current page, 5: total pages. */
        $rangeMessage = sprintf(__('Showing %1$d–%2$d of %3$d matching entries, newest first. Page %4$d of %5$d.', 'help4-builder-suite'), $firstVisible, $lastVisible, $matchingTotal, $currentPage, $totalPages);
        $forms = $wpdb->get_results($wpdb->prepare('SELECT id, name, fields_json FROM %i ORDER BY name ASC', $this->formsTable()), ARRAY_A);
        $formsById = [];
        foreach (is_array($forms) ? $forms : [] as $formRow) {
            $formsById[(int) ($formRow['id'] ?? 0)] = $formRow;
        }
        ?>
        <div class="wrap h4bs-admin-wrap">
            <h1><?php esc_html_e('Form Entries', 'help4-builder-suite'); ?></h1>
            <?php $this->renderAdminNotice(); ?>
            <p><a class="button" href="<?php echo esc_url(admin_url('admin.php?page=h4bs-forms')); ?>"><?php esc_html_e('Back to Forms', 'help4-builder-suite'); ?></a> <a class="button" href="<?php echo esc_url(admin_url('admin.php?page=h4bs-settings')); ?>"><?php esc_html_e('Work Home', 'help4-builder-suite'); ?></a><?php if (current_user_can('access_help4_form_privacy')) : ?> <a class="button" href="<?php echo esc_url(admin_url('admin.php?page=h4bs-form-privacy')); ?>"><?php esc_html_e('Privacy & Retention', 'help4-builder-suite'); ?></a><?php endif; ?></p>
            <p><a class="button <?php echo $entryView === 'current' ? 'button-primary' : ''; ?>" href="<?php echo esc_url(admin_url('admin.php?page=h4bs-form-entries')); ?>"><?php esc_html_e('Current messages', 'help4-builder-suite'); ?></a> <a class="button <?php echo $entryView === 'archived' ? 'button-primary' : ''; ?>" href="<?php echo esc_url(admin_url('admin.php?page=h4bs-form-entries&entry_view=archived')); ?>"><?php esc_html_e('Archived messages', 'help4-builder-suite'); ?></a></p>
            <nav class="h4bs-entry-queues" aria-label="<?php esc_attr_e('Message work queues', 'help4-builder-suite'); ?>">
                <a href="<?php echo esc_url(admin_url('admin.php?page=h4bs-form-entries&entry_status=new')); ?>"><strong><?php echo esc_html((string) $queueCounts['new']); ?></strong><span><?php esc_html_e('New', 'help4-builder-suite'); ?></span></a>
                <a href="<?php echo esc_url(admin_url('admin.php?page=h4bs-form-entries&assigned_user_id=unassigned')); ?>"><strong><?php echo esc_html((string) $queueCounts['unassigned']); ?></strong><span><?php esc_html_e('Unassigned', 'help4-builder-suite'); ?></span></a>
                <a href="<?php echo esc_url(admin_url('admin.php?page=h4bs-form-entries&assigned_user_id=mine')); ?>"><strong><?php echo esc_html((string) $queueCounts['mine']); ?></strong><span><?php esc_html_e('Assigned to me', 'help4-builder-suite'); ?></span></a>
                <a href="<?php echo esc_url(admin_url('admin.php?page=h4bs-form-entries&entry_status=waiting')); ?>"><strong><?php echo esc_html((string) $queueCounts['waiting']); ?></strong><span><?php esc_html_e('Waiting for reply', 'help4-builder-suite'); ?></span></a>
                <a href="<?php echo esc_url(admin_url('admin.php?page=h4bs-form-entries&entry_status=resolved')); ?>"><strong><?php echo esc_html((string) $queueCounts['resolved']); ?></strong><span><?php esc_html_e('Resolved', 'help4-builder-suite'); ?></span></a>
                <a href="<?php echo esc_url(admin_url('admin.php?page=h4bs-form-entries&entry_view=archived')); ?>"><strong><?php echo esc_html((string) $queueCounts['archived']); ?></strong><span><?php esc_html_e('Archived', 'help4-builder-suite'); ?></span></a>
            </nav>
            <div class="h4bs-entry-status"><strong><?php esc_html_e('Storage and delivery', 'help4-builder-suite'); ?></strong><p><?php esc_html_e('Accepted messages are stored here. Notification email and autoresponders are separate delivery attempts; this inbox does not claim that an email reached its recipient.', 'help4-builder-suite'); ?></p></div>
            <?php if ($dateRangeError) : ?><div class="notice notice-error inline" role="alert"><p><strong><?php esc_html_e('The From date must be on or before the Through date.', 'help4-builder-suite'); ?></strong> <?php esc_html_e('No entries were loaded or exported. Correct the dates and apply the filters again.', 'help4-builder-suite'); ?></p></div><?php endif; ?>
            <form method="get" class="h4bs-entry-filters"><input type="hidden" name="page" value="h4bs-form-entries"><input type="hidden" name="entry_view" value="<?php echo esc_attr($entryView); ?>"><label><?php esc_html_e('Search matching entries', 'help4-builder-suite'); ?><input type="search" name="s" value="<?php echo esc_attr($search); ?>" placeholder="<?php esc_attr_e('Name, email, or answer', 'help4-builder-suite'); ?>"></label><label><?php esc_html_e('Form', 'help4-builder-suite'); ?><select name="form_id"><option value="0"><?php esc_html_e('All forms', 'help4-builder-suite'); ?></option><?php foreach ($formsById as $filterId => $filterForm) : ?><option value="<?php echo esc_attr((string) $filterId); ?>" <?php selected($formFilter, $filterId); ?>><?php echo esc_html((string) ($filterForm['name'] ?? 'Form')); ?></option><?php endforeach; ?></select></label><label><?php esc_html_e('Staff status', 'help4-builder-suite'); ?><select name="entry_status"><option value="all"><?php esc_html_e('All statuses', 'help4-builder-suite'); ?></option><?php foreach (['new' => __('New', 'help4-builder-suite'), 'in_progress' => __('In progress', 'help4-builder-suite'), 'waiting' => __('Waiting for reply', 'help4-builder-suite'), 'resolved' => __('Resolved', 'help4-builder-suite')] as $statusKey => $statusLabel) : ?><option value="<?php echo esc_attr($statusKey); ?>" <?php selected($statusFilter, $statusKey); ?>><?php echo esc_html($statusLabel); ?></option><?php endforeach; ?></select></label><label><?php esc_html_e('Assigned staff', 'help4-builder-suite'); ?><select name="assigned_user_id"><option value="all"><?php esc_html_e('All assignees', 'help4-builder-suite'); ?></option><option value="unassigned" <?php selected($assigneeFilter, -1); ?>><?php esc_html_e('Unassigned', 'help4-builder-suite'); ?></option><option value="mine" <?php selected($assigneeFilter, -2); ?>><?php esc_html_e('Assigned to me', 'help4-builder-suite'); ?></option><?php foreach (get_users(['capability' => 'view_help4_form_entries', 'fields' => ['ID', 'display_name'], 'number' => 100]) as $staffUser) : ?><option value="<?php echo esc_attr((string) $staffUser->ID); ?>" <?php selected($assigneeFilter, $staffUser->ID); ?>><?php echo esc_html($staffUser->display_name); ?></option><?php endforeach; ?></select></label><label><?php esc_html_e('From date', 'help4-builder-suite'); ?><input type="date" name="date_from" value="<?php echo esc_attr($dateFrom); ?>"></label><label><?php esc_html_e('Through date', 'help4-builder-suite'); ?><input type="date" name="date_to" value="<?php echo esc_attr($dateTo); ?>"></label><button class="button" type="submit"><?php esc_html_e('Apply filters', 'help4-builder-suite'); ?></button></form>
            <div class="h4bs-entry-limit"><p><?php echo esc_html($rangeMessage); ?></p><?php if ($this->canExportEntries() && ! $dateRangeError) : ?><form method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>"><input type="hidden" name="action" value="h4bs_export_form_entries"><input type="hidden" name="form_id" value="<?php echo esc_attr((string) $formFilter); ?>"><input type="hidden" name="s" value="<?php echo esc_attr($search); ?>"><input type="hidden" name="date_from" value="<?php echo esc_attr($dateFrom); ?>"><input type="hidden" name="date_to" value="<?php echo esc_attr($dateTo); ?>"><input type="hidden" name="entry_view" value="<?php echo esc_attr($entryView); ?>"><input type="hidden" name="entry_status" value="<?php echo esc_attr($statusFilter); ?>"><input type="hidden" name="assigned_user_id" value="<?php echo esc_attr($assigneeValue); ?>"><?php wp_nonce_field('h4bs_export_form_entries'); ?><button class="button" type="submit"><?php esc_html_e('Export matching CSV', 'help4-builder-suite'); ?></button></form><?php elseif (! $this->canExportEntries()) : ?><p class="description"><?php esc_html_e('CSV export is restricted to authorized site administrators.', 'help4-builder-suite'); ?></p><?php endif; ?></div>
            <?php if ($canManageEntries && $entries) : ?>
            <form id="h4bs-entry-bulk-form" class="h4bs-entry-bulk" method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>" data-h4bs-entry-bulk data-entry-view="<?php echo esc_attr($entryView); ?>">
                <input type="hidden" name="action" value="h4bs_bulk_update_form_entries"><input type="hidden" name="request_id" value="<?php echo esc_attr(wp_generate_uuid4()); ?>"><input type="hidden" name="return_view" value="<?php echo esc_attr($entryView); ?>"><input type="hidden" name="return_s" value="<?php echo esc_attr($search); ?>"><input type="hidden" name="return_form_id" value="<?php echo esc_attr((string) $formFilter); ?>"><input type="hidden" name="return_date_from" value="<?php echo esc_attr($dateFrom); ?>"><input type="hidden" name="return_date_to" value="<?php echo esc_attr($dateTo); ?>"><input type="hidden" name="return_status" value="<?php echo esc_attr($statusFilter); ?>"><input type="hidden" name="return_assignee" value="<?php echo esc_attr($assigneeValue); ?>"><input type="hidden" name="return_page" value="<?php echo esc_attr((string) $currentPage); ?>"><?php wp_nonce_field('h4bs_bulk_update_form_entries'); ?>
                <span class="h4bs-entry-selected" role="status" aria-live="polite" data-h4bs-entry-selected><?php esc_html_e('0 selected on this page', 'help4-builder-suite'); ?></span>
                <label><?php esc_html_e('Action for selected messages', 'help4-builder-suite'); ?><select name="bulk_action" required data-h4bs-bulk-action><option value=""><?php esc_html_e('Choose an action', 'help4-builder-suite'); ?></option><option value="status:new"><?php esc_html_e('Mark New', 'help4-builder-suite'); ?></option><option value="status:in_progress"><?php esc_html_e('Mark In progress', 'help4-builder-suite'); ?></option><option value="status:waiting"><?php esc_html_e('Mark Waiting for reply', 'help4-builder-suite'); ?></option><option value="status:resolved"><?php esc_html_e('Mark Resolved', 'help4-builder-suite'); ?></option><option value="assign"><?php esc_html_e('Assign to staff', 'help4-builder-suite'); ?></option><option value="<?php echo $entryView === 'archived' ? 'restore' : 'archive'; ?>"><?php echo $entryView === 'archived' ? esc_html__('Restore messages', 'help4-builder-suite') : esc_html__('Archive messages', 'help4-builder-suite'); ?></option></select></label>
                <label data-h4bs-bulk-assignee hidden><?php esc_html_e('Assign selected messages to', 'help4-builder-suite'); ?><select name="bulk_assignee"><option value="0"><?php esc_html_e('Unassigned', 'help4-builder-suite'); ?></option><?php foreach (get_users(['capability' => 'view_help4_form_entries', 'fields' => ['ID', 'display_name'], 'number' => 100]) as $staffUser) : ?><option value="<?php echo esc_attr((string) $staffUser->ID); ?>"><?php echo esc_html($staffUser->display_name); ?></option><?php endforeach; ?></select></label>
                <button class="button button-primary" type="submit" disabled data-h4bs-bulk-submit><?php esc_html_e('Apply to selected', 'help4-builder-suite'); ?></button>
                <p class="description" data-h4bs-bulk-consequence><?php esc_html_e('Select messages from this visible page only. No permanent deletion is available here.', 'help4-builder-suite'); ?></p>
                <div class="h4bs-bulk-confirmation" role="alertdialog" aria-modal="true" aria-labelledby="h4bs-bulk-confirm-title" aria-describedby="h4bs-bulk-confirm-copy" tabindex="-1" hidden data-h4bs-bulk-confirmation>
                    <h2 id="h4bs-bulk-confirm-title"><?php esc_html_e('Archive selected messages?', 'help4-builder-suite'); ?></h2>
                    <p id="h4bs-bulk-confirm-copy" data-h4bs-bulk-confirm-copy></p>
                    <div class="h4bs-bulk-confirm-actions"><button class="button" type="button" data-h4bs-bulk-cancel><?php esc_html_e('Keep messages', 'help4-builder-suite'); ?></button><button class="button button-primary" type="button" data-h4bs-bulk-confirm><?php esc_html_e('Archive selected messages', 'help4-builder-suite'); ?></button></div>
                </div>
            </form>
            <?php endif; ?>
            <table class="widefat striped h4bs-entries-table">
                <thead>
                    <tr>
                        <?php if ($canManageEntries) : ?><th class="check-column"><input type="checkbox" data-h4bs-entry-select-all aria-label="<?php esc_attr_e('Select all messages on this visible page', 'help4-builder-suite'); ?>"></th><?php endif; ?>
                        <th><?php esc_html_e('ID', 'help4-builder-suite'); ?></th>
                        <th><?php esc_html_e('Form', 'help4-builder-suite'); ?></th>
                        <th><?php esc_html_e('Email', 'help4-builder-suite'); ?></th>
                        <th><?php esc_html_e('Name', 'help4-builder-suite'); ?></th>
                        <th><?php esc_html_e('Spam', 'help4-builder-suite'); ?></th>
                        <th><?php esc_html_e('Status', 'help4-builder-suite'); ?></th>
                        <th><?php esc_html_e('Assigned', 'help4-builder-suite'); ?></th>
                        <th><?php esc_html_e('Created', 'help4-builder-suite'); ?></th>
                        <th><?php esc_html_e('Message', 'help4-builder-suite'); ?></th>
                    </tr>
                </thead>
                <tbody>
                    <?php if (! $entries) : ?>
                        <tr><td colspan="<?php echo $canManageEntries ? '10' : '9'; ?>"><div class="h4bs-empty-state"><h2><?php esc_html_e('No stored entries match this view', 'help4-builder-suite'); ?></h2><p><?php esc_html_e('This can mean the form has not received a submission yet, or the current search and form filter have no matches. Help4 stores accepted entries here; notification email is a separate delivery step.', 'help4-builder-suite'); ?></p><p><a class="button" href="<?php echo esc_url(admin_url('admin.php?page=h4bs-forms')); ?>"><?php esc_html_e('Check form and notification settings', 'help4-builder-suite'); ?></a></p><details class="h4bs-empty-help"><summary><?php esc_html_e('How to test safely', 'help4-builder-suite'); ?></summary><p><?php esc_html_e('Open the form editor and use Preview to check labels and required fields. Preview does not send email, call webhooks, store an entry, or run CAPTCHA. When recipients are confirmed, open the real page in a separate test window and submit one clearly labeled staff test.', 'help4-builder-suite'); ?></p></details></div></td></tr>
                    <?php endif; ?>
                    <?php foreach ($entries as $entry) : ?>
                        <?php
                        $entryFormId = (int) ($entry['form_id'] ?? 0);
                        $entryForm = is_array($formsById[$entryFormId] ?? null) ? $formsById[$entryFormId] : [];
                        $fieldRows = json_decode((string) ($entryForm['fields_json'] ?? '[]'), true);
                        $labelMap = [];
                        foreach (is_array($fieldRows) ? $fieldRows : [] as $fieldRow) {
                            if (is_array($fieldRow)) {
                                $labelMap[(string) ($fieldRow['key'] ?? '')] = (string) ($fieldRow['label'] ?? $fieldRow['key'] ?? 'Answer');
                            }
                        }
                        $payload = json_decode((string) ($entry['payload'] ?? '{}'), true);
                        $answers = is_array($payload['fields'] ?? null) ? $payload['fields'] : [];
                        $deliveryEvidence = is_array($payload['delivery'] ?? null) ? $payload['delivery'] : [];
                        $entryOps = $this->entryOperations($entry);
                        ?>
                        <tr>
                            <?php if ($canManageEntries) : ?><td class="check-column" data-label="<?php esc_attr_e('Select', 'help4-builder-suite'); ?>"><input type="checkbox" name="entry_ids[]" value="<?php echo esc_attr((string) ((int) $entry['id'])); ?>" form="h4bs-entry-bulk-form" data-h4bs-entry-select aria-label="<?php echo esc_attr(sprintf(/* translators: %d: Entry ID. */ __('Select message %d', 'help4-builder-suite'), (int) $entry['id'])); ?>"><input type="hidden" name="entry_revisions[<?php echo esc_attr((string) ((int) $entry['id'])); ?>]" value="<?php echo esc_attr((string) (($entry['operations_revision'] ?? '') ?: hash('sha256', (string) ($entry['payload'] ?? '')))); ?>" form="h4bs-entry-bulk-form"></td><?php endif; ?>
                            <td data-label="<?php esc_attr_e('ID', 'help4-builder-suite'); ?>"><?php echo esc_html((string) ((int) ($entry['id'] ?? 0))); ?></td>
                            <td data-label="<?php esc_attr_e('Form', 'help4-builder-suite'); ?>"><?php echo esc_html((string) ($entry['form_name'] ?: 'Form #' . (int) ($entry['form_id'] ?? 0))); ?></td>
                            <td data-label="<?php esc_attr_e('Email', 'help4-builder-suite'); ?>"><?php echo esc_html((string) ($entry['email'] ?? '')); ?></td>
                            <td data-label="<?php esc_attr_e('Name', 'help4-builder-suite'); ?>"><?php echo esc_html((string) ($entry['name'] ?? '')); ?></td>
                            <td data-label="<?php esc_attr_e('Spam', 'help4-builder-suite'); ?>"><?php echo ! empty($entry['is_spam']) ? esc_html__('Yes', 'help4-builder-suite') : esc_html__('No', 'help4-builder-suite'); ?></td>
                            <td data-label="<?php esc_attr_e('Status', 'help4-builder-suite'); ?>"><strong><?php echo esc_html($this->entryStatusLabel($entryOps['status'])); ?></strong></td>
                            <td data-label="<?php esc_attr_e('Assigned', 'help4-builder-suite'); ?>"><?php echo esc_html($this->entryAssigneeName($entryOps['assigned_user_id'])); ?></td>
                            <td data-label="<?php esc_attr_e('Created', 'help4-builder-suite'); ?>"><?php $createdAt = (string) ($entry['created_at'] ?? ''); echo esc_html($createdAt !== '' ? mysql2date(get_option('date_format') . ' ' . get_option('time_format'), $createdAt) : __('Date unavailable', 'help4-builder-suite')); ?></td>
                            <td>
                                <details class="h4bs-entry-detail">
                                    <summary><?php esc_html_e('View message', 'help4-builder-suite'); ?></summary>
                                    <p><strong><?php esc_html_e('Staff status:', 'help4-builder-suite'); ?></strong> <?php echo esc_html($this->entryStatusLabel($entryOps['status'])); ?><?php if ($entryOps['assigned_user_id'] > 0) : ?> · <?php /* translators: %s: Assigned staff display name. */ printf(esc_html__('Assigned to %s', 'help4-builder-suite'), esc_html($this->entryAssigneeName($entryOps['assigned_user_id']))); ?><?php endif; ?></p>
                                    <?php if ($entryOps['updated_at'] !== '') : ?>
                                        <p class="description"><strong><?php esc_html_e('Last staff update:', 'help4-builder-suite'); ?></strong> <?php echo esc_html(mysql2date(get_option('date_format') . ' ' . get_option('time_format'), $entryOps['updated_at'])); ?> · <?php echo esc_html($entryOps['updated_by'] > 0 ? $this->entryAssigneeName($entryOps['updated_by']) : __('Staff member unavailable', 'help4-builder-suite')); ?></p>
                                    <?php endif; ?>
                                    <dl><?php foreach ($answers as $answerKey => $answerValue) : ?><div><dt><?php echo esc_html((string) ($labelMap[(string) $answerKey] ?? ucwords(str_replace('_', ' ', (string) $answerKey)))); ?></dt><dd><?php echo esc_html(is_scalar($answerValue) ? (string) $answerValue : (string) wp_json_encode($answerValue)); ?></dd></div><?php endforeach; ?></dl>
                                    <h3><?php esc_html_e('Storage and delivery attempts', 'help4-builder-suite'); ?></h3>
                                    <p><?php esc_html_e('Stored successfully.', 'help4-builder-suite'); ?></p>
                                    <p class="description"><?php esc_html_e('Help4 does not automatically delete stored messages. Privacy & Retention can preview a site-defined policy, protect exceptions with holds, and create an exact authorized export; it does not apply deletion or anonymization.', 'help4-builder-suite'); ?></p>
                                    <?php if (! empty($entry['privacy_hold_at'])) : ?>
                                        <div class="notice notice-warning inline"><p><strong><?php esc_html_e('Privacy hold:', 'help4-builder-suite'); ?></strong> <?php echo esc_html((string) ($entry['privacy_hold_reason'] ?? '')); ?><br><span class="description"><?php echo esc_html(mysql2date(get_option('date_format') . ' ' . get_option('time_format'), (string) $entry['privacy_hold_at'])); ?> · <?php echo esc_html($this->entryAssigneeName(absint($entry['privacy_hold_by'] ?? 0))); ?></span></p></div>
                                    <?php endif; ?>
                                    <?php if (! $deliveryEvidence) : ?>
                                        <p class="description"><?php esc_html_e('Delivery attempt evidence was not recorded by this version.', 'help4-builder-suite'); ?></p>
                                    <?php else : ?>
                                        <dl>
                                        <?php foreach (['staff_notice' => __('Staff notice', 'help4-builder-suite'), 'autoresponder' => __('Visitor confirmation', 'help4-builder-suite'), 'webhook' => __('Webhook', 'help4-builder-suite')] as $deliveryKey => $deliveryLabel) : ?>
                                            <?php $attempt = is_array($deliveryEvidence[$deliveryKey] ?? null) ? $deliveryEvidence[$deliveryKey] : []; ?>
                                            <div><dt><?php echo esc_html($deliveryLabel); ?></dt><dd>
                                            <?php if (($attempt['state'] ?? '') !== 'attempted') : ?>
                                                <?php echo esc_html($this->deliveryStateLabel((string) ($attempt['state'] ?? ''))); ?>
                                            <?php elseif (array_key_exists('accepted_by_mail_transport', $attempt)) : ?>
                                                <?php echo ! empty($attempt['accepted_by_mail_transport']) ? esc_html__('Accepted by WordPress mail transport; delivery is not guaranteed.', 'help4-builder-suite') : esc_html__('WordPress mail transport rejected the attempt.', 'help4-builder-suite'); ?>
                                            <?php elseif (isset($attempt['http_code'])) : ?>
                                                <?php /* translators: %d: HTTP response status from the configured webhook. */ ?>
                                                <?php printf(esc_html__('Webhook returned HTTP %d.', 'help4-builder-suite'), (int) $attempt['http_code']); ?>
                                            <?php else : ?>
                                                <?php /* translators: %s: Sanitized WordPress HTTP error code. */ ?>
                                                <?php printf(esc_html__('Webhook attempt failed with code %s.', 'help4-builder-suite'), esc_html((string) ($attempt['error_code'] ?? 'unknown'))); ?>
                                            <?php endif; ?>
                                            </dd></div>
                                        <?php endforeach; ?>
                                        </dl>
                                    <?php endif; ?>
                                    <?php if ($entryOps['notes']) : ?><h3><?php esc_html_e('Staff notes', 'help4-builder-suite'); ?></h3><ul><?php foreach ($entryOps['notes'] as $note) : ?><li><?php echo esc_html((string) ($note['text'] ?? '')); ?> <span class="description"><?php echo esc_html($this->entryAssigneeName(absint($note['user_id'] ?? 0))); ?> · <?php echo esc_html((string) ($note['at'] ?? '')); ?></span></li><?php endforeach; ?></ul><?php endif; ?>
                                    <?php if ($this->canManageEntries()) : ?>
                                    <form method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>" class="h4bs-entry-operations">
                                        <input type="hidden" name="action" value="h4bs_update_form_entry"><input type="hidden" name="entry_id" value="<?php echo esc_attr((string) ((int) $entry['id'])); ?>"><input type="hidden" name="revision" value="<?php echo esc_attr((string) (($entry['operations_revision'] ?? '') ?: hash('sha256', (string) ($entry['payload'] ?? '')))); ?>"><input type="hidden" name="request_id" value="<?php echo esc_attr(wp_generate_uuid4()); ?>"><input type="hidden" name="return_view" value="<?php echo esc_attr($entryView); ?>"><input type="hidden" name="return_s" value="<?php echo esc_attr($search); ?>"><input type="hidden" name="return_form_id" value="<?php echo esc_attr((string) $formFilter); ?>"><input type="hidden" name="return_date_from" value="<?php echo esc_attr($dateFrom); ?>"><input type="hidden" name="return_date_to" value="<?php echo esc_attr($dateTo); ?>"><input type="hidden" name="return_status" value="<?php echo esc_attr($statusFilter); ?>"><input type="hidden" name="return_assignee" value="<?php echo esc_attr($assigneeValue); ?>"><input type="hidden" name="return_page" value="<?php echo esc_attr((string) $currentPage); ?>"><?php wp_nonce_field('h4bs_update_form_entry_' . (int) $entry['id']); ?>
                                        <label><?php esc_html_e('Status', 'help4-builder-suite'); ?><select name="entry_status"><?php foreach (['new' => __('New', 'help4-builder-suite'), 'in_progress' => __('In progress', 'help4-builder-suite'), 'waiting' => __('Waiting for reply', 'help4-builder-suite'), 'resolved' => __('Resolved', 'help4-builder-suite')] as $statusKey => $statusLabel) : ?><option value="<?php echo esc_attr($statusKey); ?>" <?php selected($entryOps['status'], $statusKey); ?>><?php echo esc_html($statusLabel); ?></option><?php endforeach; ?></select></label>
                                        <label><?php esc_html_e('Assign to', 'help4-builder-suite'); ?><select name="assigned_user_id"><option value="0"><?php esc_html_e('Unassigned', 'help4-builder-suite'); ?></option><?php foreach (get_users(['capability' => 'view_help4_form_entries', 'fields' => ['ID', 'display_name'], 'number' => 100]) as $staffUser) : ?><option value="<?php echo esc_attr((string) $staffUser->ID); ?>" <?php selected($entryOps['assigned_user_id'], $staffUser->ID); ?>><?php echo esc_html($staffUser->display_name); ?></option><?php endforeach; ?></select></label>
                                        <label><?php esc_html_e('Add an internal note', 'help4-builder-suite'); ?><textarea name="entry_note" rows="3" maxlength="2000"></textarea></label>
                                        <button class="button button-primary" name="entry_operation" value="save" type="submit"><?php esc_html_e('Save entry work', 'help4-builder-suite'); ?></button>
                                        <button class="button" name="entry_operation" value="<?php echo $entryView === 'archived' ? 'restore' : 'archive'; ?>" type="submit"><?php echo $entryView === 'archived' ? esc_html__('Restore message', 'help4-builder-suite') : esc_html__('Archive message', 'help4-builder-suite'); ?></button>
                                        <p class="description"><?php echo $entryView === 'archived' ? esc_html__('Restore returns this stored message to Current messages.', 'help4-builder-suite') : esc_html__('Archive hides this message from Current messages. The stored message and notes remain intact, and an authorized staff member can restore it.', 'help4-builder-suite'); ?></p>
                                    </form>
                                    <?php endif; ?>
                                    <?php if (current_user_can('manage_help4_form_privacy_holds')) : ?>
                                    <p><a class="button" href="<?php echo esc_url(admin_url('admin.php?page=h4bs-form-privacy&hold_entry_id=' . (int) $entry['id'] . '#h4bs-hold-editor')); ?>"><?php echo ! empty($entry['privacy_hold_at']) ? esc_html__('Review privacy hold', 'help4-builder-suite') : esc_html__('Protect with a privacy hold', 'help4-builder-suite'); ?></a></p>
                                    <p class="description"><?php esc_html_e('The guided Privacy & Retention workspace performs the revision check and records the hold change without exposing the message answers.', 'help4-builder-suite'); ?></p>
                                    <?php endif; ?>
                                </details>
                            </td>
                        </tr>
                    <?php endforeach; ?>
                </tbody>
            </table>
            <?php if ($totalPages > 1) : ?><nav class="h4bs-entry-pages" aria-label="<?php esc_attr_e('Message pages', 'help4-builder-suite'); ?>"><?php echo wp_kses_post(paginate_links(['base' => $paginationBase, 'format' => '', 'current' => $currentPage, 'total' => $totalPages, 'type' => 'list', 'prev_text' => __('Previous', 'help4-builder-suite'), 'next_text' => __('Next', 'help4-builder-suite')])); ?></nav><?php endif; ?>
        </div>
        <?php
    }

    public function renderNewsletterPage(): void
    {
        if (! $this->canManageFormSettings()) {
            return;
        }

        global $wpdb;
        $subscribers = $wpdb->get_results($wpdb->prepare('SELECT * FROM %i ORDER BY updated_at DESC LIMIT 300', $this->newsletterTable()), ARRAY_A);
        if (! is_array($subscribers)) {
            $subscribers = [];
        }
        ?>
        <div class="wrap h4bs-admin-wrap">
            <h1><?php esc_html_e('Newsletter Subscribers', 'help4-builder-suite'); ?></h1>
            <?php $this->renderAdminNotice(); ?>

            <form method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>" style="margin-bottom:1rem;">
                <?php wp_nonce_field('h4bs_newsletter_add', 'h4bs_newsletter_add_nonce'); ?>
                <input type="hidden" name="action" value="h4bs_newsletter_add">
                <input type="email" name="email" required placeholder="<?php esc_attr_e('email@example.com', 'help4-builder-suite'); ?>">
                <input type="text" name="first_name" placeholder="<?php esc_attr_e('First Name', 'help4-builder-suite'); ?>">
                <input type="text" name="last_name" placeholder="<?php esc_attr_e('Last Name', 'help4-builder-suite'); ?>">
                <input type="text" name="tags" placeholder="<?php esc_attr_e('Tags', 'help4-builder-suite'); ?>">
                <button type="submit" class="button button-primary"><?php esc_html_e('Add Subscriber', 'help4-builder-suite'); ?></button>
            </form>

            <table class="widefat striped">
                <thead>
                    <tr>
                        <th><?php esc_html_e('Email', 'help4-builder-suite'); ?></th>
                        <th><?php esc_html_e('Name', 'help4-builder-suite'); ?></th>
                        <th><?php esc_html_e('Status', 'help4-builder-suite'); ?></th>
                        <th><?php esc_html_e('Tags', 'help4-builder-suite'); ?></th>
                        <th><?php esc_html_e('Actions', 'help4-builder-suite'); ?></th>
                    </tr>
                </thead>
                <tbody>
                    <?php if (! $subscribers) : ?>
                        <tr><td colspan="5"><?php esc_html_e('No subscribers yet.', 'help4-builder-suite'); ?></td></tr>
                    <?php endif; ?>
                    <?php foreach ($subscribers as $row) : ?>
                        <?php
                        $id = (int) ($row['id'] ?? 0);
                        $status = sanitize_key((string) ($row['status'] ?? 'subscribed'));
                        $flip = $status === 'subscribed' ? 'unsubscribed' : 'subscribed';
                        $statusUrl = wp_nonce_url(
                            add_query_arg(
                                [
                                    'action' => 'h4bs_newsletter_status',
                                    'subscriber_id' => $id,
                                    'status' => $flip,
                                ],
                                admin_url('admin-post.php')
                            ),
                            'h4bs_newsletter_status_' . $id
                        );
                        ?>
                        <tr>
                            <td><?php echo esc_html((string) ($row['email'] ?? '')); ?></td>
                            <td><?php echo esc_html(trim(((string) ($row['first_name'] ?? '')) . ' ' . ((string) ($row['last_name'] ?? '')))); ?></td>
                            <td><?php echo esc_html((string) ($row['status'] ?? 'subscribed')); ?></td>
                            <td><?php echo esc_html((string) ($row['tags'] ?? '')); ?></td>
                            <td><a class="button button-small" href="<?php echo esc_url($statusUrl); ?>"><?php echo $flip === 'subscribed' ? esc_html__('Resubscribe', 'help4-builder-suite') : esc_html__('Unsubscribe', 'help4-builder-suite'); ?></a></td>
                        </tr>
                    <?php endforeach; ?>
                </tbody>
            </table>
        </div>
        <?php
    }

    public function renderFormSettingsPage(): void
    {
        if (! $this->canManageFormSettings()) {
            return;
        }
        ?>
        <div class="wrap h4bs-admin-wrap">
            <h1><?php esc_html_e('Form security settings', 'help4-builder-suite'); ?></h1>
            <p><?php esc_html_e('Configure the sitewide CAPTCHA provider. Everyday form questions and submissions are managed in their separate workspaces.', 'help4-builder-suite'); ?></p>
            <?php $this->renderAdminNotice(); ?>
            <?php $this->renderCaptchaSettings(); ?>
        </div>
        <?php
    }

    /**
     * @param array<string, mixed> $atts
     */
    public function renderFormShortcode(array $atts = []): string
    {
        $atts = shortcode_atts(
            [
                'id' => '',
                'slug' => '',
                'type' => 'contact',
                'ajax' => '1',
                'title' => '0',
                'class' => '',
                'redirect' => '',
            ],
            $atts,
            'h4_form'
        );

        $form = $this->resolveForm((string) $atts['id'], (string) $atts['slug'], (string) $atts['type']);
        if (! is_array($form)) {
            $message = esc_html__('This form is currently unavailable. Please use another published contact method.', 'help4-builder-suite');
            if ($this->canManageForms()) {
                $requested = (string) ($atts['id'] !== '' ? 'ID ' . $atts['id'] : 'slug ' . $atts['slug']);
                /* translators: %s: Requested public form ID or slug. */
                $diagnostic = sprintf(__('Administrators: review requested form %s.', 'help4-builder-suite'), $requested);
                $message .= ' <a href="' . esc_url(admin_url('admin.php?page=h4bs-forms')) . '">' . esc_html($diagnostic) . '</a>';
            }
            return '<div class="h4bs-form h4bs-form--missing">' . $message . '</div>';
        }
        return $this->renderFormMarkup($form, $atts);
    }

    /**
     * @param array<string, mixed> $atts
     */
    public function renderContactShortcode(array $atts = []): string
    {
        $atts['type'] = 'contact';
        return $this->renderFormShortcode($atts);
    }

    /**
     * @param array<string, mixed> $atts
     */
    public function renderNewsletterShortcode(array $atts = []): string
    {
        $atts['type'] = 'newsletter';
        return $this->renderFormShortcode($atts);
    }

    /**
     * @param array<string, mixed> $atts
     */
    public function renderQuoteShortcode(array $atts = []): string
    {
        $atts['type'] = 'quote';
        return $this->renderFormShortcode($atts);
    }

    /**
     * @param array<string, mixed> $atts
     */
    public function renderCallbackShortcode(array $atts = []): string
    {
        $atts['type'] = 'callback';
        return $this->renderFormShortcode($atts);
    }

    /**
     * @param array<string, mixed> $atts
     */
    public function renderBookingShortcode(array $atts = []): string
    {
        $atts['type'] = 'booking';
        return $this->renderFormShortcode($atts);
    }

    public function handleSaveForm(): void
    {
        if (! $this->canManageForms()) {
            $this->denyAccess();
        }
        check_admin_referer('h4bs_save_form', 'h4bs_form_nonce');

        global $wpdb;

        $id = absint(wp_unslash($_POST['id'] ?? 0));
        $name = sanitize_text_field((string) wp_unslash($_POST['name'] ?? ''));
        $slug = sanitize_title((string) wp_unslash($_POST['slug'] ?? ''));
        $type = sanitize_key((string) wp_unslash($_POST['type'] ?? 'contact'));
        $status = sanitize_key((string) wp_unslash($_POST['status'] ?? 'active'));
        $fieldsJson = sanitize_textarea_field((string) wp_unslash($_POST['fields_json'] ?? '[]'));
        $isAjax = ! empty($_POST['h4bs_visual_ajax']);
        $revision = sanitize_text_field((string) wp_unslash($_POST['revision'] ?? ''));
        $requestId = sanitize_key((string) wp_unslash($_POST['request_id'] ?? ''));
        $decodedFields = json_decode($fieldsJson, true);
        if (json_last_error() !== JSON_ERROR_NONE || ! is_array($decodedFields) || ! array_is_list($decodedFields) || array_filter($decodedFields, static fn ($field): bool => ! is_array($field)) !== []) {
            if ($isAjax) {
                wp_send_json_error(['message' => __('Field data is invalid. Nothing was saved; repair the raw data or restore a backup.', 'help4-builder-suite')], 422);
            }
            $this->adminRedirect('h4bs-forms', ['h4bs_notice' => 'invalid_field_data', 'view' => 'editor', 'edit' => $id]);
        }
        if (count($decodedFields) === 0 || count($decodedFields) > 60) {
            $message = count($decodedFields) === 0
                ? __('Add at least one question before saving. Nothing was saved.', 'help4-builder-suite')
                : __('This form has more than 60 questions. Nothing was truncated or saved; remove extra questions or split the form.', 'help4-builder-suite');
            if ($isAjax) {
                wp_send_json_error(['message' => $message], 422);
            }
            $this->adminRedirect('h4bs-forms', ['h4bs_notice' => 'invalid_field_count', 'view' => 'editor', 'edit' => $id]);
        }
        $fieldKeys = [];
        foreach (is_array($decodedFields) ? $decodedFields : [] as $decodedField) {
            $fieldKey = is_array($decodedField) ? (string) ($decodedField['key'] ?? '') : '';
            if ($fieldKey === '' || preg_match('/^[A-Za-z0-9_-]+$/', $fieldKey) !== 1 || in_array($fieldKey, $fieldKeys, true)) {
                if ($isAjax) {
                    wp_send_json_error(['message' => __('Every question needs a unique internal key using only letters, numbers, dashes, or underscores. Nothing was saved.', 'help4-builder-suite')], 422);
                }
                $this->adminRedirect('h4bs-forms', ['h4bs_notice' => 'invalid_field_keys', 'view' => 'editor', 'edit' => $id]);
            }
            $fieldKeys[] = $fieldKey;
            $fieldType = sanitize_key((string) ($decodedField['type'] ?? 'text'));
            $fieldDefault = trim((string) ($decodedField['default'] ?? ''));
            $defaultValid = $fieldDefault === ''
                || ($fieldType === 'number' && is_numeric($fieldDefault))
                || ($fieldType === 'date' && preg_match('/^\d{4}-\d{2}-\d{2}$/', $fieldDefault) === 1)
                || ($fieldType === 'time' && preg_match('/^([01]\d|2[0-3]):[0-5]\d$/', $fieldDefault) === 1)
                || ($fieldType === 'email' && is_email($fieldDefault) !== false)
                || ($fieldType === 'url' && wp_http_validate_url($fieldDefault) !== false)
                || ($fieldType === 'select' && in_array($fieldDefault, $this->formFieldOptions((string) ($decodedField['options'] ?? '')), true))
                || ! in_array($fieldType, ['number', 'date', 'time', 'email', 'url', 'select'], true);
            if (! $defaultValid) {
                if ($isAjax) {
                    wp_send_json_error(['message' => __('A default answer does not match its answer type or choices. Nothing was saved.', 'help4-builder-suite')], 422);
                }
                $this->adminRedirect('h4bs-forms', ['h4bs_notice' => 'invalid_field_defaults', 'view' => 'editor', 'edit' => $id]);
            }
        }

        if ($name === '') {
            if ($isAjax) {
                wp_send_json_error(['message' => __('Form name is required. Your other changes are still here.', 'help4-builder-suite')], 422);
            }
            $this->adminRedirect('h4bs-forms', ['h4bs_notice' => 'form_name_required']);
        }
        if ($slug === '') {
            $slug = sanitize_title($name);
        }
        if (! in_array($type, ['contact', 'newsletter', 'quote', 'callback', 'booking', 'custom'], true)) {
            $type = 'custom';
        }
        if (! in_array($status, ['active', 'inactive'], true)) {
            $status = 'active';
        }

        $captchaMode = sanitize_key((string) wp_unslash($_POST['captcha_mode'] ?? 'inherit'));
        $settings = [
            'button_label' => sanitize_text_field((string) wp_unslash($_POST['button_label'] ?? 'Submit')),
            'success_message' => sanitize_text_field((string) wp_unslash($_POST['success_message'] ?? 'Thanks! We received your request.')),
            'error_message' => sanitize_text_field((string) wp_unslash($_POST['error_message'] ?? 'Please check required fields.')),
            'notify_admin' => ! empty($_POST['notify_admin']) ? '1' : '0',
            'notify_email' => sanitize_email((string) wp_unslash($_POST['notify_email'] ?? '')),
            'send_autoresponder' => ! empty($_POST['send_autoresponder']) ? '1' : '0',
            'autoresponder_subject' => sanitize_text_field((string) wp_unslash($_POST['autoresponder_subject'] ?? 'Thanks for contacting us')),
            'autoresponder_body' => sanitize_textarea_field((string) wp_unslash($_POST['autoresponder_body'] ?? 'We received your message and will reply soon.')),
            'webhook_url' => esc_url_raw((string) wp_unslash($_POST['webhook_url'] ?? '')),
            'honeypot' => ! empty($_POST['honeypot']) ? '1' : '0',
            'captcha_mode' => $this->sanitizeCaptchaMode($captchaMode),
        ];

        $payload = [
            'slug' => $slug,
            'name' => $name,
            'type' => $type,
            'status' => $status,
            'fields_json' => wp_json_encode($this->decodeFieldsJson($fieldsJson, $type)),
            'settings_json' => wp_json_encode($settings),
        ];
        $payloadHash = hash('sha256', (string) wp_json_encode(['id' => $id, 'revision' => $revision, 'payload' => $payload]));
        $replayKey = 'h4bs_form_save_' . get_current_user_id() . '_' . substr($requestId, 0, 40);
        if ($isAjax && $requestId !== '') {
            $replay = get_transient($replayKey);
            if (is_array($replay)) {
                if (hash_equals((string) ($replay['hash'] ?? ''), $payloadHash)) {
                    wp_send_json_success($replay['result'] ?? []);
                }
                wp_send_json_error(['message' => __('This save key was already used for different changes. Reload the latest form and review your work.', 'help4-builder-suite')], 409);
            }
        }

        if ($id > 0) {
            $currentRow = $wpdb->get_row($wpdb->prepare('SELECT * FROM %i WHERE id = %d LIMIT 1', $this->formsTable(), $id), ARRAY_A);
            $currentRevision = is_array($currentRow) ? $this->formRevision($currentRow) : '';
            if ($isAjax && ($revision === '' || ! hash_equals($currentRevision, $revision))) {
                wp_send_json_error(['message' => __('Someone else saved this form after you opened it. Your changes are still here. Copy them or open the latest version in another tab before retrying.', 'help4-builder-suite'), 'revision' => $currentRevision], 409);
            }
            $updated = $wpdb->update(
                $this->formsTable(),
                $payload,
                ['id' => $id],
                ['%s', '%s', '%s', '%s', '%s', '%s'],
                ['%d']
            );
            if ($updated === false) {
                if ($isAjax) {
                    wp_send_json_error(['message' => __('The form could not be saved. Your changes are still here; try again.', 'help4-builder-suite')], 500);
                }
                $this->adminRedirect('h4bs-forms', ['h4bs_notice' => 'form_save_failed', 'view' => 'editor', 'edit' => $id]);
            }
            if ($isAjax) {
                $newRow = $wpdb->get_row($wpdb->prepare('SELECT * FROM %i WHERE id = %d LIMIT 1', $this->formsTable(), $id), ARRAY_A);
                $newRevision = is_array($newRow) ? $this->formRevision($newRow) : '';
                $result = ['id' => $id, 'revision' => $newRevision, 'next_request_id' => wp_generate_uuid4(), 'message' => __('Form saved.', 'help4-builder-suite')];
                if ($requestId !== '') {
                    set_transient($replayKey, ['hash' => $payloadHash, 'result' => $result], 10 * MINUTE_IN_SECONDS);
                }
                wp_send_json_success($result);
            }
            $this->adminRedirect('h4bs-forms', ['h4bs_notice' => 'form_saved', 'view' => 'editor', 'edit' => $id]);
        }

        $inserted = $wpdb->insert(
            $this->formsTable(),
            $payload,
            ['%s', '%s', '%s', '%s', '%s', '%s']
        );
        if ($inserted === false) {
            if ($isAjax) {
                wp_send_json_error(['message' => __('The form could not be created. Your changes are still here; try again.', 'help4-builder-suite')], 500);
            }
            $this->adminRedirect('h4bs-forms', ['h4bs_notice' => 'form_save_failed', 'view' => 'editor']);
        }
        if ($isAjax) {
            $newId = (int) $wpdb->insert_id;
            $newRow = $wpdb->get_row($wpdb->prepare('SELECT * FROM %i WHERE id = %d LIMIT 1', $this->formsTable(), $newId), ARRAY_A);
            $newRevision = is_array($newRow) ? $this->formRevision($newRow) : '';
            $result = ['id' => $newId, 'revision' => $newRevision, 'next_request_id' => wp_generate_uuid4(), 'message' => __('Form saved.', 'help4-builder-suite')];
            if ($requestId !== '') {
                set_transient($replayKey, ['hash' => $payloadHash, 'result' => $result], 10 * MINUTE_IN_SECONDS);
            }
            wp_send_json_success($result);
        }
        $this->adminRedirect('h4bs-forms', ['h4bs_notice' => 'form_saved', 'view' => 'editor', 'edit' => (int) $wpdb->insert_id]);
    }

    public function handleDeleteForm(): void
    {
        if (! $this->canManageForms()) {
            $this->denyAccess();
        }
        $id = absint(wp_unslash($_POST['form_id'] ?? 0));
        if ($id <= 0) {
            $this->adminRedirect('h4bs-forms', ['h4bs_notice' => 'invalid_form']);
        }
        check_admin_referer('h4bs_delete_form_' . $id);

        global $wpdb;
        $formName = (string) $wpdb->get_var($wpdb->prepare('SELECT name FROM %i WHERE id = %d LIMIT 1', $this->formsTable(), $id));
        $confirmation = sanitize_text_field((string) wp_unslash($_POST['confirm_name'] ?? ''));
        if ($formName === '' || ! hash_equals($formName, $confirmation)) {
            $this->adminRedirect('h4bs-forms', ['h4bs_notice' => 'delete_confirmation_failed']);
        }
        if (! $this->setFormStatus($id, 'archived')) {
            $this->adminRedirect('h4bs-forms', ['h4bs_notice' => 'form_action_failed']);
        }
        $this->adminRedirect('h4bs-forms', ['h4bs_notice' => 'form_archived']);
    }

    public function handleRestoreForm(): void
    {
        if (! $this->canManageForms()) {
            $this->denyAccess();
        }
        $id = absint(wp_unslash($_POST['form_id'] ?? 0));
        check_admin_referer('h4bs_restore_form_' . $id);
        if (! $this->setFormStatus($id, 'inactive')) {
            $this->adminRedirect('h4bs-forms', ['h4bs_notice' => 'form_action_failed', 'view' => 'archived']);
        }
        $this->adminRedirect('h4bs-forms', ['h4bs_notice' => 'form_restored', 'view' => 'archived']);
    }

    public function handleNewsletterAdd(): void
    {
        if (! $this->canManageFormSettings()) {
            $this->denyAccess();
        }
        check_admin_referer('h4bs_newsletter_add', 'h4bs_newsletter_add_nonce');

        $email = sanitize_email((string) wp_unslash($_POST['email'] ?? ''));
        if ($email === '') {
            $this->adminRedirect('h4bs-newsletter', ['h4bs_notice' => 'newsletter_email_required']);
        }
        $this->upsertSubscriber(
            [
                'email' => $email,
                'first_name' => sanitize_text_field((string) wp_unslash($_POST['first_name'] ?? '')),
                'last_name' => sanitize_text_field((string) wp_unslash($_POST['last_name'] ?? '')),
                'tags' => sanitize_text_field((string) wp_unslash($_POST['tags'] ?? '')),
                'status' => 'subscribed',
                'source' => 'admin',
            ]
        );
        $this->adminRedirect('h4bs-newsletter', ['h4bs_notice' => 'newsletter_saved']);
    }

    public function handleNewsletterStatus(): void
    {
        if (! $this->canManageFormSettings()) {
            $this->denyAccess();
        }
        $id = AdminRequest::queryAbsInt('subscriber_id');
        if ($id <= 0) {
            $this->adminRedirect('h4bs-newsletter', ['h4bs_notice' => 'newsletter_invalid']);
        }
        check_admin_referer('h4bs_newsletter_status_' . $id);

        $status = AdminRequest::queryKey('status', 'subscribed');
        if (! in_array($status, ['subscribed', 'unsubscribed', 'bounced'], true)) {
            $status = 'subscribed';
        }
        global $wpdb;
        $wpdb->update($this->newsletterTable(), ['status' => $status], ['id' => $id], ['%s'], ['%d']);
        do_action('h4bs_newsletter_status_changed', $id, $status);
        $this->adminRedirect('h4bs-newsletter', ['h4bs_notice' => 'newsletter_saved']);
    }

    public function handleSaveCaptchaSettings(): void
    {
        if (! $this->canManageFormSettings()) {
            $this->denyAccess();
        }
        check_admin_referer('h4bs_save_captcha_settings', 'h4bs_captcha_nonce');
        $payload = is_array($_POST) ? $_POST : [];
        $this->captcha()->save($payload);
        $this->adminRedirect('h4bs-forms', ['h4bs_notice' => 'captcha_settings_saved']);
    }

    public function handleExportEntries(): void
    {
        if (! $this->canExportEntries()) {
            $this->denyAccess();
        }
        check_admin_referer('h4bs_export_form_entries');
        $formId = absint(wp_unslash($_POST['form_id'] ?? 0));
        $search = sanitize_text_field((string) wp_unslash($_POST['s'] ?? ''));
        $dateFrom = $this->sanitizeEntryDate(sanitize_text_field((string) wp_unslash($_POST['date_from'] ?? '')));
        $dateTo = $this->sanitizeEntryDate(sanitize_text_field((string) wp_unslash($_POST['date_to'] ?? '')));
        $entryView = sanitize_key((string) wp_unslash($_POST['entry_view'] ?? 'all'));
        $statusFilter = sanitize_key((string) wp_unslash($_POST['entry_status'] ?? 'all'));
        $assigneeRaw = sanitize_key((string) wp_unslash($_POST['assigned_user_id'] ?? 'all'));
        $assigneeFilter = $assigneeRaw === 'unassigned' ? -1 : ($assigneeRaw === 'mine' ? -2 : absint($assigneeRaw));
        if ($dateFrom !== '' && $dateTo !== '' && $dateFrom > $dateTo) {
            wp_die(esc_html__('The From date must be on or before the Through date. Nothing was exported.', 'help4-builder-suite'), esc_html__('Invalid date range', 'help4-builder-suite'), ['response' => 422]);
        }
        $entries = $this->queryEntries($formId, $search, $dateFrom, $dateTo, 5000, $entryView, $statusFilter, $assigneeFilter);

        nocache_headers();
        header('Content-Type: text/csv; charset=utf-8');
        header('Content-Disposition: attachment; filename="help4-form-entries-' . gmdate('Y-m-d-His') . '.csv"');
        $stream = fopen('php://output', 'wb');
        if ($stream === false) {
            wp_die(esc_html__('The CSV export could not be opened.', 'help4-builder-suite'));
        }
        fputcsv($stream, ['Entry ID', 'Form', 'Created', 'Staff status', 'Assigned staff', 'Archived at', 'Name', 'Email', 'Phone', 'Subject', 'Spam', 'Answers JSON'], ',', '"', '');
        foreach ($entries as $entry) {
            $payload = json_decode((string) ($entry['payload'] ?? '{}'), true);
            $answers = is_array($payload['fields'] ?? null) ? $payload['fields'] : [];
            $row = [
                (string) ((int) ($entry['id'] ?? 0)),
                (string) ($entry['form_name'] ?? ''),
                (string) ($entry['created_at'] ?? ''),
                $this->entryStatusLabel((string) ($entry['staff_status'] ?? 'new')),
                $this->entryAssigneeName((int) ($entry['assigned_user_id'] ?? 0)),
                (string) ($entry['archived_at'] ?? ''),
                (string) ($entry['name'] ?? ''),
                (string) ($entry['email'] ?? ''),
                (string) ($entry['phone'] ?? ''),
                (string) ($entry['subject'] ?? ''),
                ! empty($entry['is_spam']) ? 'Yes' : 'No',
                (string) wp_json_encode($answers, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE),
            ];
            fputcsv($stream, array_map([$this, 'safeCsvCell'], $row), ',', '"', '');
        }
        fclose($stream); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose -- The streamed CSV must be closed before terminating the download request.
        exit;
    }

    public function handleFormSubmit(): void
    {
        $formId = absint(wp_unslash($_POST['h4bs_form_id'] ?? $_POST['form_id'] ?? 0));
        if ($formId > 0) {
            check_admin_referer('h4bs_form_' . $formId, '_h4bs_form_nonce');
        }

        $payload = is_array($_POST) ? wp_unslash($_POST) : [];
        $result = $this->processSubmission($payload);

        $redirect = esc_url_raw((string) ($payload['h4bs_redirect'] ?? ''));
        if ($redirect === '') {
            $redirect = wp_get_referer() ?: home_url('/');
        }
        $target = add_query_arg(
            [
                'h4bs_form_status' => ! empty($result['ok']) ? 'success' : 'error',
                'h4bs_form_id' => (int) ($result['form_id'] ?? 0),
                'h4bs_form_error' => ! empty($result['ok']) ? false : sanitize_key((string) ($result['code'] ?? 'submission_failed')),
            ],
            $redirect
        );
        wp_safe_redirect($target);
        exit;
    }

    public function handleUpdateEntry(): void
    {
        if (! $this->canManageEntries()) {
            $this->denyAccess();
        }
        $entryId = absint(wp_unslash($_POST['entry_id'] ?? 0));
        check_admin_referer('h4bs_update_form_entry_' . $entryId);
        global $wpdb;
        $entry = $wpdb->get_row($wpdb->prepare('SELECT * FROM %i WHERE id = %d LIMIT 1', $this->entriesTable(), $entryId), ARRAY_A);
        if (! is_array($entry)) {
            wp_die(esc_html__('That stored message no longer exists.', 'help4-builder-suite'), esc_html__('Message unavailable', 'help4-builder-suite'), ['response' => 404]);
        }
        $revision = sanitize_text_field((string) wp_unslash($_POST['revision'] ?? ''));
        $currentRevision = (string) (($entry['operations_revision'] ?? '') ?: hash('sha256', (string) $entry['payload']));
        $requestId = sanitize_text_field((string) wp_unslash($_POST['request_id'] ?? ''));
        if (preg_match('/^[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$/i', $requestId) !== 1) {
            wp_die(esc_html__('The save request identifier is invalid. Nothing was changed.', 'help4-builder-suite'), esc_html__('Save request rejected', 'help4-builder-suite'), ['response' => 400]);
        }
        $operation = sanitize_key((string) wp_unslash($_POST['entry_operation'] ?? ''));
        if (! in_array($operation, ['save', 'archive', 'restore'], true)) {
            wp_die(esc_html__('That entry action is not supported. Nothing was changed.', 'help4-builder-suite'), esc_html__('Entry action rejected', 'help4-builder-suite'), ['response' => 400]);
        }
        $requestedStatus = sanitize_key((string) wp_unslash($_POST['entry_status'] ?? 'new'));
        $requestedAssignee = absint(wp_unslash($_POST['assigned_user_id'] ?? 0));
        $requestedNote = sanitize_textarea_field((string) wp_unslash($_POST['entry_note'] ?? ''));
        $requestHash = hash('sha256', (string) wp_json_encode([$entryId, $revision, $operation, $requestedStatus, $requestedAssignee, $requestedNote]));
        $ledgerKey = '_h4bs_entry_request_ledger';
        $ledger = get_user_meta(get_current_user_id(), $ledgerKey, true);
        $ledger = is_array($ledger) ? $ledger : [];
        $returnAssignee = sanitize_key((string) wp_unslash($_POST['return_assignee'] ?? 'all'));
        $returnArgs = ['entry_view' => sanitize_key((string) wp_unslash($_POST['return_view'] ?? 'current')) === 'archived' ? 'archived' : 'current', 's' => sanitize_text_field((string) wp_unslash($_POST['return_s'] ?? '')), 'form_id' => absint(wp_unslash($_POST['return_form_id'] ?? 0)), 'date_from' => $this->sanitizeEntryDate(sanitize_text_field((string) wp_unslash($_POST['return_date_from'] ?? ''))), 'date_to' => $this->sanitizeEntryDate(sanitize_text_field((string) wp_unslash($_POST['return_date_to'] ?? ''))), 'entry_status' => sanitize_key((string) wp_unslash($_POST['return_status'] ?? 'all')), 'assigned_user_id' => in_array($returnAssignee, ['all', 'unassigned', 'mine'], true) ? $returnAssignee : absint($returnAssignee), 'paged' => max(1, absint(wp_unslash($_POST['return_page'] ?? 1))), 'h4bs_notice' => 'entry_updated'];
        if ($requestId !== '' && isset($ledger[$requestId])) {
            if (hash_equals((string) ($ledger[$requestId]['hash'] ?? ''), $requestHash)) {
                $this->adminRedirect('h4bs-form-entries', $returnArgs);
            }
            wp_die(esc_html__('This retry identifier was already used for different work. Nothing was changed.', 'help4-builder-suite'), esc_html__('Save retry rejected', 'help4-builder-suite'), ['response' => 409]);
        }
        if ($revision === '' || ! hash_equals($currentRevision, $revision)) {
            wp_die(esc_html__('Someone else changed this message. Your submitted note and choices were not applied. Open the latest message in a new tab and review it before trying again.', 'help4-builder-suite'), esc_html__('Newer entry work exists', 'help4-builder-suite'), ['response' => 409]);
        }
        $payload = json_decode((string) $entry['payload'], true);
        $payload = is_array($payload) ? $payload : [];
        $ops = $this->entryOperations($entry);
        $status = $requestedStatus;
        $ops['status'] = in_array($status, ['new', 'in_progress', 'waiting', 'resolved'], true) ? $status : 'new';
        $assignee = $requestedAssignee;
        $ops['assigned_user_id'] = $assignee > 0 && user_can($assignee, 'view_help4_form_entries') ? $assignee : 0;
        $note = $requestedNote;
        if ($note !== '') {
            $ops['notes'][] = ['text' => mb_substr($note, 0, 2000), 'at' => current_time('mysql'), 'user_id' => get_current_user_id()];
            $ops['notes'] = array_slice($ops['notes'], -100);
        }
        if ($operation === 'archive') {
            $ops['archived_at'] = current_time('mysql');
        } elseif ($operation === 'restore') {
            $ops['archived_at'] = '';
        }
        $ops['updated_at'] = current_time('mysql');
        $ops['updated_by'] = get_current_user_id();
        $payload['_staff_operations'] = $ops;
        $newRevision = hash('sha256', wp_json_encode([$entryId, $ops, microtime(true)]));
        $updated = $wpdb->update($this->entriesTable(), ['payload' => wp_json_encode($payload, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE), 'staff_status' => $ops['status'], 'assigned_user_id' => $ops['assigned_user_id'], 'archived_at' => $ops['archived_at'] !== '' ? $ops['archived_at'] : null, 'operations_revision' => $newRevision], ['id' => $entryId], ['%s', '%s', '%d', '%s', '%s'], ['%d']);
        if ($updated === false) {
            wp_die(esc_html__('The message could not be updated. Nothing was discarded; return and try again.', 'help4-builder-suite'), esc_html__('Save failed', 'help4-builder-suite'), ['response' => 500]);
        }
        if ($requestId !== '') {
            $ledger[$requestId] = ['hash' => $requestHash, 'revision' => $newRevision, 'at' => time()];
            uasort($ledger, static fn (array $left, array $right): int => ((int) ($right['at'] ?? 0)) <=> ((int) ($left['at'] ?? 0)));
            update_user_meta(get_current_user_id(), $ledgerKey, array_slice($ledger, 0, 20, true));
        }
        $this->adminRedirect('h4bs-form-entries', $returnArgs);
    }

    public function handleBulkUpdateEntries(): void
    {
        if (! $this->canManageEntries()) {
            $this->denyAccess();
        }
        check_admin_referer('h4bs_bulk_update_form_entries');
        $requestId = sanitize_text_field((string) wp_unslash($_POST['request_id'] ?? ''));
        if (preg_match('/^[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$/i', $requestId) !== 1) {
            wp_die(esc_html__('The bulk request identifier is invalid. Nothing was changed.', 'help4-builder-suite'), esc_html__('Bulk request rejected', 'help4-builder-suite'), ['response' => 400]);
        }
        $idsInput = is_array($_POST['entry_ids'] ?? null) ? wp_unslash($_POST['entry_ids']) : []; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Every nested ID is normalized with absint on the next line.
        $entryIds = array_values(array_unique(array_filter(array_map('absint', $idsInput))));
        sort($entryIds, SORT_NUMERIC);
        if ($entryIds === [] || count($entryIds) > 100) {
            wp_die(esc_html__('Select between 1 and 100 visible messages. Nothing was changed.', 'help4-builder-suite'), esc_html__('Bulk selection rejected', 'help4-builder-suite'), ['response' => 400]);
        }
        $action = sanitize_text_field((string) wp_unslash($_POST['bulk_action'] ?? ''));
        $allowedActions = ['status:new', 'status:in_progress', 'status:waiting', 'status:resolved', 'assign', 'archive', 'restore'];
        if (! in_array($action, $allowedActions, true)) {
            wp_die(esc_html__('That bulk action is not supported. Nothing was changed.', 'help4-builder-suite'), esc_html__('Bulk action rejected', 'help4-builder-suite'), ['response' => 400]);
        }
        $assignee = absint(wp_unslash($_POST['bulk_assignee'] ?? 0));
        if ($action === 'assign' && $assignee > 0 && ! user_can($assignee, 'view_help4_form_entries')) {
            wp_die(esc_html__('The selected staff member cannot view form messages. Nothing was changed.', 'help4-builder-suite'), esc_html__('Assignment rejected', 'help4-builder-suite'), ['response' => 400]);
        }
        $revisionInput = is_array($_POST['entry_revisions'] ?? null) ? wp_unslash($_POST['entry_revisions']) : []; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Only selected IDs are read and every revision is sanitized and regex-validated below.
        $revisions = [];
        foreach ($entryIds as $entryId) {
            $revision = sanitize_text_field((string) ($revisionInput[$entryId] ?? ''));
            if (preg_match('/^[a-f0-9]{64}$/i', $revision) !== 1) {
                wp_die(esc_html__('A selected message has invalid revision evidence. Nothing was changed.', 'help4-builder-suite'), esc_html__('Bulk selection rejected', 'help4-builder-suite'), ['response' => 400]);
            }
            $revisions[$entryId] = $revision;
        }
        $returnView = sanitize_key((string) wp_unslash($_POST['return_view'] ?? 'current')) === 'archived' ? 'archived' : 'current';
        $returnSearch = sanitize_text_field((string) wp_unslash($_POST['return_s'] ?? ''));
        $returnFormId = absint(wp_unslash($_POST['return_form_id'] ?? 0));
        $returnDateFrom = $this->sanitizeEntryDate(sanitize_text_field((string) wp_unslash($_POST['return_date_from'] ?? '')));
        $returnDateTo = $this->sanitizeEntryDate(sanitize_text_field((string) wp_unslash($_POST['return_date_to'] ?? '')));
        $returnStatusInput = sanitize_key((string) wp_unslash($_POST['return_status'] ?? 'all'));
        $returnStatus = in_array($returnStatusInput, ['all', 'new', 'in_progress', 'waiting', 'resolved'], true) ? $returnStatusInput : 'all';
        $returnAssigneeInput = sanitize_key((string) wp_unslash($_POST['return_assignee'] ?? 'all'));
        $returnAssignee = in_array($returnAssigneeInput, ['all', 'unassigned', 'mine'], true) ? $returnAssigneeInput : absint($returnAssigneeInput);
        $returnAssigneeFilter = $returnAssignee === 'unassigned' ? -1 : ($returnAssignee === 'mine' ? -2 : absint($returnAssignee));
        $returnPage = max(1, absint(wp_unslash($_POST['return_page'] ?? 1)));
        $returnArgs = ['entry_view' => $returnView, 's' => $returnSearch, 'form_id' => $returnFormId, 'date_from' => $returnDateFrom, 'date_to' => $returnDateTo, 'entry_status' => $returnStatus, 'assigned_user_id' => $returnAssignee, 'paged' => $returnPage, 'h4bs_notice' => 'entries_bulk_updated'];
        $visiblePageContract = [$returnView, $returnSearch, $returnFormId, $returnDateFrom, $returnDateTo, $returnStatus, $returnAssignee, $returnPage, 25];
        $requestHash = hash('sha256', (string) wp_json_encode([$entryIds, $revisions, $action, $assignee, $visiblePageContract]));
        $ledgerKey = '_h4bs_entry_bulk_request_ledger';
        $ledger = get_user_meta(get_current_user_id(), $ledgerKey, true);
        $ledger = is_array($ledger) ? $ledger : [];
        if (isset($ledger[$requestId])) {
            if (hash_equals((string) ($ledger[$requestId]['hash'] ?? ''), $requestHash)) {
                $this->adminRedirect('h4bs-form-entries', $returnArgs);
            }
            wp_die(esc_html__('This bulk retry identifier was already used for different work. Nothing was changed.', 'help4-builder-suite'), esc_html__('Bulk retry rejected', 'help4-builder-suite'), ['response' => 409]);
        }

        if ($returnDateFrom !== '' && $returnDateTo !== '' && $returnDateFrom > $returnDateTo) {
            wp_die(esc_html__('The visible-page date range is invalid. Nothing was changed.', 'help4-builder-suite'), esc_html__('Bulk page contract rejected', 'help4-builder-suite'), ['response' => 400]);
        }
        $visibleRows = $this->queryEntries($returnFormId, $returnSearch, $returnDateFrom, $returnDateTo, 25, $returnView, $returnStatus, $returnAssigneeFilter, ($returnPage - 1) * 25);
        $visibleIds = array_map(static fn (array $row): int => (int) ($row['id'] ?? 0), $visibleRows);
        if (array_diff($entryIds, $visibleIds) !== []) {
            wp_die(esc_html__('One or more selected messages are not on the exact visible page represented by this request. Nothing was changed.', 'help4-builder-suite'), esc_html__('Bulk page contract is stale', 'help4-builder-suite'), ['response' => 409]);
        }

        global $wpdb;
        $placeholders = implode(', ', array_fill(0, count($entryIds), '%d'));
        $rows = $wpdb->get_results($wpdb->prepare('SELECT * FROM %i WHERE id IN (' . $placeholders . ') ORDER BY id ASC', array_merge([$this->entriesTable()], $entryIds)), ARRAY_A); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Placeholder count is derived only from validated integer IDs.
        if (! is_array($rows) || count($rows) !== count($entryIds)) {
            wp_die(esc_html__('One or more selected messages no longer exist. Nothing was changed.', 'help4-builder-suite'), esc_html__('Bulk work is stale', 'help4-builder-suite'), ['response' => 409]);
        }
        foreach ($rows as $row) {
            $entryId = (int) ($row['id'] ?? 0);
            $currentRevision = (string) (($row['operations_revision'] ?? '') ?: hash('sha256', (string) ($row['payload'] ?? '')));
            if (! isset($revisions[$entryId]) || ! hash_equals($currentRevision, $revisions[$entryId])) {
                wp_die(esc_html__('Someone changed one of the selected messages. Nothing in this bulk action was applied. Refresh and review the latest work.', 'help4-builder-suite'), esc_html__('Bulk work is stale', 'help4-builder-suite'), ['response' => 409]);
            }
            $isArchived = ! empty($row['archived_at']);
            if (($action === 'archive' && $isArchived) || ($action === 'restore' && ! $isArchived)) {
                wp_die(esc_html__('One selected message is no longer in the expected Current or Archived view. Nothing was changed.', 'help4-builder-suite'), esc_html__('Bulk work is stale', 'help4-builder-suite'), ['response' => 409]);
            }
        }

        if ($wpdb->query('START TRANSACTION') === false) { // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery -- Transaction makes the bounded bulk action all-or-nothing.
            wp_die(esc_html__('A safe bulk transaction could not be started. Nothing was changed.', 'help4-builder-suite'), esc_html__('Bulk update unavailable', 'help4-builder-suite'), ['response' => 500]);
        }
        foreach ($rows as $row) {
            $entryId = (int) $row['id'];
            $payload = json_decode((string) ($row['payload'] ?? '{}'), true);
            $payload = is_array($payload) ? $payload : [];
            $ops = $this->entryOperations($row);
            if (str_starts_with($action, 'status:')) {
                $ops['status'] = substr($action, 7);
            } elseif ($action === 'assign') {
                $ops['assigned_user_id'] = $assignee;
            } elseif ($action === 'archive') {
                $ops['archived_at'] = current_time('mysql');
            } elseif ($action === 'restore') {
                $ops['archived_at'] = '';
            }
            $ops['updated_at'] = current_time('mysql');
            $ops['updated_by'] = get_current_user_id();
            $payload['_staff_operations'] = $ops;
            $newRevision = hash('sha256', (string) wp_json_encode([$entryId, $ops, $requestId]));
            $updated = $wpdb->update($this->entriesTable(), ['payload' => wp_json_encode($payload, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE), 'staff_status' => $ops['status'], 'assigned_user_id' => $ops['assigned_user_id'], 'archived_at' => $ops['archived_at'] !== '' ? $ops['archived_at'] : null, 'operations_revision' => $newRevision], ['id' => $entryId], ['%s', '%s', '%d', '%s', '%s'], ['%d']);
            if ($updated === false) {
                $wpdb->query('ROLLBACK'); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery -- Roll back every selected message on the first database failure.
                wp_die(esc_html__('The selected messages could not be updated together. The entire bulk action was rolled back.', 'help4-builder-suite'), esc_html__('Bulk update failed', 'help4-builder-suite'), ['response' => 500]);
            }
        }
        $ledger[$requestId] = ['hash' => $requestHash, 'at' => time(), 'count' => count($entryIds)];
        uasort($ledger, static fn (array $left, array $right): int => ((int) ($right['at'] ?? 0)) <=> ((int) ($left['at'] ?? 0)));
        if (update_user_meta(get_current_user_id(), $ledgerKey, array_slice($ledger, 0, 20, true)) === false) {
            $wpdb->query('ROLLBACK'); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery -- Do not commit without replay evidence.
            wp_cache_delete(get_current_user_id(), 'user_meta');
            wp_die(esc_html__('Replay protection could not be stored. The entire bulk action was rolled back.', 'help4-builder-suite'), esc_html__('Bulk update failed', 'help4-builder-suite'), ['response' => 500]);
        }
        if ($wpdb->query('COMMIT') === false) { // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery -- Commit only after every entry and replay evidence are stored.
            $wpdb->query('ROLLBACK'); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery -- Best-effort rollback when the database rejects commit.
            wp_cache_delete(get_current_user_id(), 'user_meta');
            wp_die(esc_html__('The database could not finish the bulk update. Nothing is being reported as saved; refresh and review the selected messages.', 'help4-builder-suite'), esc_html__('Bulk update could not finish', 'help4-builder-suite'), ['response' => 500]);
        }
        $this->adminRedirect('h4bs-form-entries', $returnArgs);
    }

    public function handleAjaxFormSubmit(): void
    {
        check_ajax_referer('h4bs_form_ajax', 'security');
        $payload = is_array($_POST) ? wp_unslash($_POST) : [];
        $result = $this->processSubmission($payload);
        if (! empty($result['ok'])) {
            wp_send_json_success($result);
        }
        wp_send_json_error($result, $this->submissionStatus($result));
    }

    public function ensureDefaultForms(): void
    {
        if ((string) get_option('h4bs_forms_seeded', '0') === '1') {
            return;
        }

        global $wpdb;
        $table = $this->formsTable();
        $exists = $wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $table));
        if (! is_string($exists) || $exists === '') {
            return;
        }

        $count = (int) $wpdb->get_var($wpdb->prepare('SELECT COUNT(*) FROM %i', $table));
        if ($count > 0) {
            update_option('h4bs_forms_seeded', '1');
            return;
        }

        $defaults = [
            [
                'slug' => 'contact-main',
                'name' => 'Contact Form',
                'type' => 'contact',
                'fields' => [
                    ['key' => 'name', 'label' => 'Full Name', 'type' => 'text', 'required' => '1', 'placeholder' => 'Jane Doe', 'width' => '50'],
                    ['key' => 'email', 'label' => 'Email', 'type' => 'email', 'required' => '1', 'placeholder' => 'you@example.com', 'width' => '50'],
                    ['key' => 'phone', 'label' => 'Phone', 'type' => 'tel', 'required' => '0', 'placeholder' => '(555) 123-4567', 'width' => '50'],
                    ['key' => 'subject', 'label' => 'Subject', 'type' => 'text', 'required' => '0', 'placeholder' => 'How can we help?', 'width' => '50'],
                    ['key' => 'message', 'label' => 'Message', 'type' => 'textarea', 'required' => '1', 'placeholder' => 'Tell us what you need...', 'width' => '100'],
                ],
                'settings' => $this->defaultFormSettings('Send Message'),
            ],
            [
                'slug' => 'newsletter-main',
                'name' => 'Newsletter Signup',
                'type' => 'newsletter',
                'fields' => [
                    ['key' => 'first_name', 'label' => 'First Name', 'type' => 'text', 'required' => '0', 'placeholder' => 'First name', 'width' => '50'],
                    ['key' => 'last_name', 'label' => 'Last Name', 'type' => 'text', 'required' => '0', 'placeholder' => 'Last name', 'width' => '50'],
                    ['key' => 'email', 'label' => 'Email', 'type' => 'email', 'required' => '1', 'placeholder' => 'you@example.com', 'width' => '100'],
                    ['key' => 'marketing_consent', 'label' => 'I want to receive email updates and can unsubscribe at any time. The site may measure opens and link clicks when engagement reporting is enabled.', 'type' => 'checkbox', 'required' => '1', 'placeholder' => '', 'width' => '100'],
                    ['key' => 'tags', 'label' => 'Tags', 'type' => 'hidden', 'required' => '0', 'default' => 'website-newsletter', 'width' => '100'],
                ],
                'settings' => $this->defaultFormSettings('Subscribe'),
            ],
            [
                'slug' => 'quote-request',
                'name' => 'Quote Request',
                'type' => 'quote',
                'fields' => [
                    ['key' => 'name', 'label' => 'Name', 'type' => 'text', 'required' => '1', 'placeholder' => '', 'width' => '50'],
                    ['key' => 'email', 'label' => 'Email', 'type' => 'email', 'required' => '1', 'placeholder' => '', 'width' => '50'],
                    ['key' => 'details', 'label' => 'Project Details', 'type' => 'textarea', 'required' => '1', 'placeholder' => '', 'width' => '100'],
                ],
                'settings' => $this->defaultFormSettings('Request Quote'),
            ],
            [
                'slug' => 'callback-request',
                'name' => 'Callback Request',
                'type' => 'callback',
                'fields' => [
                    ['key' => 'name', 'label' => 'Name', 'type' => 'text', 'required' => '1', 'placeholder' => '', 'width' => '50'],
                    ['key' => 'phone', 'label' => 'Phone', 'type' => 'tel', 'required' => '1', 'placeholder' => '', 'width' => '50'],
                ],
                'settings' => $this->defaultFormSettings('Request Callback'),
            ],
            [
                'slug' => 'booking-request',
                'name' => 'Booking Request',
                'type' => 'booking',
                'fields' => [
                    ['key' => 'name', 'label' => 'Name', 'type' => 'text', 'required' => '1', 'placeholder' => '', 'width' => '50'],
                    ['key' => 'email', 'label' => 'Email', 'type' => 'email', 'required' => '1', 'placeholder' => '', 'width' => '50'],
                    ['key' => 'date', 'label' => 'Date', 'type' => 'date', 'required' => '1', 'placeholder' => '', 'width' => '50'],
                    ['key' => 'time', 'label' => 'Time', 'type' => 'time', 'required' => '1', 'placeholder' => '', 'width' => '50'],
                ],
                'settings' => $this->defaultFormSettings('Book Appointment'),
            ],
        ];

        foreach ($defaults as $row) {
            $wpdb->insert(
                $table,
                [
                    'slug' => (string) $row['slug'],
                    'name' => (string) $row['name'],
                    'type' => (string) $row['type'],
                    'status' => 'active',
                    'fields_json' => wp_json_encode($row['fields']),
                    'settings_json' => wp_json_encode($row['settings']),
                ],
                ['%s', '%s', '%s', '%s', '%s', '%s']
            );
        }

        update_option('h4bs_forms_seeded', '1');
    }

    private function renderCaptchaSettings(): void
    {
        $captcha = $this->captcha()->settings(false);
        $provider = (string) ($captcha['provider'] ?? 'disabled');
        $configured = ! empty($captcha['configured']);
        ?>
        <details class="h4bs-form-captcha-settings" style="margin:1rem 0 1.5rem;max-width:920px;" open>
            <summary style="cursor:pointer;font-weight:600;padding:.75rem 0;"><?php esc_html_e('CAPTCHA and bot protection', 'help4-builder-suite'); ?></summary>
            <div class="card" style="max-width:none;margin:0;padding:1rem 1.25rem;">
                <p><?php esc_html_e('Cloudflare Turnstile is optional and disabled by default. Existing honeypot and minimum-submit-time checks remain active independently.', 'help4-builder-suite'); ?></p>
                <?php if ($provider === 'turnstile' && ! $configured) : ?>
                    <div class="notice notice-warning inline"><p><?php esc_html_e('Turnstile is selected but its site key or secret is missing. Forms are using the migration-safe honeypot and timing fallback until both keys are saved.', 'help4-builder-suite'); ?></p></div>
                <?php elseif ($configured) : ?>
                    <div class="notice notice-success inline"><p><?php esc_html_e('Turnstile is configured for this site. Individual forms can inherit it or opt out.', 'help4-builder-suite'); ?></p></div>
                <?php endif; ?>
                <form method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>">
                    <?php wp_nonce_field('h4bs_save_captcha_settings', 'h4bs_captcha_nonce'); ?>
                    <input type="hidden" name="action" value="h4bs_save_captcha_settings">
                    <table class="form-table" role="presentation">
                        <tbody>
                            <tr>
                                <th><label for="h4bs-captcha-provider"><?php esc_html_e('Provider', 'help4-builder-suite'); ?></label></th>
                                <td>
                                    <select id="h4bs-captcha-provider" name="captcha_provider" aria-describedby="h4bs-captcha-provider-help">
                                        <option value="disabled" <?php selected($provider, 'disabled'); ?>><?php esc_html_e('Disabled', 'help4-builder-suite'); ?></option>
                                        <option value="turnstile" <?php selected($provider, 'turnstile'); ?>><?php esc_html_e('Cloudflare Turnstile', 'help4-builder-suite'); ?></option>
                                    </select>
                                    <p class="description" id="h4bs-captcha-provider-help"><?php esc_html_e('Disabled mode never loads a CAPTCHA script and preserves native non-JavaScript submission.', 'help4-builder-suite'); ?></p>
                                </td>
                            </tr>
                            <tr>
                                <th><label for="h4bs-captcha-site-key"><?php esc_html_e('Turnstile site key', 'help4-builder-suite'); ?></label></th>
                                <td><input id="h4bs-captcha-site-key" class="regular-text code" type="text" name="captcha_site_key" value="<?php echo esc_attr((string) ($captcha['site_key'] ?? '')); ?>" autocomplete="off" spellcheck="false"></td>
                            </tr>
                            <tr>
                                <th><label for="h4bs-captcha-secret-key"><?php esc_html_e('Turnstile secret key', 'help4-builder-suite'); ?></label></th>
                                <td>
                                    <input id="h4bs-captcha-secret-key" class="regular-text code" type="password" name="captcha_secret_key" value="" autocomplete="new-password" aria-describedby="h4bs-captcha-secret-help">
                                    <p class="description" id="h4bs-captcha-secret-help"><?php echo ! empty($captcha['secret_configured']) ? esc_html__('A secret is stored. Leave this blank to keep it; the saved value is never displayed.', 'help4-builder-suite') : esc_html__('No secret is stored for this site.', 'help4-builder-suite'); ?></p>
                                    <label><input type="checkbox" name="captcha_clear_secret" value="1" <?php disabled(empty($captcha['secret_configured'])); ?>> <?php echo ! empty($captcha['secret_configured']) ? esc_html__('Explicitly clear the stored secret', 'help4-builder-suite') : esc_html__('No stored secret to clear', 'help4-builder-suite'); ?></label>
                                </td>
                            </tr>
                            <tr>
                                <th><label for="h4bs-captcha-theme"><?php esc_html_e('Widget theme', 'help4-builder-suite'); ?></label></th>
                                <td><select id="h4bs-captcha-theme" name="captcha_theme"><option value="auto" <?php selected((string) ($captcha['theme'] ?? 'auto'), 'auto'); ?>><?php esc_html_e('Automatic', 'help4-builder-suite'); ?></option><option value="light" <?php selected((string) ($captcha['theme'] ?? 'auto'), 'light'); ?>><?php esc_html_e('Light', 'help4-builder-suite'); ?></option><option value="dark" <?php selected((string) ($captcha['theme'] ?? 'auto'), 'dark'); ?>><?php esc_html_e('Dark', 'help4-builder-suite'); ?></option></select></td>
                            </tr>
                            <tr>
                                <th><label for="h4bs-captcha-size"><?php esc_html_e('Widget size', 'help4-builder-suite'); ?></label></th>
                                <td><select id="h4bs-captcha-size" name="captcha_size"><option value="flexible" <?php selected((string) ($captcha['size'] ?? 'flexible'), 'flexible'); ?>><?php esc_html_e('Flexible', 'help4-builder-suite'); ?></option><option value="normal" <?php selected((string) ($captcha['size'] ?? 'flexible'), 'normal'); ?>><?php esc_html_e('Normal', 'help4-builder-suite'); ?></option><option value="compact" <?php selected((string) ($captcha['size'] ?? 'flexible'), 'compact'); ?>><?php esc_html_e('Compact', 'help4-builder-suite'); ?></option></select></td>
                            </tr>
                        </tbody>
                    </table>
                    <p class="description"><?php esc_html_e('These credentials belong only to the current site, including on multisite. They are not copied into forms, REST responses, browser data, exports, or network settings.', 'help4-builder-suite'); ?></p>
                    <?php submit_button(__('Save CAPTCHA Settings', 'help4-builder-suite'), 'secondary', 'submit', false); ?>
                </form>
            </div>
        </details>
        <?php
    }

    /**
     * @param array<string, mixed> $row
     */
    private function renderFormEditor(array $row): void
    {
        $id = (int) ($row['id'] ?? 0);
        $type = (string) ($row['type'] ?? 'contact');
        $status = (string) ($row['status'] ?? 'active');
        $name = (string) ($row['name'] ?? '');
        $slug = (string) ($row['slug'] ?? '');
        $fieldsJson = (string) ($row['fields_json'] ?? '[]');
        $decodedSchema = json_decode($fieldsJson, true);
        $schemaValid = json_last_error() === JSON_ERROR_NONE && is_array($decodedSchema) && array_is_list($decodedSchema) && array_filter($decodedSchema, static fn ($field): bool => ! is_array($field)) === [];
        $revision = $this->formRevision($row);
        $settingsJson = (string) ($row['settings_json'] ?? '{}');
        $settings = json_decode($settingsJson, true);
        if (! is_array($settings)) {
            $settings = $this->defaultFormSettings('Submit');
        }
        $captchaRuntime = $this->captcha()->runtime($settings);
        $captchaSite = $this->captcha()->settings(false);
        $captchaStatus = ! empty($captchaRuntime['enabled'])
            ? __('Turnstile active for this form', 'help4-builder-suite')
            : (! empty($captchaRuntime['fallback']) ? __('Turnstile requested, but site setup is incomplete', 'help4-builder-suite') : __('CAPTCHA disabled for this form', 'help4-builder-suite'));
        ?>
        <div id="h4bs-form-editor" class="h4bs-form-workspace" data-h4bs-form-workspace data-h4bs-schema-valid="<?php echo $schemaValid ? '1' : '0'; ?>" data-h4bs-captcha-provider="<?php echo esc_attr((string) ($captchaSite['provider'] ?? 'disabled')); ?>" data-h4bs-captcha-configured="<?php echo ! empty($captchaSite['configured']) ? '1' : '0'; ?>">
        <?php if (! $schemaValid) : ?><div class="notice notice-error inline" role="alert" data-h4bs-schema-error><p><strong><?php esc_html_e('This form has field data that cannot be read safely.', 'help4-builder-suite'); ?></strong> <?php esc_html_e('Nothing has been erased. Saving is blocked until an administrator repairs the raw field data under Advanced or restores a backup.', 'help4-builder-suite'); ?></p></div><?php endif; ?>
        <form method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>" data-h4bs-form-editor>
            <?php wp_nonce_field('h4bs_save_form', 'h4bs_form_nonce'); ?>
            <input type="hidden" name="action" value="h4bs_save_form">
            <input type="hidden" name="id" value="<?php echo esc_attr((string) $id); ?>">
            <input type="hidden" name="revision" value="<?php echo esc_attr($revision); ?>">
            <input type="hidden" name="request_id" value="<?php echo esc_attr(wp_generate_uuid4()); ?>">
            <section class="h4bs-form-panel" aria-labelledby="h4bs-form-basics"><h2 id="h4bs-form-basics"><?php esc_html_e('Form details', 'help4-builder-suite'); ?></h2><div class="h4bs-form-grid-admin">
            <label><?php esc_html_e('Form name', 'help4-builder-suite'); ?><input id="h4bs-form-name" type="text" name="name" required value="<?php echo esc_attr($name); ?>"></label>
            <label><?php esc_html_e('Purpose', 'help4-builder-suite'); ?><select id="h4bs-form-type" name="type"><?php foreach (['contact','newsletter','quote','callback','booking','custom'] as $option) : ?><option value="<?php echo esc_attr($option); ?>" <?php selected($type, $option); ?>><?php echo esc_html(ucfirst($option)); ?></option><?php endforeach; ?></select></label>
            <label><?php esc_html_e('Visitor availability', 'help4-builder-suite'); ?><select id="h4bs-form-status" name="status"><option value="active" <?php selected($status, 'active'); ?>><?php esc_html_e('Open and accepting messages', 'help4-builder-suite'); ?></option><option value="inactive" <?php selected($status, 'inactive'); ?>><?php esc_html_e('Hidden / not accepting messages', 'help4-builder-suite'); ?></option></select></label>
            <label><?php esc_html_e('Send new-message notices to', 'help4-builder-suite'); ?><input type="email" name="notify_email" value="<?php echo esc_attr((string) ($settings['notify_email'] ?? '')); ?>" placeholder="<?php echo esc_attr((string) get_option('admin_email', '')); ?>"></label>
            </div><label class="h4bs-check"><input type="checkbox" name="notify_admin" value="1" <?php checked(! empty($settings['notify_admin'])); ?>> <?php esc_html_e('Email this address when a message is received', 'help4-builder-suite'); ?></label></section>

            <section class="h4bs-form-panel" aria-labelledby="h4bs-fields-title"><div class="h4bs-panel-heading"><div><h2 id="h4bs-fields-title"><?php esc_html_e('Questions visitors answer', 'help4-builder-suite'); ?></h2><p><?php esc_html_e('Drag fields or use Move up and Move down. Open a field to change its label and choices.', 'help4-builder-suite'); ?></p></div><div class="h4bs-field-toolbar"><button class="button" type="button" data-h4bs-undo hidden><?php esc_html_e('Undo last field change', 'help4-builder-suite'); ?></button><button class="button button-primary" type="button" data-h4bs-add-field><?php esc_html_e('Add a question', 'help4-builder-suite'); ?></button></div></div><div data-h4bs-field-list></div><textarea id="h4bs-form-fields" name="fields_json" hidden><?php echo esc_textarea($fieldsJson); ?></textarea><div class="h4bs-live" aria-live="polite" data-h4bs-field-status></div></section>

            <section class="h4bs-form-panel" aria-labelledby="h4bs-response-title"><h2 id="h4bs-response-title"><?php esc_html_e('After someone submits', 'help4-builder-suite'); ?></h2><div class="h4bs-form-grid-admin"><label><?php esc_html_e('Submit button text', 'help4-builder-suite'); ?><input id="h4bs-button-label" type="text" name="button_label" value="<?php echo esc_attr((string) ($settings['button_label'] ?? 'Submit')); ?>"></label><label><?php esc_html_e('Success message', 'help4-builder-suite'); ?><input id="h4bs-success-message" type="text" name="success_message" value="<?php echo esc_attr((string) ($settings['success_message'] ?? 'Thanks! We received your request.')); ?>"></label></div><input type="hidden" name="error_message" value="<?php echo esc_attr((string) ($settings['error_message'] ?? 'Please check required fields.')); ?>"></section>

            <section class="h4bs-form-panel"><div class="h4bs-panel-heading"><div><h2><?php esc_html_e('Preview', 'help4-builder-suite'); ?></h2><p><?php esc_html_e('This preview never sends email, calls webhooks, stores an entry, or runs CAPTCHA.', 'help4-builder-suite'); ?></p></div><button type="button" class="button" data-h4bs-refresh-preview><?php esc_html_e('Refresh preview', 'help4-builder-suite'); ?></button></div><div class="h4bs-form-preview" data-h4bs-form-preview aria-label="Form preview"></div></section>
            <p class="h4bs-captcha-status"><strong><?php esc_html_e('Current spam-check result:', 'help4-builder-suite'); ?></strong> <?php echo esc_html($captchaStatus); ?></p>
            <?php $webhookHost = wp_parse_url((string) ($settings['webhook_url'] ?? ''), PHP_URL_HOST); ?>
            <section class="h4bs-form-panel h4bs-form-readiness" aria-labelledby="h4bs-readiness-title"><h2 id="h4bs-readiness-title"><?php esc_html_e('Ready-to-save summary', 'help4-builder-suite'); ?></h2><ul><li><strong><?php esc_html_e('Visitors:', 'help4-builder-suite'); ?></strong> <?php echo $status === 'active' ? esc_html__('Form is open', 'help4-builder-suite') : esc_html__('Form is hidden', 'help4-builder-suite'); ?></li><li><strong><?php esc_html_e('Staff notice:', 'help4-builder-suite'); ?></strong> <?php echo ! empty($settings['notify_admin']) ? esc_html((string) ($settings['notify_email'] ?: get_option('admin_email'))) : esc_html__('Off', 'help4-builder-suite'); ?></li><li><strong><?php esc_html_e('Visitor confirmation:', 'help4-builder-suite'); ?></strong> <?php echo ! empty($settings['send_autoresponder']) ? esc_html__('On', 'help4-builder-suite') : esc_html__('Off', 'help4-builder-suite'); ?></li><li><strong><?php esc_html_e('Spam protection:', 'help4-builder-suite'); ?></strong> <?php echo esc_html($captchaStatus); ?>; <?php echo ! empty($settings['honeypot']) ? esc_html__('invisible spam trap on', 'help4-builder-suite') : esc_html__('invisible spam trap off', 'help4-builder-suite'); ?></li><li><strong><?php esc_html_e('Webhook:', 'help4-builder-suite'); ?></strong> <?php echo $webhookHost ? esc_html((string) $webhookHost) : esc_html__('Off', 'help4-builder-suite'); ?></li></ul><p class="description"><?php esc_html_e('Preview is always safe: it does not send notices, confirmations, webhooks, stored entries, or CAPTCHA requests.', 'help4-builder-suite'); ?></p></section>

            <details class="h4bs-form-panel h4bs-form-advanced"><summary><?php esc_html_e('Delivery, spam protection, and advanced settings', 'help4-builder-suite'); ?></summary><div class="h4bs-form-grid-admin"><label><?php esc_html_e('CAPTCHA for this form', 'help4-builder-suite'); ?><select id="h4bs-captcha-mode" name="captcha_mode"><option value="inherit" <?php selected((string) ($settings['captcha_mode'] ?? 'inherit'), 'inherit'); ?>><?php esc_html_e('Use the site setting', 'help4-builder-suite'); ?></option><option value="disabled" <?php selected((string) ($settings['captcha_mode'] ?? 'inherit'), 'disabled'); ?>><?php esc_html_e('Do not use CAPTCHA', 'help4-builder-suite'); ?></option><option value="turnstile" <?php selected((string) ($settings['captcha_mode'] ?? 'inherit'), 'turnstile'); ?>><?php esc_html_e('Require Turnstile when site setup is complete', 'help4-builder-suite'); ?></option></select><span class="description"><?php esc_html_e('The site-wide provider is configured separately. No secret keys are stored in this form.', 'help4-builder-suite'); ?></span></label><label><?php esc_html_e('Internal slug', 'help4-builder-suite'); ?><input id="h4bs-form-slug" type="text" name="slug" value="<?php echo esc_attr($slug); ?>"></label><label><?php esc_html_e('Webhook URL', 'help4-builder-suite'); ?><input id="h4bs-webhook-url" type="url" name="webhook_url" value="<?php echo esc_attr((string) ($settings['webhook_url'] ?? '')); ?>"></label></div><label class="h4bs-check"><input type="checkbox" name="honeypot" value="1" <?php checked(! empty($settings['honeypot'])); ?>> <?php esc_html_e('Use invisible spam trap', 'help4-builder-suite'); ?></label><label class="h4bs-check"><input type="checkbox" name="send_autoresponder" value="1" <?php checked(! empty($settings['send_autoresponder'])); ?>> <?php esc_html_e('Send a confirmation email to the visitor', 'help4-builder-suite'); ?></label><div class="h4bs-form-grid-admin"><label><?php esc_html_e('Confirmation subject', 'help4-builder-suite'); ?><input type="text" name="autoresponder_subject" value="<?php echo esc_attr((string) ($settings['autoresponder_subject'] ?? 'Thanks for contacting us')); ?>"></label><label><?php esc_html_e('Confirmation message', 'help4-builder-suite'); ?><textarea name="autoresponder_body" rows="4"><?php echo esc_textarea((string) ($settings['autoresponder_body'] ?? 'We received your message and will reply soon.')); ?></textarea></label></div><details><summary><?php esc_html_e('Raw field data for extension compatibility', 'help4-builder-suite'); ?></summary><p class="description"><?php esc_html_e('Use this only when support asks. Visual edits preserve unknown field data.', 'help4-builder-suite'); ?></p><pre data-h4bs-raw-schema></pre></details></details>
            <div class="h4bs-form-savebar"><span role="status" aria-live="polite" data-h4bs-save-state><?php esc_html_e('All changes saved', 'help4-builder-suite'); ?></span><button type="submit" class="button button-primary button-hero" data-h4bs-save><?php esc_html_e('Save form', 'help4-builder-suite'); ?></button></div>
        </form>
        </div>
        <?php
    }

    private function renderAdminNotice(): void
    {
        $notice = AdminRequest::queryKey('h4bs_notice');
        if ($notice === '') {
            return;
        }
        $messages = [
            'form_saved' => ['success', __('Form saved.', 'help4-builder-suite')],
            'form_archived' => ['success', __('Form archived. Embeds now show an unavailable message; entries remain stored.', 'help4-builder-suite')],
            'form_restored' => ['success', __('Form restored as hidden. Review it before making it public again.', 'help4-builder-suite')],
            'delete_confirmation_failed' => ['error', __('The form name did not match. Nothing was deleted.', 'help4-builder-suite')],
            'form_save_failed' => ['error', __('The form could not be saved. Review your changes and try again.', 'help4-builder-suite')],
            'form_action_failed' => ['error', __('The form status could not be changed. Nothing was archived or restored; please try again.', 'help4-builder-suite')],
            'invalid_field_data' => ['error', __('Field data is invalid. Nothing was saved.', 'help4-builder-suite')],
            'invalid_field_keys' => ['error', __('Every question needs a unique internal key using only letters, numbers, dashes, or underscores. Nothing was saved.', 'help4-builder-suite')],
            'invalid_field_count' => ['error', __('Add at least one question and keep the form to 60 questions or fewer. Nothing was saved.', 'help4-builder-suite')],
            'invalid_field_defaults' => ['error', __('A default answer does not match its answer type or choices. Nothing was saved.', 'help4-builder-suite')],
            'form_name_required' => ['error', __('Form name is required.', 'help4-builder-suite')],
            'invalid_form' => ['error', __('Invalid form.', 'help4-builder-suite')],
            'newsletter_email_required' => ['error', __('Subscriber email is required.', 'help4-builder-suite')],
            'newsletter_saved' => ['success', __('Subscriber updated.', 'help4-builder-suite')],
            'newsletter_invalid' => ['error', __('Invalid subscriber.', 'help4-builder-suite')],
            'captcha_settings_saved' => ['success', __('CAPTCHA settings saved for this site.', 'help4-builder-suite')],
            'entry_updated' => ['success', __('Message work saved.', 'help4-builder-suite')],
            'entries_bulk_updated' => ['success', __('Selected messages updated together. No messages were permanently deleted.', 'help4-builder-suite')],
            'privacy_hold_added' => ['success', __('Privacy hold added. The stored message is excluded from privacy eligibility; it was not archived, deleted, or anonymized.', 'help4-builder-suite')],
            'privacy_hold_removed' => ['success', __('Privacy hold removed. The stored message will be evaluated by the current draft policy; no other action was applied.', 'help4-builder-suite')],
        ];
        if (! isset($messages[$notice])) {
            return;
        }
        [$type, $message] = $messages[$notice];
        echo '<div class="notice notice-' . esc_attr($type) . '"><p>' . esc_html($message) . '</p></div>';
    }

    private function adminRedirect(string $page, array $args = []): void
    {
        wp_safe_redirect(add_query_arg(array_merge(['page' => $page], $args), admin_url('admin.php')));
        exit;
    }

    private function canManageForms(): bool
    {
        return current_user_can('manage_help4_forms') || current_user_can('manage_options');
    }

    private function canViewEntries(): bool
    {
        return current_user_can('view_help4_form_entries') || current_user_can('manage_options');
    }

    private function canExportEntries(): bool
    {
        return current_user_can('export_help4_form_entries') || current_user_can('manage_options');
    }

    private function canManageEntries(): bool
    {
        return current_user_can('manage_help4_form_entries') || current_user_can('manage_options');
    }

    /** @return array{status:string,assigned_user_id:int,notes:array<int,array<string,mixed>>,archived_at:string,updated_at:string,updated_by:int} */
    private function entryOperations(array $entry): array
    {
        $payload = json_decode((string) ($entry['payload'] ?? '{}'), true);
        $raw = is_array($payload['_staff_operations'] ?? null) ? $payload['_staff_operations'] : [];
        $status = sanitize_key((string) ($entry['staff_status'] ?? $raw['status'] ?? 'new'));
        return [
            'status' => in_array($status, ['new', 'in_progress', 'waiting', 'resolved'], true) ? $status : 'new',
            'assigned_user_id' => absint($entry['assigned_user_id'] ?? $raw['assigned_user_id'] ?? 0),
            'notes' => is_array($raw['notes'] ?? null) ? array_values(array_filter($raw['notes'], 'is_array')) : [],
            'archived_at' => sanitize_text_field((string) ($entry['archived_at'] ?? $raw['archived_at'] ?? '')),
            'updated_at' => sanitize_text_field((string) ($raw['updated_at'] ?? '')),
            'updated_by' => absint($raw['updated_by'] ?? 0),
        ];
    }

    private function entryStatusLabel(string $status): string
    {
        return ['new' => __('New', 'help4-builder-suite'), 'in_progress' => __('In progress', 'help4-builder-suite'), 'waiting' => __('Waiting for reply', 'help4-builder-suite'), 'resolved' => __('Resolved', 'help4-builder-suite')][$status] ?? __('New', 'help4-builder-suite');
    }

    private function entryAssigneeName(int $userId): string
    {
        if ($userId <= 0) {
            return __('Unassigned', 'help4-builder-suite');
        }
        $user = get_userdata($userId);
        return $user instanceof \WP_User ? $user->display_name : __('Former staff member', 'help4-builder-suite');
    }

    private function canManageFormSettings(): bool
    {
        return current_user_can('manage_help4_form_settings') || current_user_can('manage_options');
    }

    private function denyAccess(): void
    {
        wp_die(
            esc_html__('You do not have permission to perform this Forms action.', 'help4-builder-suite'),
            esc_html__('Access denied', 'help4-builder-suite'),
            ['response' => 403]
        );
    }

    private function sanitizeEntryDate(string $value): string
    {
        $value = sanitize_text_field($value);
        if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $value) !== 1) {
            return '';
        }
        $date = \DateTimeImmutable::createFromFormat('!Y-m-d', $value);
        return $date instanceof \DateTimeImmutable && $date->format('Y-m-d') === $value ? $value : '';
    }

    /** @return array{0:string,1:array<int,int|string>} */
    private function entryFilterSql(int $formId, string $search, string $dateFrom, string $dateTo, string $entryView, string $status, int $assignee): array
    {
        global $wpdb;
        $where = [];
        $values = [];
        if ($formId > 0) {
            $where[] = 'e.form_id = %d';
            $values[] = $formId;
        }
        if ($entryView === 'current') {
            $where[] = 'e.archived_at IS NULL';
        } elseif ($entryView === 'archived') {
            $where[] = 'e.archived_at IS NOT NULL';
        }
        if (in_array($status, ['new', 'in_progress', 'waiting', 'resolved'], true)) {
            $where[] = 'e.staff_status = %s';
            $values[] = $status;
        }
        if ($assignee === -1) {
            $where[] = 'e.assigned_user_id = 0';
        } elseif ($assignee === -2) {
            $where[] = 'e.assigned_user_id = %d';
            $values[] = get_current_user_id();
        } elseif ($assignee > 0) {
            $where[] = 'e.assigned_user_id = %d';
            $values[] = $assignee;
        }
        if ($dateFrom !== '') {
            $where[] = 'e.created_at >= %s';
            $values[] = $dateFrom . ' 00:00:00';
        }
        if ($dateTo !== '') {
            $where[] = 'e.created_at <= %s';
            $values[] = $dateTo . ' 23:59:59';
        }
        if ($search !== '') {
            $like = '%' . $wpdb->esc_like($search) . '%';
            $where[] = '(e.name LIKE %s OR e.email LIKE %s OR e.phone LIKE %s OR e.subject LIKE %s OR e.payload LIKE %s OR f.name LIKE %s)';
            array_push($values, $like, $like, $like, $like, $like, $like);
        }
        return [$where ? ' WHERE ' . implode(' AND ', $where) : '', $values];
    }

    /** @return array<int, array<string, mixed>> */
    private function queryEntries(int $formId, string $search, string $dateFrom, string $dateTo, int $limit, string $entryView = 'all', string $status = 'all', int $assignee = 0, int $offset = 0): array
    {
        global $wpdb;
        [$whereSql, $filterValues] = $this->entryFilterSql($formId, $search, $dateFrom, $dateTo, $entryView, $status, $assignee);
        $sql = 'SELECT e.*, f.name AS form_name FROM %i e LEFT JOIN %i f ON f.id = e.form_id';
        $sql .= $whereSql . ' ORDER BY e.created_at DESC, e.id DESC LIMIT %d OFFSET %d';
        $values = array_merge([$this->entriesTable(), $this->formsTable()], $filterValues, [min(5000, max(1, $limit)), max(0, $offset)]);
        $rows = $wpdb->get_results($wpdb->prepare($sql, ...$values), ARRAY_A); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.PreparedSQL.InterpolatedNotPrepared,PluginCheck.Security.DirectDB.UnescapedDBParameter -- SQL fragments are fixed; every runtime value uses a typed placeholder.
        return is_array($rows) ? $rows : [];
    }

    private function countEntries(int $formId, string $search, string $dateFrom, string $dateTo, string $entryView = 'all', string $status = 'all', int $assignee = 0): int
    {
        global $wpdb;
        [$whereSql, $filterValues] = $this->entryFilterSql($formId, $search, $dateFrom, $dateTo, $entryView, $status, $assignee);
        $sql = 'SELECT COUNT(*) FROM %i e LEFT JOIN %i f ON f.id = e.form_id' . $whereSql;
        $values = array_merge([$this->entriesTable(), $this->formsTable()], $filterValues);
        return max(0, (int) $wpdb->get_var($wpdb->prepare($sql, ...$values))); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.PreparedSQL.InterpolatedNotPrepared,PluginCheck.Security.DirectDB.UnescapedDBParameter -- SQL fragments are fixed; every runtime value uses a typed placeholder.
    }

    /** @return array<string, int> */
    private function entryQueueCounts(): array
    {
        return [
            'new' => $this->countEntries(0, '', '', '', 'current', 'new', 0),
            'unassigned' => $this->countEntries(0, '', '', '', 'current', 'all', -1),
            'mine' => $this->countEntries(0, '', '', '', 'current', 'all', -2),
            'waiting' => $this->countEntries(0, '', '', '', 'current', 'waiting', 0),
            'resolved' => $this->countEntries(0, '', '', '', 'current', 'resolved', 0),
            'archived' => $this->countEntries(0, '', '', '', 'archived', 'all', 0),
        ];
    }

    private function safeCsvCell(string $value): string
    {
        $trimmed = ltrim($value);
        return $trimmed !== '' && in_array($trimmed[0], ['=', '+', '-', '@'], true) ? "'" . $value : $value;
    }

    /**
     * @param array<string, mixed> $payload
     * @return array<string, mixed>
     */
    private function processSubmission(array $payload): array
    {
        $form = $this->resolveForm(
            (string) ($payload['h4bs_form_id'] ?? $payload['form_id'] ?? ''),
            (string) ($payload['h4bs_form_slug'] ?? $payload['form_slug'] ?? ''),
            (string) ($payload['h4bs_form_type'] ?? $payload['form_type'] ?? 'contact')
        );
        if (! is_array($form)) {
            return ['ok' => false, 'code' => 'form_not_found', 'message' => __('Form not found.', 'help4-builder-suite')];
        }
        $formId = (int) ($form['id'] ?? 0);
        if ($formId <= 0) {
            return ['ok' => false, 'code' => 'form_not_found', 'message' => __('Form not found.', 'help4-builder-suite')];
        }

        $nonce = sanitize_text_field((string) ($payload['_h4bs_form_nonce'] ?? ''));
        if (! wp_verify_nonce($nonce, 'h4bs_form_' . $formId)) {
            return ['ok' => false, 'code' => 'security_check_failed', 'form_id' => $formId, 'message' => __('Security check failed.', 'help4-builder-suite')];
        }

        $settings = is_array($form['settings'] ?? null) ? $form['settings'] : [];
        $isSpam = ! empty($settings['honeypot']) && sanitize_text_field((string) ($payload['h4bs_hp'] ?? '')) !== '';
        $sentAt = max(0, (int) ($payload['h4bs_sent_at'] ?? 0));
        if ($sentAt > 0 && (time() - $sentAt) < 1) {
            $isSpam = true;
        }

        $fields = $this->normalizeFields($form['fields'] ?? [], (string) ($form['type'] ?? 'contact'));
        $values = [];
        $errors = [];
        foreach ($fields as $field) {
            $key = (string) ($field['key'] ?? '');
            if ($key === '' || $key === 'h4bs_hp') {
                continue;
            }
            $type = sanitize_key((string) ($field['type'] ?? 'text'));
            $required = ! empty($field['required']);
            $value = $this->sanitizeFieldValue($type, $payload[$key] ?? '');
            if ($required && trim($value) === '') {
                /* translators: %s: Form field label. */
                $errors[] = sprintf(__('%s is required.', 'help4-builder-suite'), (string) ($field['label'] ?? $key));
            }
            $values[$key] = $value;
        }
        if ($errors) {
            return ['ok' => false, 'code' => 'validation_failed', 'form_id' => $formId, 'message' => implode(' ', $errors), 'errors' => $errors];
        }

        if (! $isSpam) {
            $captchaResult = $this->captcha()->verify($payload, $formId, $settings, $this->requestIp());
            if (empty($captchaResult['ok'])) {
                $captchaResult['form_id'] = $formId;
                return $captchaResult;
            }
        }

        $name = sanitize_text_field((string) ($values['name'] ?? $values['full_name'] ?? $values['first_name'] ?? ''));
        $email = sanitize_email((string) ($values['email'] ?? $values['your_email'] ?? ''));
        $phone = sanitize_text_field((string) ($values['phone'] ?? $values['tel'] ?? ''));
        $subject = sanitize_text_field((string) ($values['subject'] ?? ''));

        global $wpdb;
        $entryInserted = $wpdb->insert(
            $this->entriesTable(),
            [
                'form_id' => $formId,
                'entry_type' => (string) ($form['type'] ?? 'submission'),
                'name' => $name,
                'email' => $email,
                'phone' => $phone,
                'subject' => $subject,
                'payload' => wp_json_encode(['fields' => $values]),
                'source_url' => esc_url_raw((string) ($payload['h4bs_source'] ?? wp_get_referer() ?: '')),
                'ip_address' => $this->requestIp(),
                'user_agent' => isset($_SERVER['HTTP_USER_AGENT']) ? sanitize_text_field((string) wp_unslash($_SERVER['HTTP_USER_AGENT'])) : '',
                'is_spam' => $isSpam ? 1 : 0,
                'timestamp_basis' => 'utc',
                'timestamp_basis_version' => H4BS_VERSION,
                'created_at' => current_time('mysql', true),
            ],
            ['%d', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%d', '%s', '%s', '%s']
        );
        $entryId = (int) $wpdb->insert_id;
        if ($entryInserted === false || $entryId <= 0) {
            return ['ok' => false, 'code' => 'entry_storage_failed', 'form_id' => $formId, 'message' => __('We could not safely store your message. Nothing was sent. Please keep your answers and try again.', 'help4-builder-suite')];
        }
        $delivery = [
            'staff_notice' => ['state' => empty($settings['notify_admin']) ? 'disabled' : ($isSpam ? 'skipped_spam' : 'pending')],
            'autoresponder' => ['state' => empty($settings['send_autoresponder']) ? 'disabled' : ($isSpam ? 'skipped_spam' : 'pending')],
            'webhook' => ['state' => empty($settings['webhook_url']) ? 'disabled' : ($isSpam ? 'skipped_spam' : 'pending')],
        ];

        if ((string) ($form['type'] ?? '') === 'newsletter' && $email !== '') {
            $this->upsertSubscriber(
                [
                    'email' => $email,
                    'first_name' => sanitize_text_field((string) ($values['first_name'] ?? '')),
                    'last_name' => sanitize_text_field((string) ($values['last_name'] ?? '')),
                    'tags' => sanitize_text_field((string) ($values['tags'] ?? '')),
                    'status' => 'subscribed',
                    'source' => esc_url_raw((string) ($payload['h4bs_source'] ?? wp_get_referer() ?: '')),
                ]
            );
        }

        if (! $isSpam && ! empty($settings['notify_admin'])) {
            $to = sanitize_email((string) ($settings['notify_email'] ?? ''));
            if ($to === '') {
                $to = sanitize_email((string) get_option('admin_email', ''));
            }
            if ($to !== '') {
                /* translators: %s: Form name. */
                $subjectLine = '[' . wp_specialchars_decode((string) get_bloginfo('name'), ENT_QUOTES) . '] ' . sprintf(__('New %s submission', 'help4-builder-suite'), (string) ($form['name'] ?? 'Form'));
                $lines = [];
                foreach ($values as $key => $value) {
                    $lines[] = $key . ': ' . $value;
                }
                $lines[] = 'Entry ID: ' . $entryId;
                $lines[] = 'IP: ' . $this->requestIp();
                $delivery['staff_notice'] = ['state' => 'attempted', 'accepted_by_mail_transport' => wp_mail($to, $subjectLine, implode("\n", $lines))];
            } else {
                $delivery['staff_notice'] = ['state' => 'skipped_missing_recipient'];
            }
        }

        if (! $isSpam && ! empty($settings['send_autoresponder']) && $email !== '') {
            $autoSubject = sanitize_text_field((string) ($settings['autoresponder_subject'] ?? 'Thanks for contacting us'));
            $autoBody = sanitize_textarea_field((string) ($settings['autoresponder_body'] ?? 'We received your message.'));
            if ($autoSubject !== '' && $autoBody !== '') {
                $delivery['autoresponder'] = ['state' => 'attempted', 'accepted_by_mail_transport' => wp_mail($email, $autoSubject, $autoBody)];
            } else {
                $delivery['autoresponder'] = ['state' => 'skipped_incomplete_message'];
            }
        } elseif (! $isSpam && ! empty($settings['send_autoresponder'])) {
            $delivery['autoresponder'] = ['state' => 'skipped_missing_visitor_email'];
        }

        $webhook = esc_url_raw((string) ($settings['webhook_url'] ?? ''));
        if (! $isSpam && $webhook !== '' && $this->isSafeWebhookUrl($webhook)) {
            $webhookResponse = wp_safe_remote_post(
                $webhook,
                [
                    'timeout' => 7,
                    'headers' => ['Content-Type' => 'application/json'],
                    'body' => wp_json_encode(['form_id' => $formId, 'entry_id' => $entryId, 'fields' => $values]),
                ]
            );
            $delivery['webhook'] = is_wp_error($webhookResponse)
                ? ['state' => 'attempted', 'error_code' => sanitize_key($webhookResponse->get_error_code())]
                : ['state' => 'attempted', 'http_code' => (int) wp_remote_retrieve_response_code($webhookResponse)];
        } elseif (! $isSpam && ! empty($settings['webhook_url'])) {
            $delivery['webhook'] = ['state' => 'skipped_invalid_address'];
        }

        $wpdb->update(
            $this->entriesTable(),
            ['payload' => wp_json_encode(['fields' => $values, 'delivery' => $delivery])],
            ['id' => $entryId],
            ['%s'],
            ['%d']
        );

        return [
            'ok' => true,
            'form_id' => $formId,
            'entry_id' => $entryId,
            'message' => sanitize_text_field((string) ($settings['success_message'] ?? __('Thanks! We received your request.', 'help4-builder-suite'))),
        ];
    }

    /**
     * @param array<string, mixed> $form
     * @param array<string, mixed> $atts
     */
    private function renderFormMarkup(array $form, array $atts): string
    {
        $formId = (int) ($form['id'] ?? 0);
        $formType = sanitize_key((string) ($form['type'] ?? 'contact'));
        $settings = is_array($form['settings'] ?? null) ? $form['settings'] : [];
        $fields = $this->normalizeFields($form['fields'] ?? [], $formType);

        $status = AdminRequest::queryKey('h4bs_form_status');
        $statusFormId = AdminRequest::queryAbsInt('h4bs_form_id');
        $errorCode = AdminRequest::queryKey('h4bs_form_error');
        $noticeHtml = '';
        if ($statusFormId === $formId && in_array($status, ['success', 'error'], true)) {
            $message = $status === 'success'
                ? (string) ($settings['success_message'] ?? __('Thanks! We received your request.', 'help4-builder-suite'))
                : $this->submissionErrorMessage($errorCode, $settings);
            $role = $status === 'error' ? 'alert' : 'status';
            $noticeHtml = '<div class="h4bs-form-notice is-' . esc_attr($status) . '" role="' . esc_attr($role) . '" tabindex="-1">' . esc_html($message) . '</div>';
        }

        $ajax = ! isset($atts['ajax']) || (string) $atts['ajax'] !== '0';
        $showTitle = (string) ($atts['title'] ?? '0') === '1';
        $redirect = esc_url_raw((string) ($atts['redirect'] ?? ''));
        if ($redirect === '') {
            $redirect = home_url(isset($_SERVER['REQUEST_URI']) ? sanitize_text_field((string) wp_unslash($_SERVER['REQUEST_URI'])) : '/');
        }
        $wrapperClass = 'h4bs-form h4bs-form--' . sanitize_html_class($formType);
        $extraClass = sanitize_html_class((string) ($atts['class'] ?? ''));
        if ($extraClass !== '') {
            $wrapperClass .= ' ' . $extraClass;
        }

        $captcha = $this->captcha()->runtime($settings);
        if (! empty($captcha['enabled'])) {
            wp_enqueue_script('h4bs-turnstile');
        }

        ob_start();
        ?>
        <div class="<?php echo esc_attr($wrapperClass); ?>">
            <?php if ($showTitle) : ?>
                <h3 class="h4bs-form-title"><?php echo esc_html((string) ($form['name'] ?? 'Form')); ?></h3>
            <?php endif; ?>
            <?php echo wp_kses_post($noticeHtml); ?>

            <form method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>" data-h4bs-form="1" data-h4bs-form-ajax="<?php echo $ajax ? '1' : '0'; ?>" data-h4bs-captcha="<?php echo esc_attr(! empty($captcha['enabled']) ? 'turnstile' : 'disabled'); ?>">
                <input type="hidden" name="action" value="h4bs_form_submit">
                <input type="hidden" name="_h4bs_form_nonce" value="<?php echo esc_attr(wp_create_nonce('h4bs_form_' . $formId)); ?>">
                <input type="hidden" name="h4bs_form_id" value="<?php echo esc_attr((string) $formId); ?>">
                <input type="hidden" name="h4bs_form_slug" value="<?php echo esc_attr((string) ($form['slug'] ?? '')); ?>">
                <input type="hidden" name="h4bs_form_type" value="<?php echo esc_attr($formType); ?>">
                <input type="hidden" name="h4bs_sent_at" value="<?php echo esc_attr((string) time()); ?>">
                <input type="hidden" name="h4bs_redirect" value="<?php echo esc_attr($redirect); ?>">
                <input type="hidden" name="h4bs_source" value="<?php echo esc_attr(home_url(isset($_SERVER['REQUEST_URI']) ? sanitize_text_field((string) wp_unslash($_SERVER['REQUEST_URI'])) : '/')); ?>">
                <?php if (! empty($settings['honeypot'])) : ?>
                    <input class="h4bs-form-honeypot" type="text" name="h4bs_hp" value="" tabindex="-1" autocomplete="off" aria-hidden="true">
                <?php endif; ?>

                <div class="h4bs-form-grid">
                    <?php foreach ($fields as $field) : ?>
                        <?php
                        $key = (string) ($field['key'] ?? '');
                        if ($key === '' || $key === 'h4bs_hp') {
                            continue;
                        }
                        $type = sanitize_key((string) ($field['type'] ?? 'text'));
                        $required = ! empty($field['required']);
                        $width = (string) ($field['width'] ?? '100');
                        if (! in_array($width, ['25', '33', '50', '66', '75', '100'], true)) {
                            $width = '100';
                        }
                        $id = 'h4bs-form-' . $formId . '-' . sanitize_html_class($key);
                        $label = (string) ($field['label'] ?? $key);
                        $placeholder = (string) ($field['placeholder'] ?? '');
                        $default = (string) ($field['default'] ?? '');
                        if ($type === 'hidden') :
                            ?>
                            <input type="hidden" name="<?php echo esc_attr($key); ?>" value="<?php echo esc_attr($default); ?>">
                            <?php
                            continue;
                        endif;
                        ?>
                        <div class="h4bs-form-field h4bs-form-field--<?php echo esc_attr($width); ?>">
                            <?php if ($type !== 'checkbox') : ?>
                                <label for="<?php echo esc_attr($id); ?>"><?php echo esc_html($label); ?><?php if ($required) : ?> <span aria-hidden="true">*</span><?php endif; ?></label>
                            <?php endif; ?>
                            <?php if ($type === 'textarea') : ?>
                                <textarea id="<?php echo esc_attr($id); ?>" name="<?php echo esc_attr($key); ?>" placeholder="<?php echo esc_attr($placeholder); ?>" <?php echo $required ? 'required' : ''; ?>><?php echo esc_textarea($default); ?></textarea>
                            <?php elseif ($type === 'select') : ?>
                                <select id="<?php echo esc_attr($id); ?>" name="<?php echo esc_attr($key); ?>" <?php echo $required ? 'required' : ''; ?>>
                                    <option value=""><?php esc_html_e('Select...', 'help4-builder-suite'); ?></option>
                                    <?php foreach ($this->formFieldOptions((string) ($field['options'] ?? '')) as $option) : ?>
                                        <option value="<?php echo esc_attr($option); ?>" <?php selected($default, $option); ?>><?php echo esc_html($option); ?></option>
                                    <?php endforeach; ?>
                                </select>
                            <?php elseif ($type === 'checkbox') : ?>
                                <label for="<?php echo esc_attr($id); ?>" class="h4bs-checkbox-label">
                                    <input id="<?php echo esc_attr($id); ?>" type="checkbox" name="<?php echo esc_attr($key); ?>" value="1" <?php checked(in_array(strtolower($default), ['1', 'true', 'yes', 'on'], true)); ?> <?php echo $required ? 'required' : ''; ?>>
                                    <span><?php echo esc_html($label); ?><?php if ($required) : ?> <span aria-hidden="true">*</span><?php endif; ?></span>
                                </label>
                            <?php else : ?>
                                <?php $htmlType = in_array($type, ['text', 'email', 'tel', 'url', 'date', 'time', 'number'], true) ? $type : 'text'; ?>
                                <input id="<?php echo esc_attr($id); ?>" type="<?php echo esc_attr($htmlType); ?>" name="<?php echo esc_attr($key); ?>" value="<?php echo esc_attr($default); ?>" placeholder="<?php echo esc_attr($placeholder); ?>" <?php echo $required ? 'required' : ''; ?>>
                            <?php endif; ?>
                        </div>
                    <?php endforeach; ?>
                </div>
                <?php if (! empty($captcha['enabled'])) : ?>
                    <?php
                    $captchaLabelId = 'h4bs-form-' . $formId . '-captcha-label';
                    $captchaHelpId = 'h4bs-form-' . $formId . '-captcha-help';
                    ?>
                    <div class="h4bs-form-captcha" role="group" aria-labelledby="<?php echo esc_attr($captchaLabelId); ?>" aria-describedby="<?php echo esc_attr($captchaHelpId); ?>">
                        <p class="h4bs-form-captcha-label" id="<?php echo esc_attr($captchaLabelId); ?>"><?php esc_html_e('Security verification', 'help4-builder-suite'); ?></p>
                        <div class="cf-turnstile" data-sitekey="<?php echo esc_attr((string) ($captcha['site_key'] ?? '')); ?>" data-theme="<?php echo esc_attr((string) ($captcha['theme'] ?? 'auto')); ?>" data-size="<?php echo esc_attr((string) ($captcha['size'] ?? 'flexible')); ?>" data-action="<?php echo esc_attr('h4bs_form_' . $formId); ?>"></div>
                        <p class="h4bs-form-captcha-help" id="<?php echo esc_attr($captchaHelpId); ?>"><?php esc_html_e('Complete this privacy-preserving check before submitting the form.', 'help4-builder-suite'); ?></p>
                        <noscript><p class="h4bs-form-captcha-noscript" role="alert"><?php esc_html_e('JavaScript is required for this security check. Please enable JavaScript or contact the site owner using another published method.', 'help4-builder-suite'); ?></p></noscript>
                    </div>
                <?php endif; ?>
                <div class="h4bs-form-actions">
                    <button type="submit" class="h4bs-form-submit"><?php echo esc_html((string) ($settings['button_label'] ?? 'Submit')); ?></button>
                    <span class="h4bs-form-live-message" data-h4bs-form-message role="status" aria-live="polite" aria-atomic="true" tabindex="-1"></span>
                </div>
            </form>
        </div>
        <?php
        return (string) ob_get_clean();
    }

    /**
     * @param mixed $value
     */
    private function sanitizeFieldValue(string $type, $value): string
    {
        if (is_array($value)) {
            $value = implode(', ', array_map('strval', $value));
        }
        $raw = (string) $value;
        return match ($type) {
            'email' => sanitize_email($raw),
            'url' => esc_url_raw($raw),
            'textarea' => sanitize_textarea_field($raw),
            'number' => (string) (float) $raw,
            'checkbox' => ! empty($raw) ? '1' : '0',
            default => sanitize_text_field($raw),
        };
    }

    /**
     * @param array<string, mixed> $payload
     */
    private function upsertSubscriber(array $payload): void
    {
        $email = sanitize_email((string) ($payload['email'] ?? ''));
        if ($email === '') {
            return;
        }

        global $wpdb;
        $existingId = (int) $wpdb->get_var($wpdb->prepare('SELECT id FROM %i WHERE email = %s LIMIT 1', $this->newsletterTable(), $email));
        $context = (string) ($payload['source'] ?? '') === 'admin' ? 'admin' : 'public';
        $filteredPayload = apply_filters('h4bs_newsletter_subscriber_payload', $payload, $existingId, $context);
        if (is_array($filteredPayload)) {
            $payload = $filteredPayload;
        }
        $row = [
            'email' => $email,
            'first_name' => sanitize_text_field((string) ($payload['first_name'] ?? '')),
            'last_name' => sanitize_text_field((string) ($payload['last_name'] ?? '')),
            'status' => sanitize_key((string) ($payload['status'] ?? 'subscribed')),
            'tags' => sanitize_text_field((string) ($payload['tags'] ?? '')),
            'source' => sanitize_text_field((string) ($payload['source'] ?? '')),
            'ip_address' => sanitize_text_field((string) ($payload['ip_address'] ?? $this->requestIp())),
            'payload' => wp_json_encode($payload),
        ];
        if (! in_array($row['status'], ['pending', 'subscribed', 'unsubscribed', 'bounced', 'erased'], true)) {
            $row['status'] = 'subscribed';
        }

        if ($existingId > 0) {
            $wpdb->update($this->newsletterTable(), $row, ['id' => $existingId], ['%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s'], ['%d']);
            do_action('h4bs_newsletter_subscriber_saved', $existingId, $row, true, $context);
            return;
        }
        $wpdb->insert($this->newsletterTable(), $row, ['%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s']);
        $subscriberId = (int) $wpdb->insert_id;
        if ($subscriberId > 0) {
            do_action('h4bs_newsletter_subscriber_saved', $subscriberId, $row, false, $context);
        }
    }

    /**
     * @return array<string, mixed>|null
     */
    private function resolveForm(string $idRaw, string $slugRaw, string $typeRaw): ?array
    {
        global $wpdb;
        $id = absint($idRaw);
        if ($id > 0) {
            $row = $wpdb->get_row($wpdb->prepare('SELECT * FROM %i WHERE id = %d LIMIT 1', $this->formsTable(), $id), ARRAY_A);
            if (is_array($row) && (string) ($row['status'] ?? '') === 'active') {
                return $this->hydrateForm($row);
            }
            return null;
        }

        $slug = sanitize_title($slugRaw);
        if ($slug !== '') {
            $row = $wpdb->get_row($wpdb->prepare('SELECT * FROM %i WHERE slug = %s LIMIT 1', $this->formsTable(), $slug), ARRAY_A);
            if (is_array($row) && (string) ($row['status'] ?? '') === 'active') {
                return $this->hydrateForm($row);
            }
            return null;
        }

        $type = sanitize_key($typeRaw);
        if (! in_array($type, ['contact', 'newsletter', 'quote', 'callback', 'booking', 'custom'], true)) {
            $type = 'contact';
        }
        $row = $wpdb->get_row($wpdb->prepare("SELECT * FROM %i WHERE type = %s AND status = 'active' ORDER BY id ASC LIMIT 1", $this->formsTable(), $type), ARRAY_A);
        if (is_array($row)) {
            return $this->hydrateForm($row);
        }
        $fallback = $wpdb->get_row($wpdb->prepare("SELECT * FROM %i WHERE status = 'active' ORDER BY id ASC LIMIT 1", $this->formsTable()), ARRAY_A);
        return is_array($fallback) ? $this->hydrateForm($fallback) : null;
    }

    /**
     * @param array<string, mixed> $row
     * @return array<string, mixed>
     */
    private function hydrateForm(array $row): array
    {
        $fields = json_decode((string) ($row['fields_json'] ?? '[]'), true);
        $settings = json_decode((string) ($row['settings_json'] ?? '{}'), true);
        return [
            'id' => (int) ($row['id'] ?? 0),
            'slug' => sanitize_title((string) ($row['slug'] ?? '')),
            'name' => sanitize_text_field((string) ($row['name'] ?? '')),
            'type' => sanitize_key((string) ($row['type'] ?? 'contact')),
            'status' => sanitize_key((string) ($row['status'] ?? 'active')),
            'fields' => is_array($fields) ? $fields : [],
            'settings' => is_array($settings) ? $settings : [],
        ];
    }

    /**
     * @return array<int, array<string, mixed>>
     */
    private function decodeFieldsJson(string $raw, string $type): array
    {
        $decoded = json_decode($raw, true);
        if (! is_array($decoded)) {
            $decoded = [];
        }
        return $this->normalizeFields($decoded, $type);
    }

    /**
     * @param mixed $fields
     * @return array<int, array<string, mixed>>
     */
    private function normalizeFields($fields, string $type): array
    {
        if (! is_array($fields)) {
            $fields = [];
        }
        $normalized = [];
        foreach ($fields as $field) {
            if (! is_array($field)) {
                continue;
            }
            $key = sanitize_key((string) ($field['key'] ?? ''));
            if ($key === '') {
                continue;
            }
            $fieldType = sanitize_key((string) ($field['type'] ?? 'text'));
            if ($fieldType === '') {
                $fieldType = 'text';
            }
            $width = sanitize_text_field((string) ($field['width'] ?? '100'));
            if (! in_array($width, ['25', '33', '50', '66', '75', '100'], true)) {
                $width = '100';
            }
            $normalized[] = array_merge($this->sanitizeFieldExtensionData($field), [
                'key' => $key,
                'label' => sanitize_text_field((string) ($field['label'] ?? $key)),
                'type' => $fieldType,
                'required' => ! empty($field['required']) ? '1' : '0',
                'placeholder' => sanitize_text_field((string) ($field['placeholder'] ?? '')),
                'options' => sanitize_textarea_field((string) ($field['options'] ?? '')),
                'default' => sanitize_text_field((string) ($field['default'] ?? '')),
                'width' => $width,
            ]);
            if (count($normalized) >= 60) {
                break;
            }
        }

        if ($normalized) {
            return $normalized;
        }

        return match ($type) {
            'newsletter' => [
                ['key' => 'email', 'label' => 'Email', 'type' => 'email', 'required' => '1', 'placeholder' => '', 'options' => '', 'default' => '', 'width' => '100'],
                ['key' => 'marketing_consent', 'label' => 'I want to receive email updates and can unsubscribe at any time. The site may measure opens and link clicks when engagement reporting is enabled.', 'type' => 'checkbox', 'required' => '1', 'placeholder' => '', 'options' => '', 'default' => '', 'width' => '100'],
            ],
            'callback' => [
                ['key' => 'name', 'label' => 'Name', 'type' => 'text', 'required' => '1', 'placeholder' => '', 'options' => '', 'default' => '', 'width' => '50'],
                ['key' => 'phone', 'label' => 'Phone', 'type' => 'tel', 'required' => '1', 'placeholder' => '', 'options' => '', 'default' => '', 'width' => '50'],
            ],
            'booking' => [
                ['key' => 'name', 'label' => 'Name', 'type' => 'text', 'required' => '1', 'placeholder' => '', 'options' => '', 'default' => '', 'width' => '50'],
                ['key' => 'email', 'label' => 'Email', 'type' => 'email', 'required' => '1', 'placeholder' => '', 'options' => '', 'default' => '', 'width' => '50'],
                ['key' => 'date', 'label' => 'Date', 'type' => 'date', 'required' => '1', 'placeholder' => '', 'options' => '', 'default' => '', 'width' => '50'],
                ['key' => 'time', 'label' => 'Time', 'type' => 'time', 'required' => '1', 'placeholder' => '', 'options' => '', 'default' => '', 'width' => '50'],
            ],
            default => [
                ['key' => 'name', 'label' => 'Name', 'type' => 'text', 'required' => '1', 'placeholder' => '', 'options' => '', 'default' => '', 'width' => '50'],
                ['key' => 'email', 'label' => 'Email', 'type' => 'email', 'required' => '1', 'placeholder' => '', 'options' => '', 'default' => '', 'width' => '50'],
                ['key' => 'message', 'label' => 'Message', 'type' => 'textarea', 'required' => '1', 'placeholder' => '', 'options' => '', 'default' => '', 'width' => '100'],
            ],
        };
    }

    /**
     * Preserve extension-owned field data while keeping stored values safe.
     *
     * @param array<string, mixed> $field
     * @return array<string, mixed>
     */
    private function sanitizeFieldExtensionData(array $field): array
    {
        $clean = [];
        foreach ($field as $key => $value) {
            $safeKey = (string) $key;
            if ($safeKey === '' || strlen($safeKey) > 80 || preg_match('/^[A-Za-z0-9_.:-]+$/', $safeKey) !== 1) {
                continue;
            }
            if (is_array($value)) {
                $clean[$safeKey] = $this->sanitizeFieldExtensionData($value);
            } elseif (is_bool($value) || is_numeric($value)) {
                $clean[$safeKey] = $value;
            } elseif (is_scalar($value)) {
                $clean[$safeKey] = sanitize_textarea_field((string) $value);
            }
        }
        return $clean;
    }

    private function deliveryStateLabel(string $state): string
    {
        return match ($state) {
            'disabled' => __('Off', 'help4-builder-suite'),
            'skipped_spam' => __('Skipped because the message was classified as spam', 'help4-builder-suite'),
            'skipped_missing_recipient' => __('Skipped — no staff recipient is configured', 'help4-builder-suite'),
            'skipped_missing_visitor_email' => __('Skipped — visitor email is missing', 'help4-builder-suite'),
            'skipped_incomplete_message' => __('Skipped — confirmation subject or message is incomplete', 'help4-builder-suite'),
            'skipped_invalid_address' => __('Skipped — webhook address is invalid or unsafe', 'help4-builder-suite'),
            'pending' => __('Attempt was not completed', 'help4-builder-suite'),
            default => __('Not recorded', 'help4-builder-suite'),
        };
    }

    /**
     * Reject obvious local/private targets before WordPress performs any HTTP work.
     * wp_safe_remote_post remains the second, DNS-aware validation layer.
     */
    private function isSafeWebhookUrl(string $url): bool
    {
        if (wp_http_validate_url($url) === false) {
            return false;
        }
        $host = strtolower((string) wp_parse_url($url, PHP_URL_HOST));
        if ($host === '' || $host === 'localhost' || str_ends_with($host, '.localhost')) {
            return false;
        }
        $ip = trim($host, '[]');
        if (filter_var($ip, FILTER_VALIDATE_IP) !== false) {
            return filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) !== false;
        }
        return true;
    }

    /**
     * Content-based optimistic-lock token; safe even when DB timestamps share a second.
     *
     * @param array<string, mixed> $row
     */
    private function formRevision(array $row): string
    {
        return hash('sha256', (string) wp_json_encode([
            'id' => (int) ($row['id'] ?? 0),
            'slug' => (string) ($row['slug'] ?? ''),
            'name' => (string) ($row['name'] ?? ''),
            'type' => (string) ($row['type'] ?? ''),
            'status' => (string) ($row['status'] ?? ''),
            'fields_json' => (string) ($row['fields_json'] ?? ''),
            'settings_json' => (string) ($row['settings_json'] ?? ''),
        ]));
    }

    /**
     * Persist an archive/restore state without treating a database error as success.
     */
    private function setFormStatus(int $id, string $status): bool
    {
        if ($id <= 0 || ! in_array($status, ['archived', 'inactive'], true)) {
            return false;
        }
        global $wpdb;
        return $wpdb->update(
            $this->formsTable(),
            ['status' => $status],
            ['id' => $id],
            ['%s'],
            ['%d']
        ) !== false;
    }

    /** @return array<int, string> */
    private function formFieldOptions(string $raw): array
    {
        $parts = preg_split('/(?:\r\n|\r|\n|\|)+/', $raw) ?: [];
        return array_values(array_filter(array_map('trim', $parts), static fn (string $value): bool => $value !== ''));
    }

    /**
     * @return array<string, string>
     */
    private function defaultFormSettings(string $button): array
    {
        return [
            'button_label' => $button,
            'success_message' => __('Thanks! We received your request.', 'help4-builder-suite'),
            'error_message' => __('Please check required fields and try again.', 'help4-builder-suite'),
            'notify_admin' => '1',
            'notify_email' => '',
            'send_autoresponder' => '0',
            'autoresponder_subject' => __('Thanks for contacting us', 'help4-builder-suite'),
            'autoresponder_body' => __('We received your message and will respond soon.', 'help4-builder-suite'),
            'webhook_url' => '',
            'honeypot' => '1',
            'captcha_mode' => 'inherit',
        ];
    }

    private function captcha(): Captcha
    {
        if (! $this->captchaService instanceof Captcha) {
            $this->captchaService = new Captcha();
        }
        return $this->captchaService;
    }

    private function sanitizeCaptchaMode(string $mode): string
    {
        $mode = sanitize_key($mode);
        return in_array($mode, ['inherit', 'disabled', 'turnstile'], true) ? $mode : 'inherit';
    }

    /**
     * @param array<string, mixed> $result
     */
    private function submissionStatus(array $result): int
    {
        $status = (int) ($result['status'] ?? 422);
        return $status >= 400 && $status <= 599 ? $status : 422;
    }

    /**
     * @param array<string, mixed> $settings
     */
    private function submissionErrorMessage(string $code, array $settings): string
    {
        return match ($code) {
            'captcha_required' => __('Please complete the security verification and try again.', 'help4-builder-suite'),
            'captcha_invalid' => __('Security verification was not accepted. Please try again.', 'help4-builder-suite'),
            'captcha_unavailable' => __('Security verification is temporarily unavailable. Please try again later.', 'help4-builder-suite'),
            'security_check_failed' => __('The form security check expired. Please refresh the page and try again.', 'help4-builder-suite'),
            default => sanitize_text_field((string) ($settings['error_message'] ?? __('Please check required fields and try again.', 'help4-builder-suite'))),
        };
    }

    private function viewHasManagedForm(): bool
    {
        if (! is_singular()) {
            return false;
        }
        $postId = get_queried_object_id();
        if (! $postId) {
            return false;
        }
        $shortcodes = ['h4_form', 'h4_contact_form', 'h4_newsletter_form', 'h4_quote_request', 'h4_callback_request', 'h4_booking'];
        $content = (string) get_post_field('post_content', $postId);
        if ($content !== '') {
            foreach ($shortcodes as $tag) {
                if (has_shortcode($content, $tag)) {
                    return true;
                }
            }
        }

        $builderEnabled = (string) get_post_meta($postId, '_h4_builder_enabled', true) === '1'
            && (string) get_post_meta($postId, '_h4_builder_mode', true) === 'help4';
        if (! $builderEnabled) {
            return false;
        }
        $layout = json_decode((string) get_post_meta($postId, '_h4_builder_layout', true), true);
        if (! is_array($layout)) {
            return false;
        }
        foreach ($layout as $block) {
            if (! is_array($block) || (string) ($block['type'] ?? '') !== 'shortcode') {
                continue;
            }
            $settings = is_array($block['settings'] ?? null) ? $block['settings'] : [];
            $sc = (string) ($settings['shortcode'] ?? '');
            foreach ($shortcodes as $tag) {
                if ($sc !== '' && has_shortcode($sc, $tag)) {
                    return true;
                }
            }
        }
        return false;
    }

    private function requestIp(): string
    {
        return IpTools::currentIp();
    }

    private function formUsageCount(int $id, string $slug): int
    {
        static $cache = [];
        if (isset($cache[$id])) {
            return $cache[$id];
        }
        global $wpdb;
        $needles = ['[h4_form id="' . $id . '"', '[h4_form id=' . $id, '"h4bs_form_id":"' . $id . '"'];
        if ($slug !== '') {
            $needles[] = '[h4_form slug="' . sanitize_title($slug) . '"';
        }
        $ids = [];
        foreach ($needles as $needle) {
            $postIds = $wpdb->get_col($wpdb->prepare('SELECT ID FROM %i WHERE post_content LIKE %s', $wpdb->posts, '%' . $wpdb->esc_like($needle) . '%'));
            $metaIds = $wpdb->get_col($wpdb->prepare('SELECT post_id FROM %i WHERE meta_key = %s AND meta_value LIKE %s', $wpdb->postmeta, '_h4_builder_layout', '%' . $wpdb->esc_like($needle) . '%'));
            foreach (array_merge(is_array($postIds) ? $postIds : [], is_array($metaIds) ? $metaIds : []) as $postId) {
                $ids[(int) $postId] = true;
            }
        }
        $cache[$id] = count($ids);
        return $cache[$id];
    }

    private function formsTable(): string
    {
        global $wpdb;
        return $wpdb->prefix . 'h4_forms';
    }

    private function entriesTable(): string
    {
        global $wpdb;
        return $wpdb->prefix . 'h4_form_entries';
    }

    private function newsletterTable(): string
    {
        global $wpdb;
        return $wpdb->prefix . 'h4_newsletter_subscribers';
    }
}
