<?php

/**
 * The public-facing functionality of the plugin.
 *
 * @link       https://chillichalli.com
 * @since      1.0.4
 *
 * @package    Card_Oracle
 * @subpackage Card_Oracle/public
 */
use Mailchimp\Mailchimp;
/**
 * The public-facing functionality of the plugin.
 *
 * Defines the plugin name, version, and two examples hooks for how to
 * enqueue the public-facing stylesheet and JavaScript.
 *
 * @package    Card_Oracle
 * @subpackage Card_Oracle/public
 * @author     Christopher Graham <support@chillichalli.com>
 */
class Card_Oracle_Public {
    /**
     * 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.13.0
     * @param string $plugin_name The name of the plugin.
     * @param string $version The version of this plugin.
     */
    public function __construct( $plugin_name, $version ) {
        $this->plugin_name = $plugin_name;
        $this->version = $version;
    }

    /**
     * Register the stylesheets for the public-facing side of the site.
     *
     * Styles are registered here and conditionally enqueued only on pages
     * that contain Card Oracle shortcodes to avoid loading assets globally.
     *
     * @since    0.27.0
     */
    public function enqueue_styles() {
        $this->register_styles();
        // Early-enqueue in <head> when the current post contains our shortcodes.
        if ( $this->page_has_card_oracle_shortcode() ) {
            wp_enqueue_style( $this->plugin_name );
        }
    }

    /**
     * Register the JavaScript for the public-facing side of the site.
     *
     * Scripts are registered here and enqueued on demand from shortcode
     * callbacks to avoid loading Stripe.js and other scripts globally,
     * which can conflict with Elementor forms and other AJAX plugins.
     *
     * @since    1.1.3
     */
    public function enqueue_scripts() {
        $this->register_scripts();
    }

    /**
     * Register Card Oracle stylesheets without enqueuing.
     *
     * @since    1.2.3
     */
    private function register_styles() {
        wp_register_style(
            $this->plugin_name,
            CARD_ORACLE_CSS_URL,
            array(),
            $this->version,
            'all'
        );
    }

    /**
     * Register Card Oracle scripts without enqueuing.
     *
     * @since    1.2.3
     */
    private function register_scripts() {
        $script_base = plugin_dir_url( __FILE__ ) . 'js/';
        wp_register_script(
            $this->plugin_name . '-shuffle',
            $script_base . 'card-oracle-shuffle-utils.js',
            array(),
            $this->version,
            true
        );
        wp_register_script(
            $this->plugin_name,
            $script_base . 'card-oracle-public.js',
            array('jquery', $this->plugin_name . '-shuffle'),
            $this->version,
            true
        );
    }

    /**
     * Enqueue all registered Card Oracle assets.
     *
     * Called from shortcode callbacks and Elementor hooks to load assets
     * only when Card Oracle content is being rendered.
     *
     * @since    1.2.3
     */
    public function enqueue_card_oracle_assets() {
        $this->register_styles();
        $this->register_scripts();
        wp_enqueue_style( $this->plugin_name );
        wp_enqueue_script( $this->plugin_name . '-shuffle' );
        wp_enqueue_script( $this->plugin_name );
    }

    /**
     * Check if the current page contains a Card Oracle shortcode.
     *
     * @since    1.2.3
     * @return bool True if the current post contains a Card Oracle shortcode.
     */
    private function page_has_card_oracle_shortcode() {
        global $post;
        if ( !$post instanceof WP_Post ) {
            return false;
        }
        return has_shortcode( $post->post_content, 'card-oracle' ) || has_shortcode( $post->post_content, 'card-oracle-daily' ) || has_shortcode( $post->post_content, 'card-oracle-random' );
    }

