<?php

/**
 * The admin-specific functionality of the plugin.
 *
 * @link       https://chillichalli.com
 * @since      1.1.3
 *
 * @package    Card_Oracle
 * @subpackage Card_Oracle/admin
 */
use MailChimp\MailChimp;
/**
 * The admin-specific functionality of the plugin.
 *
 * Defines the plugin name, version, and two examples hooks for how to
 * enqueue the admin-specific stylesheet and JavaScript.
 *
 * @package    Card_Oracle
 * @subpackage Card_Oracle/admin
 * @author     Christopher Graham <support@chillichalli.com>
 */
class CardOracleAdmin {
    /**
     * The ID of this plugin.
     *
     * @since    0.5.0
     * @access   private
     * @var      string    $plugin_name    The ID of this plugin.
     */
    private $plugin_name;

    /**
     * The version of this plugin.
     *
     * @since    0.5.0
     * @access   private
     * @var      string    $version    The current version of this plugin.
     */
    private $version;

    /**
     * Initialize the class and set its properties.
     *
     * @since 0.26.0
     * @param string $plugin_name The name of this plugin.
     * @param string $version The version of this plugin.
     */
    public function __construct( $plugin_name, $version ) {
        $this->plugin_name = $plugin_name;
        $this->version = $version;
    }

    /**
     * Register Card Oracle settings with WordPress Settings API
     *
     * @since 1.1.9
     * @return void
     */
    public function register_card_oracle_settings() {
        // Security check
        if ( !current_user_can( 'manage_options' ) ) {
            return;
        }
        // Register settings group
        register_setting( 'card_oracle_option_general', 'card_oracle_settings', array(
            'sanitize_callback' => array($this, 'sanitize_card_oracle_settings'),
            'default'           => $this->get_default_settings(),
        ) );
        // Add settings section
        add_settings_section(
            'card_oracle_general_section',
            esc_html__( 'Card Oracle General Settings', 'card-oracle' ),
            array($this, 'general_settings_section_callback'),
            'card_oracle_option_general'
        );
        // Enable RSS Feed Check
        add_settings_field(
            'enable_feed_check',
            esc_html__( 'Enable RSS Feed Check', 'card-oracle' ),
            array($this, 'enable_feed_check_callback'),
            'card_oracle_option_general',
            'card_oracle_general_section'
        );
        // Cache TTL
        add_settings_field(
            'cache_ttl',
            esc_html__( 'Cache TTL (seconds)', 'card-oracle' ),
            array($this, 'cache_ttl_callback'),
            'card_oracle_option_general',
            'card_oracle_general_section'
        );
        // Debug Logging
        add_settings_field(
            'debug_logging',
            esc_html__( 'Enable Debug Logging', 'card-oracle' ),
            array($this, 'debug_logging_callback'),
            'card_oracle_option_general',
            'card_oracle_general_section'
        );
        // Default Reading
        add_settings_field(
            'default_deck',
            esc_html__( 'Default Reading', 'card-oracle' ),
            array($this, 'default_deck_callback'),
            'card_oracle_option_general',
            'card_oracle_general_section'
        );
    }

    /**
     * Add an options page under the Card Oracle menu
     *
     * @since 0.5.0
     * @return void
     */
    public function add_card_oracle_options_page() {
        $this->plugin_screen_hook_suffix = add_options_page(
            esc_html__( 'Card Oracle Settings', 'card-oracle' ),
            esc_html__( 'Card Oracle', 'card-oracle' ),
            'manage_options',
            $this->plugin_name,
            array($this, 'display_card_oracle_options_page')
        );
    }

    /**
     * Convert html characters in to plain text characters.
     *
     * @since  0.16.0
     * @param string $text Text to convert.
     * @return string Converted text.
     */
    public function card_oracle_html2text( $text ) {
        $rules = array(
            '@<script[^>]*?>.*?</script>@si',
            '@<[\\/\\!]*?[^<>]*?>@si',
            '@([\\r\\n])[\\s]+@',
            '@&(quot|#34);@i',
            '@&(amp|#38);@i',
            '@&(lt|#60);@i',
            '@&(gt|#62);@i',
            '@&(nbsp|#160);@i',
            '@&(iexcl|#161);@i',
            '@&(cent|#162);@i',
            '@&(pound|#163);@i',
            '@&(copy|#169);@i',
            '@&(reg|#174);@i',
            '@&#(d+);@e'
        );
        $replace = array(
            '',
            '',
            '',
            '',
            '&',
            '<',
            '>',
            ' ',
            chr( 161 ),
            chr( 162 ),
            chr( 163 ),
            chr( 169 ),
            chr( 174 ),
            'chr()'
        );
        return preg_replace( $rules, $replace, $text );
    }

    /**
     * Get the total counts of a cpt
     *
     * @since 1.1.3
     * @param string $card_oracle_cpt The name of the custom post type.
     * @return int The count of custom post types.
     */
    private function get_card_oracle_cpt_count( $card_oracle_cpt ) {
        return wp_count_posts( $card_oracle_cpt )->publish;
    }

    /**
     * Determine whether any public content embeds the Card Oracle block or shortcode.
     *
     * @since 1.2.0
     * @return bool
     */
    private function has_card_oracle_embed() {
        global $wpdb;
        $post_table = $wpdb->posts;
        $shortcode_like = '%' . $wpdb->esc_like( '[card-oracle' ) . '%';
        $block_like = '%' . $wpdb->esc_like( '<!-- wp:card-oracle/card-oracle' ) . '%';
        $query = $wpdb->prepare( "SELECT ID FROM {$post_table} WHERE post_type IN ('page','post') AND post_status = 'publish' AND ( post_content LIKE %s OR post_content LIKE %s ) LIMIT 1", $shortcode_like, $block_like );
        return !empty( $wpdb->get_var( $query ) );
    }

    /**
     * Render the options page for plugin
     *
     * @since 1.1.1
     * @return void
     */
    /**
     * Handle onboarding checklist dismiss/restore actions.
     *
     * Processes POST requests for dismissing or restoring the getting started checklist.
     * Must run on admin_init to allow proper redirects before output.
     *
     * @since 1.1.9
     * @return void
     */
    public function handle_onboarding_actions() {
        // Only process if on the Card Oracle admin page with onboarding action.
        if ( !isset( $_GET['page'] ) || 'card-oracle-admin-menu' !== $_GET['page'] ) {
            // phpcs:ignore WordPress.Security.NonceVerification
            return;
        }
        if ( !isset( $_POST['card_oracle_onboarding_action'] ) || !current_user_can( 'manage_options' ) ) {
            return;
        }
        $action = sanitize_text_field( wp_unslash( $_POST['card_oracle_onboarding_action'] ) );
        check_admin_referer( 'card_oracle_onboarding' );
        $current_user_id = get_current_user_id();
        if ( 'dismiss' === $action ) {
            update_user_meta( $current_user_id, 'card_oracle_onboarding_dismissed', 1 );
        } elseif ( 'restore' === $action ) {
            delete_user_meta( $current_user_id, 'card_oracle_onboarding_dismissed' );
        }
        wp_safe_redirect( admin_url( 'admin.php?page=card-oracle-admin-menu&tab=dashboard' ) );
        exit;
    }

    public function display_card_oracle_options_page() {
        global $wpdb;
        $reading_array = array();
        $screen = get_current_screen();
        $active_tab = ( !empty( $_REQUEST['tab'] ) ? sanitize_title( wp_unslash( $_REQUEST['tab'] ) ) : 'dashboard' );
        // phpcs:ignore WordPress.Security.NonceVerification
        $current_user_id = get_current_user_id();
        $card_oracle_onboarding_dismissed = (bool) get_user_meta( $current_user_id, 'card_oracle_onboarding_dismissed', true );
        $is_premium = card_oracle_fs()->can_use_premium_code__premium_only();
        $settings_data = ( function_exists( 'card_oracle_get_setting' ) ? card_oracle_get_setting() : array() );
        $cache_ttl_seconds = ( function_exists( 'card_oracle_get_cache_ttl' ) ? card_oracle_get_cache_ttl() : (( isset( $settings_data['cache_ttl'] ) ? (int) $settings_data['cache_ttl'] : 3600 )) );
        $cache_ttl_seconds = ( $cache_ttl_seconds > 0 ? $cache_ttl_seconds : 3600 );
        $feed_check_enabled = !empty( $settings_data['enable_feed_check'] );
        $debug_logging_enabled = ( function_exists( 'card_oracle_is_debug_logging_enabled' ) ? card_oracle_is_debug_logging_enabled() : !empty( $settings_data['debug_logging'] ) );
        $default_deck_setting = ( isset( $settings_data['default_deck'] ) ? $settings_data['default_deck'] : '' );
        $minute_in_seconds = ( defined( 'MINUTE_IN_SECONDS' ) ? MINUTE_IN_SECONDS : 60 );
        $cache_ttl_human = number_format_i18n( $cache_ttl_seconds ) . ' ' . _n(
            'second',
            'seconds',
            $cache_ttl_seconds,
            'card-oracle'
        );
        if ( $cache_ttl_seconds >= $minute_in_seconds ) {
            $minutes = round( $cache_ttl_seconds / $minute_in_seconds, 1 );
            $cache_ttl_human .= ' (' . sprintf( _n(
                '%s minute',
                '%s minutes',
                $minutes,
                'card-oracle'
            ), number_format_i18n( $minutes ) ) . ')';
        }
        $default_deck_label = __( 'Not set', 'card-oracle' );
        if ( !empty( $default_deck_setting ) ) {
            $deck_post = get_post( $default_deck_setting );
            if ( $deck_post ) {
                $default_deck_label = $deck_post->post_title;
            }
        }
        $is_premium = card_oracle_fs()->can_use_premium_code__premium_only();
        $card_oracle_settings_url = admin_url( 'options-general.php?page=' . $this->plugin_name );
        $card_oracle_system_status = array(
            array(
                'label' => esc_html__( 'RSS Feed Check', 'card-oracle' ),
                'value' => ( $feed_check_enabled ? esc_html__( 'Enabled', 'card-oracle' ) : esc_html__( 'Disabled', 'card-oracle' ) ),
            ),
            array(
                'label' => esc_html__( 'Cache TTL', 'card-oracle' ),
                'value' => $cache_ttl_human,
            ),
            array(
                'label' => esc_html__( 'Debug Logging', 'card-oracle' ),
                'value' => ( $debug_logging_enabled ? esc_html__( 'Enabled', 'card-oracle' ) : esc_html__( 'Disabled', 'card-oracle' ) ),
            ),
            array(
                'label' => esc_html__( 'Default Reading', 'card-oracle' ),
                'value' => esc_html( $default_deck_label ),
            )
        );
        $card_oracle_is_premium = $is_premium;
        $card_oracle_upgrade_url = ( $is_premium ? '' : card_oracle_fs()->get_upgrade_url() );
        $tabs = array(
            array(
                'uid'      => 'dashboard',
                'name'     => esc_html__( 'Dashboard', 'card-oracle' ),
                'htmlfile' => 'partials/card-oracle-tab-dashboard.php',
                'order'    => 10,
            ),
            array(
                'uid'      => 'general',
                'name'     => esc_html__( 'General', 'card-oracle' ),
                'htmlfile' => 'partials/card-oracle-tab-general.php',
                'order'    => 20,
            ),
            array(
                'uid'      => 'email',
                'name'     => esc_html__( 'Email Settings', 'card-oracle' ),
                'htmlfile' => 'partials/card-oracle-tab-email.php',
                'order'    => 30,
            ),
            array(
                'uid'      => 'wizard',
                'name'     => esc_html__( 'Wizard', 'card-oracle' ),
                'htmlfile' => 'partials/card-oracle-tab-wizard.php',
                'order'    => 60,
            ),
            array(
                'uid'      => 'status',
                'name'     => esc_html__( 'Status', 'card-oracle' ),
                'htmlfile' => 'partials/card-oracle-tab-status.php',
                'order'    => 70,
            )
        );
        // Add premium tabs.
        if ( $is_premium ) {
            // Push premium tabs on to tabs array.
            $premium_tabs = array(array(
                'uid'      => 'integrations',
                'name'     => esc_html__( 'Integrations', 'card-oracle' ),
                'htmlfile' => 'partials/card-oracle-tab-integrations__premium_only.php',
                'order'    => 40,
            ), array(
                'uid'      => 'payments',
                'name'     => esc_html__( 'Payment Provider', 'card-oracle' ),
                'htmlfile' => 'partials/card-oracle-tab-payments__premium_only.php',
                'order'    => 60,
            ));
            $tabs = array_merge( $tabs, $premium_tabs );
        }
        $keys = array_column( $tabs, 'order' );
        array_multisort( $keys, SORT_ASC, $tabs );
        $readings_count_i18n = number_format_i18n( $this->get_card_oracle_cpt_count( 'co_readings' ) );
        /* translators: %d is a number */
        $readings_text = sprintf( _n(
            '%d Total',
            '%d Total',
            $readings_count_i18n,
            'card-oracle'
        ), $readings_count_i18n );
        $cards_count_i18n = number_format_i18n( $this->get_card_oracle_cpt_count( 'co_cards' ) );
        /* translators: %d is a number */
        $cards_text = sprintf( _n(
            '%d Total',
            '%d Total',
            $cards_count_i18n,
            'card-oracle'
        ), $cards_count_i18n );
        $positions_count_i18n = number_format_i18n( $this->get_card_oracle_cpt_count( 'co_positions' ) );
        /* translators: %d is a number */
        $positions_text = sprintf( _n(
            '%d Total',
            '%d Total',
            $positions_count_i18n,
            'card-oracle'
        ), $positions_count_i18n );
        $descriptions_count_i18n = number_format_i18n( $this->get_card_oracle_cpt_count( 'co_descriptions' ) );
        /* translators: %d is a number */
        $descriptions_text = sprintf( _n(
            '%d Total',
            '%d Total',
            $descriptions_count_i18n,
            'card-oracle'
        ), $descriptions_count_i18n );
        $readings = get_card_oracle_posts_by_cpt( 'co_readings', 'post_title' );
        $readings_count = count( $readings );
        $has_reading = $readings_count > 0;
        // Look up demo reading using stored option for reliability.
        $demo_reading_id = get_option( 'card_oracle_demo_reading_id', 0 );
        $demo_reading = ( $demo_reading_id ? get_post( $demo_reading_id ) : null );
        // Fallback to title lookup if option not set.
        if ( !$demo_reading ) {
            $demo_reading = get_page_by_title( 'Past, Present, Future (Demo)', OBJECT, 'co_readings' );
            if ( $demo_reading ) {
                update_option( 'card_oracle_demo_reading_id', $demo_reading->ID );
            }
        }
        $has_demo_data = $demo_reading instanceof WP_Post;
        $has_embed = $this->has_card_oracle_embed();
        $card_oracle_onboarding_tasks = array(array(
            'id'          => 'create-reading',
            'label'       => esc_html__( 'Create your first reading', 'card-oracle' ),
            'description' => esc_html__( 'Use the Setup Wizard to add positions and cards for your first reading.', 'card-oracle' ),
            'cta_label'   => esc_html__( 'Launch Wizard', 'card-oracle' ),
            'cta_url'     => admin_url( 'options-general.php?page=card-oracle-admin-menu&tab=wizard' ),
            'completed'   => $has_reading,
        ), array(
            'id'          => 'install-demo-data',
            'label'       => esc_html__( 'Install demo data and images', 'card-oracle' ),
            'description' => esc_html__( 'Add sample readings, cards, images, and descriptions to explore the plugin quickly. (Demo install also uploads sample images to your media library.)', 'card-oracle' ),
            'cta_label'   => esc_html__( 'Open Demo Data Tool', 'card-oracle' ),
            'cta_url'     => admin_url( 'admin.php?page=card-oracle-admin-demodata' ),
            'completed'   => $has_demo_data,
        ), array(
            'id'          => 'embed-reading',
            'label'       => esc_html__( 'Embed Card Oracle on a page', 'card-oracle' ),
            'description' => esc_html__( 'Insert the Card Oracle block or shortcode into a page or post so visitors can interact with it.', 'card-oracle' ),
            'cta_label'   => esc_html__( 'Add a New Page', 'card-oracle' ),
            'cta_url'     => admin_url( 'post-new.php?post_type=page' ),
            'completed'   => $has_embed,
        ));
        $card_oracle_onboarding_total = count( $card_oracle_onboarding_tasks );
        $card_oracle_onboarding_completed_count = count( array_filter( $card_oracle_onboarding_tasks, static function ( $task ) {
            return !empty( $task['completed'] );
        } ) );
        $card_oracle_onboarding_completed_all = $card_oracle_onboarding_total > 0 && $card_oracle_onboarding_completed_count === $card_oracle_onboarding_total;
        $card_oracle_onboarding_show = 'dashboard' === $active_tab && !$card_oracle_onboarding_completed_all && !$card_oracle_onboarding_dismissed;
        if ( $card_oracle_onboarding_completed_all && !$card_oracle_onboarding_dismissed ) {
            update_user_meta( $current_user_id, 'card_oracle_onboarding_dismissed', 1 );
            $card_oracle_onboarding_dismissed = true;
        }
        for ($i = 0; $i < $readings_count; $i++) {
            $meta_data = array(
                'key'     => CO_READING_ID,
                'value'   => $readings[$i]->ID,
                'compare' => '=',
            );
            $reading_array[$i] = new stdClass();
            $reading_array[$i]->positions = count( get_card_oracle_posts_by_cpt(
                'co_positions',
                null,
                null,
                $meta_data
            ) );
            $reading_array[$i]->cards = count( get_card_oracle_posts_by_cpt(
                'co_cards',
                'title',
                'ASC',
                $meta_data
            ) );
            $reading_array[$i]->descriptions = $this->count_card_oracle_descriptions_by_reading( $readings[$i]->ID );
        }
        // Include the Header file.
        include_once 'partials/card-oracle-admin-header.php';
        // Display the tabs.
        echo '<h2 class="nav-tab-wrapper">';
        foreach ( $tabs as $tab ) {
            $class = ( $tab['uid'] === $active_tab ? ' nav-tab-active' : '' );
            echo '<a class="nav-tab' . esc_attr( $class ) . '" href="?page=card-oracle-admin-menu&tab=' . esc_attr( $tab['uid'] ) . '">' . esc_attr( $tab['name'] ) . '</a>';
        }
        echo '</h2>';
        // End tab display.
        // include the html files for each of the tabs.
        foreach ( $tabs as $tab ) {
            include_once plugin_dir_path( __FILE__ ) . $tab['htmlfile'];
        }
        include_once 'partials/card-oracle-admin-footer.php';
    }

    /**
     * Callbacks for the admin display sections
     *
     * @since 1.1.3
     * @param array $arguments Section headings.
     * @return void
     */
    public function card_oracle_section_callback( $arguments ) {
        switch ( $arguments['id'] ) {
            case 'general_section':
                break;
            case 'email_section':
                break;
            case 'status':
                ( new CardOracleAdminStatus() )->status_report();
                break;
            default:
                break;
        }
    }

    /**
     * Setup the General tab fields
     *
     * @since 1.1.3
     */
    public function card_oracle_setup_general_options() {
        $sections = array(array(
            'uid'   => 'general_section',
            'label' => esc_html__( 'General Settings', 'card-oracle' ),
            'page'  => 'card_oracle_option_general',
        ));
        $fields = array(
            array(
                'uid'          => CARD_ORACLE_RANDOM_DAYS,
                'label'        => esc_html__( 'Days to display random card', 'card-oracle' ),
                'min'          => 0,
                'max'          => 365,
                'default'      => 0,
                'section'      => 'general_section',
                'type'         => 'numberbox',
                'supplemental' => esc_html__( 'Number of days to display the random card. If left as 0, then it resets every 10 minutes.', 'card-oracle' ),
            ),
            array(
                'uid'          => 'card_oracle_powered_by',
                'label'        => esc_html__( 'Allow "Powered by"', 'card-oracle' ),
                'section'      => 'general_section',
                'type'         => 'toggle_switch',
                'value'        => 'yes',
                'supplemental' => esc_html__( 'When YES displays a "Powered by Card Oracle" badge in the footer.', 'card-oracle' ),
            ),
            array(
                'uid'          => 'card_oracle_affiliate_link',
                'label'        => esc_html__( 'Affiliate Link', 'card-oracle' ),
                'section'      => 'general_section',
                'type'         => 'text',
                'placeholder'  => 'https://chillichalli.com/card-oracle/?ref=your-id',
                'supplemental' => esc_html__( 'Optional affiliate link. If provided, this replaces the default ChilliChalli.com link in the "Powered by" badge.', 'card-oracle' ),
            ),
            array(
                'uid'          => 'card_oracle_force_https_images',
                'label'        => esc_html__( 'Force HTTPS for images', 'card-oracle' ),
                'section'      => 'general_section',
                'type'         => 'toggle_switch',
                'value'        => 'yes',
                'supplemental' => esc_html__( 'Rewrite card image URLs to https. Useful when your host returns attachment URLs as http and triggers mixed-content warnings on the front end.', 'card-oracle' ),
            )
        );
        $this->card_oracle_register_sections_fields( $sections, $fields, 'card_oracle_option_general' );
    }

    /**
     * Callback function for Card Oracle Options
     *
     * @since 1.1.3
     * @param array $arguments A list of options to display.
     */
    public function card_oracle_option_callback( $arguments ) {
        $is_premium = card_oracle_fs()->can_use_premium_code__premium_only();
        if ( $is_premium ) {
            require_once CARD_ORACLE_DIR . 'includes/activecampaign/ActiveCampaign.class.php';
            require_once CARD_ORACLE_DIR . 'includes/class-mailchimp__premium_only.php';
        }
        $brevo_option_ids = array('card_oracle_brevo_api_key', 'card_oracle_brevo_list', 'card_oracle_brevo_daily');
        if ( 'card_oracle_integration' === $arguments['uid'] ) {
            $stored_integration = get_option( $arguments['uid'] );
            $value = card_oracle_normalize_integration_slug( $stored_integration );
            if ( $value && $value !== $stored_integration ) {
                update_option( $arguments['uid'], $value );
            }
        } elseif ( in_array( $arguments['uid'], $brevo_option_ids, true ) ) {
            $value = card_oracle_get_brevo_option( $arguments['uid'] );
        } else {
            $value = get_option( $arguments['uid'] );
        }
        if ( !$value && isset( $arguments['default'] ) ) {
            $value = $arguments['default'];
            // Set to our default.
        }
        switch ( $arguments['type'] ) {
            case 'checkbox':
                $checked = ( 'yes' === $value ? 'checked="checked"' : '' );
                printf(
                    '<label class="card-oracle-checkbox"><input name="%1$s" id="%1$s" type="%2$s" value="%3$s" %4$s /><span class="card-oracle-slider round"></span></label>',
                    esc_attr( $arguments['uid'] ),
                    esc_attr( $arguments['type'] ),
                    esc_attr( $arguments['value'] ),
                    esc_attr( $checked )
                );
                break;
            case 'text':
                printf(
                    '<input class="regular-text code" id="%1$s" name="%1$s" type="%2$s" placeholder="%3$s" value="%4$s" />',
                    esc_attr( $arguments['uid'] ),
                    esc_attr( $arguments['type'] ),
                    esc_attr( $arguments['placeholder'] ),
                    esc_attr( $value )
                );
                break;
            case 'textarea':
                $rows = ( isset( $arguments['rows'] ) ? $arguments['rows'] : 3 );
                printf(
                    '<textarea class="large-text code" id="%1$s" name="%1$s" rows="%2$s" placeholder="%3$s">%4$s</textarea>',
                    esc_attr( $arguments['uid'] ),
                    esc_attr( $rows ),
                    esc_attr( $arguments['placeholder'] ),
                    esc_html( $value )
                );
                break;
            case 'reading_list':
                echo $this->get_reading_dropdown_box( $arguments['uid'], esc_html( get_option( 'card_oracle_reading_list' ) ) );
                // phpcs:ignore
                break;
            case 'dropdown':
                // If it is a select dropdown.
                if ( !empty( $arguments['options'] ) && is_array( $arguments['options'] ) ) {
                    printf( '<select name="%1$s" id="%1$s">', esc_attr( $arguments['uid'] ) );
                    foreach ( $arguments['options'] as $key => $label ) {
                        printf(
                            '<option value="%s" %s>%s</option>',
                            esc_attr( $key ),
                            selected( esc_attr( $value ), esc_attr( $key ), false ),
                            esc_textarea( $label )
                        );
                    }
                    printf( '</select>' );
                }
                break;
            case 'numberbox':
                printf(
                    '<div class="card-oracle-grid-content"><input class="card-oracle-metabox-number" name="%1$s" id="%1$s" type="number" min="%2$d" max="%3$d" ondrop="return false" onpaste="return false" value="%4$s" /></div>',
                    esc_attr( $arguments['uid'] ),
                    esc_attr( $arguments['min'] ),
                    esc_attr( $arguments['max'] ),
                    esc_attr( $value )
                );
                break;
            case 'toggle_switch':
                $yes_checked = ( 'yes' === $value ? 'checked="checked"' : '' );
                $no_checked = ( 'yes' !== $value ? 'checked="checked"' : '' );
                printf(
                    '<div class="card-oracle-radio-group">
					<label class="card-oracle-radio-option">
					<input type="radio" name="%1$s" id="%1$s_yes" value="yes" %2$s>
					<span class="card-oracle-radio-label">%3$s</span></label>
					<label class="card-oracle-radio-option">
					<input type="radio" name="%1$s" id="%1$s_no" value="no" %4$s>
					<span class="card-oracle-radio-label">%5$s</span></label></div>',
                    esc_attr( $arguments['uid'] ),
                    esc_attr( $yes_checked ),
                    esc_html__( 'Yes', 'card-oracle' ),
                    esc_attr( $no_checked ),
                    esc_html__( 'No', 'card-oracle' )
                );
                break;
            case 'info':
                $info_value = ( isset( $arguments['value'] ) ? $arguments['value'] : '' );
                printf( '<input class="regular-text code" id="%1$s" type="text" value="%2$s" readonly onclick="this.select();" style="background-color: #f0f0f1; cursor: pointer;" />', esc_attr( $arguments['uid'] ), esc_attr( $info_value ) );
                break;
            default:
                break;
        }
        if ( $is_premium ) {
            if ( in_array( $arguments['uid'], array('card_oracle_brevo_list', 'card_oracle_brevo_daily'), true ) ) {
                $saved_list = card_oracle_get_brevo_option( $arguments['uid'] );
            } else {
                $saved_list = get_option( $arguments['uid'] );
            }
            switch ( $arguments['type'] ) {
                case 'mailchimp_data':
                    if ( get_option( 'card_oracle_mailchimp_api_key' ) ) {
                        $mailchimp = new MailChimp(get_option( 'card_oracle_mailchimp_api_key' ));
                        $campaigns = $mailchimp->get( 'campaigns' );
                    }
                    break;
                case 'activecampaign_api':
                case 'mailchimp_api':
                case 'brevo_api':
                case 'sendinblue_api':
                // Legacy slug retained for backwards compatibility.
                case 'thenewsletter_api':
                    $mail_list = card_oracle_integration_get_list( $arguments['type'] );
                    $disabled = ( isset( $mail_list['disabled'] ) ? 'disabled=""' : '' );
                    echo '<select id="' . esc_attr( $arguments['uid'] ) . '" name="' . esc_attr( $arguments['uid'] ) . '" ' . esc_attr( $disabled ) . '>';
                    if ( $mail_list ) {
                        foreach ( $mail_list as $key => $value ) {
                            $selected = ( strcmp( $saved_list, $key ) === 0 ? 'selected="selected"' : '' );
                            echo '<option value="' . esc_attr( $key ) . '" ' . esc_attr( $selected ) . '>' . esc_html( $value ) . '</option>';
                        }
                    }
                    echo '</select>';
                    break;
                default:
                    break;
            }
        }
        // If there is help text.
        if ( !empty( $arguments['helper'] ) ) {
            printf( '<span class="helper">%s</span>', esc_html( $arguments['helper'] ) );
            // Show it.
        }
        // If there is supplemental text.
        if ( !empty( $arguments['supplemental'] ) ) {
            printf( '<p class="description">%s</p>', esc_html( $arguments['supplemental'] ) );
            // Show it.
        }
    }

