<?php
/**
 * Settings UI schema provider.
 *
 * @package NimblyMail\SMTP
 */

namespace NimblyMail\SMTP;

use NimblyMail\SMTP\Admin\SetupWizard;
use NimblyMail\SMTP\Controls\EmailControls;
use NimblyMail\SMTP\Rest\SettingsController;

defined( 'ABSPATH' ) || exit;

/**
 * Class SettingsProvider
 *
 * Provides the structured schema for the Vue frontend settings tabs.
 *
 * @since 1.0.0
 */
class SettingsProvider {

    /**
     * Singleton instance.
     *
     * @var self|null
     */
    private static ?self $instance = null;

    /**
     * Get the singleton instance.
     *
     * @since 1.0.0
     *
     * @return self
     */
    public static function get_instance(): self {
        if ( null === self::$instance ) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    /**
     * Get the full schema for all tabs, merged with current values.
     *
     * The $context parameter controls which value tier is surfaced per field
     * and which fields are included:
     *   - 'site' (default): effective value via Options::get() with the full
     *     inheritance chain applied; includes every field.
     *   - 'network': values come from the network layer only (no site
     *     overrides); OAuth token fields that are intrinsically per-site
     *     (e.g. gmail.access_token) are omitted.
     *   - 'network-site': effective value for a specific sub-site when
     *     editing overrides from Network Admin. Future use; currently
     *     behaves like 'site'.
     *
     * After the `nimblymail_get_settings_tabs` filter runs, tabs are sorted
     * ascending by their `position` key. Tabs without a `position` sort last.
     *
     * @since 1.0.0
     *
     * @param string $context One of 'site', 'network', 'network-site'.
     *
     * @return array
     */
    public function get_tabs( string $context = 'site' ): array {
        $tabs = array(
            $this->get_general_tab(),
            $this->get_connection_tab(),
            $this->get_email_log_tab(),
            $this->get_mail_queue_tab(),
            $this->get_controls_tab( $context ),
            $this->get_alerts_tab(),
            $this->get_sandbox_tab(),
            $this->get_misc_tab(),
        );

        $should_show_upgrade = Plugin::instance()->can_show_upsell( $context );

        /**
         * Filter whether the Upgrade tab is visible.
         *
         * Defaults to true when Pro is inactive and the audience can act on
         * the upsell (non-multisite, network admin context, or super admin
         * browsing a per-site screen). Hook with __return_false to suppress
         * the tab entirely on white-label distributions.
         *
         * @since 1.0.0
         *
         * @param bool   $show    Whether the Upgrade tab is visible.
         * @param string $context Resolution context: 'site' | 'network' | 'network-site'.
         */
        $should_show_upgrade = (bool) apply_filters( 'nimblymail_show_upgrade_tab', $should_show_upgrade, $context );

        if ( $should_show_upgrade ) {
            $tabs[] = $this->get_upgrade_tab_payload( $context );
        }

        /**
         * Filter the raw settings tabs before field-value enrichment.
         *
         * Runs before {@see self::normalize_section_fields()} so Pro add-ons
         * that append tabs (License, Backup, Reports, Queue, mailer-specific
         * tabs) receive the same `value` / `is_const` / `is_locked` / `source`
         * treatment as the built-in tabs. Normalising before the filter left
         * every Pro field with NULL meta, so the Vue app couldn't bind saved
         * values back into the inputs on reload.
         */
        $tabs = apply_filters( 'nimblymail_get_settings_tabs', $tabs, $context );

        usort(
            $tabs,
            function ( array $a, array $b ): int {
                $pos_a = $a['position'] ?? PHP_INT_MAX;
                $pos_b = $b['position'] ?? PHP_INT_MAX;
                return $pos_a <=> $pos_b;
            }
        );

        foreach ( $tabs as &$tab ) {
            if ( empty( $tab['sections'] ) || ! is_array( $tab['sections'] ) ) {
                continue;
            }
            foreach ( $tab['sections'] as &$section ) {
                if ( empty( $section['fields'] ) || ! is_array( $section['fields'] ) ) {
                    continue;
                }
                $section['fields'] = $this->normalize_section_fields( $section['fields'], $context );
            }
            unset( $section );
        }
        unset( $tab );

        return $tabs;
    }

    /**
     * Normalize the fields of a single section, dropping any that aren't
     * applicable in the given context.
     *
     * @since 1.0.0
     *
     * @param array  $fields  Raw section field schemas.
     * @param string $context Resolution context — see get_tabs().
     *
     * @return array Normalized and filtered field list.
     */
    private function normalize_section_fields( array $fields, string $context ): array {
        $result = array();

        foreach ( $fields as $field ) {
            if ( $this->skip_field_for_context( $field, $context ) ) {
                continue;
            }
            $result[] = $this->normalize_field( $field, $context );
        }

        return $result;
    }

    /**
     * Decide whether a field should be hidden for a given context.
     *
     * OAuth access and refresh tokens are per-site by design — the network
     * admin never stores them centrally. Suppress them from the network
     * editor so super admins aren't confused by fields they can't meaningfully
     * configure from there.
     *
     * @since 1.0.0
     *
     * @param array  $field   Field schema.
     * @param string $context Resolution context.
     *
     * @return bool
     */
    private function skip_field_for_context( array $field, string $context ): bool {
        if ( 'network' !== $context ) {
            return false;
        }

        $group = $field['group'] ?? '';
        $id    = $field['id'] ?? '';

        $site_only = array(
            'gmail.access_token'  => true,
            'gmail.refresh_token' => true,
            'gmail.token_expiry'  => true,
        );

        return isset( $site_only[ $group . '.' . $id ] );
    }

    /**
     * Normalize a single field schema.
     *
     * Adds the resolved value, is_const/is_locked/source metadata, and
     * (when appropriate) a masked placeholder for encrypted fields.
     *
     * @since 1.0.0
     *
     * @param array  $field   The field schema.
     * @param string $context Resolution context — see get_tabs().
     *
     * @return array Normalized field schema.
     */
    private function normalize_field( array $field, string $context = 'site' ): array {
        // Display-only computed fields (e.g. copyable-uri rendering an admin
        // URL) carry their value in the schema; an Options-lookup would
        // clobber it with an empty string because no Options key backs them.
        $type = (string) ( $field['type'] ?? '' );
        if ( 'copyable-uri' === $type ) {
            return $field;
        }

        $options = Options::init();
        $group   = $field['group'] ?? 'general';
        $id      = $field['id'] ?? '';

        $meta = ( 'network' === $context )
            ? $options->get_network_metadata( $group, $id )
            : $options->get_effective_metadata( $group, $id );

        $field['value']     = $meta['value'];
        $field['is_const']  = $meta['is_constant'];
        $field['is_locked'] = $meta['is_locked'];
        $field['source']    = $meta['source'];

        if ( $field['is_const'] ) {
            $field['const_name'] = $options->get_const_name( $group, $id );
        }

        // Do not send actual encrypted values to JS.
        if ( ! empty( $field['encrypted'] ) && ! empty( $field['value'] ) ) {
            $field['value']     = SettingsController::ENCRYPTED_MASK;
            $field['is_masked'] = true;
        }

        return $field;
    }

    /**
     * Get general tab settings.
     *
     * @since 1.0.0
     *
     * @return array
     */
    private function get_general_tab(): array {
        return array(
            'id'       => 'general',
            'label'    => __( 'General', 'nimblymail-smtp' ),
            'icon'     => 'SettingOutlined',
            'position' => 10,
            'sections' => array(
                array(
                    'id'          => 'from_options',
                    'title'       => __( 'Sender Identity', 'nimblymail-smtp' ),
                    'description' => __( 'Configure who emails appear to be from.', 'nimblymail-smtp' ),
                    'fields'      => array(
                        array(
                            'id'          => 'from_email',
                            'group'       => 'mail',
                            'title'       => __( 'From Email', 'nimblymail-smtp' ),
                            'type'        => 'email',
                            'default'     => get_option( 'admin_email' ),
                            'placeholder' => 'noreply@yoursite.com',
                            'description' => __( 'The email address which emails are sent from.', 'nimblymail-smtp' ),
                            'required'    => true,
                        ),
                        array(
                            'id'          => 'from_email_force',
                            'group'       => 'mail',
                            'title'       => __( 'Force From Email', 'nimblymail-smtp' ),
                            'type'        => 'checkbox',
                            'default'     => true,
                            'description' => __( 'Override the From Email for all outgoing emails.', 'nimblymail-smtp' ),
                        ),
                        array(
                            'id'          => 'from_name',
                            'group'       => 'mail',
                            'title'       => __( 'From Name', 'nimblymail-smtp' ),
                            'type'        => 'text',
                            'default'     => get_option( 'blogname' ),
                            'placeholder' => __( 'Your Site Name', 'nimblymail-smtp' ),
                            'description' => __( 'The name which emails are sent from.', 'nimblymail-smtp' ),
                        ),
                        array(
                            'id'          => 'from_name_force',
                            'group'       => 'mail',
                            'title'       => __( 'Force From Name', 'nimblymail-smtp' ),
                            'type'        => 'checkbox',
                            'default'     => false,
                            'description' => __( 'Override the From Name for all outgoing emails.', 'nimblymail-smtp' ),
                        ),
                        array(
                            'id'          => 'return_path',
                            'group'       => 'mail',
                            'title'       => __( 'Return Path', 'nimblymail-smtp' ),
                            'type'        => 'checkbox',
                            'default'     => true,
                            'description' => __( 'Set the Return-Path header to match the From Email. When disabled, you can specify a custom Return-Path below.', 'nimblymail-smtp' ),
                        ),
                        array(
                            'id'           => 'return_path_email',
                            'group'        => 'mail',
                            'title'        => __( 'Custom Return Path', 'nimblymail-smtp' ),
                            'type'         => 'email',
                            'default'      => '',
                            'placeholder'  => 'bounces@yoursite.com',
                            'description'  => __( 'Set a custom Return-Path email address. Leave empty to use server default.', 'nimblymail-smtp' ),
                            'visible_when' => array( 'mail.return_path' => false ),
                        ),
                    ),
                ),
            ),
        );
    }

    /**
     * Get connection tab settings.
     *
     * Builds the Connection tab dynamically by iterating all registered mailers
     * via Processor::get_mailers(). The mailer-cards selector is built from the
     * same mailer list. Each mailer's get_settings_fields() section is appended
     * with a visible_when condition so only the active mailer's section shows.
     *
     * Mailers that return an empty array from get_settings_fields() (e.g.
     * DefaultMailer) are excluded from the settings sections but still
     * appear as cards in the selector.
     *
     * When the Pro add-on is not active, an additional "ghost" mailer card
     * and a flagged is_upsell section are appended so the Vue layer can
     * surface a Pro upsell banner in place of mailer settings without ever
     * mutating the persisted mail.mailer value.
     *
     * @since 1.0.0 Append Pro upsell card + section when Pro is inactive.
     *
     * @return array Tab descriptor for the Connection tab.
     */
    private function get_connection_tab(): array {
        $mailers      = Plugin::instance()->get_processor()->get_mailers();
        $card_options = array();
        $sections     = array();

        foreach ( $mailers as $slug => $mailer ) {
            /**
             * Filter the provider logo URL for a mailer card.
             *
             * Return an empty string to let MailerCard.vue fall back to the
             * built-in free-plugin asset at
             * `assets/images/providers/{slug}.svg`. Pro add-ons that ship
             * their own provider logos return a fully qualified URL to their
             * own `assets/images/providers/{slug}.svg`.
             *
             * @since 1.0.0
             *
             * @param string $url  Logo URL resolved so far. Empty string by default.
             * @param string $slug Mailer slug being rendered.
             */
            $logo_url = (string) apply_filters( 'nimblymail_mailer_logo_url', '', (string) $slug );

            $card_options[ $slug ] = array(
                'label'       => $mailer->get_title(),
                'description' => $mailer->get_description(),
                'logo'        => $logo_url,
            );

            $section = $mailer->get_settings_fields();

            if ( empty( $section ) ) {
                continue;
            }

            $section['visible_when'] = array( 'mail.mailer' => $slug );
            $sections[]              = $section;
        }

        // Upsell only shown when Pro is not present; once Pro is active its
        // mailers (Postmark, Mailgun, SendGrid, etc.) register themselves into
        // the cards grid via the `nimblymail_mailers` filter, displacing the
        // "Looking for something else?" placeholder.
        $show_upsell = ! Plugin::instance()->is_pro_active();

        /**
         * Filter whether to show the Pro upsell card on the Connection tab.
         *
         * Defaults to true when Pro is not installed, false otherwise. Hook
         * with __return_false to suppress the card on free installs (e.g. for
         * white-label distributions); hook with __return_true to surface the
         * card even when Pro is active (rarely useful).
         *
         * @since 1.0.0
         *
         * @param bool $show_upsell Whether to render the Pro upsell card.
         */
        $show_pro_upsell = (bool) apply_filters( 'nimblymail_show_pro_upsell', $show_upsell );

        if ( $show_pro_upsell ) {
            /**
             * Filter the Pro upsell payload shown on the Connection tab.
             *
             * Returned shape:
             *  - card_label       Card title shown in the cards grid.
             *  - card_description Card subtitle shown in the cards grid.
             *  - heading          Banner heading.
             *  - description      Banner body copy.
             *  - features         Array of feature bullet strings.
             *  - cta_label        Banner CTA button label.
             *  - cta_url          Banner CTA URL.
             *
             * @since 1.0.0
             *
             * @param array $upsell Upsell payload.
             */
            $upsell = (array) apply_filters(
                'nimblymail_pro_upsell',
                array(
                    'card_label'       => __( 'Looking for something else?', 'nimblymail-smtp' ),
                    'card_description' => __( 'Discover more providers in NimblyMail Pro.', 'nimblymail-smtp' ),
                    'heading'          => __( 'Unlock more email providers with Pro', 'nimblymail-smtp' ),
                    'description'      => __( 'NimblyMail Pro adds first-class support for additional providers, advanced routing, and priority support.', 'nimblymail-smtp' ),
                    'features'         => array(
                        __( 'More email providers (Mailgun, Postmark, SparkPost, and more)', 'nimblymail-smtp' ),
                        __( 'Per-recipient routing rules', 'nimblymail-smtp' ),
                        __( 'Priority email support', 'nimblymail-smtp' ),
                    ),
                    'cta_label'        => __( 'Get NimblyMail Pro', 'nimblymail-smtp' ),
                    'cta_url'          => Plugin::get_pro_url( 'connection-tab' ),
                )
            );

            $upsell['cta_url'] = esc_url_raw( (string) ( $upsell['cta_url'] ?? '' ) );

            $card_options['__pro_upsell'] = array(
                'label'       => $upsell['card_label'],
                'description' => $upsell['card_description'],
                'logo'        => NIMBLYMAIL_URL . 'assets/images/providers/upsell.svg',
                'is_upsell'   => true,
            );

            $sections[] = array(
                'id'        => 'pro_upsell',
                'is_upsell' => true,
                'upsell'    => $upsell,
                'fields'    => array(),
            );
        }

        $selector_section = array(
            'id'          => 'mailer_select',
            'title'       => __( 'Mailer', 'nimblymail-smtp' ),
            'description' => __( 'Choose how your site sends emails.', 'nimblymail-smtp' ),
            'fields'      => array(
                array(
                    'id'          => 'mailer',
                    'group'       => 'mail',
                    'title'       => __( 'Mailer', 'nimblymail-smtp' ),
                    'type'        => 'mailer-cards',
                    'default'     => 'smtp',
                    'description' => __( 'Select the transport used to send emails from your site.', 'nimblymail-smtp' ),
                    'options'     => $card_options,
                ),
            ),
        );

        return array(
            'id'       => 'connection',
            'label'    => __( 'Connection', 'nimblymail-smtp' ),
            'icon'     => 'LinkOutlined',
            'position' => 20,
            'sections' => array_merge( array( $selector_section ), $sections ),
        );
    }

    /**
     * Get email log tab settings.
     *
     * @since 1.0.0
     *
     * @return array
     */
    private function get_email_log_tab(): array {
        return array(
            'id'       => 'email_log',
            'label'    => __( 'Email Log', 'nimblymail-smtp' ),
            'icon'     => 'FileTextOutlined',
            'position' => 30,
            'sections' => array(
                array(
                    'id'     => 'log_settings',
                    'title'  => __( 'Logging Settings', 'nimblymail-smtp' ),
                    'fields' => array(
                        array(
                            'id'    => 'enabled',
                            'group' => 'logs',
                            'title' => __( 'Enable Logging', 'nimblymail-smtp' ),
                            'type'  => 'checkbox',
                        ),
                        array(
                            'id'           => 'log_body',
                            'group'        => 'logs',
                            'title'        => __( 'Log Email Body', 'nimblymail-smtp' ),
                            'type'         => 'checkbox',
                            'visible_when' => array( 'logs.enabled' => true ),
                        ),
                    ),
                ),
                array(
                    'id'     => 'display_settings',
                    'title'  => __( 'Display Preferences', 'nimblymail-smtp' ),
                    'fields' => array(
                        array(
                            'id'    => 'datetime_wp_format',
                            'group' => 'display',
                            'title' => __( 'WordPress Date/Time Format', 'nimblymail-smtp' ),
                            'type'  => 'checkbox',
                        ),
                        array(
                            'id'      => 'preferred_mail_format',
                            'group'   => 'display',
                            'title'   => __( 'Default Message Format', 'nimblymail-smtp' ),
                            'type'    => 'select',
                            'options' => array(
                                'html' => __( 'HTML (Rendered)', 'nimblymail-smtp' ),
                                'raw'  => __( 'Raw Source', 'nimblymail-smtp' ),
                                'json' => __( 'JSON', 'nimblymail-smtp' ),
                            ),
                        ),
                        array(
                            'id'    => 'show_attachments',
                            'group' => 'display',
                            'title' => __( 'Show Attachments Column', 'nimblymail-smtp' ),
                            'type'  => 'checkbox',
                        ),
                        array(
                            'id'    => 'per_page',
                            'group' => 'display',
                            'title' => __( 'Entries Per Page', 'nimblymail-smtp' ),
                            'type'  => 'number',
                            'min'   => 5,
                            'max'   => 100,
                        ),
                    ),
                ),
                array(
                    'id'     => 'retention_settings',
                    'title'  => __( 'Log Retention', 'nimblymail-smtp' ),
                    'fields' => array(
                        array(
                            'id'    => 'by_amount',
                            'group' => 'rotation',
                            'title' => __( 'Cleanup by Amount', 'nimblymail-smtp' ),
                            'type'  => 'checkbox',
                        ),
                        array(
                            'id'           => 'by_amount_limit',
                            'group'        => 'rotation',
                            'title'        => __( 'Maximum Logs', 'nimblymail-smtp' ),
                            'type'         => 'number',
                            'visible_when' => array( 'rotation.by_amount' => true ),
                        ),
                        array(
                            'id'    => 'by_time',
                            'group' => 'rotation',
                            'title' => __( 'Cleanup by Time', 'nimblymail-smtp' ),
                            'type'  => 'checkbox',
                        ),
                        array(
                            'id'           => 'by_time_days',
                            'group'        => 'rotation',
                            'title'        => __( 'Delete After (Days)', 'nimblymail-smtp' ),
                            'type'         => 'number',
                            'visible_when' => array( 'rotation.by_time' => true ),
                        ),
                    ),
                ),
            ),
        );
    }

    /**
     * Get controls tab settings.
     *
     * @since 1.0.0
     *
     * @param string $context One of 'site', 'network', 'network-site'.
     *
     * @return array
     */
    private function get_controls_tab( string $context = 'site' ): array {
        $sections = array();

        foreach ( EmailControls::get_controls() as $section_id => $section_data ) {
            $fields = array();

            foreach ( $section_data['emails'] as $email_id => $email_data ) {
                $multisite_only = ! empty( $email_data['multisite'] );
                $network_only   = $multisite_only || ! empty( $email_data['network_only'] );

                // Multisite controls only apply to multisite installs.
                if ( $multisite_only && ! is_multisite() ) {
                    continue;
                }

                // On multisite, network-only controls (every multisite control
                // plus those flagged network_only, e.g. automatic updates)
                // govern network-level notifications and appear exclusively on
                // the Network Admin settings screen — never on per-site or
                // per-site-override screens.
                if ( $network_only && is_multisite() && 'network' !== $context ) {
                    continue;
                }

                $field = array(
                    'id'          => $email_id,
                    'group'       => 'controls',
                    'title'       => $email_data['label'],
                    'description' => $email_data['description'],
                    'type'        => 'checkbox',
                );

                if ( ! empty( $email_data['warning'] ) ) {
                    $field['warning'] = true;
                }

                $fields[] = $field;
            }

            if ( ! empty( $fields ) ) {
                $sections[] = array(
                    'id'     => $section_id,
                    'title'  => $section_data['title'],
                    'fields' => $fields,
                );
            }
        }

        return array(
            'id'       => 'controls',
            'label'    => __( 'Email Controls', 'nimblymail-smtp' ),
            'icon'     => 'ControlOutlined',
            'position' => 40,
            'sections' => $sections,
        );
    }

    /**
     * Get alerts tab settings.
     *
     * @since 1.0.0
     *
     * @return array
     */
    private function get_alerts_tab(): array {
        return array(
            'id'       => 'alerts',
            'label'    => __( 'Failure Alerts', 'nimblymail-smtp' ),
            'icon'     => 'AlertOutlined',
            'position' => 50,
            'sections' => array(
                array(
                    'id'     => 'failure_alerts',
                    'title'  => __( 'Failure Alerts', 'nimblymail-smtp' ),
                    'fields' => array(
                        array(
                            'id'    => 'enabled',
                            'group' => 'alerts',
                            'title' => __( 'Enable Alerts', 'nimblymail-smtp' ),
                            'type'  => 'checkbox',
                        ),
                        array(
                            'id'           => 'email_to',
                            'group'        => 'alerts',
                            'title'        => __( 'Send Alerts To', 'nimblymail-smtp' ),
                            'type'         => 'text',
                            'visible_when' => array( 'alerts.enabled' => true ),
                        ),
                        array(
                            'id'           => 'cc',
                            'group'        => 'alerts',
                            'title'        => __( 'CC', 'nimblymail-smtp' ),
                            'type'         => 'text',
                            'placeholder'  => 'cc@example.com',
                            'description'  => __( 'Send a copy of alert emails to these addresses (comma-separated, max 3).', 'nimblymail-smtp' ),
                            'visible_when' => array( 'alerts.enabled' => true ),
                        ),
                        array(
                            'id'           => 'bcc',
                            'group'        => 'alerts',
                            'title'        => __( 'BCC', 'nimblymail-smtp' ),
                            'type'         => 'text',
                            'placeholder'  => 'bcc@example.com',
                            'description'  => __( 'Send a blind copy of alert emails to these addresses (comma-separated, max 3).', 'nimblymail-smtp' ),
                            'visible_when' => array( 'alerts.enabled' => true ),
                        ),
                    ),
                ),
            ),
        );
    }

    /**
     * Get misc tab settings.
     *
     * @since 1.0.0
     *
     * @return array
     */
    private function get_misc_tab(): array {
        return array(
            'id'       => 'misc',
            'label'    => __( 'Miscellaneous', 'nimblymail-smtp' ),
            'icon'     => 'ToolOutlined',
            'position' => 60,
            'sections' => array(
                array(
                    'id'     => 'misc_settings',
                    'title'  => __( 'Miscellaneous', 'nimblymail-smtp' ),
                    'fields' => array(
                        array(
                            'id'          => 'do_not_send',
                            'group'       => 'general',
                            'title'       => __( 'Do Not Send', 'nimblymail-smtp' ),
                            'type'        => 'checkbox',
                            'description' => __( 'Stop all outgoing email site-wide — nothing is delivered. Use on staging sites or during maintenance. This is a hard global switch; for testing where you still want to log, inspect, or reroute mail to a safe address, use the Developer Sandbox instead.', 'nimblymail-smtp' ),
                        ),
                        array(
                            'id'    => 'hide_delivery_errors',
                            'group' => 'general',
                            'title' => __( 'Hide Delivery Errors', 'nimblymail-smtp' ),
                            'type'  => 'checkbox',
                        ),
                        array(
                            'id'    => 'uninstall_data',
                            'group' => 'general',
                            'title' => __( 'Uninstall Data', 'nimblymail-smtp' ),
                            'type'  => 'checkbox',
                        ),
                        array(
                            'id'    => 'debug_mode',
                            'group' => 'general',
                            'title' => __( 'Debug Mode', 'nimblymail-smtp' ),
                            'type'  => 'checkbox',
                        ),
                        array(
                            'id'    => 'debug_events_retention_days',
                            'group' => 'general',
                            'title' => __( 'Debug Log Retention (Days)', 'nimblymail-smtp' ),
                            'type'  => 'number',
                        ),
                        array(
                            'id'          => 'setup_wizard',
                            'group'       => 'general',
                            'title'       => __( 'Setup Wizard', 'nimblymail-smtp' ),
                            'type'        => 'link',
                            'label'       => __( 'Launch Setup Wizard', 'nimblymail-smtp' ),
                            'url'         => SetupWizard::get_url(),
                            'description' => __( 'Re-run the guided setup wizard to reconfigure your email settings.', 'nimblymail-smtp' ),
                        ),
                    ),
                ),
            ),
        );
    }

    /**
     * Get the Developer Sandbox tab schema.
     *
     * The Sandbox tab houses the master switch, mode selector, redirect
     * target, subject prefix and allowlist textarea. All fields belong to
     * the `sandbox` settings group.
     *
     * @since 1.0.0
     *
     * @return array Tab descriptor for the Sandbox tab.
     */
    private function get_sandbox_tab(): array {
        return array(
            'id'       => 'sandbox',
            'label'    => __( 'Sandbox', 'nimblymail-smtp' ),
            'icon'     => 'SafetyOutlined',
            'position' => 55,
            'sections' => array(
                array(
                    'id'          => 'sandbox_status',
                    'title'       => __( 'Developer Sandbox', 'nimblymail-smtp' ),
                    'description' => __( 'Prevent emails from leaving this site. Useful on staging and local development environments. When active, every wp_mail() call is captured (Log Only) or redirected to a single safe address (Redirect).', 'nimblymail-smtp' ),
                    'fields'      => array(
                        array(
                            'id'          => 'enabled',
                            'group'       => 'sandbox',
                            'title'       => __( 'Enable Sandbox', 'nimblymail-smtp' ),
                            'type'        => 'switch',
                            'description' => __( 'Master switch. When on, no real emails are delivered to their intended recipients.', 'nimblymail-smtp' ),
                        ),
                        array(
                            'id'           => 'mode',
                            'group'        => 'sandbox',
                            'title'        => __( 'Mode', 'nimblymail-smtp' ),
                            'type'         => 'segmented',
                            'options'      => array(
                                'log'      => __( 'Log Only', 'nimblymail-smtp' ),
                                'redirect' => __( 'Redirect', 'nimblymail-smtp' ),
                            ),
                            'description'  => __( 'Log Only captures the email in the log without sending. Redirect rewrites the recipient to your Safe Address and sends one copy there.', 'nimblymail-smtp' ),
                            'visible_when' => array( 'sandbox.enabled' => true ),
                        ),
                    ),
                ),
                array(
                    'id'           => 'sandbox_redirect',
                    'title'        => __( 'Redirect Target', 'nimblymail-smtp' ),
                    'description'  => __( 'Where redirected emails are sent and how they are tagged so you can spot them in your inbox.', 'nimblymail-smtp' ),
                    'visible_when' => array(
                        'sandbox.enabled' => true,
                        'sandbox.mode'    => 'redirect',
                    ),
                    'fields'       => array(
                        array(
                            'id'          => 'safe_address',
                            'group'       => 'sandbox',
                            'title'       => __( 'Safe Address', 'nimblymail-smtp' ),
                            'type'        => 'email',
                            'placeholder' => 'dev@example.com',
                            'description' => __( 'Every outgoing email will be redirected here. Original recipients are preserved in X-NimblyMail-Original-* headers and in the Email Log.', 'nimblymail-smtp' ),
                            'required'    => true,
                        ),
                        array(
                            'id'          => 'subject_prefix',
                            'group'       => 'sandbox',
                            'title'       => __( 'Subject Prefix', 'nimblymail-smtp' ),
                            'type'        => 'text',
                            'placeholder' => '[SANDBOX] ',
                            'description' => __( 'Prepended to the subject line of redirected emails so they are easy to filter. Leave empty to disable prefixing.', 'nimblymail-smtp' ),
                        ),
                    ),
                ),
                array(
                    'id'           => 'sandbox_allowlist',
                    'title'        => __( 'Allowlist', 'nimblymail-smtp' ),
                    'description'  => __( 'An email bypasses the sandbox entirely only when every To, Cc, and Bcc address matches an allowlist pattern. One pattern per line — exact email (dev@example.com) or domain wildcard (*@mycompany.com). Lines starting with # are ignored.', 'nimblymail-smtp' ),
                    'visible_when' => array( 'sandbox.enabled' => true ),
                    'fields'       => array(
                        array(
                            'id'          => 'allowlist',
                            'group'       => 'sandbox',
                            'title'       => __( 'Patterns', 'nimblymail-smtp' ),
                            'type'        => 'textarea',
                            'rows'        => 6,
                            'placeholder' => "*@mycompany.com\ndev@example.com",
                            'description' => __( 'Empty allowlist means every email is captured by the sandbox.', 'nimblymail-smtp' ),
                        ),
                    ),
                ),
            ),
        );
    }

    /**
     * Get mail queue tab settings.
     *
     * @since 1.0.0
     *
     * @return array
     */
    private function get_mail_queue_tab(): array {
        return array(
            'id'       => 'mail_queue',
            'label'    => __( 'Mail Queue', 'nimblymail-smtp' ),
            'icon'     => 'ClockCircleOutlined',
            'position' => 35,
            'sections' => array(
                array(
                    'id'          => 'queue_settings',
                    'title'       => __( 'Background Queue', 'nimblymail-smtp' ),
                    'description' => __( 'Defer wp_mail() calls to a background queue so the request returns immediately. Failed sends are retried with exponential backoff.', 'nimblymail-smtp' ),
                    'fields'      => array(
                        array(
                            'id'          => 'enabled',
                            'group'       => 'queue',
                            'title'       => __( 'Enable Queue', 'nimblymail-smtp' ),
                            'type'        => 'switch',
                            'description' => __( 'Turn the queue on to defer all wp_mail() calls. Test emails and admin alerts always send synchronously when "Bypass admin emails" is on.', 'nimblymail-smtp' ),
                        ),
                        array(
                            'id'           => 'pause_processing',
                            'group'        => 'queue',
                            'title'        => __( 'Pause Processing', 'nimblymail-smtp' ),
                            'type'         => 'switch',
                            'description'  => __( 'Keep accepting new mail but stop dispatching it. Useful during provider outages — drain the backlog later by turning this off.', 'nimblymail-smtp' ),
                            'visible_when' => array( 'queue.enabled' => true ),
                        ),
                        array(
                            'id'           => 'max_attempts',
                            'group'        => 'queue',
                            'title'        => __( 'Max Attempts', 'nimblymail-smtp' ),
                            'type'         => 'number',
                            'description'  => __( 'Total delivery attempts before a queued email is marked as permanently failed.', 'nimblymail-smtp' ),
                            'visible_when' => array( 'queue.enabled' => true ),
                        ),
                        array(
                            'id'           => 'initial_backoff_seconds',
                            'group'        => 'queue',
                            'title'        => __( 'Initial Backoff (seconds)', 'nimblymail-smtp' ),
                            'type'         => 'number',
                            'description'  => __( 'Wait time before the first retry. Each subsequent retry doubles this delay (exponential backoff).', 'nimblymail-smtp' ),
                            'visible_when' => array( 'queue.enabled' => true ),
                        ),
                        array(
                            'id'           => 'batch_size',
                            'group'        => 'queue',
                            'title'        => __( 'Batch Size', 'nimblymail-smtp' ),
                            'type'         => 'number',
                            'description'  => __( 'Maximum number of queued emails Action Scheduler may process in a single dispatch.', 'nimblymail-smtp' ),
                            'visible_when' => array( 'queue.enabled' => true ),
                        ),
                        array(
                            'id'           => 'default_priority',
                            'group'        => 'queue',
                            'title'        => __( 'Default Priority', 'nimblymail-smtp' ),
                            'type'         => 'radio',
                            'options'      => array(
                                'high'   => __( 'High', 'nimblymail-smtp' ),
                                'normal' => __( 'Normal', 'nimblymail-smtp' ),
                                'bulk'   => __( 'Bulk', 'nimblymail-smtp' ),
                            ),
                            'description'  => __( 'Lane new messages enter when no per-message priority is set via the X-Nimbly-Mail-Priority header or the nimblymail_priority filter.', 'nimblymail-smtp' ),
                            'visible_when' => array( 'queue.enabled' => true ),
                        ),
                        array(
                            'id'           => 'bypass_admin_emails',
                            'group'        => 'queue',
                            'title'        => __( 'Bypass Admin Emails', 'nimblymail-smtp' ),
                            'type'         => 'switch',
                            'description'  => __( 'Send test emails and failure alerts synchronously, bypassing the queue. Recommended so the alert path is not subject to the same failures it reports on.', 'nimblymail-smtp' ),
                            'visible_when' => array( 'queue.enabled' => true ),
                        ),
                        array(
                            'id'           => 'failed_retention_days',
                            'group'        => 'queue',
                            'title'        => __( 'Failed Retention (Days)', 'nimblymail-smtp' ),
                            'type'         => 'number',
                            'description'  => __( 'How long permanently-failed queue rows are kept for inspection before the rotation cron purges them.', 'nimblymail-smtp' ),
                            'visible_when' => array( 'queue.enabled' => true ),
                        ),
                        array(
                            'id'           => 'stuck_reclaim_minutes',
                            'group'        => 'queue',
                            'title'        => __( 'Stuck Reclaim (Minutes)', 'nimblymail-smtp' ),
                            'type'         => 'number',
                            'description'  => __( 'Auto-release rows that have been stuck in "processing" for this many minutes — recovers from worker crashes mid-send.', 'nimblymail-smtp' ),
                            'visible_when' => array( 'queue.enabled' => true ),
                        ),
                    ),
                ),
            ),
        );
    }

    /**
     * Decide whether the Upgrade tab is visible.
     *
     * Pure decision function — extracted from get_tabs() so unit tests can
     * drive the truth table without booting the Plugin singleton, Processor,
     * or the WordPress multisite stack.
     *
     * Truth table:
     *   - Pro active                            → hidden (no upsell when paid).
     *   - Non-multisite                         → shown.
     *   - Multisite, 'network' context          → shown (network admins land here).
     *   - Multisite, per-site, super admin      → shown (super admins can install Pro).
     *   - Multisite, per-site, non-super admin  → hidden (can't act on the upsell).
     *
     * @since 1.0.0
     *
     * @param string $context     Resolution context: 'site' | 'network' | 'network-site'.
     * @param bool   $pro_active  Whether the NimblyMail Pro add-on is active.
     * @param bool   $multisite   Whether the install is multisite.
     * @param bool   $super_admin Whether the current user is a Super Admin.
     *
     * @return bool True when the tab should appear in get_tabs() output.
     */
    public static function should_show_upgrade_tab( string $context, bool $pro_active, bool $multisite, bool $super_admin ): bool {
        if ( $pro_active ) {
            return false;
        }

        if ( ! $multisite ) {
            return true;
        }

        if ( 'network' === $context ) {
            return true;
        }

        return $super_admin;
    }

    /**
     * Get the default tile payload for the Upgrade tab.
     *
     * Each tile describes one Pro feature surfaced on the Upgrade tab. Tile
     * types:
     *   - 'providers' — special tile, renders a logo grid using the slugs in
     *     the `providers` key (logos resolved from
     *     `assets/images/providers/<slug>.svg`).
     *   - 'feature'   — standard tile, renders an Ant Design icon plus title
     *     and description.
     *
     * Pure data — extracted into a helper so unit tests can assert the shape
     * without invoking the filter.
     *
     * @since 1.0.0
     *
     * @return array<int, array<string, mixed>> Default tile payload.
     */
    private function get_default_upgrade_tiles(): array {
        return array(
            array(
                'id'          => 'providers',
                'type'        => 'providers',
                'title'       => __( 'More Providers', 'nimblymail-smtp' ),
                'description' => __( '11 additional premium providers ship with Pro — connect to whichever fits your deliverability and pricing needs.', 'nimblymail-smtp' ),
                'providers'   => array(
                    'postmark',
                    'mailgun',
                    'sendgrid',
                    'sparkpost',
                    'mailjet',
                    'brevo',
                    'smtp-com',
                    'smtp2go',
                    'elasticemail',
                    'mailersend',
                    'zeptomail',
                ),
            ),
            array(
                'id'          => 'routing',
                'type'        => 'feature',
                'icon'        => 'BranchesOutlined',
                'title'       => __( 'Smart Routing', 'nimblymail-smtp' ),
                'description' => __( 'Route messages to specific mailers by recipient domain, sender, subject, or custom headers — keep transactional and marketing traffic on separate providers.', 'nimblymail-smtp' ),
            ),
            array(
                'id'          => 'backup',
                'type'        => 'feature',
                'icon'        => 'SafetyCertificateOutlined',
                'title'       => __( 'Backup Mailer & Failover', 'nimblymail-smtp' ),
                'description' => __( 'When the primary mailer fails, NimblyMail automatically retries through a secondary provider so a transient outage never costs you a delivery.', 'nimblymail-smtp' ),
            ),
            array(
                'id'          => 'rate_limit',
                'type'        => 'feature',
                'icon'        => 'DashboardOutlined',
                'title'       => __( 'Rate Limiting', 'nimblymail-smtp' ),
                'description' => __( 'Per-mailer per-minute, per-hour, and per-day budgets. Excess messages queue instead of being dropped — stay inside your provider\'s limits without losing mail.', 'nimblymail-smtp' ),
            ),
            array(
                'id'          => 'reports',
                'type'        => 'feature',
                'icon'        => 'BarChartOutlined',
                'title'       => __( 'Reports & Analytics', 'nimblymail-smtp' ),
                'description' => __( 'Weekly HTML digest plus an analytics dashboard with per-mailer trends, success rate, and top failing mailers — see how your email stack is performing at a glance.', 'nimblymail-smtp' ),
            ),
            array(
                'id'          => 'multisite',
                'type'        => 'feature',
                'icon'        => 'ControlOutlined',
                'title'       => __( 'Multisite Network Control', 'nimblymail-smtp' ),
                'description' => __( 'Manage settings centrally, allow per-site overrides, lock fields via constants, and aggregate reports across the whole network.', 'nimblymail-smtp' ),
            ),
        );
    }

    /**
     * Get the Upgrade tab descriptor.
     *
     * Builds the full tab payload (heading, description, feature tiles, CTA,
     * footer note) and runs it through the `nimblymail_upgrade_tab_content`
     * filter so white-label distributions can replace copy or CTA without
     * forking the plugin. The CTA URL is run through `esc_url_raw()` after
     * the filter so a hostile hook cannot inject a `javascript:` pseudo-scheme.
     *
     * The returned tab declares `renderer = 'upgrade'`, which the Vue layer
     * uses to swap in the UpgradePanel component in place of the standard
     * SettingsForm; the `sections` array is therefore intentionally empty.
     *
     * @since 1.0.0
     *
     * @param string $context Resolution context: 'site' | 'network' | 'network-site'.
     *
     * @return array Tab descriptor consumed by get_tabs() and serialised to
     *               the Vue settings app via the REST settings endpoint.
     */
    public function get_upgrade_tab_payload( string $context = 'site' ): array {
        $payload = array(
            'heading'     => __( 'Unlock the full NimblyMail experience with Pro', 'nimblymail-smtp' ),
            'description' => __( 'NimblyMail Pro adds 11 premium mailers, smart routing, automatic failover, rate limiting, and weekly analytics — everything you need to run production-grade email at any scale.', 'nimblymail-smtp' ),
            'tiles'       => $this->get_default_upgrade_tiles(),
            'cta_label'   => __( 'Get NimblyMail Pro', 'nimblymail-smtp' ),
            'cta_url'     => Plugin::get_pro_url( 'upgrade-tab' ),
            'footer_note' => __( 'Already purchased? Install the Pro plugin to activate.', 'nimblymail-smtp' ),
        );

        /**
         * Filter the Upgrade tab payload.
         *
         * Returned shape:
         *  - heading     Banner heading.
         *  - description Banner body copy.
         *  - tiles       Array of tile descriptors (see
         *                {@see self::get_default_upgrade_tiles()} for shape).
         *  - cta_label   CTA button label.
         *  - cta_url     CTA URL (escaped via esc_url_raw after this filter).
         *  - footer_note Short helper text rendered under the CTA.
         *
         * @since 1.0.0
         *
         * @param array  $payload Default payload.
         * @param string $context Resolution context: 'site' | 'network' | 'network-site'.
         */
        $payload = (array) apply_filters( 'nimblymail_upgrade_tab_content', $payload, $context );

        $payload['cta_url'] = esc_url_raw( (string) ( $payload['cta_url'] ?? '' ) );

        return array(
            'id'       => 'upgrade',
            'label'    => __( 'Upgrade', 'nimblymail-smtp' ),
            'icon'     => 'GiftOutlined',
            'position' => 999,
            'renderer' => 'upgrade',
            'sections' => array(),
            'upgrade'  => $payload,
        );
    }
}