    /**
     * Card Oracle sends an email with the reading via ajax
     *
     * @since  1.1.3
     * @return void
     */
    public function card_oracle_send_reading_email() {
        global $co_logs;
        $list_id = '';
        co_debug_log( 'class-card-oracle-public.php:card_oracle_send_reading_email: start' );
        // Check the nonce.
        if ( !isset( $_POST['security'] ) || !wp_verify_nonce( sanitize_key( $_POST['security'] ), 'card-oracle-send-reader-email' ) ) {
            co_debug_log( 'class-card-oracle-public.php:card_oracle_send_reading_email: end: invalid nonce' );
            wp_die();
        }
        if ( isset( $_POST['email'] ) ) {
            $to_email = sanitize_email( wp_unslash( $_POST['email'] ) );
            // Get the email address from the POST.
            if ( !is_email( $to_email ) ) {
                co_debug_log( 'class-card-oracle-public.php:card_oracle_send_reading_email: Invalid email address' );
                wp_die();
            }
            $subject = esc_html__( 'Your Reading', 'card-oracle' );
            // Set the email subject.
            co_debug_log( 'class-card-oracle-public.php:card_oracle_send_reading_email: $_POST email: to_email [' . $to_email . '], subject [' . $subject . ']' );
            // Create the headers. Add From name and address if options are set.
            $headers = card_oracle_get_from_email_address() . "MIME-Version: 1.0\r\nContent-Type: text/html; charset=UTF-8\r\n";
            // @codingStandardsIgnoreLine
            // The email body is looked up server-side from a single-use token; it
            // is never accepted from the client. This prevents the public nopriv
            // handler being abused as an open mail relay with attacker-controlled
            // content.
            if ( isset( $_POST['emailtoken'] ) ) {
                $email_token = preg_replace( '/[^A-Za-z0-9]/', '', wp_unslash( $_POST['emailtoken'] ) );
                $email_body = ( $email_token ? get_transient( 'card_oracle_email_' . $email_token ) : false );
                if ( false === $email_body ) {
                    co_debug_log( 'class-card-oracle-public.php:card_oracle_send_reading_email: missing or expired email token' );
                    wp_send_json_error( esc_html__( 'This reading is no longer available to email. Please reload the page and try again.', 'card-oracle' ) );
                    return;
                }
                // Throttle abuse: cap the number of sends per client per hour.
                $rate_limit = (int) apply_filters( 'card_oracle_email_rate_limit', 10 );
                $rate_key = 'card_oracle_email_rate_' . md5( ( isset( $_SERVER['REMOTE_ADDR'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ) ) : '' ) );
                $sent_count = (int) get_transient( $rate_key );
                if ( $sent_count >= $rate_limit ) {
                    co_debug_log( 'class-card-oracle-public.php:card_oracle_send_reading_email: rate limit exceeded' );
                    wp_send_json_error( esc_html__( 'Too many emails have been sent recently. Please try again later.', 'card-oracle' ) );
                    return;
                }
                $result = wp_mail(
                    $to_email,
                    $subject,
                    $email_body,
                    $headers
                );
                co_debug_log( 'class-card-oracle-public.php:card_oracle_send_reading_email: wp_mail result [' . (( $result ? 'true' : 'false' )) . ']' );
                if ( !$result ) {
                    co_debug_log( 'class-card-oracle-public.php:card_oracle_send_reading_email: wp_mail failed for ' . $to_email );
                    wp_send_json_error( esc_html__( 'Unable to send email. Please try again later.', 'card-oracle' ) );
                    return;
                }
                // One-shot token; bump the per-client send counter.
                delete_transient( 'card_oracle_email_' . $email_token );
                set_transient( $rate_key, $sent_count + 1, HOUR_IN_SECONDS );
                $email_success = ( get_option( 'card_oracle_email_success' ) ? get_option( 'card_oracle_email_success' ) : esc_html__( 'Your email has been sent. Please make sure to check your spam folder.', 'card-oracle' ) );
                wp_send_json_success( $email_success, 'readingsend' );
            }
        }
        co_debug_log( 'class-card-oracle-public.php:card_oracle_send_reading_email: end' );
        wp_die();
    }