    /**
     * Get all the posts of a custom post type, optional orderby and order
     *
     * @since 0.13.1
     * @param string $reading_id ID of the Reading.
     * @return array An array of all custom post_types requested.
     */
    public function count_card_oracle_descriptions_by_reading( $reading_id ) {
        // Get the Card IDs that relate to the Reading ID.
        $args_cards = array(
            'fields'      => 'ids',
            'numberposts' => -1,
            'post_status' => 'publish',
            'post_type'   => 'co_cards',
            'meta_query'  => array(
                // @codingStandardsIgnoreLine
                array(
                    'key'     => CO_READING_ID,
                    'value'   => $reading_id,
                    'compare' => '=',
                ),
            ),
        );
        // Get the Position IDs that relate to the Reading ID.
        $args_positions = array(
            'fields'      => 'ids',
            'numberposts' => -1,
            'post_status' => 'publish',
            'post_type'   => 'co_positions',
            'meta_query'  => array(
                // @codingStandardsIgnoreLine
                array(
                    'key'     => CO_READING_ID,
                    'value'   => $reading_id,
                    'compare' => '=',
                ),
            ),
        );
        // Run the queries.
        $cards = get_posts( $args_cards );
        $positions = get_posts( $args_positions );
        // Get the Descriptions IDs that relate to the Card IDs.
        $args_descriptions_cards = array(
            'fields'      => 'ids',
            'numberposts' => -1,
            'post_status' => 'publish',
            'post_type'   => 'co_descriptions',
            'meta_query'  => array(
                // @codingStandardsIgnoreLine
                array(
                    'key'     => CO_CARD_ID,
                    'value'   => $cards,
                    'compare' => 'IN',
                ),
            ),
        );
        // Get the Descriptions IDs that relate to the Position IDs.
        $args_descriptions_positions = array(
            'fields'      => 'ids',
            'numberposts' => -1,
            'post_status' => 'publish',
            'post_type'   => 'co_descriptions',
            'meta_query'  => array(
                // @codingStandardsIgnoreLine
                array(
                    'key'     => CO_POSITION_ID,
                    'value'   => $positions,
                    'compare' => 'IN',
                ),
            ),
        );
        // Get the Descriptions IDs for the Cards and Positions.
        $desc_cards = ( empty( $cards ) ? array() : get_posts( $args_descriptions_cards ) );
        $desc_positions = ( empty( $positions ) ? array() : get_posts( $args_descriptions_positions ) );
        // Merge the two arrays, get the unique IDs and return the count.
        return count( array_unique( array_merge( $desc_cards, $desc_positions ) ) );
    }

    /**
     * Create our custom metabox for cards
     *
     * @since 0.13.0
     * @param string $name Name of reading.
     * @param string $selected_reading (optional) Reading to select in dropdown.
     * @return array A html dropdown element.
     */
    public function get_reading_dropdown_box( $name, $selected_reading = null ) {
        return wp_dropdown_pages( array(
            'echo'             => 0,
            'name'             => esc_html( $name ),
            'show_option_none' => esc_html__( 'Select a Reading:', 'card-oracle' ),
            'post_type'        => 'co_readings',
            'selected'         => esc_attr( $selected_reading ),
            'sort_column'      => 'post_title',
        ) );
    }

    /**
     * Create our custom metabox for readings
     *
     * @since 0.13.0
     * @return void
     */
    public function add_meta_boxes_for_readings_cpt() {
        $screens = array('co_readings');
        add_meta_box(
            'card-reading',
            esc_html__( 'Settings', 'card-oracle' ),
            array($this, 'render_reading_metabox'),
            $screens,
            'normal',
            'high'
        );
    }

    /**
     * Create our custom metabox for positions
     *
     * @since 0.13.0
     * @return void
     */
    public function add_meta_boxes_for_positions_cpt() {
        $screens = array('co_positions');
        add_meta_box(
            'card-reading',
            esc_html__( 'Settings', 'card-oracle' ),
            array($this, 'render_position_metabox'),
            $screens,
            'normal',
            'high'
        );
    }

    /**
     * Create our custom metabox for cards
     *
     * @since 0.13.0
     * @return void
     */
    public function add_meta_boxes_for_cards_cpt() {
        $screens = array('co_cards');
        add_meta_box(
            'card-reading',
            esc_html__( 'Settings', 'card-oracle' ),
            array($this, 'render_card_metabox'),
            $screens,
            'normal',
            'high'
        );
    }

    // add_meta_boxes_for_cards_cpt
    /**
     * Create our custom metabox for descriptions
     *
     * @since 0.13.0
     * @return void
     */
    public function add_meta_boxes_for_descriptions_cpt() {
        $screens = array('co_descriptions');
        add_meta_box(
            'reverse',
            esc_html__( 'Reverse Card Description', 'card-oracle' ),
            array($this, 'render_reverse_description_metabox'),
            $screens,
            'normal',
            'high'
        );
        add_meta_box(
            'card',
            esc_html__( 'Settings', 'card-oracle' ),
            array($this, 'render_description_metabox'),
            $screens,
            'normal',
            'high'
        );
    }

    // add_meta_boxes_for_descriptions_cpt
    /**
     * Create our menu and submenus
     *
     * @since 1.1.3
     * @return void
     */
    public function card_oracle_menu_items() {
        $is_premium = card_oracle_fs()->can_use_premium_code__premium_only();
        // Card Oracle icon for admin menu svg.
        $co_admin_icon = 'data:image/svg+xml;base64,' . base64_encode( 
            // @codingStandardsIgnoreLine
            '<svg height="100px" width="100px"  fill="black" 
				xmlns:x="http://ns.adobe.com/Extensibility/1.0/" 
				xmlns:i="http://ns.adobe.com/AdobeIllustrator/10.0/" 
				xmlns:graph="http://ns.adobe.com/Graphs/1.0/" 
				xmlns="http://www.w3.org/2000/svg" 
				xmlns:xlink="http://www.w3.org/1999/xlink" 
				version="1.1" x="0px" y="0px" viewBox="0 0 100 100" enable-background="new 0 0 100 100" 
				xml:space="preserve"><g><g i:extraneous="self">
				<circle fill="black" cx="49.926" cy="57.893" r="10.125"></circle>
				<path fill="black" d="M50,78.988c-19.872,0-35.541-16.789-36.198-17.503l-1.95-2.12l1.788-2.259c0.164-0.208,4.097-5.142,
					10.443-10.102 C32.664,40.296,41.626,36.751,50,36.751c8.374,0,17.336,3.546,25.918,10.253c6.346,4.96,10.278,9.894,
					10.443,10.102l1.788,2.259 l-1.95,2.12C85.541,62.2,69.872,78.988,50,78.988z M20.944,59.019C25.56,63.219,36.99,
					72.238,50,72.238 c13.059,0,24.457-9.013,29.061-13.214C74.565,54.226,63.054,43.501,50,43.501C36.951,43.501,25.444,
					54.218,20.944,59.019z"></path>
				<path fill="black" d="M44.305,30.939L50,21.075l5.695,9.864c3.002,0.427,6.045,1.185,9.102,2.265L50,7.575L35.203,33.204 
					C38.26,32.124,41.303,31.366,44.305,30.939z"></path>
				<path fill="black" d="M81.252,74.857L87.309,85H12.691l6.057-10.143c-2.029-1.279-3.894-2.629-5.578-3.887L1,92h98L86.83,
					70.97 C85.146,72.228,83.28,73.578,81.252,74.857z"></path>
				</g></g></svg>'
         );
        // Add the main Card Oracle admin menu.
        add_menu_page(
            esc_html__( 'Card Oracle', 'card-oracle' ),
            esc_html__( 'Card Oracle', 'card-oracle' ),
            'manage_options',
            'card-oracle-admin-menu',
            array($this, 'display_card_oracle_options_page'),
            $co_admin_icon,
            40
        );
        // Add the Options submenu as the default menu when you click on Card Oracle menu.
        add_submenu_page(
            'card-oracle-admin-menu',
            esc_html__( 'Card Oracle Options', 'card-oracle' ),
            esc_html__( 'Dashboard', 'card-oracle' ),
            'manage_options',
            'card-oracle-admin-menu',
            array($this, 'display_card_oracle_options_page')
        );
        // Add the Readings submenu page.
        add_submenu_page(
            'card-oracle-admin-menu',
            esc_html__( 'Card Oracle Readings Admin', 'card-oracle' ),
            esc_html__( 'Readings', 'card-oracle' ),
            'manage_options',
            'edit.php?post_type=co_readings'
        );
        // Add the Positions submenu page.
        add_submenu_page(
            'card-oracle-admin-menu',
            esc_html__( 'Card Oracle positions Admin', 'card-oracle' ),
            esc_html__( 'Positions', 'card-oracle' ),
            'manage_options',
            'edit.php?post_type=co_positions'
        );
        // Add the Cards submenu page.
        add_submenu_page(
            'card-oracle-admin-menu',
            esc_html__( 'Card Oracle cards Admin', 'card-oracle' ),
            esc_html__( 'Cards', 'card-oracle' ),
            'manage_options',
            'edit.php?post_type=co_cards'
        );
        // Add the Descriptions submenu page.
        add_submenu_page(
            'card-oracle-admin-menu',
            esc_html__( 'Card Oracle Descriptions Admin', 'card-oracle' ),
            esc_html__( 'Descriptions', 'card-oracle' ),
            'manage_options',
            'edit.php?post_type=co_descriptions'
        );
        // Add the Demo Data submenu page.
        add_submenu_page(
            'card-oracle-admin-menu',
            esc_html__( 'Card Oracle Demo Data', 'card-oracle' ),
            esc_html__( 'Demo Data', 'card-oracle' ),
            'manage_options',
            'card-oracle-admin-demodata',
            array($this, 'display_card_oracle_demodata_page')
        );
        // Add the Documentation submenu page.
        add_submenu_page(
            'card-oracle-admin-menu',
            esc_html__( 'Card Oracle Documentation', 'card-oracle' ),
            esc_html__( 'Documentation', 'card-oracle' ),
            'manage_options',
            'card-oracle-admin-documentation',
            array($this, 'display_card_oracle_documentation_page')
        );
        // Add the Documentation Viewer page (hidden from menu).
        // Menu highlighting is handled by fix_admin_menu_highlighting().
        add_submenu_page(
            null,
            esc_html__( 'View Documentation', 'card-oracle' ),
            esc_html__( 'View Documentation', 'card-oracle' ),
            'manage_options',
            'card-oracle-doc-viewer',
            array($this, 'display_card_oracle_doc_viewer_page')
        );
        if ( $is_premium ) {
            // Add the Purchased Readings submenu page.
            add_submenu_page(
                'card-oracle-admin-menu',
                esc_html__( 'Card Oracle Purchased Readings Admin', 'card-oracle' ),
                esc_html__( 'Purchased Readings', 'card-oracle' ),
                'manage_options',
                'edit.php?post_type=co_order'
            );
            // Add the Validation submenu page.
            add_submenu_page(
                'card-oracle-admin-menu',
                esc_html__( 'Card Oracle Validation', 'card-oracle' ),
                esc_html__( 'Validation', 'card-oracle' ),
                'manage_options',
                'card-oracle-admin-validation',
                array($this, 'display_card_oracle_validation__premium_only')
            );
        }
    }

    /**
     * Fix admin menu highlighting for custom post type edit screens and hidden pages.
     *
     * Ensures the Card Oracle parent menu stays open when editing
     * readings, positions, cards, descriptions, or viewing documentation.
     *
     * @since 1.2.0
     * @param string $parent_file The parent file.
     * @return string Modified parent file.
     */
    public function fix_admin_menu_highlighting( $parent_file ) {
        global $current_screen;
        // Check if we're on the doc-viewer page (hidden submenu page).
        // phpcs:ignore WordPress.Security.NonceVerification.Recommended
        if ( isset( $_GET['page'] ) && 'card-oracle-doc-viewer' === $_GET['page'] ) {
            return 'card-oracle-admin-menu';
        }
        // Check if we're on an edit screen for our custom post types.
        if ( !$current_screen ) {
            return $parent_file;
        }
        $cpt_types = array(
            'co_readings',
            'co_positions',
            'co_cards',
            'co_descriptions'
        );
        // If editing one of our CPTs, set parent menu to Card Oracle.
        if ( in_array( $current_screen->post_type, $cpt_types, true ) ) {
            return 'card-oracle-admin-menu';
        }
        return $parent_file;
    }

    /**
     * Fix submenu highlighting for custom post type edit screens.
     *
     * Ensures the correct submenu item is highlighted when editing
     * readings, positions, cards, or descriptions.
     *
     * @since 1.2.0
     * @param string $submenu_file The submenu file.
     * @return string Modified submenu file.
     */
    public function fix_submenu_highlighting( $submenu_file ) {
        global $current_screen;
        // Check if we're on an edit screen for our custom post types.
        if ( !$current_screen ) {
            return $submenu_file;
        }
        // Map CPT to the correct submenu file.
        $submenu_map = array(
            'co_readings'     => 'edit.php?post_type=co_readings',
            'co_positions'    => 'edit.php?post_type=co_positions',
            'co_cards'        => 'edit.php?post_type=co_cards',
            'co_descriptions' => 'edit.php?post_type=co_descriptions',
        );
        // If editing one of our CPTs, set the correct submenu.
        if ( isset( $submenu_map[$current_screen->post_type] ) ) {
            return $submenu_map[$current_screen->post_type];
        }
        return $submenu_file;
    }

    /**
     * Limit free users to a maximum number of published readings.
     * Converts publish attempts to drafts once the cap is reached.
     *
     * @since 1.1.9
     *
     * @param array $data Post data to be saved.
     * @param array $postarr Original post array.
     * @return array Filtered post data.
     */
    public function limit_free_readings_publish( $data, $postarr ) {
        if ( 'co_readings' !== $data['post_type'] ) {
            return $data;
        }
        if ( 'publish' !== $data['post_status'] ) {
            return $data;
        }
        $current_count = $this->get_published_readings_count();
        // If we are editing an already published Reading, don't double count it.
        if ( !empty( $postarr['ID'] ) && 'publish' === get_post_status( $postarr['ID'] ) ) {
            $current_count = max( 0, $current_count - 1 );
        }
        if ( $current_count >= 3 ) {
            $data['post_status'] = 'draft';
            $message = esc_html__( 'The free version is limited to 3 published readings. Upgrade to publish more.', 'card-oracle' );
            set_transient( 'card_oracle_reading_limit_notice_' . get_current_user_id(), $message, MINUTE_IN_SECONDS );
        }
        return $data;
    }

    /**
     * Show admin notices for free reading limits.
     *
     * @since 1.1.9
     * @return void
     */
    public function render_reading_limit_notices() {
        // Show transient notice after a blocked publish attempt.
        $transient_key = 'card_oracle_reading_limit_notice_' . get_current_user_id();
        $message = get_transient( $transient_key );
        if ( $message ) {
            delete_transient( $transient_key );
            $this->output_reading_limit_notice( $message, 'warning' );
        }
        // Proactive notice when on Reading screens and limit is reached.
        $screen = get_current_screen();
        if ( !$screen || 'co_readings' !== $screen->post_type || card_oracle_fs()->can_use_premium_code__premium_only() ) {
            return;
        }
        if ( $this->get_published_readings_count() >= 3 ) {
            $message = esc_html__( 'The free version allows up to 3 published readings. Upgrade to add more.', 'card-oracle' );
            $this->output_reading_limit_notice( $message, 'info' );
        }
    }

    /**
     * Output a formatted admin notice with an upgrade link.
     *
     * @since 1.1.9
     * @param string $message Notice text.
     * @param string $type    Notice type (info|warning|error|success).
     * @return void
     */
    private function output_reading_limit_notice( $message, $type = 'info' ) {
        $upgrade_url = '';
        if ( function_exists( 'card_oracle_fs' ) && card_oracle_fs()->is_registered() ) {
            $upgrade_url = card_oracle_fs()->get_upgrade_url();
        }
        echo '<div class="notice notice-' . esc_attr( $type ) . ' is-dismissible">';
        echo '<p>' . esc_html( $message );
        if ( $upgrade_url ) {
            echo ' <a href="' . esc_url( $upgrade_url ) . '" target="_blank" rel="noopener noreferrer">' . esc_html__( 'Upgrade now', 'card-oracle' ) . '</a>';
        }
        echo '</p>';
        echo '</div>';
    }

    /**
     * Get count of published readings.
     *
     * @since 1.1.9
     * @return int
     */
    private function get_published_readings_count() {
        $counts = wp_count_posts( 'co_readings' );
        return ( isset( $counts->publish ) ? (int) $counts->publish : 0 );
    }

    /**
     * Move the featured image box for readings
     *
     * @since 0.13.0
     * @return void
     */
    public function cpt_image_box() {
        // Move the image metabox from the sidebar to the normal position.
        $screens = array('co_cards');
        remove_meta_box( 'postimagediv', $screens, 'side' );
        add_meta_box(
            'postimagediv',
            __( 'Front of Card Image', 'card-oracle' ),
            'post_thumbnail_meta_box',
            $screens,
            'side',
            'default'
        );
        // Move the image metabox from the sidebar to the normal position.
        $screens = array('co_readings');
        remove_meta_box( 'postimagediv', $screens, 'side' );
        add_meta_box(
            'postimagediv',
            __( 'Back of Card Image', 'card-oracle' ),
            'post_thumbnail_meta_box',
            $screens,
            'side',
            'default'
        );
        // Remove Astra metaboxes from our cpt.
        $screens = array(
            'co_cards',
            'co_readings',
            'co_positions',
            'co_descriptions'
        );
        // Remove Astra Settings in Posts.
        remove_meta_box( 'astra_settings_meta_box', $screens, 'side' );
        add_meta_box(
            'back-metabox',
            __( 'Previous Page', 'card-oracle' ),
            array($this, 'render_back_button_metabox'),
            $screens,
            'side',
            'high'
        );
    }

    /**
     * Display the custom admin columns for Cards
     *
     * @since 1.1.1
     * @param string $column Name of column.
     * @return void
     */
    public function custom_card_column( $column ) {
        global $post;
        global $wpdb;
        switch ( $column ) {
            case 'card_reading':
                $readings = get_post_meta( $post->ID, CO_READING_ID, false );
                foreach ( $readings as $reading ) {
                    echo '<p>';
                    echo esc_html( get_the_title( $reading ) );
                    echo '</p>';
                }
                break;
            case 'card_order':
                echo esc_html( get_post_meta( $post->ID, CO_CARD_ORDER, true ) );
                break;
            case 'co_shortcode':
                $alt_tag = esc_html__( 'Copy to clipboard', 'card-oracle' );
                echo '<input class="card-oracle-shortcode" id="copy' . esc_attr( $post->ID ) . '" value="[card-oracle id=&quot;' . esc_attr( $post->ID ) . '&quot;]"><button id="copy-action-btn" class="button" value="[card-oracle id=&quot;' . esc_attr( $post->ID ) . '&quot;]"> <img src="' . esc_url( CARD_ORACLE_CLIPPY ) . '" alt="' . esc_attr( $alt_tag ) . '"></button>';
                break;
            case 'description_reading':
                $position_id = get_post_meta( $post->ID, CO_POSITION_ID, false );
                $reading_id = get_post_meta( $position_id, CO_READING_ID, false );
                echo esc_html( get_the_title( $reading_id[0] ) );
                break;
            case 'number_card_descriptions':
                $meta_data = array(
                    'key'     => CO_CARD_ID,
                    'value'   => $post->ID,
                    'compare' => '=',
                );
                $count = count( get_card_oracle_posts_by_cpt(
                    'co_descriptions',
                    null,
                    null,
                    $meta_data
                ) );
                echo '<p>' . esc_html( $count ) . '</p>';
                break;
            case 'number_reading_positions':
                $meta_data = array(
                    'key'     => CO_READING_ID,
                    'value'   => $post->ID,
                    'compare' => '=',
                );
                echo count( get_card_oracle_posts_by_cpt(
                    'co_positions',
                    null,
                    null,
                    $meta_data
                ) );
                break;
            case 'card_title':
                $card_id = get_post_meta( $post->ID, CO_CARD_ID, true );
                $card_title = get_the_title( $card_id );
                echo '<strong><a class="row-title" href="' . esc_url( admin_url() ) . 'post.php?post=' . esc_attr( $card_id ) . '&action=edit">' . esc_html( $card_title ) . '</a></strong>';
                break;
            case 'position_title':
                $position_id = get_post_meta( $post->ID, CO_POSITION_ID, false );
                foreach ( $position_id as $id ) {
                    echo '<p>' . esc_html( get_the_title( $id ) ) . '</p>';
                }
                break;
            case 'position_number':
                $position_id = get_post_meta( $post->ID, CO_POSITION_ID, false );
                foreach ( $position_id as $id ) {
                    echo '<p>' . esc_html( get_post_meta( $id, CO_CARD_ORDER, true ) ) . '</p>';
                }
                break;
            case 'order_id':
                break;
            case 'order_email':
                echo esc_html( get_post_meta( $post->ID, CO_IPN_EMAIL, true ) );
                break;
            case 'order_price':
                echo esc_html( get_post_meta( $post->ID, CO_AMOUNT, true ) );
                break;
            case 'order_status':
                echo esc_html( get_post_meta( $post->ID, CO_ORDER_STATUS, true ) );
                break;
            case 'order_content':
                echo wp_kses_post( card_oracle_modal( $post->ID, esc_html( wp_kses_post( $post->post_content ) ), __( 'Copy Content', 'card-oracle' ) ) );
                break;
            case 'order_txn_id':
                $transaction_id = get_post_meta( $post->ID, CO_TXN_ID_LINK, true );
                if ( $transaction_id ) {
                    echo wp_kses_post( card_oracle_modal( $transaction_id, get_the_content( null, false, $transaction_id ), get_the_title( $transaction_id ) ) );
                }
                break;
            default:
                break;
        }
    }

    /**
     * Register the stylesheets for the admin area.
     *
     * @since 0.27.0
     * @return void
     */
    public function enqueue_styles() {
        /**
         * This function is provided for demonstration purposes only.
         *
         * An instance of this class should be passed to the run() function
         * defined in Card_Oracle_Loader as all of the hooks are defined
         * in that particular class.
         *
         * The Card_Oracle_Loader will then create the relationship
         * between the defined hooks and the functions defined in this
         * class.
         */
        wp_enqueue_style(
            $this->plugin_name,
            CARD_ORACLE_ADMIN_CSS_URL,
            array(),
            $this->version,
            'all'
        );
    }

    /**
     * Register the JavaScript for the admin area.
     *
     * @since 1.1.1
     * @return void
     */
    public function enqueue_scripts() {
        /**
         * This function is provided for demonstration purposes only.
         *
         * An instance of this class should be passed to the run() function
         * defined in Card_Oracle_Loader as all of the hooks are defined
         * in that particular class.
         *
         * The Card_Oracle_Loader will then create the relationship
         * between the defined hooks and the functions defined in this
         * class.
         */
        wp_register_script(
            'card-oracle-sortable',
            plugin_dir_url( __FILE__ ) . 'js/min/card-oracle-sortable.min.js',
            array('jquery-ui-sortable'),
            $this->version,
            true
        );
        wp_localize_script( 'card-oracle-sortable', 'cardOracleWizard', array(
            'nonce'             => wp_create_nonce( 'co_wizard_sort_nonce' ),
            'checkReadingNonce' => wp_create_nonce( 'co_wizard_check_reading' ),
            'ajaxUrl'           => admin_url( 'admin-ajax.php' ),
            'endpoint'          => add_query_arg( array(
                'page' => 'card-oracle-admin-menu',
                'tab'  => 'wizard',
            ), admin_url( 'admin.php' ) ),
        ) );
        wp_enqueue_script( 'jquery-ui-sortable' );
        wp_enqueue_script(
            $this->plugin_name,
            plugin_dir_url( __FILE__ ) . 'js/min/card-oracle-admin.min.js',
            array('jquery'),
            $this->version,
            false
        );
        wp_enqueue_script( 'card-oracle-sortable' );
        // Color picker and media uploader on the reading edit screen.
        $screen = ( function_exists( 'get_current_screen' ) ? get_current_screen() : null );
        if ( $screen && 'co_readings' === $screen->post_type ) {
            wp_enqueue_style( 'wp-color-picker' );
            wp_enqueue_script( 'wp-color-picker' );
            wp_add_inline_script( 'wp-color-picker', 'jQuery(function($){$(".card-oracle-color-picker").wpColorPicker();});' );
            wp_enqueue_media();
            wp_add_inline_script( 'jquery-core', 'jQuery(function($){
					var frames = {};
					$(document).on("click", ".card-oracle-media-choose", function(e){
						e.preventDefault();
						var target = $(this).data("target");
						if (!frames[target]) {
							frames[target] = wp.media({title: "' . esc_js( __( 'Select Background Image', 'card-oracle' ) ) . '", button: {text: "' . esc_js( __( 'Use this image', 'card-oracle' ) ) . '"}, multiple: false, library: {type: "image"}});
							frames[target].on("select", function(){
								var attachment = frames[target].state().get("selection").first().toJSON();
								var $wrap = $(\'#\' + target).closest(".card-oracle-media-picker");
								$wrap.find(".card-oracle-media-id").val(attachment.id);
								var url = attachment.sizes && attachment.sizes.medium ? attachment.sizes.medium.url : attachment.url;
								$wrap.find(".card-oracle-media-preview").attr("src", url).show();
								$wrap.find(".card-oracle-media-remove").show();
								$wrap.find(".card-oracle-media-choose").text("' . esc_js( __( 'Change image', 'card-oracle' ) ) . '");
							});
						}
						frames[target].open();
					});
					$(document).on("click", ".card-oracle-media-remove", function(e){
						e.preventDefault();
						var $wrap = $(this).closest(".card-oracle-media-picker");
						$wrap.find(".card-oracle-media-id").val("");
						$wrap.find(".card-oracle-media-preview").attr("src", "").hide();
						$(this).hide();
						$wrap.find(".card-oracle-media-choose").text("' . esc_js( __( 'Choose image', 'card-oracle' ) ) . '");
					});
				});' );
        }
        // Enqueue documentation viewer script.
        // Check if we're on a page with documentation content.
        if ( isset( $_GET['page'] ) && 'card-oracle-doc-viewer' === $_GET['page'] ) {
            wp_enqueue_script(
                $this->plugin_name . '-docs',
                plugin_dir_url( __FILE__ ) . 'js/min/card-oracle-docs.min.js',
                array('jquery'),
                $this->version,
                false
            );
        }
    }

    /**
     * Register and enqueue Gutenberg block assets
     *
     * @since 1.1.9
     * @return void
     */
    public function enqueue_block_editor_assets() {
        wp_enqueue_script(
            'card-oracle-block',
            plugin_dir_url( __FILE__ ) . 'js/card-oracle-block.js',
            array(
                'wp-blocks',
                'wp-element',
                'wp-block-editor',
                'wp-editor',
                'wp-components',
                'wp-i18n',
                'wp-api-fetch',
                'wp-server-side-render'
            ),
            $this->version,
            true
        );
        wp_enqueue_style(
            'card-oracle-block-style',
            plugin_dir_url( __FILE__ ) . 'css/card-oracle-block.css',
            array(),
            $this->version
        );
    }

    /**
     * Register REST API routes for the Gutenberg block
     *
     * @since 1.1.9
     * @return void
     */
    public function register_rest_routes() {
        register_rest_route( 'card-oracle/v1', '/readings', array(
            'methods'             => 'GET',
            'callback'            => array($this, 'get_readings_for_block'),
            'permission_callback' => array($this, 'check_block_permissions'),
        ) );
    }

    /**
     * Get readings for the Gutenberg block
     *
     * @since 1.1.9
     * @param WP_REST_Request $request Full details about the request.
     * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
     */
    public function get_readings_for_block( $request ) {
        $readings = get_posts( array(
            'post_type'   => 'co_readings',
            'post_status' => 'publish',
            'numberposts' => -1,
            'orderby'     => 'title',
            'order'       => 'ASC',
        ) );
        $deck_layout_options = array();
        foreach ( get_deck_layouts() as $value => $label ) {
            $deck_layout_options[] = array(
                'value' => (string) $value,
                'label' => (string) $label,
            );
        }
        $question_layout_options = array();
        foreach ( card_oracle_get_question_layout_options() as $value => $label ) {
            $question_layout_options[] = array(
                'value' => (string) $value,
                'label' => (string) $label,
            );
        }
        $presentation_layouts = get_presentation_layouts();
        $readings_data = array();
        foreach ( $readings as $reading ) {
            $presentation_options = array();
            $positions = get_positions_for_reading( $reading->ID );
            $positions_count = count( $positions );
            foreach ( $presentation_layouts as $layout ) {
                if ( !isset( $layout['uid'], $layout['label'] ) ) {
                    continue;
                }
                $layout_positions = ( isset( $layout['positions'] ) ? (int) $layout['positions'] : 0 );
                if ( 0 !== $layout_positions && $layout_positions !== $positions_count ) {
                    continue;
                }
                $presentation_options[] = array(
                    'value' => (string) $layout['uid'],
                    'label' => (string) $layout['label'],
                );
            }
            $deck_layout_value = get_post_meta( $reading->ID, CO_DECK_LAYOUT, true );
            if ( empty( $deck_layout_value ) ) {
                $deck_layout_value = 'standard';
            }
            $presentation_value = get_post_meta( $reading->ID, CO_PRESENTATION_LAYOUT, true );
            if ( empty( $presentation_value ) ) {
                $presentation_value = 'standard';
            }
            $question_layout_value = get_post_meta( $reading->ID, CO_QUESTION_LAYOUT, true );
            if ( empty( $question_layout_value ) ) {
                $question_layout_value = 'card-oracle-form-left';
            }
            $readings_data[] = array(
                'id'                   => $reading->ID,
                'title'                => $reading->post_title,
                'slug'                 => $reading->post_name,
                'deckLayout'           => $deck_layout_value,
                'presentationLayout'   => $presentation_value,
                'showLayoutTable'      => 'yes' === get_post_meta( $reading->ID, CO_LAYOUT_TABLE, true ),
                'displayQuestionInput' => 'yes' === get_post_meta( $reading->ID, DISPLAY_QUESTION, true ),
                'questionLayout'       => $question_layout_value,
                'presentationOptions'  => $presentation_options,
            );
        }
        return rest_ensure_response( array(
            'readings' => $readings_data,
            'options'  => array(
                'deckLayouts'     => $deck_layout_options,
                'questionLayouts' => $question_layout_options,
            ),
        ) );
    }

    /**
     * Register server-side block for proper shortcode processing
     *
     * @since 1.1.9
     * @return void
     */
    public function register_blocks() {
        if ( function_exists( 'register_block_type' ) ) {
            register_block_type( 'card-oracle/shortcode-block', array(
                'editor_script'   => 'card-oracle-block',
                'editor_style'    => 'card-oracle-block-style',
                'render_callback' => array($this, 'render_shortcode_block'),
                'attributes'      => array(
                    'readingId'            => array(
                        'type'    => 'string',
                        'default' => '',
                    ),
                    'readingName'          => array(
                        'type'    => 'string',
                        'default' => '',
                    ),
                    'shortcodeType'        => array(
                        'type'    => 'string',
                        'default' => 'daily',
                    ),
                    'deckLayout'           => array(
                        'type'    => 'string',
                        'default' => '',
                    ),
                    'presentationLayout'   => array(
                        'type'    => 'string',
                        'default' => '',
                    ),
                    'showLayoutTable'      => array(
                        'type'    => 'string',
                        'default' => '',
                    ),
                    'displayQuestionInput' => array(
                        'type'    => 'string',
                        'default' => '',
                    ),
                    'questionLayout'       => array(
                        'type'    => 'string',
                        'default' => '',
                    ),
                ),
            ) );
        }
    }

    /**
     * Render callback for the shortcode block
     *
     * @since 1.1.9
     * @param array $attributes Block attributes.
     * @return string Rendered block content.
     */
    public function render_shortcode_block( $attributes ) {
        $reading_name = ( isset( $attributes['readingName'] ) ? $attributes['readingName'] : '' );
        $shortcode_type = ( isset( $attributes['shortcodeType'] ) ? $attributes['shortcodeType'] : 'daily' );
        if ( empty( $reading_name ) ) {
            return '';
        }
        $shortcode_map = array(
            'daily'   => 'card-oracle-daily',
            'random'  => 'card-oracle-random',
            'reading' => 'card-oracle',
        );
        $shortcode_name = ( isset( $shortcode_map[$shortcode_type] ) ? $shortcode_map[$shortcode_type] : 'card-oracle-daily' );
        $shortcode = '[' . $shortcode_name . ' name="' . esc_attr( $reading_name ) . '"';
        if ( 'card-oracle' === $shortcode_name ) {
            $overrides = array();
            if ( !empty( $attributes['deckLayout'] ) ) {
                $deck_layout = card_oracle_sanitize_key( $attributes['deckLayout'] );
                $deck_layouts = get_deck_layouts();
                if ( $deck_layout && isset( $deck_layouts[$deck_layout] ) ) {
                    $overrides['deck_layout'] = $deck_layout;
                }
            }
            if ( !empty( $attributes['presentationLayout'] ) ) {
                $presentation = card_oracle_sanitize_key( $attributes['presentationLayout'] );
                if ( $presentation ) {
                    $overrides['presentation'] = $presentation;
                }
            }
            $normalize_yes_no = static function ( $value ) {
                if ( '' === $value ) {
                    return '';
                }
                $value = strtolower( (string) $value );
                if ( in_array( $value, array('1', 'true', 'yes'), true ) ) {
                    return 'yes';
                }
                if ( in_array( $value, array('0', 'false', 'no'), true ) ) {
                    return 'no';
                }
                return '';
            };
            if ( isset( $attributes['showLayoutTable'] ) ) {
                $layout_table = $normalize_yes_no( $attributes['showLayoutTable'] );
                if ( '' !== $layout_table ) {
                    $overrides['show_layout_table'] = $layout_table;
                }
            }
            if ( isset( $attributes['displayQuestionInput'] ) ) {
                $display_question = $normalize_yes_no( $attributes['displayQuestionInput'] );
                if ( '' !== $display_question ) {
                    $overrides['display_question'] = $display_question;
                }
            }
            if ( !empty( $attributes['questionLayout'] ) ) {
                $question_layout = card_oracle_sanitize_key( $attributes['questionLayout'] );
                $question_layouts = card_oracle_get_question_layout_options();
                if ( $question_layout && isset( $question_layouts[$question_layout] ) ) {
                    $overrides['question_layout'] = $question_layout;
                }
            }
            foreach ( $overrides as $key => $value ) {
                $shortcode .= ' ' . $key . '="' . esc_attr( $value ) . '"';
            }
        }
        $shortcode .= ']';
        return do_shortcode( $shortcode );
    }

    /**
     * Check permissions for block REST API endpoints
     *
     * @since 1.1.9
     * @return bool Whether the user has permission to access the endpoint.
     */
    public function check_block_permissions() {
        return current_user_can( 'edit_posts' );
    }

    /**
     * Create our custom post type for readings
     *
     * @since 0.25.0
     * @return void
     */
    public function register_card_oracle_cpt() {
        $is_premium = card_oracle_fs()->can_use_premium_code__premium_only();
        // Register the Cards cpt.
        // Set the labels for the custom post type.
        $labels = array(
            'name'               => esc_html__( 'Cards', 'card-oracle' ),
            'singular_name'      => esc_html__( 'Card', 'card-oracle' ),
            'add_new'            => esc_html__( 'Add New Card', 'card-oracle' ),
            'add_new_item'       => esc_html__( 'Add New Card', 'card-oracle' ),
            'edit_item'          => esc_html__( 'Edit Card', 'card-oracle' ),
            'new_item'           => esc_html__( 'New Card', 'card-oracle' ),
            'all_items'          => esc_html__( 'All Cards', 'card-oracle' ),
            'view_item'          => esc_html__( 'View Card', 'card-oracle' ),
            'search_items'       => esc_html__( 'Search Cards', 'card-oracle' ),
            'featured_image'     => esc_html__( 'Card Image', 'card-oracle' ),
            'set_featured_image' => esc_html__( 'Add Card Image', 'card-oracle' ),
        );
        // Settings for our post type.
        $args = array(
            'description'       => 'Holds our card information',
            'has_archive'       => false,
            'hierarchical'      => true,
            'labels'            => $labels,
            'menu_icon'         => 'dashicons-media-default',
            'menu_position'     => 42,
            'public'            => true,
            'show_in_menu'      => false,
            'show_in_admin_bar' => true,
            'show_in_nav_menus' => false,
            'supports'          => array('title', 'editor', 'thumbnail'),
            'query_var'         => true,
        );
        register_post_type( 'co_cards', $args );
        // Register the Descriptions cpt.
        // Set the labels for the custom post type.
        $labels = array(
            'name'          => esc_html__( 'Descriptions', 'card-oracle' ),
            'singular_name' => esc_html__( 'Description', 'card-oracle' ),
            'add_new'       => esc_html__( 'Add New Description', 'card-oracle' ),
            'add_new_item'  => esc_html__( 'Add New Description', 'card-oracle' ),
            'edit_item'     => esc_html__( 'Edit Description', 'card-oracle' ),
            'new_item'      => esc_html__( 'New Description', 'card-oracle' ),
            'all_items'     => esc_html__( 'All Descriptions', 'card-oracle' ),
            'view_item'     => esc_html__( 'View Description', 'card-oracle' ),
            'search_items'  => esc_html__( 'Search Descriptions', 'card-oracle' ),
        );
        // Settings for our post type.
        $args = array(
            'description'       => 'Holds our description information',
            'has_archive'       => false,
            'hierarchical'      => true,
            'labels'            => $labels,
            'menu_icon'         => 'dashicons-format-gallery',
            'menu_position'     => 43,
            'public'            => true,
            'show_in_menu'      => false,
            'show_in_admin_bar' => true,
            'show_in_nav_menus' => false,
            'supports'          => array('title', 'editor'),
            'query_var'         => true,
        );
        register_post_type( 'co_descriptions', $args );
        // register the Purchased Readings cpt.
        // Set the labels for the custom post type.
        $labels = array(
            'name'          => esc_html__( 'Purchased Readings', 'card-oracle' ),
            'singular_name' => esc_html__( 'Purchased Reading', 'card-oracle' ),
            'add_new'       => esc_html__( 'Add New Purchased Reading', 'card-oracle' ),
            'add_new_item'  => esc_html__( 'Add New Purchased Reading', 'card-oracle' ),
            'edit_item'     => esc_html__( 'Edit Purchased Reading', 'card-oracle' ),
            'new_item'      => esc_html__( 'New Purchased Reading', 'card-oracle' ),
            'all_items'     => esc_html__( 'All Purchased Readings', 'card-oracle' ),
            'view_item'     => esc_html__( 'View Purchased Reading', 'card-oracle' ),
            'search_items'  => esc_html__( 'Search Purchased Readings', 'card-oracle' ),
        );
        // Settings for our post type.
        $args = array(
            'description'       => 'Holds our orders information',
            'has_archive'       => false,
            'hierarchical'      => true,
            'labels'            => $labels,
            'menu_icon'         => 'dashicons-admin-page',
            'menu_position'     => 44,
            'public'            => true,
            'show_in_menu'      => false,
            'show_in_admin_bar' => false,
            'show_in_nav_menus' => false,
            'supports'          => array(''),
            'query_var'         => true,
            'capabilities'      => array(
                'create_posts' => 'do_not_allow',
            ),
        );
        register_post_type( 'co_order', $args );
        // Register the Positions cpt.
        // Set the labels for the custom post type.
        $labels = array(
            'name'          => esc_html__( 'Positions', 'card-oracle' ),
            'singular_name' => esc_html__( 'Position', 'card-oracle' ),
            'add_new'       => esc_html__( 'Add New Position', 'card-oracle' ),
            'add_new_item'  => esc_html__( 'Add New Position', 'card-oracle' ),
            'edit_item'     => esc_html__( 'Edit Position', 'card-oracle' ),
            'new_item'      => esc_html__( 'New Position', 'card-oracle' ),
            'all_items'     => esc_html__( 'All Positions', 'card-oracle' ),
            'view_item'     => esc_html__( 'View Position', 'card-oracle' ),
            'search_items'  => esc_html__( 'Search Positions', 'card-oracle' ),
        );
        // Settings for our post type.
        $args = array(
            'description'       => 'Holds our position information',
            'has_archive'       => false,
            'hierarchical'      => true,
            'labels'            => $labels,
            'menu_icon'         => 'dashicons-format-gallery',
            'menu_position'     => 41,
            'public'            => true,
            'show_in_menu'      => false,
            'show_in_admin_bar' => true,
            'show_in_nav_menus' => false,
            'supports'          => array('title'),
            'query_var'         => true,
        );
        if ( $is_premium ) {
            // Add the editor for Position text to display to the user.
            $args['supports'] = array('title', 'editor');
        }
        register_post_type( 'co_positions', $args );
        // register the Readings cpt.
        // Set the labels for the custom post type.
        $labels = array(
            'name'               => esc_html__( 'Readings', 'card-oracle' ),
            'singular_name'      => esc_html__( 'Reading', 'card-oracle' ),
            'add_new'            => esc_html__( 'Add New Reading', 'card-oracle' ),
            'add_new_item'       => esc_html__( 'Add New Reading', 'card-oracle' ),
            'edit_item'          => esc_html__( 'Edit Reading', 'card-oracle' ),
            'new_item'           => esc_html__( 'New Reading', 'card-oracle' ),
            'all_items'          => esc_html__( 'All Readings', 'card-oracle' ),
            'view_item'          => esc_html__( 'View Reading', 'card-oracle' ),
            'search_items'       => esc_html__( 'Search Readings', 'card-oracle' ),
            'featured_image'     => esc_html__( 'Card Back', 'card-oracle' ),
            'set_featured_image' => esc_html__( 'Add Card Back', 'card-oracle' ),
        );
        // Settings for our post type.
        $args = array(
            'description'       => 'Holds our reading information',
            'has_archive'       => false,
            'hierarchical'      => true,
            'labels'            => $labels,
            'menu_icon'         => 'dashicons-admin-page',
            'menu_position'     => 40,
            'public'            => true,
            'show_in_menu'      => false,
            'show_in_admin_bar' => false,
            'show_in_nav_menus' => false,
            'supports'          => array('title', 'thumbnail'),
            'query_var'         => true,
        );
        register_post_type( 'co_readings', $args );
        // register the Transactions cpt.
        // Set the labels for the custom post type.
        $labels = array(
            'name'          => esc_html__( 'Transactions', 'card-oracle' ),
            'singular_name' => esc_html__( 'Transaction', 'card-oracle' ),
            'add_new'       => esc_html__( 'Add New Transaction', 'card-oracle' ),
            'add_new_item'  => esc_html__( 'Add New Transaction', 'card-oracle' ),
            'edit_item'     => esc_html__( 'Edit Transaction', 'card-oracle' ),
            'new_item'      => esc_html__( 'New Transaction', 'card-oracle' ),
            'all_items'     => esc_html__( 'All Transactions', 'card-oracle' ),
            'view_item'     => esc_html__( 'View Transaction', 'card-oracle' ),
            'search_items'  => esc_html__( 'Search Transactions', 'card-oracle' ),
        );
        // Settings for our post type.
        $args = array(
            'description'       => 'Holds our transactions information',
            'has_archive'       => false,
            'hierarchical'      => true,
            'labels'            => $labels,
            'menu_icon'         => 'dashicons-admin-page',
            'menu_position'     => 44,
            'public'            => true,
            'show_in_menu'      => false,
            'show_in_admin_bar' => false,
            'show_in_nav_menus' => false,
            'supports'          => array('title'),
            'query_var'         => true,
        );
        register_post_type( 'co_transaction', $args );
    }

    /**
     * Render the Reading Metabox for Cards CPT
     *
     * @since 0.13.0
     * @return void
     */
    public function render_back_button_metabox() {
        global $post_type;
        $posttypes = get_post_types( array(
            'name' => $post_type,
        ), 'objects' );
        foreach ( $posttypes as $posttype ) {
            $page = $posttype->labels->name;
        }
        $text = esc_html__( 'Back to ', 'card-oracle' );
        echo '<a href="edit.php?post_type=' . esc_attr( $post_type ) . '"<button class="button button-primary button-large">' . esc_attr( $text ) . esc_attr( $page ) . '</button></a>';
    }