    /**
     * Write out failed emails to the log
     *
     * @since  0.26.0
     *
     * @param  array $wp_error The WP error message.
     */
    public function card_oracle_mail_error( $wp_error ) {
        global $co_logs;
        co_debug_log( 'class-card-oracle-public.php:card_oracle_mail_error: start' );
        // Handle WP_Error object properly.
        $error_message = '';
        if ( is_wp_error( $wp_error ) ) {
            $error_message = $wp_error->get_error_message();
        } elseif ( is_array( $wp_error ) ) {
            $error_message = wp_json_encode( $wp_error );
        } elseif ( is_string( $wp_error ) ) {
            $error_message = $wp_error;
        } else {
            $error_message = print_r( $wp_error, true );
        }
        co_debug_log( 'class-card-oracle-public.php:card_oracle_mail_error: ' . $error_message );
        if ( $co_logs ) {
            $co_logs->add(
                'Mail',
                $error_message,
                null,
                'error'
            );
        }
        co_debug_log( 'class-card-oracle-public.php:card_oracle_mail_error: end' );
    }

    /**
     * Card Oracle shortcode to display reading
     *
     * @since 1.1.6
     * @param string $atts This is the ID of the Reading.
     * @return string html for display.
     */
    public function display_card_oracle_card_of_day( $atts ) {
        $this->enqueue_card_oracle_assets();
        // Resolve reading ID up front so the cache key can be scoped per reading
        // and per render variant. With a single global key, every daily-card
        // shortcode shared one transient and showed whichever reading rendered
        // first (and an email render could be served to the web, or vice versa).
        $reading_id = resolve_reading_id_from_atts( $atts );
        // If the reading id is not set return (not cached).
        if ( empty( $reading_id ) ) {
            return '<p>' . esc_html__( 'This Reading is missing. Please contact the site administrator.', 'card-oracle' ) . '</p>';
        }
        $transient_key = CARD_ORACLE_DAILY_CARD . '_' . $reading_id . (( isset( $atts['email'] ) ? '_email' : '' ));
        $daily_card = get_transient( $transient_key );
        if ( false === $daily_card ) {
            // Can not change the Transient Time unless you delete the _transient_timeout_ option for the transient.
            delete_option( '_transient_timeout_' . $transient_key );
            $daily_card = '';
            $card_ids = get_cards_for_reading( $reading_id );
            if ( empty( $card_ids ) ) {
                return '<p>' . esc_html__( 'This Daily Card is missing. Please contact the site administrator.', 'card-oracle' ) . '</p>';
            }
            $index = gmdate( 'z' ) % max( count( $card_ids ), 1 );
            $card_of_day = get_post( $card_ids[$index] );
            // Guard against a card trashed or deleted within the cache window
            // (get_post() returns null), which would otherwise fatal below.
            if ( !$card_of_day ) {
                return '<p>' . esc_html__( 'This Daily Card is missing. Please contact the site administrator.', 'card-oracle' ) . '</p>';
            }
            $image = '<img src="' . esc_url( card_oracle_maybe_force_https_image_url( wp_get_attachment_url( get_post_thumbnail_id( $card_of_day ) ) ) ) . '" loading="lazy">';
            $footer = get_post_meta( $reading_id, 'footer_text', true );
            if ( isset( $atts['email'] ) ) {
                $daily_card .= '<table class="card-oracle-table"><thead>';
                $daily_card .= '<tr><th colspan="2"><h1>' . esc_html( $card_of_day->post_title ) . '</h1></th></tr>';
                $daily_card .= '</thead><tbody>';
                $daily_card .= '<tr><td width="200" valign="top">' . $image . '</td><td style="vertical-align:top">' . apply_filters( 'the_content', $card_of_day->post_content ) . '</td></tr>';
                if ( !empty( $footer ) ) {
                    $daily_card .= '<tr colspan="2"><td>' . wp_kses( $footer, card_oracle_allowed_html() ) . '</td></tr>';
                }
                if ( 'yes' === get_option( 'card_oracle_powered_by' ) ) {
                    $affiliate_link = get_option( 'card_oracle_affiliate_link' );
                    $link_url = ( !empty( $affiliate_link ) ? esc_url( $affiliate_link ) : 'https://tarotcardoracle.com/turn-your-intuition-into-income-with-card-oracle/' );
                    $powered_badge = '<div class="cotd-powered-by">';
                    $powered_badge .= '<a href="' . $link_url . '" target="_blank" rel="noopener" class="cotd-powered-by-link">';
                    $powered_badge .= '<span class="cotd-powered-by-icon">&#x2728;</span> ';
                    $powered_badge .= '<span class="cotd-powered-by-text">' . esc_html__( 'Powered by', 'card-oracle' ) . '</span> ';
                    $powered_badge .= '<span class="cotd-powered-by-brand">Card Oracle</span>';
                    $powered_badge .= '<span class="cotd-powered-by-cta">. ' . esc_html__( 'Try it for yourself now.', 'card-oracle' ) . '</span>';
                    $powered_badge .= '</a>';
                    $powered_badge .= '</div>';
                    $daily_card .= '<tr><td colspan="2" style="text-align:center;padding:10px;">' . $powered_badge . '</td></tr>';
                }
                $daily_card .= '</tbody></table>';
            } else {
                $daily_card .= '<div class="cotd-wrapper alignwide">
					<div class="cotd-header">' . esc_html( $card_of_day->post_title ) . '</div>
					<div class="cotd-main">' . apply_filters( 'the_content', $card_of_day->post_content ) . '</div>';
                $daily_card .= '<div class="cotd-aside">' . $image . '</div>';
                $daily_card .= card_oracle_display_footer( $footer );
                $daily_card .= '</div>';
            }
            // Time until midnight.
            $midnight_time = strtotime( 'tomorrow' ) - time();
            set_transient( $transient_key, $daily_card, $midnight_time );
        }
        return $daily_card;
    }