    /**
     * Display fields for the Metaboxes.
     *
     * @since 1.1.3
     * @param array $arguments Array of elements to display.
     */
    private function display_metabox_fields( $arguments ) {
        global $post;
        if ( 'checkbox_list' !== $arguments['type'] ) {
            printf( '<div class="card-oracle-grid-label">%1$s</div>', esc_html( $arguments['label'] ) );
        }
        switch ( $arguments['type'] ) {
            case 'checkbox':
                $checked = ( $arguments['value'] === $arguments['checked'] ? 'checked="checked"' : '' );
                printf(
                    '<div class="card-oracle-grid-content"><label class="card-oracle-checkbox"><input name="%1$s" id="%1$s" type="%2$s" value="%3$s" %4$s /><span class="card-oracle-slider round"></span></label></div>',
                    esc_attr( $arguments['uid'] ),
                    esc_attr( $arguments['type'] ),
                    esc_attr( $arguments['value'] ),
                    esc_attr( $checked )
                );
                if ( array_key_exists( 'supplemental', $arguments ) ) {
                    printf( '<div class="card-oracle-grid-footer">%s</div>', esc_html( $arguments['supplemental'] ) );
                }
                printf( '</td></tr>' );
                break;
            case 'checkbox_list':
                $checked = ( is_array( $arguments['checked'] ) && in_array( (string) $arguments['uid'], $arguments['checked'], true ) ? 'checked="checked"' : '' );
                printf(
                    '<div class="card-oracle-multiitem"><input id="reading%1$d" type="checkbox" name="_co_reading_id[]" value="%1$d" %2$s /><label for="reading%1$d">%3$s</label></div>',
                    esc_attr( $arguments['uid'] ),
                    esc_attr( $checked ),
                    esc_html( $arguments['label'] )
                );
                break;
            case 'dropdown':
                // If it is a select dropdown.
                if ( !empty( $arguments['options'] ) && is_array( $arguments['options'] ) ) {
                    printf( '<div class="card-oracle-grid-content"><select name="%1$s" id="%1$s">', esc_attr( $arguments['uid'] ) );
                    foreach ( $arguments['options'] as $key => $label ) {
                        printf(
                            '<option value="%s" %s>%s</option>',
                            esc_attr( $key ),
                            selected( esc_attr( $arguments['value'] ), esc_attr( $key ), false ),
                            esc_textarea( $label )
                        );
                    }
                    printf( '</select></div>' );
                }
                if ( array_key_exists( 'supplemental', $arguments ) ) {
                    printf( '<div class="card-oracle-grid-footer">%s</div>', esc_html( $arguments['supplemental'] ) );
                }
                break;
            case 'layout_dropdown':
                // If it is a select dropdown.
                $presentation_layouts = get_presentation_layouts();
                if ( !empty( $arguments['options'] ) && is_array( $arguments['options'] ) ) {
                    printf( '<div class="card-oracle-grid-content"><select name="%1$s" id="%1$s">', esc_attr( $arguments['uid'] ) );
                    foreach ( $arguments['options'] as $key => $label ) {
                        printf(
                            '<option value="%s" %s>%s</option>',
                            esc_attr( $key ),
                            selected( esc_attr( $arguments['value'] ), esc_attr( $key ), false ),
                            esc_textarea( $label )
                        );
                    }
                    printf( '</select>' );
                    $modal_content = '<div class="card-oracle-presentation-grid">';
                    foreach ( $presentation_layouts as $layout ) {
                        if ( $layout['image'] && file_exists( $layout['file'] ) ) {
                            $modal_content .= '<div class="card-oracle-layout-card">';
                            $modal_content .= '<img class="card-oracle-layout-image" src="' . esc_url( $layout['image'] ) . '" alt="' . esc_attr( $layout['label'] ) . '" />';
                            $modal_content .= '<p class="card-oracle-layout-label">' . esc_html( $layout['label'] ) . '</p>';
                            $modal_content .= '</div>';
                        }
                    }
                    $modal_content .= '</div>';
                    printf( wp_kses_post( card_oracle_modal(
                        'presentation',
                        $modal_content,
                        __( 'Examples', 'card-oracle' ),
                        'card-oracle-presentation-example',
                        __( 'Presentation Layout Examples', 'card-oracle' )
                    ) ) );
                    printf( '</div>' );
                    if ( array_key_exists( 'supplemental', $arguments ) ) {
                        printf( '<div class="card-oracle-grid-footer">%s</div>', esc_html( $arguments['supplemental'] ) );
                    }
                }
                break;
            case 'question_layout_dropdown':
                // Visual layout selector with two button groups.
                // Parse current value to determine layout type and alignment.
                $current_value = $arguments['value'];
                // Set default if empty.
                if ( empty( $current_value ) ) {
                    $current_value = 'card-oracle-form-left';
                }
                $is_inline = ( strpos( $current_value, 'inline' ) !== false ? 'inline' : 'stacked' );
                $current_alignment = str_replace( array('card-oracle-form-inline-', 'card-oracle-form-'), '', $current_value );
                // Hidden input for form submission.
                printf( '<input type="hidden" name="%1$s" id="%1$s" value="%2$s" />', esc_attr( $arguments['uid'] ), esc_attr( $current_value ) );
                // Layout type selector (Stacked vs Inline).
                $layout_types = array(
                    'stacked' => array(
                        'label' => esc_html__( 'Stacked', 'card-oracle' ),
                        'icon'  => '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="4" y1="6" x2="20" y2="6"/><line x1="4" y1="12" x2="20" y2="12"/><line x1="4" y1="18" x2="20" y2="18"/></svg>',
                    ),
                    'inline'  => array(
                        'label' => esc_html__( 'Inline', 'card-oracle' ),
                        'icon'  => '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="8" width="8" height="8" rx="1"/><rect x="13" y="8" width="8" height="8" rx="1"/></svg>',
                    ),
                );
                printf( '<div class="card-oracle-grid-content">' );
                printf( '<div class="card-oracle-layout-group"><span class="layout-group-label">%s</span>', esc_html__( 'Layout:', 'card-oracle' ) );
                printf( '<div class="card-oracle-layout-selector" data-group="layout">' );
                foreach ( $layout_types as $type_key => $type_data ) {
                    $type_selected = ( $is_inline === $type_key ? ' selected' : '' );
                    printf(
                        '<button type="button" class="layout-option%s" data-value="%s" title="%s"><span class="layout-icon">%s</span><span class="layout-label">%s</span></button>',
                        esc_attr( $type_selected ),
                        esc_attr( $type_key ),
                        esc_attr( $type_data['label'] ),
                        $type_data['icon'],
                        // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- SVG is hardcoded above.
                        esc_html( $type_data['label'] )
                    );
                }
                printf( '</div></div>' );
                // Alignment selector (Left, Center, Right).
                $alignments = array(
                    'left'   => array(
                        'label' => esc_html__( 'Left', 'card-oracle' ),
                        'icon'  => '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="3" y1="6" x2="16" y2="6"/><line x1="3" y1="12" x2="12" y2="12"/><line x1="3" y1="18" x2="18" y2="18"/></svg>',
                    ),
                    'center' => array(
                        'label' => esc_html__( 'Center', 'card-oracle' ),
                        'icon'  => '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="4" y1="6" x2="20" y2="6"/><line x1="6" y1="12" x2="18" y2="12"/><line x1="3" y1="18" x2="21" y2="18"/></svg>',
                    ),
                    'right'  => array(
                        'label' => esc_html__( 'Right', 'card-oracle' ),
                        'icon'  => '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="8" y1="6" x2="21" y2="6"/><line x1="12" y1="12" x2="21" y2="12"/><line x1="6" y1="18" x2="21" y2="18"/></svg>',
                    ),
                );
                printf( '<div class="card-oracle-layout-group"><span class="layout-group-label">%s</span>', esc_html__( 'Alignment:', 'card-oracle' ) );
                printf( '<div class="card-oracle-layout-selector" data-group="alignment">' );
                foreach ( $alignments as $align_key => $align_data ) {
                    $align_selected = ( $current_alignment === $align_key ? ' selected' : '' );
                    printf(
                        '<button type="button" class="layout-option%s" data-value="%s" title="%s"><span class="layout-icon">%s</span><span class="layout-label">%s</span></button>',
                        esc_attr( $align_selected ),
                        esc_attr( $align_key ),
                        esc_attr( $align_data['label'] ),
                        $align_data['icon'],
                        // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- SVG is hardcoded above.
                        esc_html( $align_data['label'] )
                    );
                }
                printf( '</div></div></div>' );
                // Add footer with supplemental text and layout preview in a single grid-footer.
                printf( '<div class="card-oracle-grid-footer card-oracle-grid-footer--with-preview">' );
                if ( array_key_exists( 'supplemental', $arguments ) ) {
                    printf( '<div class="card-oracle-supplemental">%s</div>', esc_html( $arguments['supplemental'] ) );
                }
                printf( '<div class="card-oracle-layout-preview">' );
                printf( '<span class="layout-group-label">%s</span>', esc_html__( 'Preview:', 'card-oracle' ) );
                printf( '<div class="card-oracle-layout-demo">' );
                printf( '<div id="card-oracle-display-layout" class="%s">', esc_attr( $current_value ) );
                printf( '<input class="card-oracle-form-textbox" type="text" placeholder="%s" disabled=""/>', esc_attr__( 'Enter your question...', 'card-oracle' ) );
                printf( '<button class="card-oracle-form-button" type="button" disabled="">%s</button>', esc_html__( 'Submit', 'card-oracle' ) );
                printf( '</div></div></div></div>' );
                break;
            case 'numberbox':
                printf(
                    '<div class="card-oracle-grid-content"><input class="card-oracle-metabox-number" name="%1$s" id="%1$s" type="number" min="%2$d" max="%3$d" ondrop="return false" onpaste="return false" value="%4$s" /></div>',
                    esc_attr( $arguments['uid'] ),
                    esc_attr( $arguments['min'] ),
                    esc_attr( $arguments['max'] ),
                    esc_html( $arguments['value'] )
                );
                if ( array_key_exists( 'supplemental', $arguments ) ) {
                    printf( '<div class="card-oracle-grid-footer">%s</div>', esc_html( $arguments['supplemental'] ) );
                }
                break;
            case 'color_picker':
                printf( '<div class="card-oracle-grid-content"><input class="card-oracle-color-picker" name="%1$s" id="%1$s" type="text" value="%2$s" data-default-color="" /></div>', esc_attr( $arguments['uid'] ), esc_attr( $arguments['value'] ) );
                if ( array_key_exists( 'supplemental', $arguments ) ) {
                    printf( '<div class="card-oracle-grid-footer">%s</div>', esc_html( $arguments['supplemental'] ) );
                }
                break;
            case 'media_image':
                $attachment_id = (int) $arguments['value'];
                $preview_url = ( $attachment_id ? wp_get_attachment_image_url( $attachment_id, 'medium' ) : '' );
                $hidden_display = ( $preview_url ? '' : ' style="display:none;"' );
                $choose_label = ( $attachment_id ? esc_html__( 'Change image', 'card-oracle' ) : esc_html__( 'Choose image', 'card-oracle' ) );
                printf(
                    '<div class="card-oracle-grid-content card-oracle-media-picker">
						<img class="card-oracle-media-preview" src="%2$s" alt=""%3$s />
						<input class="card-oracle-media-id" type="hidden" name="%1$s" id="%1$s" value="%4$d" />
						<button type="button" class="button card-oracle-media-choose" data-target="%1$s">%5$s</button>
						<button type="button" class="button-link-delete card-oracle-media-remove" data-target="%1$s"%3$s>%6$s</button>
					</div>',
                    esc_attr( $arguments['uid'] ),
                    esc_url( $preview_url ),
                    $hidden_display,
                    // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- pre-escaped attribute fragment.
                    (int) $attachment_id,
                    $choose_label,
                    // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- escaped above.
                    esc_html__( 'Remove', 'card-oracle' )
                );
                if ( array_key_exists( 'supplemental', $arguments ) ) {
                    printf( '<div class="card-oracle-grid-footer">%s</div>', esc_html( $arguments['supplemental'] ) );
                }
                break;
            case 'circular_diameter':
                printf(
                    '<div class="card-oracle-grid-content circular-diameter-field" data-deck-layout-field="%1$s"><input class="card-oracle-metabox-number" name="%2$s" id="%2$s" type="number" min="150" max="500" step="5" ondrop="return false" onpaste="return false" value="%3$s" /></div>',
                    esc_attr( CO_DECK_LAYOUT ),
                    esc_attr( $arguments['uid'] ),
                    esc_attr( $arguments['value'] )
                );
                if ( array_key_exists( 'supplemental', $arguments ) ) {
                    printf( '<div class="card-oracle-grid-footer">%s</div>', esc_html( $arguments['supplemental'] ) );
                }
                break;
            case 'reading_list':
                echo $this->get_reading_dropdown_box( $arguments['uid'], esc_html( get_option( 'card_oracle_reading_list' ) ) );
                // phpcs:ignore
                break;
            case 'radiobox':
                echo '<div class="card-oracle-grid-content"><div class="card-oracle-radio-toolbar">';
                foreach ( $arguments['options'] as $key => $label ) {
                    $checked = ( $arguments['value'] === $key ? 'checked="checked"' : '' );
                    printf(
                        '<input type="radio" id="%1$s" name="%2$s" value="%1$s" %3$s /><label for="%1$s">%4$s</label>',
                        esc_attr( $key ),
                        esc_attr( $arguments['uid'] ),
                        esc_attr( $checked ),
                        esc_attr( $label )
                    );
                }
                echo '</div></div>';
                if ( array_key_exists( 'supplemental', $arguments ) ) {
                    printf( '<div class="card-oracle-grid-footer">%s</div>', esc_html( $arguments['supplemental'] ) );
                }
                break;
            case 'text':
                printf(
                    '<div class="card-oracle-grid-content"><input class="regular-text code" id="%1$s" name="%1$s" type="%2$s" placeholder="%3$s" value="%4$s" /></div>',
                    esc_attr( $arguments['uid'] ),
                    esc_attr( $arguments['type'] ),
                    esc_attr( $arguments['placeholder'] ),
                    wp_kses( $arguments['value'], array() )
                );
                if ( array_key_exists( 'supplemental', $arguments ) ) {
                    printf( '<div class="card-oracle-grid-footer">%s</div>', esc_html( $arguments['supplemental'] ) );
                }
                break;
            case 'textarea':
                $rows = ( isset( $arguments['rows'] ) ? $arguments['rows'] : 3 );
                printf(
                    '<div class="card-oracle-grid-content"><textarea class="large-text code" id="%1$s" name="%1$s" rows="%2$s" placeholder="%3$s">%4$s</textarea></div>',
                    esc_attr( $arguments['uid'] ),
                    esc_attr( $rows ),
                    esc_attr( $arguments['placeholder'] ),
                    esc_html( $arguments['value'] )
                );
                break;
            case 'toggle_switch':
                $yes_checked = ( $arguments['value'] === $arguments['checked'] ? 'checked="checked"' : '' );
                $no_checked = ( $arguments['value'] !== $arguments['checked'] ? 'checked="checked"' : '' );
                printf(
                    '<div class="card-oracle-grid-content"><div class="card-oracle-radio-group">
					<label class="card-oracle-radio-option">
					<input type="radio" name="%1$s" id="%1$s_yes" value="%2$s" %3$s>
					<span class="card-oracle-radio-label">%4$s</span></label>
					<label class="card-oracle-radio-option">
					<input type="radio" name="%1$s" id="%1$s_no" value="no" %5$s>
					<span class="card-oracle-radio-label">%6$s</span></label></div></div>',
                    esc_attr( $arguments['uid'] ),
                    esc_attr( $arguments['value'] ),
                    esc_attr( $yes_checked ),
                    esc_html__( 'Yes', 'card-oracle' ),
                    esc_attr( $no_checked ),
                    esc_html__( 'No', 'card-oracle' )
                );
                if ( array_key_exists( 'supplemental', $arguments ) ) {
                    printf( '<div class="card-oracle-grid-footer">%s</div>', esc_html( $arguments['supplemental'] ) );
                }
                break;
            default:
                break;
        }
    }

    /**
     * Render the Reading Metabox for Cards CPT
     *
     * @since 1.1.3
     * @return void
     */
    public function render_card_metabox() {
        global $post;
        // Generate nonce.
        wp_nonce_field( 'meta_box_nonce', 'meta_box_nonce' );
        $readings = get_card_oracle_posts_by_cpt( 'co_readings', 'post_title' );
        $selected_readings = get_post_meta( $post->ID, CO_READING_ID, false );
        $audio_url = (string) get_post_meta( $post->ID, CO_CARD_AUDIO, true );
        echo '<div class="card-oracle-metabox-section">';
        echo '<h3 class="card-oracle-metabox-section-heading">';
        esc_html_e( 'Reading', 'card-oracle' );
        echo '</h3>';
        echo '<div class="card-oracle-multiflex">';
        foreach ( $readings as $id ) {
            $checked = ( is_array( $selected_readings ) && in_array( (string) $id->ID, $selected_readings, true ) ? 'checked="checked"' : '' );
            echo '<div class="card-oracle-multiitem">
					<input id="reading' . esc_attr( $id->ID ) . '" type="checkbox" name="_co_reading_id[]" value="' . esc_attr( $id->ID ) . '" ' . esc_attr( $checked ) . ' /><label for="reading' . esc_attr( $id->ID ) . '">' . esc_html( $id->post_title ) . '</label></div>';
        }
        echo '</div>';
        echo '</div>';
        echo '<div class="card-oracle-metabox-section">';
        echo '<h3 class="card-oracle-metabox-section-heading">';
        esc_html_e( 'Audio', 'card-oracle' );
        echo '</h3>';
        echo '<div class="card-oracle-grid-wrapper">';
        echo '<div class="card-oracle-grid-label"><label for="' . esc_attr( CO_CARD_AUDIO ) . '">' . esc_html__( 'Audio URL', 'card-oracle' ) . '</label></div>';
        printf( '<div class="card-oracle-grid-content"><input class="regular-text code" type="url" id="%1$s" name="%1$s" value="%2$s" placeholder="https://example.com/audio.mp3" /></div>', esc_attr( CO_CARD_AUDIO ), esc_attr( $audio_url ) );
        echo '<div class="card-oracle-grid-footer">' . esc_html__( 'Optional audio clip played alongside the card on the reading page. Paste a URL from your media library or anywhere reachable over HTTPS.', 'card-oracle' ) . '</div>';
        echo '</div>';
        echo '</div>';
    }

    /**
     * Render the Card Metabox for Descriptions CPT
     *
     * @since 1.1.3
     * @return void
     */
    public function render_description_metabox() {
        global $post;
        // Generate nonce.
        wp_nonce_field( 'meta_box_nonce', 'meta_box_nonce' );
        $selected_card = get_post_meta( $post->ID, CO_CARD_ID, true );
        echo '<div class="card-oracle-metabox-section">';
        echo '<h3 class="card-oracle-metabox-section-heading">';
        esc_html_e( 'Card', 'card-oracle' );
        echo '</h3>';
        $dropdown = wp_dropdown_pages( array(
            'post_type'        => 'co_cards',
            'selected'         => esc_attr( $selected_card ),
            'name'             => esc_attr( CO_CARD_ID ),
            'show_option_none' => esc_html__( '(no card)', 'card-oracle' ),
            'sort_column'      => 'post_title',
            'echo'             => 0,
        ) );
        $allowed_html = array(
            'select' => array(
                'name' => array(),
            ),
            'option' => array(
                'value'    => array(),
                'selected' => array(),
            ),
        );
        echo wp_kses( $dropdown, $allowed_html );
        echo '</div>';
        $selected_position = get_post_meta( $post->ID, CO_POSITION_ID, false );
        echo '<div class="card-oracle-metabox-section">';
        echo '<h3 class="card-oracle-metabox-section-heading">';
        esc_html_e( 'Description Position', 'card-oracle' );
        echo '</h3>';
        $positions = get_card_oracle_posts_by_cpt( 'co_positions', 'title', 'ASC' );
        echo '<div class="card-oracle-multiflex">';
        foreach ( $positions as $id ) {
            $selected = get_post_meta( $post->ID, CO_POSITION_ID, false );
            $checked = ( is_array( $selected_position ) && in_array( (string) $id->ID, $selected_position, true ) ? 'checked="checked"' : '' );
            echo '<div class="card-oracle-multiitem"><input id="position' . esc_attr( $id->ID ) . '" class="card-oracle-multibox" type="checkbox" name="_co_position_id[]" value="' . esc_attr( $id->ID ) . '" ' . esc_attr( $checked ) . ' /><label for="position' . esc_attr( $id->ID ) . '">' . esc_html( get_the_title( $id->ID ) ) . '</label></div>';
        }
        echo '</div>';
        echo '</div>';
    }

    // render_description_metabox
    /**
     * Render the Reverse Descriptions Metabox for Descriptions CPT
     *
     * @since 0.13.0
     * @return void
     */
    public function render_reverse_description_metabox() {
        global $post;
        // Generate nonce.
        wp_nonce_field( 'meta_box_nonce', 'meta_box_nonce' );
        $reverse_description = wpautop( get_post_meta( $post->ID, CO_REVERSE_DESCRIPTION, true ), true );
        wp_editor( $reverse_description, 'meta_content_editor', array(
            'wpautop'       => true,
            'media_buttons' => false,
            'textarea_name' => CO_REVERSE_DESCRIPTION,
            'textarea_rows' => 10,
            'teeny'         => true,
        ) );
    }

    /**
     * Render the Reading and Order Metabox for Positions CPT
     *
     * @since 1.0.4
     * @return void
     */
    public function render_position_metabox() {
        global $post;
        // Check if premium features are available.
        $is_premium = card_oracle_fs()->can_use_premium_code__premium_only();
        // Generate nonce.
        wp_nonce_field( 'meta_box_nonce', 'meta_box_nonce' );
        $readings = get_card_oracle_posts_by_cpt( 'co_readings', 'post_title' );
        $selected_readings = get_post_meta( $post->ID, CO_READING_ID, false );
        $fields = array(array(
            'uid'   => CO_CARD_ORDER,
            'label' => esc_html__( 'Order', 'card-oracle' ),
            'min'   => 1,
            'max'   => 999,
            'value' => $post->_co_card_order,
            'type'  => 'numberbox',
        ));
        // Add premium fields.
        if ( $is_premium ) {
            // Push premium fields on to fields array.
            $premium_fields = array(array(
                'uid'          => CO_POSITION_TEXT,
                'label'        => esc_html__( 'Text to Display for Positions', 'card-oracle' ),
                'value'        => $post->_co_position_text,
                'options'      => array(
                    'titletext' => __( 'Position Title', 'card-oracle' ),
                    'posttext'  => __( 'Position Text', 'card-oracle' ),
                    'notext'    => __( 'None', 'card-oracle' ),
                ),
                'type'         => 'radiobox',
                'supplemental' => esc_html__( 'This is the text displayed to the user above this card position on the results page and email.', 'card-oracle' ),
            ));
            $fields = array_merge( $fields, $premium_fields );
        }
        // Display the Reading Setting.
        echo '<div class="card-oracle-metabox-section">';
        printf( '<h3 class="card-oracle-metabox-section-heading">%s</h3><div class="card-oracle-multiflex">', esc_html__( 'Reading', 'card-oracle' ) );
        foreach ( $readings as $id ) {
            $this->display_metabox_fields( array(
                'uid'     => $id->ID,
                'checked' => $selected_readings,
                'label'   => $id->post_title,
                'type'    => 'checkbox_list',
            ) );
        }
        echo '</div></div>';
        // Display the all the Settings.
        echo '<div class="card-oracle-metabox-section">';
        printf( '<h3 class="card-oracle-metabox-section-heading">%s</h3>', esc_html__( 'Settings', 'card-oracle' ) );
        foreach ( $fields as $field ) {
            echo '<div class="card-oracle-grid-wrapper">';
            $this->display_metabox_fields( $field );
            echo '</div>';
        }
        echo '</div>';
    }

    /**
     * Render the Reading Metabox for Cards CPT
     *
     * @since 1.1.1
     * @return void
     */
    public function render_reading_metabox() {
        global $post;
        // Check if premium features are available.
        $is_premium = card_oracle_fs()->can_use_premium_code__premium_only();
        // Get the deck layouts to populate the setting field.
        $deck_layouts = get_deck_layouts();
        $presentation_layouts = get_presentation_layouts();
        $number_positions = count( get_positions_for_reading( $post->ID ) );
        // Create the option list for the Presentation layouts.
        foreach ( $presentation_layouts as $presentation_layout ) {
            if ( file_exists( $presentation_layout['file'] ) ) {
                if ( 0 === $presentation_layout['positions'] || $number_positions === $presentation_layout['positions'] ) {
                    $presentation_options[$presentation_layout['uid']] = $presentation_layout['label'];
                }
            }
        }
        $settings = array(
            'wpautop'          => true,
            'media_buttons'    => false,
            'textarea_rows'    => 3,
            'tabindex'         => '',
            'editor_css'       => '',
            'editor_class'     => '',
            'teeny'            => false,
            'dfw'              => false,
            'tinymce'          => true,
            'quicktags'        => true,
            'drag_drop_upload' => false,
        );
        $sections = array(array(
            'uid'   => 'general_section',
            'label' => esc_html__( 'General', 'card-oracle' ),
        ), array(
            'uid'   => 'question_section',
            'label' => esc_html__( 'Display Question Options', 'card-oracle' ),
        ));
        $fields = array(
            array(
                'uid'          => CO_AUTO_SUBMIT,
                'label'        => esc_html__( 'Auto Submit', 'card-oracle' ),
                'type'         => 'toggle_switch',
                'order'        => 10,
                'section'      => 'general_section',
                'checked'      => $post->_co_auto_submit,
                'value'        => 'yes',
                'supplemental' => esc_html__( 'Automatically submit the reading when the last card is selected by the user. The submit button is hidden from the user.', 'card-oracle' ),
            ),
            array(
                'uid'          => CO_SHUFFLE_CARDS,
                'label'        => esc_html__( 'Shuffle Cards', 'card-oracle' ),
                'type'         => 'toggle_switch',
                'order'        => 15,
                'section'      => 'general_section',
                'checked'      => $post->_co_shuffle_cards,
                'value'        => 'yes',
                'supplemental' => esc_html__( 'Allow users to shuffle the cards before making their selection. When disabled, cards are displayed in their original order without a shuffle button.', 'card-oracle' ),
            ),
            array(
                'uid'          => CO_DECK_LAYOUT,
                'label'        => esc_html__( 'Deck Display Layout', 'card-oracle' ),
                'type'         => 'dropdown',
                'order'        => 20,
                'section'      => 'general_section',
                'options'      => $deck_layouts,
                'value'        => $post->_co_deck_layout,
                'supplemental' => esc_html__( 'How the deck of cards will be displayed to the users.', 'card-oracle' ),
            ),
            array(
                'uid'          => CO_CIRCULAR_DIAMETER,
                'label'        => esc_html__( 'Circular Diameter Scale', 'card-oracle' ),
                'type'         => 'circular_diameter',
                'order'        => 25,
                'section'      => 'general_section',
                'value'        => ( $post->_co_circular_diameter ? $post->_co_circular_diameter : '235' ),
                'supplemental' => esc_html__( 'Controls the diameter of the circular card layout. Higher values create a larger circle.', 'card-oracle' ),
            ),
            array(
                'uid'          => CO_REVERSE_PERCENT,
                'label'        => esc_html__( 'Reversed Percentage', 'card-oracle' ),
                'type'         => 'numberbox',
                'order'        => 30,
                'section'      => 'general_section',
                'min'          => 0,
                'max'          => 100,
                'value'        => $post->_co_reverse_percent,
                'supplemental' => esc_html__( 'Percentage chance of a card displaying in reverse.', 'card-oracle' ),
            ),
            array(
                'uid'          => CO_PRESENTATION_LAYOUT,
                'label'        => esc_html__( 'Presentation Layout', 'card-oracle' ),
                'type'         => 'layout_dropdown',
                'order'        => 40,
                'section'      => 'general_section',
                'options'      => $presentation_options,
                'value'        => $post->_co_presentation_layout,
                'supplemental' => esc_html__( 'Only Layouts with the same number of positions as the reading will be available in the dropdown.' ),
            ),
            array(
                'uid'          => CO_LAYOUT_TABLE,
                'label'        => esc_html__( 'Display Layout table', 'card-oracle' ),
                'type'         => 'toggle_switch',
                'order'        => 50,
                'section'      => 'general_section',
                'checked'      => $post->_co_layout_table,
                'value'        => 'yes',
                'supplemental' => esc_html__( 'Displays a table of the positions and the cards picked next to the layout.', 'card-oracle' ),
            ),
            array(
                'uid'          => CO_TARGET_BLANK,
                'label'        => esc_html__( 'New Window', 'card-oracle' ),
                'type'         => 'toggle_switch',
                'order'        => 55,
                'section'      => 'general_section',
                'checked'      => $post->_co_target_blank,
                'value'        => 'yes',
                'supplemental' => esc_html__( 'The result page will open in a new browser window or tab.', 'card-oracle' ),
            ),
            array(
                'uid'          => CO_ACCENT_COLOR,
                'label'        => esc_html__( 'Accent Color', 'card-oracle' ),
                'type'         => 'color_picker',
                'order'        => 57,
                'section'      => 'general_section',
                'value'        => get_post_meta( $post->ID, CO_ACCENT_COLOR, true ),
                'supplemental' => esc_html__( 'Optional theme color for the reading. Leave blank to inherit the site theme. Exposed as the CSS variable --co-accent on the reading wrapper.', 'card-oracle' ),
            ),
            array(
                'uid'          => CO_BACKGROUND_IMAGE,
                'label'        => esc_html__( 'Background Image', 'card-oracle' ),
                'type'         => 'media_image',
                'order'        => 58,
                'section'      => 'general_section',
                'value'        => get_post_meta( $post->ID, CO_BACKGROUND_IMAGE, true ),
                'supplemental' => esc_html__( 'Optional background image for the reading. Applied to the reading wrapper as a cover image.', 'card-oracle' ),
            ),
            array(
                'uid'          => DISPLAY_QUESTION,
                'label'        => esc_html__( 'Display Question Input Box', 'card-oracle' ),
                'type'         => 'toggle_switch',
                'order'        => 60,
                'section'      => 'question_section',
                'checked'      => $post->display_question,
                'value'        => 'yes',
                'supplemental' => esc_html__( 'Enabling this will display an input field to the users to enter a question.', 'card-oracle' ),
            ),
            array(
                'uid'          => 'question_text',
                'label'        => esc_html__( 'Question text', 'card-oracle' ),
                'type'         => 'text',
                'order'        => 70,
                'section'      => 'question_section',
                'value'        => get_post_meta( $post->ID, 'question_text', true ),
                'placeholder'  => '',
                'supplemental' => esc_html__( 'Avoid using apostrophes in the text if you plan on allowing users to email the readings.', 'card-oracle' ),
            )
        );
        $text_editors = array(
            // Text editor for text to display between Question and Cards.
            array(
                'uid'          => 'before_cards_text',
                'label'        => esc_html__( 'Text to display before Cards.', 'card-oracle' ),
                'order'        => 20,
                'value'        => $post->before_cards_text,
                'supplemental' => '',
            ),
            // Add a text editor for the footer text on daily and random cards.
            array(
                'uid'          => 'footer_text',
                'label'        => esc_html__( 'Footer to be displayed on daily and random cards.', 'card-oracle' ),
                'order'        => 30,
                'value'        => $post->footer_text,
                'supplemental' => '',
            ),
        );
        // Append a WooCommerce product gate field when WooCommerce is active.
        // Available in both free and premium because WC bridges Card Oracle to
        // the merchant's existing storefront, independent of premium gating.
        if ( class_exists( 'Card_Oracle_WC' ) && Card_Oracle_WC::is_active() ) {
            $fields[] = array(
                'uid'          => CO_WC_PRODUCT_ID,
                'label'        => esc_html__( 'WooCommerce Product', 'card-oracle' ),
                'type'         => 'dropdown',
                'order'        => 59,
                'section'      => 'general_section',
                'options'      => Card_Oracle_WC::get_products_for_select(),
                'value'        => get_post_meta( $post->ID, CO_WC_PRODUCT_ID, true ),
                'supplemental' => esc_html__( 'Optional. Link this reading to a WooCommerce product. Until the visitor has a completed order containing that product, the shortcode shows a "Buy this reading" gate instead of the cards.', 'card-oracle' ),
            );
        }
        // Append an Easy Digital Downloads gate field when EDD is active.
        if ( class_exists( 'Card_Oracle_EDD' ) && Card_Oracle_EDD::is_active() ) {
            $fields[] = array(
                'uid'          => CO_EDD_DOWNLOAD_ID,
                'label'        => esc_html__( 'EDD Download', 'card-oracle' ),
                'type'         => 'dropdown',
                'order'        => 59,
                'section'      => 'general_section',
                'options'      => Card_Oracle_EDD::get_downloads_for_select(),
                'value'        => get_post_meta( $post->ID, CO_EDD_DOWNLOAD_ID, true ),
                'supplemental' => esc_html__( 'Optional. Link this reading to an Easy Digital Downloads product. Until the logged-in customer has a completed purchase of that download, the shortcode shows a "Buy this reading" gate.', 'card-oracle' ),
            );
        }
        // Add premium fields.
        if ( $is_premium ) {
            $integration = get_option( 'card_oracle_integration' );
            $payment = get_option( 'card_oracle_payment_provider' );
            // Push premium fields on to fields array.
            $premium_fields = array(array(
                'uid'          => CO_QUESTION_LAYOUT,
                'label'        => esc_html__( 'Question Display Layout', 'card-oracle' ),
                'order'        => 70,
                'section'      => 'question_section',
                'type'         => 'question_layout_dropdown',
                'options'      => array(
                    'card-oracle-form-left'          => esc_html__( 'Left', 'card-oracle' ),
                    'card-oracle-form-center'        => esc_html__( 'Center', 'card-oracle' ),
                    'card-oracle-form-right'         => esc_html__( 'Right', 'card-oracle' ),
                    'card-oracle-form-inline-left'   => esc_html__( 'Inline Left', 'card-oracle' ),
                    'card-oracle-form-inline-center' => esc_html__( 'Inline Center', 'card-oracle' ),
                    'card-oracle-form-inline-right'  => esc_html__( 'Inline Right', 'card-oracle' ),
                ),
                'value'        => $post->_co_question_layout,
                'supplemental' => esc_html__( 'Example of how it will display on the page.', 'card-oracle' ),
            ));
            $fields = array_merge( $fields, $premium_fields );
            // If Email integration set then push premium section on to the sections array.
            if ( $integration ) {
                $integration_sections = array(array(
                    'uid'   => 'email_section',
                    'label' => esc_html__( 'Email Options', 'card-oracle' ),
                ));
                $sections = array_merge( $sections, $integration_sections );
                $subscriber_list = card_oracle_integration_get_list( $integration );
                $integration_fields = array(array(
                    'uid'          => CO_SUBSCRIBER_LIST,
                    'label'        => esc_html__( 'Subscriber List', 'card-oracle' ),
                    'type'         => 'dropdown',
                    'order'        => 80,
                    'section'      => 'email_section',
                    'options'      => $subscriber_list,
                    'value'        => $post->_co_subscriber_list,
                    'supplemental' => esc_html__( 'Which list the user will be subscribed to for this Reading.', 'card-oracle' ),
                ));
                $fields = array_merge( $fields, $integration_fields );
            }
            // If Email integration set then push premium section on to the sections array.
            if ( $payment ) {
                $payment_sections = array(array(
                    'uid'   => 'payment_section',
                    'label' => esc_html__( 'Payment Options', 'card-oracle' ),
                ));
                $sections = array_merge( $sections, $payment_sections );
                $integration_fields = array(
                    array(
                        'uid'         => CO_PURCHASE_NAME,
                        'label'       => esc_html__( 'Receipt Description', 'card-oracle' ),
                        'type'        => 'text',
                        'order'       => 90,
                        'section'     => 'payment_section',
                        'value'       => $post->_co_purchase_name,
                        'placeholder' => 'This is the description displayed on the receipt to the user.',
                    ),
                    array(
                        'uid'          => CO_PRICE,
                        'label'        => esc_html__( 'Reading Price', 'card-oracle' ),
                        'type'         => 'text',
                        'order'        => 100,
                        'section'      => 'payment_section',
                        'value'        => $post->_co_price,
                        'placeholder'  => '',
                        'supplemental' => esc_html__( 'If blank or 0, reading will be free.', 'card-oracle' ),
                    ),
                    array(
                        'uid'          => CO_PURCHASE_SUBJECT,
                        'label'        => esc_html__( 'Subject Text', 'card-oracle' ),
                        'type'         => 'text',
                        'order'        => 100,
                        'section'      => 'payment_section',
                        'value'        => $post->_co_purchase_subject,
                        'placeholder'  => '',
                        'supplemental' => esc_html__( 'This is the text used for the email subject when someone purchases this reading.', 'card-oracle' ),
                    ),
                    array(
                        'uid'          => CO_BUTTON_TEXT,
                        'label'        => esc_html__( 'Payment Button Text', 'card-oracle' ),
                        'type'         => 'text',
                        'order'        => 110,
                        'section'      => 'payment_section',
                        'value'        => get_post_meta( $post->ID, CO_BUTTON_TEXT, true ),
                        'placeholder'  => esc_attr( ( get_option( 'card_oracle_payment_button_text' ) ? get_option( 'card_oracle_payment_button_text' ) : __( 'Buy Now', 'card-oracle' ) ) ),
                        'supplemental' => esc_html__( 'Override the global payment button text for this reading only. Leave blank to use the global default.', 'card-oracle' ),
                    )
                );
                $fields = array_merge( $fields, $integration_fields );
            }
        }
        // Generate nonce.
        wp_nonce_field( 'meta_box_nonce', 'meta_box_nonce' );
        // Get whether or not the Display Input box should be checked.
        $display_question_checked = $post->display_question;
        if ( 'yes' === $display_question_checked ) {
            $display_question_checked = 'checked="checked"';
        }
        foreach ( $sections as $section ) {
            echo '<div class="card-oracle-metabox-section">';
            printf( '<h3 class="card-oracle-metabox-section-heading">%s</h3>', esc_html( $section['label'] ) );
            foreach ( $fields as $field ) {
                if ( $field['section'] === $section['uid'] ) {
                    echo '<div class="card-oracle-grid-wrapper">';
                    $this->display_metabox_fields( $field );
                    echo '</div>';
                }
            }
            echo '</div>';
        }
        // Add premium text editors.
        if ( $is_premium ) {
            $premium_text_editors = array(
                // Text editor for text to display between Question and Cards.
                array(
                    'uid'          => CO_SALES_TEXT,
                    'label'        => esc_html__( 'Payment Sales Text', 'card-oracle' ),
                    'order'        => 10,
                    'value'        => $post->_co_sales_text,
                    'supplemental' => esc_html__( 'This text is placed above the payment Buy Now button on the results page.', 'card-oracle' ),
                ),
            );
            $text_editors = array_merge( $text_editors, $premium_text_editors );
        }
        $keys = array_column( $text_editors, 'order' );
        array_multisort( $keys, SORT_ASC, $text_editors );
        // Add text editor fields at the bottom.
        foreach ( $text_editors as $text_editor ) {
            echo '<div class="card-oracle-metabox-text-editor">';
            printf( '<h3 class="card-oracle-metabox-text-editor-heading">%s</h3>', wp_kses_post( $text_editor['label'] ) );
            wp_editor( html_entity_decode( $text_editor['value'] ), $text_editor['uid'], $settings );
            if ( $text_editor['supplemental'] ) {
                printf( '<p class="card-oracle-grid-footer">%s</p>', esc_html( $text_editor['supplemental'] ) );
            }
            echo '</div>';
        }
    }