    // End display_card_oracle_card_of_day.
    /**
     * Card Oracle shortcode to display random card
     *
     * @since 1.1.6
     * @param array $atts Shortcode attributes.
     * @return string HTML for display.
     */
    public function display_card_oracle_random_card( $atts ) {
        $this->enqueue_card_oracle_assets();
        // Resolve reading ID up front so the cache key is scoped per reading;
        // a single global key made every random-card shortcode share one
        // transient and show whichever reading rendered first.
        $reading_id = resolve_reading_id_from_atts( $atts );
        if ( empty( $reading_id ) ) {
            return '<p>' . esc_html__( 'This Reading is missing. Please contact the site administrator.', 'card-oracle' ) . '</p>';
        }
        $transient_key = CARD_ORACLE_RANDOM_CARD . '_' . $reading_id;
        $random_card = get_transient( $transient_key );
        if ( false === $random_card ) {
            delete_transient( $transient_key );
            $random_card = '';
            $card_ids = get_cards_for_reading( $reading_id );
            if ( empty( $card_ids ) ) {
                return '<p>' . esc_html__( 'This Random Card is missing. Please contact the site administrator.', 'card-oracle' ) . '</p>';
            }
            $card_count = count( $card_ids ) - 1;
            $card_of_day = get_post( $card_ids[wp_rand( 0, $card_count )] );
            // Guard against a card trashed or deleted within the cache window
            // (get_post() returns null), which would otherwise fatal below.
            if ( !$card_of_day ) {
                return '<p>' . esc_html__( 'This Random Card is missing. Please contact the site administrator.', 'card-oracle' ) . '</p>';
            }
            $image = '<img src="' . esc_url( card_oracle_maybe_force_https_image_url( wp_get_attachment_url( get_post_thumbnail_id( $card_of_day ) ) ) ) . '" loading="lazy">';
            $footer = get_post_meta( $reading_id, 'footer_text', true );
            $random_card .= '<div class="cotd-wrapper alignwide"><div class="cotd-header">' . $card_of_day->post_title . '</div><div class="cotd-main">' . $card_of_day->post_content . '</div><div class="cotd-aside">' . $image . '</div>';
            $random_card .= card_oracle_display_footer( $footer );
            $random_card .= '</div>';
            $days = (int) get_option( CARD_ORACLE_RANDOM_DAYS, 0 );
            $time = ( 0 === $days ? 600 : $days * DAY_IN_SECONDS );
            set_transient( $transient_key, $random_card, $time );
        }
        return $random_card;
    }