    // render_reading_metabox.
    /**
     * Save the card post meta for Card Oracle
     *
     * @since 1.1.3
     * @return void
     */
    public function save_card_oracle_meta_data() {
        global $post, $co_notices;
        // If this is an autosave, our form has not been submitted, so we don't want to do anything.
        if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
            return;
        }
        // Prevent quick edit from clearing custom fields.
        if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) {
            return;
        }
        if ( !$post instanceof WP_Post ) {
            return;
        }
        // Check the user's permissions.
        if ( !current_user_can( 'edit_post', $post->ID ) ) {
            return;
        }
        // Only process Card Oracle post types.
        $allowed_post_types = array(
            'co_readings',
            'co_positions',
            'co_cards',
            'co_descriptions'
        );
        if ( !in_array( $post->post_type, $allowed_post_types, true ) ) {
            return;
        }
        // Check nonce for all meta fields - required for CSRF protection.
        if ( !isset( $_POST['meta_box_nonce'] ) || !wp_verify_nonce( sanitize_key( $_POST['meta_box_nonce'] ), 'meta_box_nonce' ) ) {
            return;
        }
        // Validate unique reading title for co_readings post type.
        if ( 'co_readings' === $post->post_type && isset( $_POST['post_title'] ) ) {
            $post_title = sanitize_text_field( wp_unslash( $_POST['post_title'] ) );
            if ( !empty( $post_title ) ) {
                global $wpdb;
                // Check if another reading with this title exists (excluding current post).
                $duplicate = $wpdb->get_var( $wpdb->prepare( "SELECT ID FROM {$wpdb->posts}\n\t\t\t\t\t\tWHERE post_title = %s\n\t\t\t\t\t\tAND post_type = 'co_readings'\n\t\t\t\t\t\tAND ID != %d\n\t\t\t\t\t\tAND post_status != 'trash'\n\t\t\t\t\t\tLIMIT 1", $post_title, $post->ID ) );
                if ( $duplicate ) {
                    // Duplicate title found - prevent save and show error.
                    if ( isset( $co_notices ) && $co_notices instanceof CardOracleNotices ) {
                        $co_notices->add( 'duplicate_reading_title', sprintf( 
                            /* translators: %s: reading title */
                            __( 'A reading with the title "%s" already exists. Please choose a different title.', 'card-oracle' ),
                            esc_html( $post_title )
                         ), 'error' );
                    }
                    // Unhook this function to prevent infinite loop.
                    remove_action( 'save_post', array($this, 'save_card_oracle_meta_data') );
                    // Update post to revert title to original.
                    $original_title = get_the_title( $post->ID );
                    wp_update_post( array(
                        'ID'         => $post->ID,
                        'post_title' => $original_title,
                    ) );
                    // Re-hook this function.
                    add_action( 'save_post', array($this, 'save_card_oracle_meta_data') );
                    return;
                }
            }
        }
        // Validate unique reading slug for co_readings post type.
        if ( 'co_readings' === $post->post_type ) {
            // Get the post slug - WordPress may auto-generate it.
            $post_slug = $post->post_name;
            if ( !empty( $post_slug ) ) {
                global $wpdb;
                // Check if another reading with this slug exists (excluding current post).
                $duplicate = $wpdb->get_var( $wpdb->prepare( "SELECT ID FROM {$wpdb->posts}\n\t\t\t\t\t\tWHERE post_name = %s\n\t\t\t\t\t\tAND post_type = 'co_readings'\n\t\t\t\t\t\tAND ID != %d\n\t\t\t\t\t\tAND post_status != 'trash'\n\t\t\t\t\t\tLIMIT 1", $post_slug, $post->ID ) );
                if ( $duplicate ) {
                    // Duplicate slug found - show warning.
                    if ( isset( $co_notices ) && $co_notices instanceof CardOracleNotices ) {
                        $co_notices->add( 'duplicate_reading_slug', sprintf( 
                            /* translators: 1: Reading slug, 2: Duplicate reading ID */
                            __( 'Another reading with the URL slug "%1$s" already exists (ID: %2$d). WordPress has automatically modified your slug. Consider renaming to avoid confusion.', 'card-oracle' ),
                            esc_html( $post_slug ),
                            (int) $duplicate
                         ), 'warning' );
                    }
                }
            }
        }
        // Save reading associations (works for all CPT types).
        if ( isset( $_POST[CO_READING_ID] ) ) {
            $current_readings = get_post_meta( $post->ID, CO_READING_ID, false );
            // Multiple Readings ID update - sanitize array to integers only.
            if ( isset( $_POST[CO_READING_ID] ) && is_array( $_POST[CO_READING_ID] ) ) {
                $post_readings = array_map( 'absint', wp_unslash( $_POST[CO_READING_ID] ) );
                // If a current reading ID is not in the POST array then remove it.
                if ( !empty( $current_readings ) ) {
                    foreach ( $current_readings as $current_reading ) {
                        if ( !in_array( (int) $current_reading, $post_readings, true ) ) {
                            delete_post_meta( $post->ID, CO_READING_ID, $current_reading );
                        }
                    }
                }
                // If a POST reading is not in the current array then add it.
                if ( !empty( $post_readings ) ) {
                    foreach ( $post_readings as $reading ) {
                        if ( !in_array( (int) $reading, array_map( 'intval', $current_readings ), true ) ) {
                            add_post_meta( $post->ID, CO_READING_ID, $reading );
                        }
                    }
                }
            }
        } else {
            delete_post_meta( $post->ID, CO_READING_ID );
        }
        // Save card-specific fields.
        if ( 'co_cards' === $post->post_type ) {
            if ( isset( $_POST[CO_CARD_AUDIO] ) ) {
                // phpcs:ignore WordPress.Security.NonceVerification.Missing
                $audio_raw = esc_url_raw( wp_unslash( $_POST[CO_CARD_AUDIO] ) );
                // phpcs:ignore WordPress.Security.NonceVerification.Missing
                if ( '' !== $audio_raw ) {
                    update_post_meta( $post->ID, CO_CARD_AUDIO, $audio_raw );
                } else {
                    delete_post_meta( $post->ID, CO_CARD_AUDIO );
                }
            }
            return;
        }
        // Save position-specific fields.
        if ( 'co_positions' === $post->post_type ) {
            // If the Order has been selected update it.
            $this->update_post_meta_using_sanitize( $_POST, $post->ID, CO_CARD_ORDER );
            // If the Position Text has been selected update it (premium only).
            $this->update_post_meta_using_sanitize( $_POST, $post->ID, CO_POSITION_TEXT );
            return;
        }
        // Save description-specific fields.
        if ( 'co_descriptions' === $post->post_type ) {
            // If the Card has been selected update it.
            $this->update_post_meta_using_sanitize( $_POST, $post->ID, CO_CARD_ID );
            // If the Reverse Description is set update it.
            $this->update_post_meta_using_wp_kses_post( $_POST, $post->ID, CO_REVERSE_DESCRIPTION );
            // If the Position has been selected update it (multi-value checkboxes).
            if ( isset( $_POST[CO_POSITION_ID] ) && is_array( $_POST[CO_POSITION_ID] ) ) {
                $current_positions = get_post_meta( $post->ID, CO_POSITION_ID, false );
                // Remove positions that are no longer selected.
                foreach ( $current_positions as $current_position ) {
                    if ( !in_array( $current_position, $_POST[CO_POSITION_ID], true ) ) {
                        delete_post_meta( $post->ID, CO_POSITION_ID, $current_position );
                    }
                }
                // Add newly selected positions.
                $positions = array_map( 'sanitize_text_field', wp_unslash( $_POST[CO_POSITION_ID] ) );
                foreach ( $positions as $position ) {
                    if ( !in_array( $position, $current_positions, true ) ) {
                        add_post_meta( $post->ID, CO_POSITION_ID, sanitize_text_field( $position ) );
                    }
                }
            } else {
                delete_post_meta( $post->ID, CO_POSITION_ID );
            }
            return;
        }
        // The remaining fields are specific to co_readings post type.
        if ( 'co_readings' !== $post->post_type ) {
            return;
        }
        // Save accent color with hex sanitization (empty clears the meta).
        if ( isset( $_POST[CO_ACCENT_COLOR] ) ) {
            // phpcs:ignore WordPress.Security.NonceVerification.Missing
            $accent_raw = sanitize_hex_color( wp_unslash( $_POST[CO_ACCENT_COLOR] ) );
            // phpcs:ignore WordPress.Security.NonceVerification.Missing
            if ( $accent_raw ) {
                update_post_meta( $post->ID, CO_ACCENT_COLOR, $accent_raw );
            } else {
                delete_post_meta( $post->ID, CO_ACCENT_COLOR );
            }
        }
        // Save WooCommerce product link (empty or 0 clears the meta).
        if ( isset( $_POST[CO_WC_PRODUCT_ID] ) ) {
            // phpcs:ignore WordPress.Security.NonceVerification.Missing
            $wc_product_id = absint( $_POST[CO_WC_PRODUCT_ID] );
            // phpcs:ignore WordPress.Security.NonceVerification.Missing
            if ( $wc_product_id > 0 ) {
                update_post_meta( $post->ID, CO_WC_PRODUCT_ID, $wc_product_id );
            } else {
                delete_post_meta( $post->ID, CO_WC_PRODUCT_ID );
            }
        }
        // Save EDD download link (empty or 0 clears the meta).
        if ( isset( $_POST[CO_EDD_DOWNLOAD_ID] ) ) {
            // phpcs:ignore WordPress.Security.NonceVerification.Missing
            $edd_download_id = absint( $_POST[CO_EDD_DOWNLOAD_ID] );
            // phpcs:ignore WordPress.Security.NonceVerification.Missing
            if ( $edd_download_id > 0 ) {
                update_post_meta( $post->ID, CO_EDD_DOWNLOAD_ID, $edd_download_id );
            } else {
                delete_post_meta( $post->ID, CO_EDD_DOWNLOAD_ID );
            }
        }
        // Save background image attachment ID (empty or 0 clears the meta).
        if ( isset( $_POST[CO_BACKGROUND_IMAGE] ) ) {
            // phpcs:ignore WordPress.Security.NonceVerification.Missing
            $bg_id = absint( $_POST[CO_BACKGROUND_IMAGE] );
            // phpcs:ignore WordPress.Security.NonceVerification.Missing
            if ( $bg_id && wp_attachment_is_image( $bg_id ) ) {
                update_post_meta( $post->ID, CO_BACKGROUND_IMAGE, $bg_id );
            } else {
                delete_post_meta( $post->ID, CO_BACKGROUND_IMAGE );
            }
        }
        // If the Auto Submit has been selected update it.
        $this->update_post_meta_using_sanitize( $_POST, $post->ID, CO_AUTO_SUBMIT );
        // Save Shuffle Cards as 'yes' or 'no' explicitly (default enabled for backwards compatibility).
        // The toggle posts the selected radio value ('yes' or 'no'), so honour it rather than
        // treating the key's mere presence as 'yes' -- otherwise "No" can never be saved.
        $shuffle_value = ( isset( $_POST[CO_SHUFFLE_CARDS] ) && 'no' === $_POST[CO_SHUFFLE_CARDS] ? 'no' : 'yes' );
        // phpcs:ignore WordPress.Security.NonceVerification.Missing
        update_post_meta( $post->ID, CO_SHUFFLE_CARDS, $shuffle_value );
        // If the Card has been selected update it.
        $this->update_post_meta_using_sanitize( $_POST, $post->ID, CO_CARD_ID );
        // If the Order has been selected update it.
        $this->update_post_meta_using_sanitize( $_POST, $post->ID, CO_CARD_ORDER );
        // If the Deck Layout has been selected update it.
        $this->update_post_meta_using_sanitize( $_POST, $post->ID, CO_DECK_LAYOUT );
        // If the Circular Diameter has been selected update it.
        $this->update_post_meta_using_sanitize( $_POST, $post->ID, CO_CIRCULAR_DIAMETER );
        // If the Deck Layout has been selected update it.
        $this->update_post_meta_using_sanitize( $_POST, $post->ID, CO_LAYOUT_TABLE );
        // If the Position Text has been selected update it.
        $this->update_post_meta_using_sanitize( $_POST, $post->ID, CO_POSITION_TEXT );
        // If the Deck Layout has been selected update it.
        $this->update_post_meta_using_sanitize( $_POST, $post->ID, CO_PRESENTATION_LAYOUT );
        // If the Price is set update it.
        $this->update_post_meta_using_sanitize( $_POST, $post->ID, CO_PRICE );
        // If the Payment Button Text is set update it (per-reading override).
        $this->update_post_meta_using_sanitize( $_POST, $post->ID, CO_BUTTON_TEXT );
        // If the Purchase Name is set update it.
        $this->update_post_meta_using_sanitize( $_POST, $post->ID, CO_PURCHASE_NAME );
        // If the Purchase Name is set update it.
        $this->update_post_meta_using_sanitize( $_POST, $post->ID, CO_PURCHASE_SUBJECT );
        // If the Reading Question Layout has been selected update it.
        $this->update_post_meta_using_sanitize( $_POST, $post->ID, CO_QUESTION_LAYOUT );
        // If the Reverse Percentage has been selected update it.
        $this->update_post_meta_using_sanitize( $_POST, $post->ID, CO_REVERSE_PERCENT );
        // If the New Window has been selected update it.
        $this->update_post_meta_using_sanitize( $_POST, $post->ID, CO_TARGET_BLANK );
        // If the Reading Display has been selected update it.
        $this->update_post_meta_using_sanitize( $_POST, $post->ID, DISPLAY_QUESTION );
        // If the Reading Text to display before Cards has been selected update it.
        $this->update_post_meta_using_wp_kses_post( $_POST, $post->ID, BEFORE_CARDS_TEXT );
        // If the Reverse Description is set update it.
        $this->update_post_meta_using_wp_kses_post( $_POST, $post->ID, CO_REVERSE_DESCRIPTION );
        // If the Sales Text is set update it.
        $this->update_post_meta_using_wp_kses_post( $_POST, $post->ID, CO_SALES_TEXT );
        // If the Subscriber List is set update it.
        $this->update_post_meta_using_wp_kses_post( $_POST, $post->ID, CO_SUBSCRIBER_LIST );
        // If the Reading Footer text has been selected update it.
        $this->update_post_meta_using_wp_kses_post( $_POST, $post->ID, FOOTER_TEXT );
        // If the Reading Display has been selected update it.
        $this->update_post_meta_using_wp_kses_post( $_POST, $post->ID, QUESTION_TEXT );
        // If the Position has been selected update it.
        if ( isset( $_POST[CO_POSITION_ID] ) ) {
            $current_positions = get_post_meta( $post->ID, CO_POSITION_ID, false );
            // If a current _co_position_id is not in the POST array then remove it.
            foreach ( $current_positions as $current_position ) {
                if ( !in_array( $current_position, $_POST[CO_POSITION_ID], true ) ) {
                    delete_post_meta( $post->ID, CO_POSITION_ID, $current_position );
                }
            }
            // If a current POST position is not in the current array then add it.
            $positions = array_map( 'sanitize_text_field', wp_unslash( $_POST[CO_POSITION_ID] ) );
            foreach ( $positions as $position ) {
                if ( !in_array( $position, $current_positions, true ) ) {
                    add_post_meta( $post->ID, CO_POSITION_ID, sanitize_text_field( $position ) );
                }
            }
        } else {
            delete_post_meta( $post->ID, CO_POSITION_ID );
        }
        $this->maybe_add_reading_validation_notices( $post->ID );
    }

    /**
     * Display validation notices when a reading is incomplete.
     *
     * @since 1.1.9
     * @param int $reading_id Reading ID being saved.
     * @return void
     */
    private function maybe_add_reading_validation_notices( $reading_id ) {
        global $co_notices;
        if ( !isset( $co_notices ) || !$co_notices instanceof CardOracleNotices ) {
            return;
        }
        $meta_filter = array(
            'key'     => CO_READING_ID,
            'value'   => $reading_id,
            'compare' => '=',
        );
        $positions = get_card_oracle_posts_by_cpt(
            'co_positions',
            null,
            null,
            $meta_filter
        );
        $cards = get_card_oracle_posts_by_cpt(
            'co_cards',
            'title',
            'ASC',
            $meta_filter
        );
        $positions_count = ( is_array( $positions ) ? count( $positions ) : 0 );
        $cards_count = ( is_array( $cards ) ? count( $cards ) : 0 );
        if ( 0 === $positions_count ) {
            $co_notices->add( 'card_oracle_missing_positions', esc_html__( 'No positions are linked to this reading yet. Add positions so cards have a place in the layout.', 'card-oracle' ), 'error' );
        }
        if ( 0 === $cards_count ) {
            $co_notices->add( 'card_oracle_missing_cards', esc_html__( 'This reading does not contain any cards. Add cards to avoid empty shortcode output.', 'card-oracle' ), 'error' );
        }
        if ( $positions_count > 0 && $cards_count > 0 ) {
            $expected_descriptions = $positions_count * $cards_count;
            $descriptions_total = $this->count_card_oracle_descriptions_by_reading( $reading_id );
            if ( $descriptions_total < $expected_descriptions ) {
                $co_notices->add( 'card_oracle_incomplete_descriptions', sprintf( esc_html__( 'Only %1$s of %2$s card/position descriptions are set up. Add the missing descriptions so each combination has guidance.', 'card-oracle' ), number_format_i18n( $descriptions_total ), number_format_i18n( $expected_descriptions ) ), 'warning' );
            }
        }
    }

    /**
     * Update the meta data for a post using sanitize_text_field.
     *
     * @since 1.1.3
     * @param string $post_data  The _POST.
     * @param int    $post_id  The id of the post to update.
     * @param string $meta_name  The id of the post to update.
     */
    private function update_post_meta_using_sanitize( $post_data, $post_id, $meta_name ) {
        ( isset( $post_data[$meta_name] ) ? update_post_meta( $post_id, $meta_name, sanitize_text_field( wp_unslash( $post_data[$meta_name] ) ) ) : delete_post_meta( $post_id, $meta_name ) );
    }

    /**
     * Update the meta data for a post using wp_kses_post.
     *
     * @since 1.1.3
     * @param string $post_data  The _POST.
     * @param int    $post_id  The id of the post to update.
     * @param string $meta_name  The id of the post to update.
     */
    private function update_post_meta_using_wp_kses_post( $post_data, $post_id, $meta_name ) {
        ( isset( $post_data[$meta_name] ) ? update_post_meta( $post_id, $meta_name, wp_kses_post( wp_unslash( $post_data[$meta_name] ) ) ) : delete_post_meta( $post_id, $meta_name ) );
    }

    /**
     * Set the admin columns for Cards
     *
     * @since 0.13.0
     * @param array $columns Set the columns for Cards.
     * @return array $columns
     */
    public function set_custom_cards_columns( $columns ) {
        // unset the date so we can move it to the end.
        unset($columns['date']);
        $columns['card_reading'] = esc_html__( 'Associated Reading(s)', 'card-oracle' );
        $columns['number_card_descriptions'] = esc_html__( 'Number of Descriptions', 'card-oracle' );
        $columns['date'] = esc_html__( 'Date', 'card-oracle' );
        return $columns;
    }

    /**
     * Set the admin columns for Descriptions
     *
     * @since 0.13.0
     * @param array $columns Set the columns for Descriptions.
     * @return $columns
     */
    public function set_custom_descriptions_columns( $columns ) {
        // unset the date so we can move it to the end.
        unset($columns['date']);
        $columns['card_title'] = esc_html__( 'Card', 'card-oracle' );
        $columns['position_title'] = esc_html__( 'Position', 'card-oracle' );
        $columns['position_number'] = esc_html__( 'Position Number', 'card-oracle' );
        $columns['date'] = esc_html__( 'Date', 'card-oracle' );
        return $columns;
    }

    /**
     * Set the admin columns for Readings
     *
     * @since 0.13.0
     * @param array $columns Set the columns for Readings.
     * @return array $columns
     */
    public function set_custom_readings_columns( $columns ) {
        // unset the date so we can move it to the end.
        unset($columns['date']);
        $columns['co_shortcode'] = esc_html__( 'Shortcode', 'card-oracle' );
        $columns['number_reading_positions'] = esc_html__( 'Positions', 'card-oracle' );
        $columns['date'] = esc_html__( 'Date', 'card-oracle' );
        return $columns;
    }

    /**
     * Set the admin columns for Positions
     *
     * @since 0.3.0
     * @param array $columns Set the columns for Positions.
     * @return array $columns
     */
    public function set_custom_positions_columns( $columns ) {
        // unset the date so we can move it to the end.
        unset($columns['date']);
        $columns['card_reading'] = esc_html__( 'Reading', 'card-oracle' );
        $columns['card_order'] = esc_html__( 'Position', 'card-oracle' );
        $columns['date'] = esc_html__( 'Date', 'card-oracle' );
        return $columns;
    }

    /**
     * Set the admin columns for Purchased Readings
     *
     * @since 0.25.0
     * @param array $columns Set the columns for Purchased Readings.
     * @return array $columns
     */
    public function set_custom_order_columns( $columns ) {
        // unset the Checkbox and the Title.
        unset($columns['cb']);
        unset($columns['title']);
        // unset the date so we can move it to the end.
        unset($columns['date']);
        $columns['order_email'] = esc_html__( 'Email Address', 'card-oracle' );
        $columns['order_price'] = esc_html__( 'Price', 'card-oracle' );
        $columns['order_status'] = esc_html__( 'Status', 'card-oracle' );
        $columns['order_content'] = esc_html__( 'Content', 'card-oracle' );
        $columns['order_txn_id'] = esc_html__( 'Transaction ID', 'card-oracle' );
        $columns['date'] = esc_html__( 'Date', 'card-oracle' );
        return $columns;
    }

    /**
     * Set the sortable columns for Cards.
     *
     * @since 0.13.0
     * @param array $columns Set the sortable columns for Cards.
     * @return array $columns
     */
    public function set_custom_sortable_card_columns( $columns ) {
        $columns['card_reading'] = 'card_reading';
        $columns['number_card_descriptions'] = 'number_card_descriptions';
        return $columns;
    }

    /**
     * Set the sortable columns for Descriptions
     *
     * @since 0.13.0
     * @param array $columns Set the sortable columns for Descriptions.
     * @return array $columns
     */
    public function set_custom_sortable_description_columns( $columns ) {
        $columns['card_title'] = 'card_title';
        $columns['description_reading'] = 'description_reading';
        $columns['position_title'] = 'position_title';
        $columns['position_number'] = 'position_number';
        return $columns;
    }

    /**
     * Set the sortable columns for Positions
     *
     * @since 0.13.0
     * @param array $columns Set the sortable columns for Positions.
     * @return array $columns
     */
    public function set_custom_sortable_position_columns( $columns ) {
        $columns['card_reading'] = 'card_reading';
        $columns['card_order'] = 'card_order';
        return $columns;
    }

    /**
     * Set the sortable columns for Purchased Readings.
     *
     * @since 1.0.4
     * @param array $columns Set the sortable columns for Purchased Readings.
     * @return array $columns
     */
    public function set_custom_sortable_order_columns( $columns ) {
        $columns['order_email'] = 'order_email';
        $columns['order_status'] = 'order_status';
        return $columns;
    }

    /**
     * Add order by clause to the query.
     *
     * @since 1.1.1
     * @param mixed $query Query to add the order by clause.
     */
    public function card_oracle_column_orderby( $query ) {
        if ( !is_admin() ) {
            return;
        }
        $orderby = $query->get( 'orderby' );
        if ( 'order_status' === $orderby ) {
            $query->set( 'meta_key', CO_ORDER_STATUS );
            $query->set( 'orderby', 'meta_value' );
        }
        if ( 'order_email' === $orderby ) {
            $query->set( 'orderby', 'meta_value' );
            $query->set( 'meta_query', array(
                'relation' => 'or',
                array(
                    'key' => CO_IPN_EMAIL,
                ),
                array(
                    'key'     => CO_IPN_EMAIL,
                    'compare' => 'NOT EXISTS',
                ),
            ) );
        }
    }

    /**
     * Remove edit from the bulk actions.
     *
     * @since 1.0.5
     * @param array $actions List of menu actions.
     */
    public function card_oracle_remove_from_bulk_actions( $actions ) {
        unset($actions['edit']);
        return array();
    }

    /**
     * Page for adding Demo Data.
     *
     * @since 1.1.3
     */
    public function display_card_oracle_demodata_page() {
        if ( !current_user_can( 'manage_options' ) ) {
            wp_die( esc_html__( 'You do not have sufficient permissions to access this page.', 'card-oracle' ) );
        }
        global $co_logs, $co_notices;
        $demo_handler = new CardOracleDemoData();
        $readings_dir = 'assets/readings/';
        $readings_path = trailingslashit( CARD_ORACLE_DIR . $readings_dir );
        $reading_packages = array();
        $image_data = $demo_handler->import_json_data( 'assets/data/images.json' );
        // Check if images have already been installed.
        $images_installed = false;
        if ( $image_data && isset( $image_data['images'] ) ) {
            $sample_card_name = ( isset( $image_data['images'][0]['name'] ) ? $image_data['images'][0]['name'] : '' );
            if ( $sample_card_name ) {
                $sample_title = 'Marseille - ' . $sample_card_name;
                $images_installed = ( post_exists( $sample_title ) ? true : false );
            }
        }
        $requested_file = '';
        if ( isset( $_POST['demo_reading_file'] ) ) {
            $requested_file = sanitize_text_field( wp_unslash( $_POST['demo_reading_file'] ) );
        }
        if ( $image_data && isset( $_POST['image_button'] ) && check_admin_referer( 'image_button_clicked' ) ) {
            $co_logs->add(
                DEMO_DATA,
                'Inserting image data.',
                null,
                'event'
            );
            $demo_handler->insert_images( $image_data );
            $co_notices->add( 'images_installed', esc_html__( 'Images installed.', 'card-oracle' ), 'success' );
            $images_installed = true;
        }
        // Handle image removal.
        if ( $image_data && isset( $_POST['remove_images_button'] ) && check_admin_referer( 'remove_images_button_clicked' ) ) {
            $co_logs->add(
                DEMO_DATA,
                'Removing image data.',
                null,
                'event'
            );
            $removed_count = 0;
            if ( isset( $image_data['images'] ) && is_array( $image_data['images'] ) ) {
                foreach ( $image_data['images'] as $element ) {
                    $card_name = $element['name'];
                    $title = 'Marseille - ' . $card_name;
                    $existing = get_page_by_title( $title, OBJECT, 'attachment' );
                    if ( $existing instanceof WP_Post ) {
                        wp_delete_attachment( $existing->ID, true );
                        ++$removed_count;
                    }
                }
            }
            if ( $removed_count > 0 ) {
                /* translators: %d: number of images removed */
                $co_notices->add( 'images_removed', sprintf( esc_html__( '%d images removed.', 'card-oracle' ), $removed_count ), 'success' );
                $images_installed = false;
            } else {
                $co_notices->add( 'images_not_found', esc_html__( 'No images found to remove.', 'card-oracle' ), 'warning' );
            }
        }
        // Handle image repair.
        if ( $image_data && isset( $_POST['repair_images_button'] ) && check_admin_referer( 'repair_images_button_clicked' ) ) {
            $co_logs->add(
                DEMO_DATA,
                'Repairing image data.',
                null,
                'event'
            );
            $result = $demo_handler->repair_images( $image_data );
            $relinked = ( isset( $result['relinked'] ) ? $result['relinked'] : 0 );
            if ( $result['errors'] > 0 ) {
                /* translators: %1$d: repaired count, %2$d: error count, %3$d: relinked count */
                $co_notices->add( 'images_repaired', sprintf(
                    esc_html__( '%1$d images repaired, %2$d errors, %3$d cards relinked.', 'card-oracle' ),
                    $result['repaired'],
                    $result['errors'],
                    $relinked
                ), 'warning' );
            } elseif ( $relinked > 0 ) {
                /* translators: %1$d: repaired count, %2$d: relinked count */
                $co_notices->add( 'images_repaired', sprintf( esc_html__( '%1$d images repaired, %2$d cards relinked.', 'card-oracle' ), $result['repaired'], $relinked ), 'success' );
            } else {
                /* translators: %d: number of images repaired */
                $co_notices->add( 'images_repaired', sprintf( esc_html__( '%d images repaired.', 'card-oracle' ), $result['repaired'] ), 'success' );
            }
        }
        if ( $requested_file && isset( $_POST['demo_data_button'] ) && 0 === strpos( $requested_file, $readings_dir ) ) {
            $nonce_action = 'demo_data_install_' . md5( $requested_file );
            if ( check_admin_referer( $nonce_action ) && file_exists( CARD_ORACLE_DIR . $requested_file ) ) {
                $co_logs->add(
                    DEMO_DATA,
                    'Inserting demo data.',
                    null,
                    'event'
                );
                $package_data = $demo_handler->import_json_data( $requested_file );
                if ( !empty( $package_data ) ) {
                    $reading_name = ( isset( $package_data['reading']['name'] ) ? $package_data['reading']['name'] : '' );
                    // Check if this demo package is already installed using option lookup.
                    $package_slug = sanitize_title( $reading_name );
                    $existing_id = get_option( "card_oracle_demo_package_{$package_slug}", 0 );
                    $existing = ( $existing_id ? get_post( $existing_id ) : null );
                    // Verify the post still exists and is the right type.
                    if ( $existing && ('co_readings' !== $existing->post_type || 'publish' !== $existing->post_status) ) {
                        delete_option( "card_oracle_demo_package_{$package_slug}" );
                        $existing = null;
                    }
                    if ( $existing instanceof WP_Post ) {
                        $co_notices->add( 'demo_data_exists', esc_html__( 'Appears you have already installed this demo reading. Delete the existing content to reinstall.', 'card-oracle' ), 'warning' );
                    } else {
                        $demo_handler->insert_data( $package_data );
                        $co_notices->add( 'demo_data_installed', esc_html__( 'Demo data installed.', 'card-oracle' ), 'success' );
                    }
                } else {
                    $co_notices->add( 'demo_data_missing', esc_html__( 'Unable to load the selected reading file.', 'card-oracle' ), 'error' );
                }
            }
        }
        // Handle reading removal.
        if ( $requested_file && isset( $_POST['remove_reading_button'] ) && 0 === strpos( $requested_file, $readings_dir ) ) {
            $nonce_action = 'demo_data_remove_' . md5( $requested_file );
            if ( check_admin_referer( $nonce_action ) && file_exists( CARD_ORACLE_DIR . $requested_file ) ) {
                $co_logs->add(
                    DEMO_DATA,
                    'Removing demo data.',
                    null,
                    'event'
                );
                $package_data = $demo_handler->import_json_data( $requested_file );
                if ( !empty( $package_data ) ) {
                    $reading_name = ( isset( $package_data['reading']['name'] ) ? $package_data['reading']['name'] : '' );
                    // Check if this demo package exists using option lookup.
                    $package_slug = sanitize_title( $reading_name );
                    $existing_id = get_option( "card_oracle_demo_package_{$package_slug}", 0 );
                    $existing = ( $existing_id ? get_post( $existing_id ) : null );
                    // Verify the post still exists and is the right type.
                    if ( $existing && ('co_readings' !== $existing->post_type || 'publish' !== $existing->post_status) ) {
                        delete_option( "card_oracle_demo_package_{$package_slug}" );
                        $existing = null;
                    }
                    if ( $existing instanceof WP_Post ) {
                        // Delete the reading and all associated posts.
                        $reading_id = $existing->ID;
                        // Get all positions for this reading.
                        $positions = get_posts( array(
                            'post_type'      => 'co_positions',
                            'posts_per_page' => -1,
                            'meta_query'     => array(array(
                                'key'   => CO_READING_ID,
                                'value' => $reading_id,
                            )),
                        ) );
                        // Get all cards for this reading.
                        $cards = get_posts( array(
                            'post_type'      => 'co_cards',
                            'posts_per_page' => -1,
                            'meta_query'     => array(array(
                                'key'   => CO_READING_ID,
                                'value' => $reading_id,
                            )),
                        ) );
                        // Get all descriptions for this reading.
                        // Descriptions are linked via position_id and card_id, not reading_id.
                        $position_ids = wp_list_pluck( $positions, 'ID' );
                        $card_ids = wp_list_pluck( $cards, 'ID' );
                        $descriptions = array();
                        if ( !empty( $position_ids ) || !empty( $card_ids ) ) {
                            $meta_query = array(
                                'relation' => 'OR',
                            );
                            if ( !empty( $position_ids ) ) {
                                $meta_query[] = array(
                                    'key'     => CO_POSITION_ID,
                                    'value'   => $position_ids,
                                    'compare' => 'IN',
                                );
                            }
                            if ( !empty( $card_ids ) ) {
                                $meta_query[] = array(
                                    'key'     => CO_CARD_ID,
                                    'value'   => $card_ids,
                                    'compare' => 'IN',
                                );
                            }
                            $descriptions = get_posts( array(
                                'post_type'      => 'co_descriptions',
                                'posts_per_page' => -1,
                                'meta_query'     => $meta_query,
                            ) );
                        }
                        // Delete all associated posts.
                        foreach ( $positions as $position ) {
                            wp_delete_post( $position->ID, true );
                        }
                        foreach ( $cards as $card ) {
                            wp_delete_post( $card->ID, true );
                        }
                        foreach ( $descriptions as $description ) {
                            wp_delete_post( $description->ID, true );
                        }
                        // Delete the reading itself.
                        wp_delete_post( $reading_id, true );
                        $co_notices->add( 'demo_data_removed', esc_html__( 'Demo reading and all associated data removed.', 'card-oracle' ), 'success' );
                    } else {
                        $co_notices->add( 'demo_data_not_found', esc_html__( 'Reading not found. It may have already been removed.', 'card-oracle' ), 'warning' );
                    }
                } else {
                    $co_notices->add( 'demo_data_missing', esc_html__( 'Unable to load the selected reading file.', 'card-oracle' ), 'error' );
                }
            }
        }
        if ( is_dir( $readings_path ) ) {
            $files = glob( $readings_path . '*.json' );
            if ( $files ) {
                foreach ( $files as $file_path ) {
                    $relative_file = $readings_dir . basename( $file_path );
                    $data = $demo_handler->import_json_data( $relative_file );
                    if ( empty( $data ) || !isset( $data['reading'] ) ) {
                        continue;
                    }
                    $name = ( isset( $data['reading']['name'] ) ? $data['reading']['name'] : basename( $file_path, '.json' ) );
                    $description = ( isset( $data['description'] ) ? $data['description'] : '' );
                    $positions = ( isset( $data['positions'] ) && is_array( $data['positions'] ) ? count( $data['positions'] ) : 0 );
                    $cards = ( isset( $data['cards'] ) && is_array( $data['cards'] ) ? count( $data['cards'] ) : 0 );
                    $is_premium = false !== strpos( basename( $file_path ), '__premium_only' );
                    // Check if demo package is installed using option lookup.
                    $package_slug = sanitize_title( $name );
                    $existing_id = get_option( "card_oracle_demo_package_{$package_slug}", 0 );
                    $existing = ( $existing_id ? get_post( $existing_id ) : null );
                    $installed = $existing instanceof WP_Post;
                    $reading_packages[] = array(
                        'file'        => $relative_file,
                        'name'        => $name,
                        'description' => $description,
                        'positions'   => $positions,
                        'cards'       => $cards,
                        'is_premium'  => $is_premium,
                        'installed'   => $installed,
                        'nonce'       => 'demo_data_install_' . md5( $relative_file ),
                    );
                }
            }
        }
        include_once 'partials/card-oracle-admin-header.php';
        $co_notices->display();
        ?>
		<div class="co-dashboard">
			<header class="co-dashboard__hero co-dashboard__hero--subtle">
				<div class="co-dashboard__hero-text">
					<span class="co-dashboard__badge"><?php 
        esc_html_e( 'Demo Data', 'card-oracle' );
        ?></span>
					<h1 class="co-dashboard__hero-title"><?php 
        esc_html_e( 'Install Sample Readings', 'card-oracle' );
        ?></h1>
					<p class="co-dashboard__hero-description">
						<?php 
        esc_html_e( 'Install our demo spread onto your site so that you can explore Card Oracle and see how it all hangs together, before creating your own readings.', 'card-oracle' );
        ?>
					</p>
				</div>
				<div class="co-dashboard__hero-actions">
					<a class="button button-secondary" href="<?php 
        echo esc_url( admin_url( 'admin.php?page=card-oracle-doc-viewer&doc=user-guide' ) );
        ?>">
						<?php 
        esc_html_e( 'View Demo Data Guide', 'card-oracle' );
        ?>
					</a>
				</div>
			</header>

			<section class="co-dashboard__section co-dashboard__section--panel">
				<div class="co-dashboard__section-heading">
					<h2><?php 
        esc_html_e( 'Available Demo Readings', 'card-oracle' );
        ?></h2>
					<p><?php 
        esc_html_e( 'Install any sample spread to generate readings, positions, cards, and descriptions automatically.', 'card-oracle' );
        ?></p>
				</div>
				<div class="co-dashboard__panel-body">
					<?php 
        if ( empty( $reading_packages ) ) {
            ?>
						<p><?php 
            esc_html_e( 'No demo readings were found. Add JSON files to assets/readings to populate this list.', 'card-oracle' );
            ?></p>
					<?php 
        } else {
            ?>
						<div class="co-dashboard__tile-grid co-dashboard__tile-grid--readings">
							<?php 
            foreach ( $reading_packages as $package ) {
                ?>
								<article class="co-dashboard-tile co-dashboard-tile--reading">
									<div class="co-dashboard-tile__label">
										<?php 
                echo esc_html( $package['name'] );
                ?>
										<?php 
                if ( $package['is_premium'] ) {
                    ?>
											<span class="co-dashboard__badge co-dashboard__badge--inline"><?php 
                    esc_html_e( 'Premium', 'card-oracle' );
                    ?></span>
										<?php 
                }
                ?>
									</div>
									<div class="co-dashboard-tile__value">
										<?php 
                printf( esc_html__( '%1$d positions • %2$d cards', 'card-oracle' ), intval( $package['positions'] ), intval( $package['cards'] ) );
                ?>
									</div>
									<div class="co-dashboard-tile__description co-dashboard-tile__description--reading">
										<?php 
                if ( !empty( $package['description'] ) ) {
                    ?>
											<div class="co-dashboard-tile__copy">
												<?php 
                    echo wp_kses_post( $package['description'] );
                    ?>
											</div>
										<?php 
                }
                ?>
										<?php 
                if ( !$package['installed'] ) {
                    ?>
											<form class="co-dashboard-tile__form" method="post">
												<?php 
                    wp_nonce_field( $package['nonce'] );
                    ?>
												<input type="hidden" name="demo_reading_file" value="<?php 
                    echo esc_attr( $package['file'] );
                    ?>" />
												<input type="hidden" name="demo_data_button" value="1" />
												<button type="submit" class="button button-primary co-install-reading-btn">
													<?php 
                    esc_html_e( 'Install Reading', 'card-oracle' );
                    ?>
												</button>
											</form>
										<?php 
                } else {
                    ?>
											<p class="co-dashboard-tile__installed"><?php 
                    esc_html_e( 'Already installed', 'card-oracle' );
                    ?></p>
											<form class="co-dashboard-tile__form" method="post">
												<?php 
                    wp_nonce_field( 'demo_data_remove_' . md5( $package['file'] ) );
                    ?>
												<input type="hidden" name="demo_reading_file" value="<?php 
                    echo esc_attr( $package['file'] );
                    ?>" />
												<input type="hidden" name="remove_reading_button" value="1" />
												<button type="submit" class="button button-secondary co-remove-reading-btn">
													<?php 
                    esc_html_e( 'Remove Reading', 'card-oracle' );
                    ?>
												</button>
											</form>
										<?php 
                }
                ?>
									</div>
								</article>
							<?php 
            }
            ?>
						</div>
					<?php 
        }
        ?>
				</div>
			</section>

			<?php 
        if ( $image_data ) {
            ?>
			<section class="co-dashboard__section co-dashboard__section--panel">
				<div class="co-dashboard__section-heading">
					<h2><?php 
            esc_html_e( 'Optional: Import Tarot Card Images', 'card-oracle' );
            ?></h2>
					<p><?php 
            esc_html_e( 'Upload the Marseille deck artwork so the demo cards include imagery.', 'card-oracle' );
            ?></p>
				</div>
				<div class="co-dashboard__panel-body">
					<p><?php 
            esc_html_e( 'Images are matched by post title. If an attachment with the same title exists, it is skipped to prevent duplicates.', 'card-oracle' );
            ?></p>
					<?php 
            if ( !$images_installed ) {
                ?>
						<form id="image_form" action="" method="post" class="co-demo-card__form">
							<?php 
                wp_nonce_field( 'image_button_clicked' );
                ?>
							<input type="hidden" value="true" name="image_button" />
							<button type="submit" class="button button-primary co-install-images-btn">
								<?php 
                esc_html_e( 'Insert Images', 'card-oracle' );
                ?>
							</button>
						</form>
					<?php 
            } else {
                ?>
						<p class="co-dashboard-tile__installed"><?php 
                esc_html_e( 'Already installed', 'card-oracle' );
                ?></p>
						<form id="repair_image_form" action="" method="post" class="co-demo-card__form" style="display:inline;">
							<?php 
                wp_nonce_field( 'repair_images_button_clicked' );
                ?>
							<input type="hidden" value="true" name="repair_images_button" />
							<button type="submit" class="button button-primary">
								<?php 
                esc_html_e( 'Repair Images', 'card-oracle' );
                ?>
							</button>
						</form>
						<form id="remove_image_form" action="" method="post" class="co-demo-card__form" style="display:inline;">
							<?php 
                wp_nonce_field( 'remove_images_button_clicked' );
                ?>
							<input type="hidden" value="true" name="remove_images_button" />
							<button type="submit" class="button button-secondary co-remove-images-btn">
								<?php 
                esc_html_e( 'Remove Images', 'card-oracle' );
                ?>
							</button>
						</form>
					<?php 
            }
            ?>
				</div>
			</section>
			<?php 
        }
        ?>
		</div>

		<!-- Installation Progress Modal -->
		<div id="co-install-modal" class="co-install-modal" style="display: none;">
			<div class="co-install-modal-overlay"></div>
			<div class="co-install-modal-content">
				<div class="co-install-modal-icon">
					<span class="dashicons dashicons-update-alt co-install-spinner"></span>
				</div>
				<h2 class="co-install-modal-title"><?php 
        esc_html_e( 'Installing Reading', 'card-oracle' );
        ?></h2>
				<p class="co-install-modal-message"><?php 
        esc_html_e( 'Please wait while the demo reading is being installed. This may take a few moments.', 'card-oracle' );
        ?></p>
			</div>
		</div>

		<!-- Remove Images Warning Modal -->
		<div id="co-remove-images-modal" class="co-install-modal" style="display: none;">
			<div class="co-install-modal-overlay"></div>
			<div class="co-install-modal-content">
				<div class="co-install-modal-icon">
					<span class="dashicons dashicons-warning co-warning-icon"></span>
				</div>
				<h2 class="co-install-modal-title"><?php 
        esc_html_e( 'Remove Images?', 'card-oracle' );
        ?></h2>
				<p class="co-install-modal-message"><?php 
        esc_html_e( 'This will permanently delete the demo card images from your media library, even if they are currently being used by cards or other content.', 'card-oracle' );
        ?></p>
				<div class="co-modal-buttons">
					<button type="button" id="co-remove-images-cancel" class="button button-secondary"><?php 
        esc_html_e( 'Cancel', 'card-oracle' );
        ?></button>
					<button type="button" id="co-remove-images-confirm" class="button button-primary co-button-danger"><?php 
        esc_html_e( 'Remove Images', 'card-oracle' );
        ?></button>
				</div>
			</div>
		</div>

		<!-- Remove Reading Confirmation Modal -->
		<div id="co-remove-reading-modal" class="co-install-modal" style="display: none;">
			<div class="co-install-modal-overlay"></div>
			<div class="co-install-modal-content">
				<div class="co-install-modal-icon">
					<span class="dashicons dashicons-warning co-warning-icon"></span>
				</div>
				<h2 class="co-install-modal-title"><?php 
        esc_html_e( 'Remove Reading?', 'card-oracle' );
        ?></h2>
				<p class="co-install-modal-message"><?php 
        esc_html_e( 'This will permanently remove the reading and all associated positions, cards, and descriptions.', 'card-oracle' );
        ?></p>
				<p class="co-install-modal-message" style="margin-top: 10px; font-size: 12px; color: #757575;"><?php 
        esc_html_e( 'Note: Card images are shared across readings and can be removed separately using the "Remove Images" button.', 'card-oracle' );
        ?></p>
				<div class="co-modal-buttons">
					<button type="button" id="co-remove-reading-cancel" class="button button-secondary"><?php 
        esc_html_e( 'Cancel', 'card-oracle' );
        ?></button>
					<button type="button" id="co-remove-reading-confirm" class="button button-primary co-button-danger"><?php 
        esc_html_e( 'Remove Reading', 'card-oracle' );
        ?></button>
				</div>
			</div>
		</div>

		<style>
			.co-install-modal {
				position: fixed;
				top: 0;
				left: 0;
				width: 100%;
				height: 100%;
				z-index: 100000;
			}
			.co-install-modal-overlay {
				position: absolute;
				top: 0;
				left: 0;
				width: 100%;
				height: 100%;
				background: rgba(0, 0, 0, 0.7);
			}
			.co-install-modal-content {
				position: absolute;
				top: 50%;
				left: 50%;
				transform: translate(-50%, -50%);
				background: #fff;
				padding: 40px;
				border-radius: 8px;
				box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
				text-align: center;
				max-width: 500px;
				z-index: 100001;
			}
			.co-install-modal-icon {
				margin-bottom: 20px;
			}
			.co-install-spinner {
				font-size: 48px;
				width: 48px;
				height: 48px;
				color: #2271b1;
				animation: co-spin 1s linear infinite;
			}
			@keyframes co-spin {
				from { transform: rotate(0deg); }
				to { transform: rotate(360deg); }
			}
			.co-install-modal-content h2 {
				margin: 0 0 15px 0;
				font-size: 24px;
				color: #1d2327;
			}
			.co-install-modal-content p {
				margin: 0;
				font-size: 14px;
				color: #50575e;
				line-height: 1.6;
			}
			.co-warning-icon {
				font-size: 48px;
				width: 48px;
				height: 48px;
				color: #dba617;
			}
			.co-modal-buttons {
				margin-top: 25px;
				display: flex;
				gap: 10px;
				justify-content: center;
			}
			.co-button-danger {
				background: #d63638 !important;
				border-color: #d63638 !important;
			}
			.co-button-danger:hover {
				background: #b32d2e !important;
				border-color: #b32d2e !important;
			}
		</style>

		<script>
		jQuery(document).ready(function($) {
			var pendingRemoveForm = null;
			var removeReadingConfirmed = false;

			// Handle reading installation
			$('.co-dashboard-tile__form').on('submit', function(e) {
				var $form = $(this);
				var $installButton = $form.find('.co-install-reading-btn');
				var $removeButton = $form.find('.co-remove-reading-btn');

				if ($installButton.length && !$installButton.prop('disabled')) {
					$('.co-install-modal-title').text('<?php 
        echo esc_js( __( 'Installing Reading', 'card-oracle' ) );
        ?>');
					$('.co-install-modal-message').text('<?php 
        echo esc_js( __( 'Please wait while the demo reading is being installed. This may take a few moments.', 'card-oracle' ) );
        ?>');
					$('#co-install-modal').fadeIn(200);
				} else if ($removeButton.length) {
					// Show the remove reading confirmation modal first
					if (!removeReadingConfirmed) {
						e.preventDefault();
						pendingRemoveForm = $form;
						$('#co-remove-reading-modal').fadeIn(200);
						return false;
					}
					// User confirmed, proceed with removal
					$('.co-install-modal-title').text('<?php 
        echo esc_js( __( 'Removing Reading', 'card-oracle' ) );
        ?>');
					$('.co-install-modal-message').text('<?php 
        echo esc_js( __( 'Please wait while the demo reading and all associated data are being removed. This may take a few moments.', 'card-oracle' ) );
        ?>');
					$('#co-install-modal').fadeIn(200);
				}
			});

			// Handle image installation
			$('#image_form').on('submit', function(e) {
				$('.co-install-modal-title').text('<?php 
        echo esc_js( __( 'Installing Images', 'card-oracle' ) );
        ?>');
				$('.co-install-modal-message').text('<?php 
        echo esc_js( __( 'Please wait while the card images are being installed. This may take a few moments.', 'card-oracle' ) );
        ?>');
				$('#co-install-modal').fadeIn(200);
			});

			// Handle image removal - show warning modal first
			var removeImagesConfirmed = false;
			$('#remove_image_form').on('submit', function(e) {
				if (!removeImagesConfirmed) {
					e.preventDefault();
					$('#co-remove-images-modal').fadeIn(200);
					return false;
				}
				// User confirmed, show progress modal
				$('.co-install-modal-title').text('<?php 
        echo esc_js( __( 'Removing Images', 'card-oracle' ) );
        ?>');
				$('.co-install-modal-message').text('<?php 
        echo esc_js( __( 'Please wait while the card images are being removed. This may take a few moments.', 'card-oracle' ) );
        ?>');
				$('#co-install-modal').fadeIn(200);
			});

			// Handle warning modal cancel
			$('#co-remove-images-cancel, #co-remove-images-modal .co-install-modal-overlay').on('click', function() {
				$('#co-remove-images-modal').fadeOut(200);
			});

			// Handle warning modal confirm
			$('#co-remove-images-confirm').on('click', function() {
				$('#co-remove-images-modal').fadeOut(200);
				removeImagesConfirmed = true;
				$('#remove_image_form').submit();
			});

			// Handle remove reading modal cancel
			$('#co-remove-reading-cancel, #co-remove-reading-modal .co-install-modal-overlay').on('click', function() {
				$('#co-remove-reading-modal').fadeOut(200);
				pendingRemoveForm = null;
				removeReadingConfirmed = false;
			});

			// Handle remove reading confirm
			$('#co-remove-reading-confirm').on('click', function() {
				$('#co-remove-reading-modal').fadeOut(200);
				if (pendingRemoveForm) {
					removeReadingConfirmed = true;
					pendingRemoveForm.submit();
				}
			});
		});
		</script>
		<?php 
        include_once 'partials/card-oracle-admin-footer.php';
    }

    /**
     * Page for displaying Documentation.
     *
     * @since 1.1.9
     */
    public function display_card_oracle_documentation_page() {
        if ( !current_user_can( 'manage_options' ) ) {
            wp_die( esc_html__( 'You do not have sufficient permissions to access this page.', 'card-oracle' ) );
        }
        $docs_dir = trailingslashit( CARD_ORACLE_DIR . 'docs' );
        $docs = array(
            array(
                'file'        => 'user-guide.md',
                'title'       => __( 'User Guide', 'card-oracle' ),
                'description' => __( 'Complete guide to using Card Oracle, from setup to creating readings and managing cards.', 'card-oracle' ),
                'icon'        => 'dashicons-book',
            ),
            array(
                'file'        => 'wizard-guide.md',
                'title'       => __( 'Wizard Guide', 'card-oracle' ),
                'description' => __( 'Step-by-step instructions for using the Setup Wizard to create your first reading.', 'card-oracle' ),
                'icon'        => 'dashicons-admin-tools',
            ),
            array(
                'file'        => 'help.md',
                'title'       => __( 'Help Documentation', 'card-oracle' ),
                'description' => __( 'Common questions and answers about using Card Oracle features and functionality.', 'card-oracle' ),
                'icon'        => 'dashicons-sos',
            ),
            array(
                'file'        => 'troubleshooting.md',
                'title'       => __( 'Troubleshooting', 'card-oracle' ),
                'description' => __( 'Solutions to common issues and problems you might encounter while using the plugin.', 'card-oracle' ),
                'icon'        => 'dashicons-warning',
            ),
            array(
                'file'        => 'status-report.md',
                'title'       => __( 'Understanding the Status Report', 'card-oracle' ),
                'description' => __( 'Comprehensive guide to interpreting system diagnostics and diagnostic information for troubleshooting.', 'card-oracle' ),
                'icon'        => 'dashicons-info',
            ),
            array(
                'file'        => 'architecture.md',
                'title'       => __( 'Architecture', 'card-oracle' ),
                'description' => __( 'Technical overview of the plugin structure, data models, and development guidelines.', 'card-oracle' ),
                'icon'        => 'dashicons-admin-site-alt3',
            ),
            array(
                'file'        => 'reading-converter.md',
                'title'       => __( 'Reading Converter', 'card-oracle' ),
                'description' => __( 'Information about converting and migrating readings between different formats.', 'card-oracle' ),
                'icon'        => 'dashicons-update',
            )
        );
        include_once 'partials/card-oracle-admin-header.php';
        ?>
		<div class="co-dashboard">
			<header class="co-dashboard__hero co-dashboard__hero--subtle">
				<div class="co-dashboard__hero-text">
					<span class="co-dashboard__badge"><?php 
        esc_html_e( 'Documentation', 'card-oracle' );
        ?></span>
					<h1 class="co-dashboard__hero-title"><?php 
        esc_html_e( 'Card Oracle Documentation', 'card-oracle' );
        ?></h1>
					<p class="co-dashboard__hero-description">
						<?php 
        esc_html_e( 'Browse comprehensive guides and reference materials to help you get the most out of Card Oracle.', 'card-oracle' );
        ?>
					</p>
				</div>
			</header>

			<section class="co-dashboard__section co-dashboard__section--panel">
				<div class="co-dashboard__section-heading">
					<h2><?php 
        esc_html_e( 'Available Documentation', 'card-oracle' );
        ?></h2>
					<p><?php 
        esc_html_e( 'Select any document below to view detailed information and instructions.', 'card-oracle' );
        ?></p>
				</div>
				<div class="co-dashboard__panel-body">
					<div class="co-dashboard__tile-grid co-dashboard__tile-grid--readings">
						<?php 
        foreach ( $docs as $doc ) {
            ?>
							<?php 
            $doc_path = $docs_dir . $doc['file'];
            $doc_exists = file_exists( $doc_path );
            $doc_slug = str_replace( '.md', '', $doc['file'] );
            $doc_url = admin_url( 'admin.php?page=card-oracle-doc-viewer&doc=' . $doc_slug );
            ?>
							<article class="co-dashboard-tile co-dashboard-tile--doc">
								<span class="dashicons <?php 
            echo esc_attr( $doc['icon'] );
            ?> co-dashboard-tile__icon"></span>
								<div class="co-dashboard-tile__label"><?php 
            echo esc_html( $doc['title'] );
            ?></div>
								<div class="co-dashboard-tile__description">
									<?php 
            echo esc_html( $doc['description'] );
            ?>
								</div>
								<div class="co-dashboard-tile__actions">
									<?php 
            if ( $doc_exists ) {
                ?>
										<a class="button button-secondary" href="<?php 
                echo esc_url( $doc_url );
                ?>">
											<?php 
                esc_html_e( 'View Documentation', 'card-oracle' );
                ?>
										</a>
									<?php 
            } else {
                ?>
										<span class="button button-secondary disabled" disabled>
											<?php 
                esc_html_e( 'Not Available', 'card-oracle' );
                ?>
										</span>
									<?php 
            }
            ?>
								</div>
							</article>
						<?php 
        }
        ?>
					</div>
				</div>
			</section>
		</div>
		<?php 
        include_once 'partials/card-oracle-admin-footer.php';
    }

    /**
     * Display the documentation viewer page.
     *
     * Renders markdown documentation files as formatted HTML with security measures.
     *
     * @since 1.2.1
     */
    public function display_card_oracle_doc_viewer_page() {
        // Security check - ensure user has proper capabilities.
        if ( !current_user_can( 'manage_options' ) ) {
            wp_die( esc_html__( 'You do not have sufficient permissions to access this page.', 'card-oracle' ) );
        }
        // Whitelist of allowed documentation files.
        $allowed_docs = array(
            'user-guide',
            'wizard-guide',
            'help',
            'troubleshooting',
            'status-report',
            'architecture',
            'reading-converter'
        );
        // Get and sanitize the doc parameter.
        $doc_slug = ( isset( $_GET['doc'] ) ? sanitize_key( wp_unslash( $_GET['doc'] ) ) : '' );
        // phpcs:ignore WordPress.Security.NonceVerification.Recommended
        // Validate against whitelist.
        if ( !in_array( $doc_slug, $allowed_docs, true ) ) {
            wp_die( esc_html__( 'Invalid documentation file.', 'card-oracle' ) );
        }
        // Build file path securely.
        $docs_dir = trailingslashit( CARD_ORACLE_DIR . 'docs' );
        $doc_file = $docs_dir . $doc_slug . '.md';
        // Path traversal prevention using realpath.
        $real_docs_dir = realpath( $docs_dir );
        $real_doc_file = realpath( $doc_file );
        if ( !$real_doc_file || !$real_docs_dir || strpos( $real_doc_file, $real_docs_dir ) !== 0 ) {
            wp_die( esc_html__( 'Invalid file path.', 'card-oracle' ) );
        }
        // Check file existence.
        if ( !file_exists( $real_doc_file ) ) {
            wp_die( esc_html__( 'Documentation file not found.', 'card-oracle' ) );
        }
        // Read markdown content.
        $markdown_content = file_get_contents( $real_doc_file );
        // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
        if ( false === $markdown_content ) {
            wp_die( esc_html__( 'Unable to read documentation file.', 'card-oracle' ) );
        }
        // Parse markdown to HTML using Parsedown.
        $parsedown = new Parsedown();
        $parsedown->setSafeMode( true );
        // Enable XSS prevention.
        $parsedown->setMarkupEscaped( true );
        // Escape HTML in markdown.
        $html_content = $parsedown->text( $markdown_content );
        // Sanitize HTML output with comprehensive whitelist.
        $allowed_html = array(
            'h1'         => array(
                'id'    => array(),
                'class' => array(),
            ),
            'h2'         => array(
                'id'    => array(),
                'class' => array(),
            ),
            'h3'         => array(
                'id'    => array(),
                'class' => array(),
            ),
            'h4'         => array(
                'id'    => array(),
                'class' => array(),
            ),
            'h5'         => array(
                'id'    => array(),
                'class' => array(),
            ),
            'h6'         => array(
                'id'    => array(),
                'class' => array(),
            ),
            'p'          => array(
                'class' => array(),
            ),
            'a'          => array(
                'href'   => array(),
                'title'  => array(),
                'class'  => array(),
                'id'     => array(),
                'target' => array(),
                'rel'    => array(),
            ),
            'ul'         => array(
                'class' => array(),
            ),
            'ol'         => array(
                'class' => array(),
                'start' => array(),
            ),
            'li'         => array(
                'class' => array(),
            ),
            'strong'     => array(),
            'em'         => array(),
            'code'       => array(
                'class' => array(),
            ),
            'pre'        => array(
                'class' => array(),
            ),
            'blockquote' => array(
                'class' => array(),
            ),
            'table'      => array(
                'class' => array(),
            ),
            'thead'      => array(),
            'tbody'      => array(),
            'tr'         => array(),
            'th'         => array(
                'colspan' => array(),
                'rowspan' => array(),
                'scope'   => array(),
            ),
            'td'         => array(
                'colspan' => array(),
                'rowspan' => array(),
            ),
            'br'         => array(),
            'hr'         => array(),
            'div'        => array(
                'class' => array(),
            ),
            'span'       => array(
                'class' => array(),
            ),
            'img'        => array(
                'src'   => array(),
                'alt'   => array(),
                'title' => array(),
                'class' => array(),
            ),
        );
        $safe_html = wp_kses( $html_content, $allowed_html );
        // Document metadata for display.
        $doc_meta = array(
            'user-guide'        => array(
                'title' => __( 'User Guide', 'card-oracle' ),
                'icon'  => 'dashicons-book',
            ),
            'wizard-guide'      => array(
                'title' => __( 'Wizard Guide', 'card-oracle' ),
                'icon'  => 'dashicons-admin-tools',
            ),
            'help'              => array(
                'title' => __( 'Help Documentation', 'card-oracle' ),
                'icon'  => 'dashicons-sos',
            ),
            'troubleshooting'   => array(
                'title' => __( 'Troubleshooting', 'card-oracle' ),
                'icon'  => 'dashicons-warning',
            ),
            'status-report'     => array(
                'title' => __( 'Understanding the Status Report', 'card-oracle' ),
                'icon'  => 'dashicons-info',
            ),
            'architecture'      => array(
                'title' => __( 'Architecture', 'card-oracle' ),
                'icon'  => 'dashicons-admin-site-alt3',
            ),
            'reading-converter' => array(
                'title' => __( 'Reading Converter', 'card-oracle' ),
                'icon'  => 'dashicons-update',
            ),
        );
        $current_doc = ( isset( $doc_meta[$doc_slug] ) ? $doc_meta[$doc_slug] : array(
            'title' => $doc_slug,
            'icon'  => 'dashicons-media-document',
        ) );
        // Include the viewer template.
        include_once 'partials/card-oracle-admin-header.php';
        include_once 'partials/card-oracle-doc-viewer.php';
        include_once 'partials/card-oracle-admin-footer.php';
    }

    /**
     * Setup the Email tab fields
     *
     * @since 1.1.3
     */
    public function card_oracle_setup_email_options() {
        if ( !current_user_can( 'manage_options' ) ) {
            return;
        }
        $is_premium = card_oracle_fs()->can_use_premium_code__premium_only();
        $sections = array(array(
            'uid'   => 'email_section',
            'label' => esc_html__( 'General Settings', 'card-oracle' ),
            'page'  => 'card_oracle_option_email',
        ));
        // Add premium daily email section if premium.
        if ( $is_premium ) {
            $sections[] = array(
                'uid'   => 'daily_section',
                'label' => esc_html__( 'Daily Card Email Options', 'card-oracle' ),
                'page'  => 'card_oracle_option_email',
            );
        }
        // Basic email fields (available to all users).
        $fields = array(
            array(
                'uid'     => 'card_oracle_allow_email',
                'label'   => esc_html__( 'Allow users to send reading to an email address', 'card-oracle' ),
                'section' => 'email_section',
                'type'    => 'toggle_switch',
                'value'   => 'yes',
            ),
            array(
                'uid'          => 'card_oracle_from_email',
                'label'        => esc_html__( 'From email address', 'card-oracle' ),
                'section'      => 'email_section',
                'type'         => 'text',
                'placeholder'  => get_option( 'admin_email' ),
                'supplemental' => esc_html__( 'The From email address displayed when the user sends themselves the reading.', 'card-oracle' ),
            ),
            array(
                'uid'          => 'card_oracle_from_email_name',
                'label'        => esc_html__( 'From email name', 'card-oracle' ),
                'section'      => 'email_section',
                'type'         => 'text',
                'placeholder'  => get_bloginfo( 'name' ),
                'supplemental' => esc_html__( 'The Name displayed as the From email address.', 'card-oracle' ),
            ),
            array(
                'uid'          => 'card_oracle_email_text',
                'label'        => esc_html__( 'Text to display', 'card-oracle' ),
                'section'      => 'email_section',
                'type'         => 'text',
                'placeholder'  => esc_html__( 'Email this Reading to:', 'card-oracle' ),
                'supplemental' => esc_html__( 'Text to display on the email form.', 'card-oracle' ),
            ),
            array(
                'uid'          => 'card_oracle_email_success',
                'label'        => esc_html__( 'Text to display after Submission', 'card-oracle' ),
                'section'      => 'email_section',
                'type'         => 'text',
                'placeholder'  => esc_html__( 'Your email has been sent. Please make sure to check your spam folder.', 'card-oracle' ),
                'supplemental' => esc_html__( 'Text to display after the user submits the email form.', 'card-oracle' ),
            )
        );
        // Add premium fields for list subscription and daily emails.
        if ( $is_premium ) {
            $premium_fields = array(
                array(
                    'uid'          => 'card_oracle_subscribe',
                    'label'        => esc_html__( 'Allow Users to subscribe when emailing the reading.', 'card-oracle' ),
                    'section'      => 'email_section',
                    'type'         => 'toggle_switch',
                    'value'        => 'yes',
                    'supplemental' => esc_html__( 'Enabling this will allow your users to subscribe to a list.', 'card-oracle' ),
                ),
                array(
                    'uid'          => 'card_oracle_email_consent_text',
                    'label'        => esc_html__( 'Email Consent Text', 'card-oracle' ),
                    'section'      => 'email_section',
                    'type'         => 'text',
                    'placeholder'  => esc_html__( 'Subscribe to our list to keep up to date with our latest news.', 'card-oracle' ),
                    'supplemental' => esc_html__( 'This is the text display when a user opts into your mailing list.', 'card-oracle' ),
                ),
                array(
                    'uid'          => 'card_oracle_email_daily',
                    'label'        => esc_html__( 'Send the Daily Card to an Audience.', 'card-oracle' ),
                    'section'      => 'daily_section',
                    'type'         => 'toggle_switch',
                    'value'        => 'yes',
                    'supplemental' => esc_html__( 'Enabling this will send your daily card to a list.', 'card-oracle' ),
                ),
                array(
                    'uid'     => 'card_oracle_reading_list',
                    'label'   => esc_html__( 'Send Daily Card from Reading:', 'card-oracle' ),
                    'section' => 'daily_section',
                    'type'    => 'reading_list',
                    'options' => false,
                ),
                array(
                    'uid'          => 'card_oracle_daily_subject',
                    'default'      => esc_html__( 'Your Daily Card', 'card-oracle' ),
                    'label'        => esc_html__( 'Daily Card email subject', 'card-oracle' ),
                    'section'      => 'daily_section',
                    'type'         => 'text',
                    'placeholder'  => '',
                    'supplemental' => esc_html__( 'This is the Subject for the Daily Card email.', 'card-oracle' ),
                ),
                array(
                    'uid'          => 'card_oracle_daily_before',
                    'default'      => '',
                    'label'        => esc_html__( 'Daily Card email text before card', 'card-oracle' ),
                    'section'      => 'daily_section',
                    'type'         => 'textarea',
                    'rows'         => '5',
                    'placeholder'  => '',
                    'helper'       => '',
                    'supplemental' => esc_html__( 'This text will be added to the email before the Daily Card.', 'card-oracle' ),
                ),
                array(
                    'uid'          => 'card_oracle_daily_after',
                    'default'      => '',
                    'label'        => esc_html__( 'Daily Card email text after card', 'card-oracle' ),
                    'section'      => 'daily_section',
                    'type'         => 'textarea',
                    'rows'         => '5',
                    'placeholder'  => '',
                    'helper'       => '',
                    'supplemental' => esc_html__( 'This text will be added to the email after the Daily Card.', 'card-oracle' ),
                )
            );
            $fields = array_merge( $fields, $premium_fields );
        }
        $this->card_oracle_register_sections_fields( $sections, $fields, 'card_oracle_option_email' );
    }

    /**
     * Set the sections and fields for the admin screens
     *
     * @since 0.25.0
     *
     * @param array  $sections Sections to display on screen.
     * @param array  $fields Fields to display on screen.
     * @param string $page Which page are the fields for.
     * @return void
     */
    public function card_oracle_register_sections_fields( $sections, $fields, $page ) {
        $current_integration = card_oracle_normalize_integration_slug( get_option( 'card_oracle_integration' ) );
        if ( !$current_integration && !empty( $_POST['card_oracle_integration'] ) ) {
            // phpcs:ignore WordPress.Security.NonceVerification
            $current_integration = card_oracle_normalize_integration_slug( sanitize_text_field( wp_unslash( $_POST['card_oracle_integration'] ) ) );
            // phpcs:ignore WordPress.Security.NonceVerification
        }
        foreach ( $sections as $section ) {
            $args = array();
            if ( isset( $section['provider'] ) ) {
                $args['provider'] = $section['provider'];
            }
            add_settings_section(
                $section['uid'],
                $section['label'],
                array($this, 'card_oracle_section_callback'),
                $section['page'],
                $args
            );
        }
        $current_provider = '';
        if ( 'card_oracle_option_payments' === $page ) {
            $current_provider = get_option( 'card_oracle_payment_provider', '' );
            if ( !$current_provider && !empty( $_POST['card_oracle_payment_provider'] ) ) {
                $current_provider = sanitize_key( wp_unslash( $_POST['card_oracle_payment_provider'] ) );
            }
        }
        foreach ( $fields as $field ) {
            add_settings_field(
                $field['uid'],
                $field['label'],
                array($this, 'card_oracle_option_callback'),
                $page,
                $field['section'],
                $field
            );
            // Add validation for API key fields.
            $register_args = array();
            if ( in_array( $field['uid'], array(
                'card_oracle_mailchimp_api_key',
                'card_oracle_activecampaign_api_key',
                'card_oracle_activecampaign_url',
                'card_oracle_brevo_api_key',
                'card_oracle_thenewsletter_key',
                'card_oracle_thenewsletter_secret'
            ), true ) ) {
                $register_args['sanitize_callback'] = array($this, 'validate_integration_api_key');
            } elseif ( in_array( $field['uid'], array('card_oracle_paypal_email', 'card_oracle_stripe_live_secret_key', 'card_oracle_stripe_test_secret_key'), true ) ) {
                $register_args['sanitize_callback'] = array($this, 'validate_payment_credentials');
            }
            register_setting( $page, $field['uid'], $register_args );
        }
    }

    /**
     * Validate integration API keys
     *
     * @since 1.2.0
     * @param string $value The API key value to validate.
     * @return string The validated API key or old value if invalid.
     */
    public function validate_integration_api_key( $value ) {
        $value = sanitize_text_field( $value );
        // If empty, allow it (user might be clearing the key).
        if ( empty( $value ) ) {
            return $value;
        }
        // Load integration classes if premium.
        $is_premium = card_oracle_fs()->can_use_premium_code__premium_only();
        if ( $is_premium ) {
            if ( !class_exists( MailChimp::class ) ) {
                require_once CARD_ORACLE_DIR . 'includes/class-mailchimp__premium_only.php';
            }
            if ( !class_exists( 'ActiveCampaign' ) ) {
                require_once CARD_ORACLE_DIR . 'includes/activecampaign/ActiveCampaign.class.php';
            }
        }
        // Determine which API key is being validated from the filter.
        $option_name = current_filter();
        $option_name = str_replace( 'sanitize_option_', '', $option_name );
        // Get currently selected integration.
        $current_integration = ( isset( $_POST['card_oracle_integration'] ) ? sanitize_text_field( wp_unslash( $_POST['card_oracle_integration'] ) ) : get_option( 'card_oracle_integration' ) );
        // phpcs:ignore WordPress.Security.NonceVerification
        // Map field names to their integration types.
        $field_integration_map = array(
            'card_oracle_mailchimp_api_key'      => 'mailchimp_api',
            'card_oracle_activecampaign_url'     => 'activecampaign_api',
            'card_oracle_activecampaign_api_key' => 'activecampaign_api',
            'card_oracle_brevo_api_key'          => 'brevo_api',
            'card_oracle_thenewsletter_key'      => 'thenewsletter_api',
            'card_oracle_thenewsletter_secret'   => 'thenewsletter_api',
        );
        // Only validate if this field belongs to the currently selected integration.
        if ( isset( $field_integration_map[$option_name] ) && $field_integration_map[$option_name] !== $current_integration ) {
            // This field is not for the currently selected integration, allow save without validation.
            return $value;
        }
        $is_valid = false;
        $error_message = '';
        // Validate based on the integration type.
        if ( 'card_oracle_mailchimp_api_key' === $option_name ) {
            // Test MailChimp API key via ping endpoint.
            if ( strpos( $value, '-' ) === false ) {
                $error_message = __( 'Invalid MailChimp API key format. Key should contain a hyphen followed by the data center.', 'card-oracle' );
            } else {
                $data_center = substr( $value, strpos( $value, '-' ) + 1 );
                $response = wp_remote_get( 'https://' . $data_center . '.api.mailchimp.com/3.0/ping', array(
                    'headers' => array(
                        'Authorization' => 'apikey ' . $value,
                        'Accept'        => 'application/json',
                    ),
                    'timeout' => 10,
                ) );
                if ( !is_wp_error( $response ) ) {
                    $response_code = wp_remote_retrieve_response_code( $response );
                    $is_valid = 200 === $response_code;
                    if ( !$is_valid ) {
                        $error_message = __( 'Invalid MailChimp API key. Please check your API key and try again.', 'card-oracle' );
                    }
                } else {
                    $error_message = __( 'Could not validate MailChimp API key. Please check your internet connection and try again.', 'card-oracle' );
                }
            }
        } elseif ( 'card_oracle_activecampaign_url' === $option_name ) {
            // Test ActiveCampaign API URL.
            $api_key = get_option( 'card_oracle_activecampaign_api_key' );
            if ( $value && $api_key && class_exists( 'ActiveCampaign' ) ) {
                // Validate both URL and key together.
                try {
                    $activecampaign = new ActiveCampaign($value, $api_key);
                    $result = $activecampaign->api( 'user/me' );
                    $is_valid = isset( $result->result_code ) && 1 === (int) $result->result_code;
                    if ( !$is_valid ) {
                        $error_message = __( 'Invalid ActiveCampaign API URL. Please check your API URL and key, then try again.', 'card-oracle' );
                    }
                } catch ( Exception $e ) {
                    co_debug_log( 'class-card-oracle-admin.php: ActiveCampaign URL validation failed: ' . $e->getMessage() );
                    $error_message = __( 'Invalid ActiveCampaign API URL. Please check your API URL and key, then try again.', 'card-oracle' );
                }
            } elseif ( $value && !$api_key ) {
                // URL provided but no API key.
                $error_message = __( 'ActiveCampaign: Both API URL and API Key are required. Please enter both credentials.', 'card-oracle' );
            } elseif ( !$value && $api_key ) {
                // No URL but API key exists.
                $error_message = __( 'ActiveCampaign: Both API URL and API Key are required. Please enter both credentials.', 'card-oracle' );
            } else {
                // Both empty - allow (clearing credentials).
                $is_valid = true;
            }
        } elseif ( 'card_oracle_activecampaign_api_key' === $option_name ) {
            // Test ActiveCampaign API key.
            $api_url = get_option( 'card_oracle_activecampaign_url' );
            if ( $api_url && $value && class_exists( 'ActiveCampaign' ) ) {
                // Validate both URL and key together.
                try {
                    $activecampaign = new ActiveCampaign($api_url, $value);
                    $result = $activecampaign->api( 'user/me' );
                    $is_valid = isset( $result->result_code ) && 1 === (int) $result->result_code;
                    if ( !$is_valid ) {
                        $error_message = __( 'Invalid ActiveCampaign API key. Please check your API URL and key, then try again.', 'card-oracle' );
                    }
                } catch ( Exception $e ) {
                    co_debug_log( 'class-card-oracle-admin.php: ActiveCampaign key validation failed: ' . $e->getMessage() );
                    $error_message = __( 'Invalid ActiveCampaign API key. Please check your API URL and key, then try again.', 'card-oracle' );
                }
            } elseif ( $value && !$api_url ) {
                // API key provided but no URL.
                $error_message = __( 'ActiveCampaign: Both API URL and API Key are required. Please enter both credentials.', 'card-oracle' );
            } elseif ( !$value && $api_url ) {
                // No API key but URL exists.
                $error_message = __( 'ActiveCampaign: Both API URL and API Key are required. Please enter both credentials.', 'card-oracle' );
            } else {
                // Both empty - allow (clearing credentials).
                $is_valid = true;
            }
        } elseif ( 'card_oracle_brevo_api_key' === $option_name ) {
            // Test Brevo (Sendinblue) API key.
            $response = wp_remote_get( 'https://api.brevo.com/v3/account', array(
                'headers' => array(
                    'api-key' => $value,
                    'Accept'  => 'application/json',
                ),
                'timeout' => 10,
            ) );
            if ( !is_wp_error( $response ) ) {
                $response_code = wp_remote_retrieve_response_code( $response );
                $is_valid = 200 === $response_code;
                if ( !$is_valid ) {
                    $error_message = __( 'Invalid Brevo API key. Please check your API key and try again.', 'card-oracle' );
                }
            } else {
                $error_message = __( 'Could not validate Brevo API key. Please check your internet connection and try again.', 'card-oracle' );
            }
        } elseif ( 'card_oracle_thenewsletter_key' === $option_name || 'card_oracle_thenewsletter_secret' === $option_name ) {
            // Test The Newsletter Plugin credentials.
            $key = ( 'card_oracle_thenewsletter_key' === $option_name ? $value : get_option( 'card_oracle_thenewsletter_key' ) );
            $secret = ( 'card_oracle_thenewsletter_secret' === $option_name ? $value : get_option( 'card_oracle_thenewsletter_secret' ) );
            if ( $key && $secret ) {
                // Validate when both credentials are present.
                $response = wp_remote_get( home_url( '/' ) . 'wp-json/newsletter/v2/lists', array(
                    'headers' => array(
                        'Authorization' => 'Basic ' . base64_encode( $key . ':' . $secret ),
                    ),
                    'timeout' => 10,
                ) );
                if ( !is_wp_error( $response ) ) {
                    $response_code = wp_remote_retrieve_response_code( $response );
                    $is_valid = 200 === $response_code;
                    if ( !$is_valid ) {
                        $error_message = __( 'Invalid Newsletter Plugin credentials. Please check your Client Key and Secret, then try again.', 'card-oracle' );
                    }
                } else {
                    $error_message = __( 'Could not validate Newsletter Plugin credentials. Please ensure the Newsletter plugin is installed and the API addon is active.', 'card-oracle' );
                }
            } elseif ( $key || $secret ) {
                // Only one field is filled - require both.
                $error_message = __( 'The Newsletter Plugin: Both Client Key and Client Secret are required. Please enter both credentials.', 'card-oracle' );
            } else {
                // Both empty - allow (clearing credentials).
                $is_valid = true;
            }
        }
        // If validation failed, add error and return old value.
        if ( !$is_valid && !empty( $error_message ) ) {
            add_settings_error(
                $option_name,
                'invalid_api_key',
                $error_message,
                'error'
            );
            // Return the old value.
            return get_option( $option_name, '' );
        }
        if ( $is_valid ) {
            add_settings_error(
                $option_name,
                'settings_updated',
                __( 'Settings saved.', 'card-oracle' ),
                'success'
            );
        }
        return $value;
    }

    /**
     * Validate payment provider credentials
     *
     * @since 1.2.0
     * @param string $value The credential value to validate.
     * @return string The validated credential or old value if invalid.
     */
    public function validate_payment_credentials( $value ) {
        $value = sanitize_text_field( $value );
        // If empty, allow it (user might be clearing the credential).
        if ( empty( $value ) ) {
            return $value;
        }
        // Determine which credential is being validated.
        $option_name = current_filter();
        $option_name = str_replace( 'sanitize_option_', '', $option_name );
        $is_valid = false;
        $error_message = '';
        // Validate based on the payment provider.
        if ( 'card_oracle_paypal_email' === $option_name ) {
            // Validate PayPal email format.
            if ( is_email( $value ) ) {
                $is_valid = true;
            } else {
                $error_message = __( 'Invalid PayPal email address. Please enter a valid email address.', 'card-oracle' );
            }
        } elseif ( 'card_oracle_stripe_live_secret_key' === $option_name || 'card_oracle_stripe_test_secret_key' === $option_name ) {
            // Validate Stripe API keys.
            $is_test_key = 'card_oracle_stripe_test_secret_key' === $option_name;
            $expected_prefix = ( $is_test_key ? 'sk_test_' : 'sk_live_' );
            // Check key format.
            if ( strpos( $value, $expected_prefix ) !== 0 ) {
                $key_type = ( $is_test_key ? __( 'test', 'card-oracle' ) : __( 'live', 'card-oracle' ) );
                $error_message = sprintf( 
                    /* translators: %s: key type (test or live) */
                    __( 'Invalid Stripe %1$s secret key format. Key should start with %2$s.', 'card-oracle' ),
                    $key_type,
                    $expected_prefix
                 );
            } else {
                // Test the key by making an API call to Stripe.
                $response = wp_remote_get( 'https://api.stripe.com/v1/balance', array(
                    'headers' => array(
                        'Authorization' => 'Bearer ' . $value,
                    ),
                    'timeout' => 10,
                ) );
                if ( !is_wp_error( $response ) ) {
                    $response_code = wp_remote_retrieve_response_code( $response );
                    $is_valid = 200 === $response_code;
                    if ( !$is_valid ) {
                        $key_type = ( $is_test_key ? __( 'test', 'card-oracle' ) : __( 'live', 'card-oracle' ) );
                        $error_message = sprintf( 
                            /* translators: %s: key type (test or live) */
                            __( 'Invalid Stripe %s secret key. Please check your key and try again.', 'card-oracle' ),
                            $key_type
                         );
                    }
                } else {
                    $error_message = __( 'Could not validate Stripe API key. Please check your internet connection and try again.', 'card-oracle' );
                }
            }
        }
        // If validation failed, add error and return old value.
        if ( !$is_valid && !empty( $error_message ) ) {
            add_settings_error(
                $option_name,
                'invalid_payment_credential',
                $error_message,
                'error'
            );
            // Return the old value.
            return get_option( $option_name, '' );
        }
        if ( $is_valid ) {
            add_settings_error(
                $option_name,
                'settings_updated',
                __( 'Settings saved.', 'card-oracle' ),
                'success'
            );
        }
        return $value;
    }

    /**
     * Get default settings values
     *
     * @since 1.1.9
     * @return array Default settings
     */
    private function get_default_settings() {
        return array(
            'enable_feed_check' => false,
            'cache_ttl'         => 3600,
            'debug_logging'     => false,
            'default_deck'      => '',
        );
    }

    /**
     * Get current settings with defaults
     *
     * @since 1.1.9
     * @return array Current settings
     */
    private function get_card_oracle_settings() {
        $defaults = $this->get_default_settings();
        $settings = get_option( 'card_oracle_settings', $defaults );
        return wp_parse_args( $settings, $defaults );
    }

    /**
     * Sanitize and validate settings
     *
     * @since 1.1.9
     * @param array $input Raw input data
     * @return array Sanitized settings
     */
    public function sanitize_card_oracle_settings( $input ) {
        // Verify nonce for security
        if ( !isset( $_POST['_wpnonce'] ) || !wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['_wpnonce'] ) ), 'card_oracle_option_general-options' ) ) {
            add_settings_error( 'card_oracle_settings', 'nonce_error', esc_html__( 'Security check failed. Please try again.', 'card-oracle' ) );
            return get_option( 'card_oracle_settings', $this->get_default_settings() );
        }
        $sanitized = array();
        // Sanitize enable_feed_check (boolean)
        $sanitized['enable_feed_check'] = ( isset( $input['enable_feed_check'] ) ? (bool) $input['enable_feed_check'] : false );
        // Sanitize cache_ttl (positive integer, min 60 seconds, max 24 hours)
        $cache_ttl = ( isset( $input['cache_ttl'] ) ? absint( $input['cache_ttl'] ) : 3600 );
        if ( $cache_ttl < 60 ) {
            $cache_ttl = 60;
            add_settings_error( 'card_oracle_settings', 'cache_ttl_min', esc_html__( 'Cache TTL must be at least 60 seconds.', 'card-oracle' ) );
        } elseif ( $cache_ttl > 86400 ) {
            $cache_ttl = 86400;
            add_settings_error( 'card_oracle_settings', 'cache_ttl_max', esc_html__( 'Cache TTL cannot exceed 24 hours (86400 seconds).', 'card-oracle' ) );
        }
        $sanitized['cache_ttl'] = $cache_ttl;
        // Sanitize debug_logging (boolean)
        $sanitized['debug_logging'] = ( isset( $input['debug_logging'] ) ? (bool) $input['debug_logging'] : false );
        // Sanitize default_deck (select from available readings)
        $available_decks = $this->get_available_readings();
        $default_deck = ( isset( $input['default_deck'] ) ? sanitize_text_field( $input['default_deck'] ) : '' );
        if ( !empty( $default_deck ) && !array_key_exists( $default_deck, $available_decks ) ) {
            $default_deck = '';
            add_settings_error( 'card_oracle_settings', 'invalid_deck', esc_html__( 'Invalid deck selection. Please choose from available readings.', 'card-oracle' ) );
        }
        $sanitized['default_deck'] = $default_deck;
        return $sanitized;
    }

    /**
     * Get available readings for deck selection
     *
     * @since 1.1.9
     * @return array Available readings
     */
    private function get_available_readings() {
        global $wpdb;
        $readings = array();
        $results = $wpdb->get_results( $wpdb->prepare( "SELECT ID, post_title FROM {$wpdb->posts} WHERE post_type = %s AND post_status = %s ORDER BY post_title ASC", 'co_readings', 'publish' ), ARRAY_A );
        if ( $results ) {
            foreach ( $results as $reading ) {
                $readings[$reading['ID']] = $reading['post_title'];
            }
        }
        return $readings;
    }

    /**
     * Settings section callback
     *
     * @since 1.1.9
     * @return void
     */
    public function general_settings_section_callback() {
        echo '<p>' . esc_html__( 'Configure general Card Oracle plugin settings.', 'card-oracle' ) . '</p>';
    }

    /**
     * Enable RSS Feed Check field callback
     *
     * @since 1.1.9
     * @return void
     */
    public function enable_feed_check_callback() {
        $settings = $this->get_card_oracle_settings();
        $value = $settings['enable_feed_check'];
        echo '<input type="checkbox" id="enable_feed_check" name="card_oracle_settings[enable_feed_check]" value="1" ' . checked( 1, $value, false ) . ' />';
        echo '<label for="enable_feed_check">' . esc_html__( 'Enable RSS feed checking for daily readings', 'card-oracle' ) . '</label>';
        echo '<p class="description">' . esc_html__( 'When enabled, the plugin will check RSS feeds for daily card readings.', 'card-oracle' ) . ' <a href="https://wordpress.com/support/feeds" target="_blank" rel="noopener noreferrer">' . esc_html__( 'Help', 'card-oracle' ) . '</a></p>';
    }

    /**
     * Cache TTL field callback
     *
     * @since 1.1.9
     * @return void
     */
    public function cache_ttl_callback() {
        $settings = $this->get_card_oracle_settings();
        $value = $settings['cache_ttl'];
        echo '<input type="number" id="cache_ttl" name="card_oracle_settings[cache_ttl]" value="' . esc_attr( $value ) . '" min="60" max="86400" step="60" class="regular-text" />';
        echo '<p class="description">' . esc_html__( 'Cache duration in seconds (60-86400). Default: 3600 (1 hour).', 'card-oracle' ) . '</p>';
    }

    /**
     * Debug Logging field callback
     *
     * @since 1.1.9
     * @return void
     */
    public function debug_logging_callback() {
        $settings = $this->get_card_oracle_settings();
        $value = $settings['debug_logging'];
        echo '<input type="checkbox" id="debug_logging" name="card_oracle_settings[debug_logging]" value="1" ' . checked( 1, $value, false ) . ' />';
        echo '<label for="debug_logging">' . esc_html__( 'Enable debug logging for troubleshooting', 'card-oracle' ) . '</label>';
        echo '<p class="description">' . esc_html__( 'When enabled, debug information will be logged to help with troubleshooting.', 'card-oracle' ) . '</p>';
    }

    /**
     * Default Reading field callback
     *
     * @since 1.1.9
     * @return void
     */
    public function default_deck_callback() {
        $settings = $this->get_card_oracle_settings();
        $value = $settings['default_deck'];
        $available_readings = $this->get_available_readings();
        echo '<select id="default_deck" name="card_oracle_settings[default_deck]" class="regular-text">';
        echo '<option value="">' . esc_html__( 'Select a default reading...', 'card-oracle' ) . '</option>';
        foreach ( $available_readings as $reading_id => $reading_title ) {
            echo '<option value="' . esc_attr( $reading_id ) . '"' . selected( $reading_id, $value, false ) . '>';
            echo esc_html( $reading_title );
            echo '</option>';
        }
        echo '</select>';
        echo '<p class="description">' . esc_html__( 'Select the default reading to use for new card draws.', 'card-oracle' ) . '</p>';
    }

    /**
     * Clean up demo package options when reading is deleted.
     *
     * Removes stored options that track demo package installations to prevent
     * orphaned data in the options table.
     *
     * @since 1.2.0
     * @param int $post_id Post ID being deleted.
     * @return void
     */
    public function cleanup_demo_package_options( $post_id ) {
        $post = get_post( $post_id );
        if ( !$post || 'co_readings' !== $post->post_type ) {
            return;
        }
        // Check if this was a demo package.
        $package_slug = get_post_meta( $post_id, 'card_oracle_demo_package', true );
        if ( $package_slug ) {
            delete_option( "card_oracle_demo_package_{$package_slug}" );
        }
        // Check if this was THE demo reading.
        $demo_reading_id = get_option( 'card_oracle_demo_reading_id', 0 );
        if ( absint( $demo_reading_id ) === $post_id ) {
            delete_option( 'card_oracle_demo_reading_id' );
        }
    }

    /**
     * AJAX handler to check if a reading name already exists.
     *
     * @since 1.2.1
     * @return void
     */
    public function ajax_check_reading_exists() {
        // Verify nonce.
        if ( !isset( $_POST['nonce'] ) || !wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['nonce'] ) ), 'co_wizard_check_reading' ) ) {
            wp_send_json_error( array(
                'message' => __( 'Security check failed.', 'card-oracle' ),
            ) );
        }
        // Verify user capability.
        if ( !current_user_can( 'manage_options' ) ) {
            wp_send_json_error( array(
                'message' => __( 'Permission denied.', 'card-oracle' ),
            ) );
        }
        // Get and sanitize the reading name.
        $reading_name = ( isset( $_POST['reading_name'] ) ? sanitize_text_field( wp_unslash( $_POST['reading_name'] ) ) : '' );
        if ( empty( $reading_name ) ) {
            wp_send_json_error( array(
                'message' => __( 'No reading name provided.', 'card-oracle' ),
            ) );
        }
        // Check if a reading with this name exists.
        $existing = get_posts( array(
            'post_type'      => 'co_readings',
            'post_status'    => array(
                'publish',
                'draft',
                'pending',
                'private'
            ),
            'title'          => $reading_name,
            'posts_per_page' => 1,
            'fields'         => 'ids',
        ) );
        if ( !empty( $existing ) ) {
            wp_send_json_success( array(
                'exists'  => true,
                'message' => sprintf( 
                    /* translators: %s: reading name */
                    __( 'A reading named "%s" already exists. Do you want to continue and create another reading with the same name?', 'card-oracle' ),
                    esc_html( $reading_name )
                 ),
            ) );
        }
        wp_send_json_success( array(
            'exists' => false,
        ) );
    }

}