    /**
     * Card Oracle shortcode to display reading
     *
     * @since 1.1.3
     * @param string $atts This is the ID of the Reading.
     * @return string html for display.
     */
    public function display_card_oracle_set( $atts ) {
        $this->enqueue_card_oracle_assets();
        global $payment_options;
        $description_html = '';
        $page_display = '';
        $paid_display = '';
        $paid_reading = false;
        $defaults = array(
            'id'                => '',
            'name'              => '',
            'deck_layout'       => '',
            'presentation'      => '',
            'show_layout_table' => '',
            'display_question'  => '',
            'question_layout'   => '',
        );
        $atts = ( is_array( $atts ) ? $atts : array() );
        foreach ( $defaults as $key => $value ) {
            if ( !array_key_exists( $key, $atts ) ) {
                $atts[$key] = $value;
            }
        }
        $reading_id = resolve_reading_id_from_atts( $atts );
        if ( empty( $reading_id ) ) {
            // Return helpful error message for admins, nothing for visitors.
            if ( current_user_can( 'manage_options' ) ) {
                $error_msg = esc_html__( 'Card Oracle Error: No valid reading found.', 'card-oracle' );
                if ( !empty( $atts['name'] ) ) {
                    /* translators: %s is the reading name */
                    $error_msg .= ' ' . sprintf( esc_html__( 'Reading name "%s" does not exist.', 'card-oracle' ), esc_html( $atts['name'] ) );
                } elseif ( !empty( $atts['id'] ) ) {
                    /* translators: %s is the reading ID */
                    $error_msg .= ' ' . sprintf( esc_html__( 'Reading ID "%s" does not exist.', 'card-oracle' ), esc_html( $atts['id'] ) );
                } else {
                    $error_msg .= ' ' . esc_html__( 'Please specify an id or name attribute.', 'card-oracle' );
                }
                return '<p style="color: #dc3545; padding: 10px; background: #f8d7da; border: 1px solid #f5c6cb; border-radius: 4px;">' . $error_msg . '</p>';
            }
            return '';
        }
        $deck_layouts = get_deck_layouts();
        $presentation_layouts = get_presentation_layouts();
        $positions = get_positions_for_reading( $reading_id );
        $positions_count = count( $positions );
        $question_layouts = card_oracle_get_question_layout_options();
        $available_presentations = array();
        foreach ( $presentation_layouts as $layout ) {
            if ( !isset( $layout['uid'] ) ) {
                continue;
            }
            $layout_positions = ( isset( $layout['positions'] ) ? (int) $layout['positions'] : 0 );
            if ( 0 !== $layout_positions && $layout_positions !== $positions_count ) {
                continue;
            }
            $available_presentations[$layout['uid']] = $layout;
        }
        $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 '';
        };
        $overrides = array();
        $deck_layout = card_oracle_sanitize_key( $atts['deck_layout'] );
        if ( $deck_layout && isset( $deck_layouts[$deck_layout] ) ) {
            $overrides['deck_layout'] = $deck_layout;
        }
        $presentation_override = card_oracle_sanitize_key( $atts['presentation'] );
        if ( $presentation_override && isset( $available_presentations[$presentation_override] ) ) {
            $overrides['presentation_layout'] = $presentation_override;
        }
        $layout_table_override = $normalize_yes_no( $atts['show_layout_table'] );
        if ( '' !== $layout_table_override ) {
            $overrides['layout_table'] = $layout_table_override;
        }
        $question_display_override = $normalize_yes_no( $atts['display_question'] );
        if ( '' !== $question_display_override ) {
            $overrides['display_question'] = $question_display_override;
        }
        $question_layout_override = card_oracle_sanitize_key( $atts['question_layout'] );
        if ( $question_layout_override && isset( $question_layouts[$question_layout_override] ) ) {
            $overrides['question_layout'] = $question_layout_override;
        }
        card_oracle_set_block_overrides( $overrides );
        try {
            if ( isset( $_GET['payment'] ) && 'paid' === $_GET['payment'] ) {
                if ( isset( $_GET['id'] ) && isset( $_GET['_wpnonce'] ) ) {
                    $get_id = absint( $_GET['id'] );
                    // Validate nonce, order type, and completed payment status before displaying.
                    if ( wp_verify_nonce( sanitize_key( $_GET['_wpnonce'] ), 'display-reading_' . $get_id ) && 'co_order' === get_post_type( $get_id ) && in_array( get_post_meta( $get_id, CO_ORDER_STATUS, true ), array('Completed', CO_COMPLETED), true ) ) {
                        return apply_filters( 'the_content', get_the_content( null, false, $get_id ) );
                    }
                }
                $redirect = esc_url( remove_query_arg( array('payment', 'id', '_wpnonce') ) );
                wp_safe_redirect( $redirect );
                if ( !defined( 'CARD_ORACLE_TESTING' ) ) {
                    exit;
                }
                return '';
            }
            ob_start();
            if ( 'publish' !== get_post_status( $reading_id ) ) {
                if ( current_user_can( 'manage_options' ) ) {
                    $post_status = get_post_status( $reading_id );
                    echo '<p style="color: #dc3545; padding: 10px; background: #f8d7da; border: 1px solid #f5c6cb; border-radius: 4px;">';
                    esc_html_e( 'Card Oracle Error: Reading not found or not published.', 'card-oracle' );
                    echo ' ID: <code>' . esc_html( $reading_id ) . '</code>';
                    echo ' Status: <code>' . esc_html( ( $post_status ? $post_status : 'not found' ) ) . '</code>';
                    echo '</p>';
                } else {
                    echo '<p>';
                    esc_html_e( 'This Reading is missing. Please contact the site administrator.', 'card-oracle' );
                    echo '</p>';
                }
                return ob_get_clean();
            }
            // WooCommerce gate: if the reading is linked to a WC product and the
            // current customer hasn't purchased it yet, swap the selection
            // screen for a "Buy this reading" gate. Returns early.
            if ( class_exists( 'Card_Oracle_WC' ) ) {
                $wc_gate = Card_Oracle_WC::maybe_render_gate( $reading_id );
                if ( '' !== $wc_gate ) {
                    echo wp_kses_post( $wc_gate );
                    // phpcs:ignore WordPress.Security.EscapeOutput -- gate HTML pre-escaped by class.
                    return ob_get_clean();
                }
            }
            // Same gate for Easy Digital Downloads when the reading is linked
            // to an EDD download instead of a WC product.
            if ( class_exists( 'Card_Oracle_EDD' ) ) {
                $edd_gate = Card_Oracle_EDD::maybe_render_gate( $reading_id );
                if ( '' !== $edd_gate ) {
                    echo wp_kses_post( $edd_gate );
                    // phpcs:ignore WordPress.Security.EscapeOutput -- gate HTML pre-escaped by class.
                    return ob_get_clean();
                }
            }
            $stored_presentation = get_post_meta( $reading_id, CO_PRESENTATION_LAYOUT, true );
            $presentation = card_oracle_get_block_override( 'presentation_layout', ( $stored_presentation ? $stored_presentation : 'standard' ) );
            $key = array_search( $presentation, array_column( $presentation_layouts, 'uid' ), true );
            if ( false === $key ) {
                $presentation = 'standard';
                $key = array_search( 'standard', array_column( $presentation_layouts, 'uid' ), true );
            }
            if ( false === $key ) {
                return ob_get_clean();
            }
            $filename = $presentation_layouts[$key]['file'];
            // Use include (not include_once) to allow multiple shortcode instances on same page.
            include $filename;
            // Add powered by badge if enabled.
            if ( 'yes' === get_option( 'card_oracle_powered_by' ) ) {
                $affiliate_link = get_option( 'card_oracle_affiliate_link' );
                $link_url = ( !empty( $affiliate_link ) ? esc_url( $affiliate_link ) : 'https://tarotcardoracle.com/turn-your-intuition-into-income-with-card-oracle/' );
                echo '<div class="cotd-powered-by" style="text-align:center;margin-top:15px;">';
                echo '<a href="' . esc_url( $link_url ) . '" target="_blank" rel="noopener" class="cotd-powered-by-link">';
                echo '<span class="cotd-powered-by-icon">&#x2728;</span> ';
                echo '<span class="cotd-powered-by-text">' . esc_html__( 'Powered by', 'card-oracle' ) . '</span> ';
                echo '<span class="cotd-powered-by-brand">Card Oracle</span>';
                echo '<span class="cotd-powered-by-cta">. ' . esc_html__( 'Try it for yourself now.', 'card-oracle' ) . '</span>';
                echo '</a>';
                echo '</div>';
            }
            $page_display = ob_get_clean();
            // Wrap output in a reading container, optionally injecting a per-reading accent color and background.
            $accent_color = sanitize_hex_color( (string) get_post_meta( $reading_id, CO_ACCENT_COLOR, true ) );
            $bg_id = (int) get_post_meta( $reading_id, CO_BACKGROUND_IMAGE, true );
            $bg_url = ( $bg_id ? card_oracle_maybe_force_https_image_url( wp_get_attachment_image_url( $bg_id, 'full' ) ) : '' );
            $wrapper_classes = array('card-oracle-reading');
            if ( $bg_url ) {
                $wrapper_classes[] = 'card-oracle-reading--has-bg';
            }
            $styles = array();
            if ( $accent_color ) {
                $styles[] = '--co-accent: ' . $accent_color;
            }
            if ( $bg_url ) {
                $styles[] = '--co-bg: url(' . esc_url_raw( $bg_url ) . ')';
            }
            $wrapper_attrs = 'class="' . esc_attr( implode( ' ', $wrapper_classes ) ) . '" data-reading-id="' . esc_attr( $reading_id ) . '"';
            if ( !empty( $styles ) ) {
                $wrapper_attrs .= ' style="' . esc_attr( implode( '; ', $styles ) . ';' ) . '"';
            }
            $page_display = '<div ' . $wrapper_attrs . '>' . $page_display . '</div>';
            if ( $paid_reading ) {
                co_debug_log( 'Description HTML [' . $page_display . ']' );
                $order_data = array(
                    'post_content' => $description_html,
                    'meta_input'   => array(
                        CO_AMOUNT        => $payment_options['price'],
                        CO_ORDER_STATUS  => esc_html__( 'New', 'card-oracle' ),
                        '_co_reading_id' => $reading_id,
                        '_co_subscribe'  => ( 'yes' === get_option( 'card_oracle_subscribe' ) ? '1' : '0' ),
                    ),
                );
                $payment_options['item_number'] = card_oracle_insert_order( $order_data );
                co_debug_log( 'item number [' . $payment_options['item_number'] . '], amount [' . $payment_options['price'] . '], item_name [' . $payment_options['item_name'] . ']' );
                $sale_text = get_post_meta( $reading_id, CO_SALES_TEXT, true );
                if ( $sale_text ) {
                    $paid_display .= '<div class="card-oracle-sales-text"><p>' . $sale_text . '</p></div>';
                }
                if ( isset( $payment_options['shortcode'] ) ) {
                    $paid_display .= do_shortcode( $payment_options['shortcode'] );
                }
                return $paid_display;
            }
            return $page_display;
        } finally {
            card_oracle_clear_block_overrides();
        }
    }

}

// End Class Card_Oracle_Public.