<?php

/**
 * Plugin Name:       3D Product Customizer & Configurator for WooCommerce
 * Plugin URI:        https://wordpress.org/plugins/3d-product-customizer/
 * Description:       Adds powerful 3D customization capabilities to WooCommerce products, including material swapping, geometry toggling, and AR viewing.
 * Version:           3.6.7
 * Requires at least: 6.0
 * Requires PHP:      7.4
 * Requires Plugins:  woocommerce
 * Author:            deosebIT Soft
 * Author URI:        https://deosebitsoft.ro/
 * License:           GPL v2 or later
 * License URI:       https://www.gnu.org/licenses/gpl-2.0.html
 * Text Domain:       3d-product-customizer
 * Domain Path:       /languages
 *
 * @package ProductCustomizer
 * @version 3.6.7
 */
// Exit if accessed directly
if ( !defined( 'ABSPATH' ) ) {
    exit;
}
/**
 * Freemius auto-deactivation of the free version when the premium one is activated.
 * If dprcu_fs() already exists, this is the premium plugin loading alongside the free
 * one & tell Freemius to switch to the premium basename and stop here.
 */
if ( function_exists( 'dprcu_fs' ) ) {
    dprcu_fs()->set_basename( false, __FILE__ );
} else {
    if ( !function_exists( 'dprcu_fs' ) ) {
        require_once plugin_dir_path( __FILE__ ) . 'includes/freemius.php';
    }
    // The free version relies on WordPress.org auto-loading translations (WP 4.6+).
    // The premium version runs under a different folder name (3d-product-customizer-premium),
    // so WordPress won't auto-load the free text domain & load it explicitly instead.
    // We also load the premium slug as an alias pointing to the same translation files
    // so that the Text Domain header in the premium plugin file matches PCP expectations.
    if ( strpos( plugin_basename( __FILE__ ), '3d-product-customizer/' ) === false ) {
        add_action( 'init', function () {
            $lang_dir = dirname( plugin_basename( __FILE__ ) ) . '/languages';
            load_plugin_textdomain( '3d-product-customizer', false, $lang_dir );
            // phpcs:ignore PluginCheck.CodeAnalysis.DiscouragedFunctions.load_plugin_textdomainFound -- Required: premium runs under a different folder and is not auto-loaded by WordPress.org.
            load_plugin_textdomain( '3d-customizer-premium', false, $lang_dir );
            // phpcs:ignore PluginCheck.CodeAnalysis.DiscouragedFunctions.load_plugin_textdomainFound -- Alias for the premium Text Domain header.
        } );
    }
    // Init customizer block for block-based themes (FSE)
    add_action( 'init', function () {
        $block_dir = plugin_dir_path( __FILE__ ) . 'blocks/customizer';
        $asset_file = $block_dir . '/index.asset.php';
        $asset = ( file_exists( $asset_file ) ? require $asset_file : array(
            'dependencies' => array('wp-blocks', 'wp-element'),
            'version'      => '1.0.0',
        ) );
        wp_register_script(
            'dprcu-customizer-block-editor',
            plugin_dir_url( __FILE__ ) . 'blocks/customizer/index.js',
            $asset['dependencies'],
            $asset['version'],
            true
        );
        register_block_type( $block_dir . '/block.json', array(
            'render_callback' => 'dprcu_block_render',
            'editor_script'   => 'dprcu-customizer-block-editor',
        ) );
    } );
    /**
     * Check if WooCommerce is active. If not, show admin notice and deactivate plugin.
     */
    if ( !function_exists( 'is_plugin_active' ) ) {
        require_once ABSPATH . 'wp-admin/includes/plugin.php';
    }
    if ( !is_plugin_active( 'woocommerce/woocommerce.php' ) ) {
        add_action( 'admin_notices', function () {
            echo '<div class="notice notice-error is-dismissible"><p>';
            echo esc_html__( '3D Product Customizer & Configurator requires WooCommerce to be installed and activated.', '3d-product-customizer' );
            echo '</p></div>';
        } );
        return;
    }
    require_once plugin_dir_path( __FILE__ ) . 'admin/plugin-settings.php';
    require_once plugin_dir_path( __FILE__ ) . 'cpt-model-textures.php';
    require_once plugin_dir_path( __FILE__ ) . 'admin/product-metabox.php';
    /**
     * Registers the correct output hook for this product page.
     *
     * Runs at template_redirect & after the queried object is known but
     * before any template rendering & so hook setup is complete and ordered
     * with no risk of registering hooks from inside another hook callback.
     *
     * Viewer-only mode uses render_block instead of woocommerce_before_single_product_summary
     * because block-based (FSE) themes render the gallery via the
     * woocommerce/product-image-gallery Gutenberg block inside <main>.
     * Classic WooCommerce action hooks fire outside <main> in FSE themes,
     * so they cannot place output inside the gallery column.
     */
    function dprcu_setup_product_hooks() {
        if ( !function_exists( 'is_product' ) || !is_product() ) {
            return;
        }
        $product_id = get_queried_object_id();
        $model_url = get_post_meta( $product_id, '_dprcu_3d_model_url', true );
        $viewer_only = get_post_meta( $product_id, '_dprcu_3d_viewer_only', true );
        if ( empty( $model_url ) ) {
            return;
        }
        /**
         * Fires when product hooks are being set up for the customizer.
         *
         * @since 3.4.0
         * @param int $product_id The current product ID.
         * @param string $model_url The 3D model URL.
         * @param string $viewer_only Whether viewer-only mode is enabled.
         */
        do_action(
            'dprcu_setup_product_hooks',
            $product_id,
            $model_url,
            $viewer_only
        );
        $settings = get_option( 'dprcu_settings' );
        $display_method = $settings['display_method'] ?? 'auto';
        // Only auto-register hooks if display method is set to 'auto'
        if ( 'auto' !== $display_method ) {
            return;
        }
        if ( '1' === $viewer_only ) {
            // Use render_block to intercept the gallery block in FSE/block themes.
            add_filter(
                'render_block',
                'dprcu_intercept_gallery_block',
                10,
                2
            );
        } else {
            add_action( 'woocommerce_before_single_product_summary', 'dprcu_html_output', 5 );
        }
    }

    add_action( 'template_redirect', 'dprcu_setup_product_hooks' );
    /**
     * Handles manual display of customizer via do_action() hook.
     *
     * Allows themes and plugins to display the customizer by calling:
     * do_action('dprcu_display_customizer')
     *
     * @since 3.4.0
     */
    function dprcu_display_customizer_hook(  $product_id = null  ) {
        if ( !$product_id ) {
            if ( !function_exists( 'is_product' ) || !is_product() ) {
                return;
            }
            $product_id = get_the_ID();
        }
        $model_url = get_post_meta( $product_id, '_dprcu_3d_model_url', true );
        $viewer_only = get_post_meta( $product_id, '_dprcu_3d_viewer_only', true );
        if ( empty( $model_url ) ) {
            return;
        }
        if ( '1' === $viewer_only ) {
            dprcu_viewer_canvas_html( $product_id );
        } else {
            dprcu_html_output( $product_id );
        }
    }

    add_action(
        'dprcu_display_customizer',
        'dprcu_display_customizer_hook',
        10,
        1
    );
    /**
     * Shortcode handler for displaying the 3D customizer.
     *
     * Usage: [dprcu-customizer id="123"]
     *
     * @since 3.4.0
     * @param array $atts Shortcode attributes.
     * @return string The customizer HTML output.
     */
    function dprcu_customizer_shortcode(  $atts  ) {
        $atts = shortcode_atts( array(
            'id' => 0,
        ), $atts, 'dprcu-customizer' );
        $product_id = intval( $atts['id'] );
        if ( $product_id ) {
            $product = wc_get_product( $product_id );
            if ( !$product ) {
                return '';
            }
        } else {
            if ( !function_exists( 'is_product' ) || !is_product() ) {
                return '';
            }
            $product_id = get_the_ID();
        }
        $model_url = get_post_meta( $product_id, '_dprcu_3d_model_url', true );
        $viewer_only = get_post_meta( $product_id, '_dprcu_3d_viewer_only', true );
        if ( empty( $model_url ) ) {
            return '';
        }
        dprcu_enqueue_scripts( $product_id );
        ob_start();
        if ( '1' === $viewer_only ) {
            dprcu_viewer_canvas_html( $product_id );
        } else {
            dprcu_html_output( $product_id );
        }
        return ob_get_clean() ?? '';
    }

    add_shortcode( 'dprcu-customizer', 'dprcu_customizer_shortcode' );
    function dprcu_block_render(  $attributes, $content  ) {
        $product_id = ( !empty( $attributes['productId'] ) ? intval( $attributes['productId'] ) : 0 );
        if ( !$product_id ) {
            if ( !function_exists( 'is_product' ) || !is_product() ) {
                return '';
            }
            $product_id = get_the_ID();
        }
        $model_url = get_post_meta( $product_id, '_dprcu_3d_model_url', true );
        $viewer_only = get_post_meta( $product_id, '_dprcu_3d_viewer_only', true );
        if ( empty( $model_url ) ) {
            return '';
        }
        dprcu_enqueue_scripts( $product_id );
        ob_start();
        if ( '1' === $viewer_only ) {
            dprcu_viewer_canvas_html( $product_id );
        } else {
            dprcu_html_output( $product_id );
        }
        return ob_get_clean() ?? '';
    }

    /**
     * Remove wpautop-generated markers from shortcode output.
     *
     * Hooked to 'the_content' at priority 12 (after wpautop at priority 10).
     * This preserves the shortcode output without wpautop interference.
     */
    function dprcu_remove_shortcode_markers(  $content  ) {
        // Remove the temporary markers
        $content = str_replace( '<!-- dprcu-shortcode-start -->', '', $content );
        $content = str_replace( '<!-- dprcu-shortcode-end -->', '', $content );
        return $content;
    }

    add_filter( 'the_content', 'dprcu_remove_shortcode_markers', 12 );
    /**
     * Replaces the WooCommerce product image gallery block with the 3D viewer canvas
     * when the product has Viewer Only Mode enabled.
     *
     * Hooked to render_block so it works in both classic and block-based (FSE) themes.
     *
     * @param string $block_content The rendered block HTML.
     * @param array  $block         The block data array.
     * @return string
     */
    function dprcu_intercept_gallery_block(  $block_content, $block  ) {
        if ( 'woocommerce/product-image-gallery' !== ($block['blockName'] ?? '') ) {
            return $block_content;
        }
        ob_start();
        dprcu_viewer_canvas_html();
        return ob_get_clean();
    }

    function dprcu_render_ar_button(  $mode = 'simple'  ) {
        ?>
		<button class="control-btn <?php 
        if ( 'simple' !== $mode ) {
            echo 'floating';
        }
        ?>" id="btn-view-ar"
			title="<?php 
        esc_attr_e( 'View in AR (Mobile Only)', '3d-product-customizer' );
        ?>"><i data-lucide="box"></i><?php 
        if ( 'simple' !== $mode ) {
            esc_html_e( 'View in your room', '3d-product-customizer' );
        }
        ?></button>
		<?php 
    }

    /**
     * Outputs the full customizer HTML (canvas + sidebar + steps).
     * Registered conditionally by dprcu_setup_product_hooks().
     */
    function dprcu_html_output(  $product_id = null  ) {
        if ( !$product_id ) {
            global $post;
            $product_id = $post->ID;
        }
        $config_json = get_post_meta( $product_id, '_dprcu_customizer_config', true );
        $price = get_post_meta( $product_id, '_price', true );
        $ar_display_mode = get_option( 'dprcu_settings' )['ar_display_mode'] ?? 'simple';
        if ( empty( $config_json ) ) {
            return;
        }
        $config_data = json_decode( $config_json, true );
        $has_valid_options = false;
        if ( is_array( $config_data ) ) {
            foreach ( $config_data as $step ) {
                if ( !empty( $step['options'] ) ) {
                    $has_valid_options = true;
                    break;
                }
            }
        }
        if ( !$has_valid_options ) {
            return;
        }
        /**
         * Fires before the customizer HTML output is rendered.
         *
         * @since 3.4.0
         * @param int $product_id The current product ID.
         * @param array $config_data The decoded customizer configuration.
         */
        do_action( 'dprcu_before_customizer_output', $product_id, $config_data );
        ?>
		<div id="app-container">
			<?php 
        /**
         * Fires before the canvas container.
         *
         * @since 3.4.0
         * @param int $product_id The current product ID.
         */
        do_action( 'dprcu_before_canvas_container', $product_id );
        ?>
			<div id="canvas-container">
				<canvas id="product-canvas"></canvas>
				<div id="customizer-loader">
					<div class="loader-spinner"></div>
					<span id="loader-text"><?php 
        esc_html_e( 'Loading 3D Model...', '3d-product-customizer' );
        ?></span>
				</div>
				<div id="view-controls">
					<button class="control-btn" id="btn-zoom-in"
						title="<?php 
        esc_attr_e( 'Zoom In', '3d-product-customizer' );
        ?>"><i data-lucide="zoom-in"></i></button>
					<button class="control-btn" id="btn-zoom-out"
						title="<?php 
        esc_attr_e( 'Zoom Out', '3d-product-customizer' );
        ?>"><i
							data-lucide="zoom-out"></i></button>
					<button class="control-btn" id="btn-reset"
						title="<?php 
        esc_attr_e( 'Reset Configurator', '3d-product-customizer' );
        ?>"><i
							data-lucide="rotate-ccw"></i></button>
					<button class="control-btn" id="btn-undo" title="<?php 
        esc_attr_e( 'Undo', '3d-product-customizer' );
        ?>"><i
							data-lucide="undo-2"></i></button>
					<button class="control-btn" id="btn-redo" title="<?php 
        esc_attr_e( 'Redo', '3d-product-customizer' );
        ?>"><i
							data-lucide="redo-2"></i></button>
					<button class="control-btn" id="btn-fullscreen"
						title="<?php 
        esc_attr_e( 'Fullscreen', '3d-product-customizer' );
        ?>"><i
							data-lucide="maximize"></i></button>
					<button class="control-btn" id="btn-bg"
						title="<?php 
        esc_attr_e( 'Change Background', '3d-product-customizer' );
        ?>"><i
							data-lucide="image"></i></button>
					<?php 
        if ( dprcu_fs()?->can_use_premium_code__premium_only() ) {
            dprcu_render_hotspot_toggle__premium_only();
        }
        if ( 'simple' === $ar_display_mode ) {
            dprcu_render_ar_button();
        }
        ?>

					<button class="control-btn" id="btn-help"
						title="<?php 
        esc_attr_e( 'Keyboard Shortcuts & Help', '3d-product-customizer' );
        ?>"><i
							data-lucide="help-circle"></i></button>
				</div>
				<?php 
        if ( 'detailed' === $ar_display_mode ) {
            dprcu_render_ar_button( 'detailed' );
        }
        dprcu_maybe_render_watermark();
        ?>
			</div>
			<?php 
        /**
         * Fires after the canvas container.
         *
         * @since 3.4.0
         * @param int $product_id The current product ID.
         */
        do_action( 'dprcu_after_canvas_container', $product_id );
        /**
         * Fires before the sidebar container.
         *
         * @since 3.4.0
         * @param int $product_id The current product ID.
         */
        do_action( 'dprcu_before_sidebar_container', $product_id );
        ?>

			<!-- Share Modal -->
			<div id="share-modal" class="share-modal">
				<div class="share-modal-content">
					<div class="share-modal-header">
						<span><?php 
        esc_html_e( 'Share Your Design', '3d-product-customizer' );
        ?></span>
						<button id="share-modal-close" class="share-modal-close"
							aria-label="<?php 
        esc_attr_e( 'Close', '3d-product-customizer' );
        ?>"><i data-lucide="x"></i></button>
					</div>
					<div class="share-modal-body">
						<div class="share-preview">
							<img id="share-preview-img" alt="<?php 
        esc_attr_e( 'Design preview', '3d-product-customizer' );
        ?>" />
						</div>
						<div class="share-option">
							<label><?php 
        esc_html_e( 'Share Link', '3d-product-customizer' );
        ?></label>
							<div class="share-link-container">
								<input id="share-link-input" type="text" readonly />
								<button id="share-link-copy"
									class="btn-link-copy"><?php 
        esc_html_e( 'Copy', '3d-product-customizer' );
        ?></button>
							</div>
						</div>
						<div class="share-option">
							<label><?php 
        esc_html_e( 'Share on Social', '3d-product-customizer' );
        ?></label>
							<div class="share-buttons">
								<button id="share-facebook" class="share-btn share-btn-facebook"
									title="<?php 
        esc_attr_e( 'Share on Facebook', '3d-product-customizer' );
        ?>"><span><?php 
        esc_html_e( 'Facebook', '3d-product-customizer' );
        ?></span></button>
								<button id="share-twitter" class="share-btn share-btn-twitter"
									title="<?php 
        esc_attr_e( 'Share on Twitter', '3d-product-customizer' );
        ?>"><span><?php 
        esc_html_e( 'Twitter', '3d-product-customizer' );
        ?></span></button>
								<button id="share-linkedin" class="share-btn share-btn-linkedin"
									title="<?php 
        esc_attr_e( 'Share on LinkedIn', '3d-product-customizer' );
        ?>"><span><?php 
        esc_html_e( 'LinkedIn', '3d-product-customizer' );
        ?></span></button>
								<button id="share-pinterest" class="share-btn share-btn-pinterest"
									title="<?php 
        esc_attr_e( 'Share on Pinterest', '3d-product-customizer' );
        ?>"><span><?php 
        esc_html_e( 'Pinterest', '3d-product-customizer' );
        ?></span></button>
							</div>
						</div>
						<div class="share-option">
							<button id="share-download-img" class="btn-download-image">
								<i data-lucide="download"></i> <?php 
        esc_html_e( 'Download Image', '3d-product-customizer' );
        ?>
							</button>
						</div>
					</div>
				</div>
				<div class="share-modal-backdrop"></div>
			</div>

			<div id="dprcu-ar-overlay" class="dprcu-ar-overlay" hidden>
				<button class="dprcu-ar-exit" type="button" aria-label="Exit AR"><i data-lucide="x"></i></button>

				<div class="dprcu-ar-hint" data-state="searching">
					<?php 
        esc_html_e( 'Point your phone at the floor', '3d-product-customizer' );
        ?>
				</div>

				<div class="dprcu-ar-hint" data-state="ready" hidden>
					<?php 
        esc_html_e( 'Tap to place', '3d-product-customizer' );
        ?>
				</div>

				<div class="dprcu-ar-hint" data-state="placed" hidden>
					<?php 
        esc_html_e( 'Tap to reposition', '3d-product-customizer' );
        ?>
				</div>
			</div>

			<!-- Help Modal -->
			<div id="help-modal" class="help-modal">
				<div class="help-modal-content">
					<div class="help-modal-header">
						<span><?php 
        esc_html_e( 'Keyboard Shortcuts & Help', '3d-product-customizer' );
        ?></span>
						<button id="help-modal-close" class="help-modal-close"
							aria-label="<?php 
        esc_attr_e( 'Close', '3d-product-customizer' );
        ?>"><i data-lucide="x"></i></button>
					</div>
					<div class="help-modal-body">
						<div class="shortcuts-section">
							<h4><?php 
        esc_html_e( 'View Controls', '3d-product-customizer' );
        ?></h4>
							<div class="shortcut-item">
								<i class="shortcut-icon" data-lucide="undo-2"></i>
								<span class="shortcut-desc"><?php 
        esc_html_e( 'Undo', '3d-product-customizer' );
        ?></span>
								<span class="shortcut-keys">
									<kbd class="mod-key">Ctrl</kbd><span class="kbd-plus">+</span><kbd>Z</kbd>
								</span>
							</div>
							<div class="shortcut-item">
								<i class="shortcut-icon" data-lucide="redo-2"></i>
								<span class="shortcut-desc"><?php 
        esc_html_e( 'Redo', '3d-product-customizer' );
        ?></span>
								<span class="shortcut-keys">
									<kbd class="mod-key">Ctrl</kbd><span class="kbd-plus">+</span><kbd>Y</kbd>
								</span>
							</div>
							<div class="shortcut-item">
								<i class="shortcut-icon" data-lucide="rotate-ccw"></i>
								<span
									class="shortcut-desc"><?php 
        esc_html_e( 'Reset Configurator', '3d-product-customizer' );
        ?></span>
								<kbd>R</kbd>
							</div>
							<div class="shortcut-item">
								<i class="shortcut-icon" data-lucide="zoom-in"></i>
								<span class="shortcut-desc"><?php 
        esc_html_e( 'Zoom In', '3d-product-customizer' );
        ?></span>
								<kbd>+</kbd>
							</div>
							<div class="shortcut-item">
								<i class="shortcut-icon" data-lucide="zoom-out"></i>
								<span class="shortcut-desc"><?php 
        esc_html_e( 'Zoom Out', '3d-product-customizer' );
        ?></span>
								<kbd>-</kbd>
							</div>
							<div class="shortcut-item">
								<i class="shortcut-icon" data-lucide="maximize"></i>
								<span class="shortcut-desc"><?php 
        esc_html_e( 'Fullscreen', '3d-product-customizer' );
        ?></span>
								<kbd>F</kbd>
							</div>
							<div class="shortcut-item">
								<i class="shortcut-icon" data-lucide="image"></i>
								<span
									class="shortcut-desc"><?php 
        esc_html_e( 'Change Background', '3d-product-customizer' );
        ?></span>
								<kbd>B</kbd>
							</div>
							<?php 
        if ( dprcu_fs()?->is__premium_only() ) {
            ?>
								<div class="shortcut-item">
									<i class="shortcut-icon" data-lucide="eye"></i>
									<span
										class="shortcut-desc"><?php 
            esc_html_e( 'Toggle Annotations', '3d-product-customizer' );
            ?></span>
								</div>
								<?php 
        }
        ?>
							<div class="shortcut-item">
								<i class="shortcut-icon" data-lucide="rectangle-goggles"></i>
								<span class="shortcut-desc"><?php 
        esc_html_e( 'View in AR', '3d-product-customizer' );
        ?></span>
							</div>
						</div>
						<div class="shortcuts-section">
							<h4><?php 
        esc_html_e( 'Interaction Tips', '3d-product-customizer' );
        ?></h4>
							<div class="shortcut-item">
								<span class="shortcut-key"><?php 
        esc_html_e( 'Drag', '3d-product-customizer' );
        ?></span>
								<span
									class="shortcut-desc"><?php 
        esc_html_e( 'Rotate the product', '3d-product-customizer' );
        ?></span>
							</div>
							<div class="shortcut-item">
								<span class="shortcut-key"><?php 
        esc_html_e( 'Scroll', '3d-product-customizer' );
        ?></span>
								<span class="shortcut-desc"><?php 
        esc_html_e( 'Zoom in/out', '3d-product-customizer' );
        ?></span>
							</div>
							<div class="shortcut-item">
								<span class="shortcut-key"><?php 
        esc_html_e( 'Click Hotspot', '3d-product-customizer' );
        ?></span>
								<span
									class="shortcut-desc"><?php 
        esc_html_e( 'Select customization options', '3d-product-customizer' );
        ?></span>
							</div>
						</div>
					</div>
				</div>
				<div class="help-modal-backdrop"></div>
			</div>

			<div id="sidebar">
				<h2 class="customizer-title"><?php 
        echo esc_html( get_the_title( $product_id ) );
        ?></h2>
				<div id="sidebar-content"></div>
				<div id="sidebar-action-bar">
					<div>
						<button id="btn-download-preview"
							title="<?php 
        esc_attr_e( 'Download Preview', '3d-product-customizer' );
        ?>">
							<i data-lucide="download"></i>
						</button>
						<button id="btn-share-config" title="<?php 
        esc_attr_e( 'Share Config', '3d-product-customizer' );
        ?>">
							<i data-lucide="share-2"></i>
							<span></span>
						</button>
					</div>
					<div class="add-to-cart-container">
						<?php 
        if ( $price > 0 ) {
            ?>
							<span id="dprcu-total-price"
								data-base-price="<?php 
            echo esc_attr( floatval( $price ) );
            ?>"><?php 
            echo wp_kses_post( wc_price( $price ) );
            ?></span>
							<button id="btn-add-to-cart" title="<?php 
            esc_attr_e( 'Add to Cart', '3d-product-customizer' );
            ?>"
								data-product-id="<?php 
            echo esc_attr( $product_id );
            ?>">
								<i data-lucide="shopping-cart"></i>
								<span><?php 
            esc_html_e( 'Add to cart', '3d-product-customizer' );
            ?></span>
							</button>
						<?php 
        }
        ?>
					</div>
				</div>
			</div>
			<?php 
        /**
         * Fires after the sidebar container.
         *
         * @since 3.4.0
         * @param int $product_id The current product ID.
         */
        do_action( 'dprcu_after_sidebar_container', $product_id );
        ?>
		</div>
		<?php 
        /**
         * Fires after the customizer HTML output is rendered.
         *
         * @since 3.4.0
         * @param int $product_id The current product ID.
         * @param array $config_data The decoded customizer configuration.
         */
        do_action( 'dprcu_after_customizer_output', $product_id, $config_data );
    }

    // Note: dprcu_html_output is registered by dprcu_setup_product_hooks() above.
    /**
     * Builds the per-step/option customizer config consumed by JS, plus a
     * texture-URL => last-modified-timestamp map used for front-end cache busting.
     *
     * Primes the post/meta object cache for every texture (and the attachments
     * their maps point to) before reading them, then walks the step/option tree
     * exactly once to build both return values together.
     *
     * @param string $config_json Raw `_dprcu_customizer_config` postmeta JSON.
     * @return array{0: array, 1: array} [$final_config_for_js, $texture_versions]
     */
    function dprcu_build_customizer_options(  $config_json  ) {
        $config_data = json_decode( $config_json, true );
        if ( !is_array( $config_data ) ) {
            return array(array(), array());
        }
        $texture_ids_to_prime = array();
        foreach ( $config_data as $step ) {
            foreach ( $step['options'] ?? array() as $option ) {
                $raw_ids = $option['texture_ids'] ?? $option['texture_id'] ?? '';
                $ids = ( is_array( $raw_ids ) ? $raw_ids : array_filter( array_map( 'intval', explode( ',', $raw_ids ) ) ) );
                $texture_ids_to_prime = array_merge( $texture_ids_to_prime, $ids );
            }
        }
        $texture_ids_to_prime = array_unique( array_filter( $texture_ids_to_prime ) );
        if ( !empty( $texture_ids_to_prime ) ) {
            _prime_post_caches( $texture_ids_to_prime, false, true );
            $attachment_ids_to_prime = array();
            foreach ( $texture_ids_to_prime as $texture_id ) {
                $attachment_ids_to_prime[] = get_post_thumbnail_id( $texture_id );
                $attachment_ids_to_prime[] = get_post_meta( $texture_id, '_dprcu_texture_roughness_id', true );
                $attachment_ids_to_prime[] = get_post_meta( $texture_id, '_dprcu_texture_normal_id', true );
                $attachment_ids_to_prime[] = get_post_meta( $texture_id, '_dprcu_texture_ao_id', true );
                $attachment_ids_to_prime[] = get_post_meta( $texture_id, '_dprcu_texture_metalness_id', true );
                $attachment_ids_to_prime[] = get_post_meta( $texture_id, '_dprcu_texture_displacement_id', true );
            }
            $attachment_ids_to_prime = array_unique( array_filter( array_map( 'intval', $attachment_ids_to_prime ) ) );
            if ( !empty( $attachment_ids_to_prime ) ) {
                _prime_post_caches( $attachment_ids_to_prime, false, false );
            }
        }
        $final_config_for_js = array();
        $texture_versions = array();
        foreach ( $config_data as $step_id => $step ) {
            $valid_options = array();
            $step_targets = $step['targets'] ?? array();
            foreach ( $step['options'] ?? array() as $option ) {
                $texture_ids_raw = $option['texture_ids'] ?? $option['texture_id'] ?? '';
                $texture_ids = ( is_array( $texture_ids_raw ) ? $texture_ids_raw : array_filter( array_map( 'intval', explode( ',', $texture_ids_raw ) ) ) );
                $opt_name = $option['name'] ?? 'Option';
                $show_meshes = $option['show'] ?? array();
                $hide_meshes = $option['hide'] ?? array();
                $opt_price = floatval( $option['price'] ?? 0 );
                $opt_icon = $option['icon'] ?? '';
                $has_textures = !empty( $texture_ids );
                $has_geometry = !empty( $show_meshes ) || !empty( $hide_meshes );
                if ( $has_textures ) {
                    foreach ( $texture_ids as $texture_id ) {
                        if ( !$texture_id ) {
                            continue;
                        }
                        $texture_post = get_post( $texture_id );
                        $tex_name = ( $texture_post ? ( $texture_post->post_title ?: 'Material' ) : 'Material' );
                        $valid_options[] = array(
                            'name'  => $tex_name,
                            'icon'  => $opt_icon,
                            'type'  => 'material',
                            'price' => $opt_price,
                            'value' => array(
                                'texture'           => ( get_the_post_thumbnail_url( $texture_id, 'full' ) ?: null ),
                                'textureThumbnail'  => ( get_the_post_thumbnail_url( $texture_id, 'thumbnail' ) ?: null ),
                                'color'             => ( get_post_meta( $texture_id, '_dprcu_texture_color', true ) ?: null ),
                                'roughnessMap'      => ( wp_get_attachment_url( get_post_meta( $texture_id, '_dprcu_texture_roughness_id', true ) ) ?: null ),
                                'normalMap'         => ( wp_get_attachment_url( get_post_meta( $texture_id, '_dprcu_texture_normal_id', true ) ) ?: null ),
                                'aoMap'             => ( wp_get_attachment_url( get_post_meta( $texture_id, '_dprcu_texture_ao_id', true ) ) ?: null ),
                                'metalnessMap'      => ( wp_get_attachment_url( get_post_meta( $texture_id, '_dprcu_texture_metalness_id', true ) ) ?: null ),
                                'displacementMap'   => ( wp_get_attachment_url( get_post_meta( $texture_id, '_dprcu_texture_displacement_id', true ) ) ?: null ),
                                'roughness'         => floatval( get_post_meta( $texture_id, '_dprcu_texture_roughness', true ) ?? '0.9' ),
                                'metalness'         => floatval( get_post_meta( $texture_id, '_dprcu_texture_metalness', true ) ?? '0.1' ),
                                'displacementScale' => floatval( get_post_meta( $texture_id, '_dprcu_texture_disp_scale', true ) ?? '0.0' ),
                                'envIntensity'      => floatval( get_post_meta( $texture_id, '_dprcu_texture_env_intensity', true ) ?? '1.0' ),
                                'show'              => $show_meshes,
                                'hide'              => $hide_meshes,
                            ),
                        );
                        $texture_url = get_post_meta( $texture_id, '_dprcu_texture_url', true );
                        if ( $texture_url && $texture_post ) {
                            $texture_versions[$texture_url] = strtotime( $texture_post->post_modified );
                        }
                    }
                } elseif ( $has_geometry ) {
                    $valid_options[] = array(
                        'name'  => $opt_name,
                        'icon'  => $opt_icon,
                        'type'  => 'object',
                        'price' => $opt_price,
                        'value' => array(
                            'show'             => $show_meshes,
                            'hide'             => $hide_meshes,
                            'textureThumbnail' => plugin_dir_url( __FILE__ ) . 'assets/geometry-icon.png',
                        ),
                    );
                }
            }
            if ( !empty( $valid_options ) ) {
                $final_config_for_js[$step_id] = array(
                    'name'        => $step['name'] ?? 'Untitled Step',
                    'description' => $step['description'] ?? '',
                    'icon'        => $step['icon'] ?? '',
                    'parts'       => $step_targets,
                    'options'     => $valid_options,
                );
            }
        }
        return array($final_config_for_js, $texture_versions);
    }

    /**
     * Enqueues the front-end bundled JavaScript, styles, and passes data.
     *
     * Activates in both viewer-only mode (model URL only) and full customizer
     * mode (model URL + step config). Passes a `viewerOnly` boolean so the JS
     * bundle can skip UI rendering when no steps are configured.
     */
    function dprcu_enqueue_scripts(  $product_id = null  ) {
        static $enqueued = array();
        $cache_key = ( $product_id ? (int) $product_id : 0 );
        if ( isset( $enqueued[$cache_key] ) ) {
            return;
        }
        $enqueued[$cache_key] = true;
        if ( !$product_id ) {
            if ( !function_exists( 'is_product' ) || !is_product() ) {
                return;
            }
            global $post;
            $product_id = $post->ID;
        }
        $model_url = get_post_meta( $product_id, '_dprcu_3d_model_url', true );
        $viewer_only = get_post_meta( $product_id, '_dprcu_3d_viewer_only', true );
        // <fs_premium_only>
        $hotspot_enabled = get_option( 'dprcu_settings' )['hotspot_enabled'] ?? true;
        // </fs_premium_only>
        $config_json = get_post_meta( $product_id, '_dprcu_customizer_config', true );
        $is_viewer_only = '1' === $viewer_only && !empty( $model_url );
        $is_customizer = !empty( $config_json ) && !empty( $model_url );
        if ( !$is_viewer_only && !$is_customizer ) {
            return;
        }
        /**
         * Fires before scripts and styles are enqueued for the customizer.
         *
         * @since 3.4.0
         * @param int $product_id The current product ID.
         * @param bool $is_viewer_only Whether this is viewer-only mode.
         * @param bool $is_customizer Whether customizer is active.
         */
        do_action(
            'dprcu_before_enqueue_scripts',
            $product_id,
            $is_viewer_only,
            $is_customizer
        );
        wp_enqueue_style(
            'dprcu-customizer-style',
            plugin_dir_url( __FILE__ ) . 'style.css',
            array(),
            '1.0'
        );
        wp_enqueue_script(
            'dprcu-customizer-bundle',
            plugin_dir_url( __FILE__ ) . 'dist/customizer.bundle.js',
            array(),
            '1.0',
            true
        );
        list( $final_config_for_js, $texture_versions ) = ( $is_customizer ? dprcu_build_customizer_options( $config_json ) : array(array(), array()) );
        $global_settings = get_option( 'dprcu_settings' );
        $bg_colors_str = $global_settings['bg_colors'] ?? "#f0f0f0\n#ffffff\n#222222";
        $bg_colors = preg_split( '/\\r\\n|[\\r\\n]/', $bg_colors_str );
        $zoom_limits = $global_settings['zoom_limits'] ?? array(1, 10);
        $hdr_file = $global_settings['hdr_file'] ?? '';
        $cam_dist_mult = (float) ($global_settings['camera_distance_multiplier'] ?? 1.2);
        $logo_id = get_theme_mod( 'custom_logo' );
        $logo_url = ( $logo_id ? wp_get_attachment_image_url( $logo_id, 'full' ) : get_site_icon_url() );
        $camera_position_raw = get_post_meta( $product_id, '_dprcu_camera_position', true );
        $camera_position = ( $camera_position_raw ? json_decode( $camera_position_raw, true ) : null );
        $customizer_config = array(
            'productId'            => $product_id,
            'ajaxUrl'              => admin_url( 'admin-ajax.php' ),
            'modelUrl'             => esc_url( $model_url ),
            'viewerOnly'           => $is_viewer_only,
            'customizationOptions' => $final_config_for_js,
            'textureVersions'      => $texture_versions,
            'cameraPosition'       => $camera_position,
            'currency'             => ( function_exists( 'get_woocommerce_currency' ) ? get_woocommerce_currency() : 'USD' ),
            'currencySymbol'       => ( function_exists( 'get_woocommerce_currency_symbol' ) ? html_entity_decode( get_woocommerce_currency_symbol(), ENT_QUOTES, 'UTF-8' ) : '$' ),
            'currencyPosition'     => get_option( 'woocommerce_currency_pos', 'left' ),
            'siteLogoUrl'          => ( $logo_url ? esc_url( $logo_url ) : '' ),
            'settings'             => array(
                'hotspotEnabled'           => $hotspot_enabled,
                'hdrFile'                  => esc_url( $hdr_file ),
                'cameraDistanceMultiplier' => $cam_dist_mult,
                'bgColors'                 => array_values( array_filter( $bg_colors ) ),
                'zoomLimits'               => $zoom_limits,
            ),
            'i18n'                 => array(
                'copied'               => __( 'Copied!', '3d-product-customizer' ),
                'shareDefaultMsg'      => __( 'Check out my custom design!', '3d-product-customizer' ),
                'adding'               => __( 'Adding...', '3d-product-customizer' ),
                'added'                => __( 'Added!', '3d-product-customizer' ),
                'loadingTexture'       => __( 'Loading Texture...', '3d-product-customizer' ),
                'loadingModel'         => __( 'Loading 3D Model...', '3d-product-customizer' ),
                'generatingPdf'        => __( 'Generating PDF...', '3d-product-customizer' ),
                'productIdMissing'     => __( 'Product ID missing', '3d-product-customizer' ),
                'couldNotAddToCart'    => __( 'Could not add to cart.', '3d-product-customizer' ),
                'productConfiguration' => __( 'Product Configuration', '3d-product-customizer' ),
                'selectedOptions'      => __( 'Selected Options', '3d-product-customizer' ),
                'pluginPage'           => __( 'Plugin Page', '3d-product-customizer' ),
                'docs'                 => __( 'Docs', '3d-product-customizer' ),
                'demo'                 => __( 'Demo', '3d-product-customizer' ),
            ),
        );
        /**
         * Filters the customizer configuration data passed to JavaScript.
         *
         * @since 3.4.0
         * @param array $customizer_config The configuration array.
         * @param int $product_id The current product ID.
         * @param bool $is_viewer_only Whether this is viewer-only mode.
         * @return array The filtered configuration array.
         */
        $customizer_config = apply_filters(
            'dprcu_customizer_config',
            $customizer_config,
            $product_id,
            $is_viewer_only
        );
        wp_add_inline_script( 'dprcu-customizer-bundle', 'var customizerData = ' . wp_json_encode( $customizer_config, JSON_UNESCAPED_UNICODE ) . ';', 'before' );
        /**
         * Fires after scripts and styles are enqueued for the customizer.
         *
         * @since 3.4.0
         * @param int $product_id The current product ID.
         * @param bool $is_viewer_only Whether this is viewer-only mode.
         * @param bool $is_customizer Whether customizer is active.
         * @param array $customizer_config The customizer configuration array.
         */
        do_action(
            'dprcu_after_enqueue_scripts',
            $product_id,
            $is_viewer_only,
            $is_customizer,
            $customizer_config
        );
    }

    add_action( 'wp_enqueue_scripts', 'dprcu_enqueue_scripts' );
    function dprcu_admin_enqueue_scripts(  $hook  ) {
        global $post_type;
        if ( ('post.php' === $hook || 'post-new.php' === $hook) && 'dprcu_model_texture' === $post_type ) {
            wp_enqueue_media();
            wp_enqueue_style( 'wp-color-picker' );
            wp_enqueue_script(
                'dprcu-customizer-admin-textures',
                plugin_dir_url( __FILE__ ) . 'admin/js/cpt-textures.js',
                array('jquery', 'wp-color-picker'),
                '1.0',
                true
            );
            // Enqueue texture slider sync script
            wp_enqueue_script(
                'dprcu-texture-sliders',
                plugin_dir_url( __FILE__ ) . 'admin/js/texture-sliders.js',
                array('jquery'),
                '1.0',
                true
            );
            if ( dprcu_fs()?->can_use_premium_code__premium_only() ) {
                $post_id = get_the_ID();
                $roughness = get_post_meta( $post_id, '_dprcu_texture_roughness', true );
                $metalness = get_post_meta( $post_id, '_dprcu_texture_metalness', true );
                $env_intensity = get_post_meta( $post_id, '_dprcu_texture_env_intensity', true );
                $disp_scale = get_post_meta( $post_id, '_dprcu_texture_disp_scale', true );
                $roughness_id = get_post_meta( $post_id, '_dprcu_texture_roughness_id', true );
                $normal_id = get_post_meta( $post_id, '_dprcu_texture_normal_id', true );
                $ao_id = get_post_meta( $post_id, '_dprcu_texture_ao_id', true );
                $metalness_id = get_post_meta( $post_id, '_dprcu_texture_metalness_id', true );
                $displacement_id = get_post_meta( $post_id, '_dprcu_texture_displacement_id', true );
                $global_settings = get_option( 'dprcu_settings', array() );
                wp_enqueue_script(
                    'dprcu-material-preview',
                    plugin_dir_url( __FILE__ ) . 'dist/materialPreview.bundle.js',
                    array(),
                    ( filemtime( plugin_dir_path( __FILE__ ) . 'dist/materialPreview.bundle.js' ) ?: '1.0' ),
                    true
                );
                wp_localize_script( 'dprcu-material-preview', 'dprcu_material_preview', array(
                    'textureUrl'      => ( get_the_post_thumbnail_url( $post_id, 'full' ) ? get_the_post_thumbnail_url( $post_id, 'full' ) : '' ),
                    'color'           => ( get_post_meta( $post_id, '_dprcu_texture_color', true ) ? get_post_meta( $post_id, '_dprcu_texture_color', true ) : '' ),
                    'roughness'       => ( '' !== $roughness ? $roughness : '0.5' ),
                    'metalness'       => ( '' !== $metalness ? $metalness : '0.0' ),
                    'envIntensity'    => ( '' !== $env_intensity ? $env_intensity : '1.0' ),
                    'dispScale'       => ( '' !== $disp_scale ? $disp_scale : '0.0' ),
                    'roughnessMap'    => ( $roughness_id ? wp_get_attachment_url( $roughness_id ) : '' ),
                    'normalMap'       => ( $normal_id ? wp_get_attachment_url( $normal_id ) : '' ),
                    'aoMap'           => ( $ao_id ? wp_get_attachment_url( $ao_id ) : '' ),
                    'metalnessMap'    => ( $metalness_id ? wp_get_attachment_url( $metalness_id ) : '' ),
                    'displacementMap' => ( $displacement_id ? wp_get_attachment_url( $displacement_id ) : '' ),
                    'hdrFile'         => esc_url( $global_settings['hdr_file'] ?? '' ),
                ) );
            }
        }
        if ( ('post.php' === $hook || 'post-new.php' === $hook) && 'product' === $post_type ) {
            wp_enqueue_script(
                'dprcu-admin-model-scanner',
                plugin_dir_url( __FILE__ ) . 'dist/adminScanner.bundle.js',
                array('jquery'),
                '1.0',
                true
            );
        }
    }

    add_action( 'admin_enqueue_scripts', 'dprcu_admin_enqueue_scripts' );
    /**
     * Add custom configuration data to the cart item.
     *
     * This function fires when an item is added to the cart
     * and checks for our 'shoe_config' POST data.
     */
    function dprcu_add_cart_item_data(  $cart_item_data, $product_id, $variation_id  ) {
        /**
         * Fires before customizer configuration is added to the cart item.
         *
         * @since 3.4.0
         * @param array $cart_item_data The cart item data.
         * @param int $product_id The product ID.
         * @param int $variation_id The variation ID.
         */
        do_action(
            'dprcu_before_add_to_cart',
            $cart_item_data,
            $product_id,
            $variation_id
        );
        if ( isset( $_POST['shoe_config'] ) ) {
            // phpcs:ignore WordPress.Security.NonceVerification.Missing
            $config_string = sanitize_text_field( wp_unslash( $_POST['shoe_config'] ) );
            // phpcs:ignore WordPress.Security.NonceVerification.Missing
            $cart_item_data['_dprcu_custom_shoe_config'] = $config_string;
        }
        /**
         * Filters the cart item data before it's added to the cart.
         *
         * @since 3.4.0
         * @param array $cart_item_data The cart item data.
         * @param int $product_id The product ID.
         * @param int $variation_id The variation ID.
         * @return array The filtered cart item data.
         */
        $cart_item_data = apply_filters(
            'dprcu_cart_item_data',
            $cart_item_data,
            $product_id,
            $variation_id
        );
        return $cart_item_data;
    }

    add_filter(
        'woocommerce_add_cart_item_data',
        'dprcu_add_cart_item_data',
        10,
        3
    );
    /**
     * Apply option price surcharges to the cart item.
     *
     * Reads the stored shoe_config JSON, sums the per-option prices,
     * and adds them to the base product price.
     */
    function dprcu_apply_option_prices(  $cart_object  ) {
        if ( is_admin() && !defined( 'DOING_AJAX' ) ) {
            return;
        }
        foreach ( $cart_object->get_cart() as $cart_item ) {
            if ( empty( $cart_item['_dprcu_custom_shoe_config'] ) ) {
                continue;
            }
            $config = json_decode( $cart_item['_dprcu_custom_shoe_config'], true );
            if ( !is_array( $config ) ) {
                continue;
            }
            $surcharge = 0;
            foreach ( $config as $entry ) {
                $surcharge += floatval( $entry['price'] ?? 0 );
            }
            if ( $surcharge > 0 ) {
                $cart_item['data']->set_price( $cart_item['data']->get_price() + $surcharge );
            }
        }
    }

    add_action(
        'woocommerce_before_calculate_totals',
        'dprcu_apply_option_prices',
        10,
        1
    );
    /**
     * Display the custom configuration in the cart.
     *
     * This reads the 'shoe_config' JSON and formats it
     * as line items for the user to see.
     */
    function dprcu_display_cart_item_data(  $item_data, $cart_item  ) {
        /**
         * Fires before cart item data is displayed.
         *
         * @since 3.4.0
         * @param array $item_data The item data.
         * @param array $cart_item The cart item.
         */
        do_action( 'dprcu_before_display_cart_item_data', $item_data, $cart_item );
        if ( isset( $cart_item['_dprcu_custom_shoe_config'] ) ) {
            $config = json_decode( $cart_item['_dprcu_custom_shoe_config'], true );
            if ( is_array( $config ) ) {
                foreach ( $config as $item ) {
                    $item_data[] = array(
                        'key'     => sanitize_text_field( $item['part'] ),
                        'value'   => sanitize_text_field( $item['option'] ),
                        'display' => sanitize_text_field( $item['option'] ),
                    );
                }
            }
        }
        /**
         * Filters the cart item display data.
         *
         * @since 3.4.0
         * @param array $item_data The item data to display.
         * @param array $cart_item The cart item.
         * @return array The filtered item data.
         */
        $item_data = apply_filters( 'dprcu_cart_item_display_data', $item_data, $cart_item );
        return $item_data;
    }

    add_filter(
        'woocommerce_get_item_data',
        'dprcu_display_cart_item_data',
        10,
        2
    );
    /**
     * Save the custom configuration to the order line item.
     *
     * This runs when the order is created.
     */
    function dprcu_add_order_item_meta(
        $item,
        $cart_item_key,
        $values,
        $order
    ) {
        /**
         * Fires before customizer data is added to an order item.
         *
         * @since 3.4.0
         * @param WC_Order_Item $item The order item object.
         * @param string $cart_item_key The cart item key.
         * @param array $values The cart item values.
         * @param WC_Order $order The order object.
         */
        do_action(
            'dprcu_before_add_order_item_meta',
            $item,
            $cart_item_key,
            $values,
            $order
        );
        if ( isset( $values['_dprcu_custom_shoe_config'] ) ) {
            $config = json_decode( $values['_dprcu_custom_shoe_config'], true );
            if ( is_array( $config ) ) {
                foreach ( $config as $line ) {
                    $item->add_meta_data( sanitize_text_field( $line['part'] ), sanitize_text_field( $line['option'] ) );
                }
            }
        }
        /**
         * Fires after customizer data is added to an order item.
         *
         * @since 3.4.0
         * @param WC_Order_Item $item The order item object.
         * @param string $cart_item_key The cart item key.
         * @param array $values The cart item values.
         * @param WC_Order $order The order object.
         */
        do_action(
            'dprcu_after_add_order_item_meta',
            $item,
            $cart_item_key,
            $values,
            $order
        );
    }

    add_action(
        'woocommerce_checkout_create_order_line_item',
        'dprcu_add_order_item_meta',
        10,
        4
    );
    /**
     * Renders the watermark/branding HTML.
     */
    function dprcu_render_watermark() {
        ?>
		<div class="dprcu-watermark"><a class="drcpu-watermark-link" href="<?php 
        echo esc_url( 'https://deosebitsoft.ro/' );
        ?>"
				target="_blank" rel="noopener noreferrer"
				title="<?php 
        esc_attr_e( '3D Product Customizer & Configurator for WooCommerce', '3d-product-customizer' );
        ?>">Made
				by <img src="<?php 
        echo esc_url( plugins_url( 'assets/logo.webp', __FILE__ ) );
        ?>"
					alt="<?php 
        esc_attr_e( '3D Product Customizer & Configurator for WooCommerce', '3d-product-customizer' );
        ?>" /></a>
		</div><?php 
    }

    /**
     * Conditionally renders the watermark based on premium status and settings.
     */
    function dprcu_maybe_render_watermark() {
        dprcu_render_watermark();
    }

    /**
     * Renders the 3D viewer canvas in place of the WooCommerce product gallery.
     *
     * Hooked to woocommerce_before_single_product_summary at priority 20,
     * replacing the default gallery which was removed at the same priority.
     * The outer div uses WooCommerce's own gallery CSS classes so the active
     * theme's column layout (float/flex/grid) positions it correctly without
     * any extra CSS from us.
     */
    function dprcu_viewer_canvas_html(  $product_id = null  ) {
        if ( !$product_id ) {
            $product_id = get_queried_object_id();
        }
        /**
         * Fires before the viewer canvas is rendered (viewer-only mode).
         *
         * @since 3.4.0
         * @param int $product_id The current product ID.
         */
        do_action( 'dprcu_before_viewer_canvas', $product_id );
        $ar_display_mode = get_option( 'dprcu_settings' )['ar_display_mode'] ?? 'simple';
        ?>
		<div class="woocommerce-product-gallery woocommerce-product-gallery--with-images images psc-viewer-only">
			<div id="app-container">

				<div id="canvas-container">
					<canvas id="product-canvas"></canvas>
					<div id="customizer-loader">
						<div class="loader-spinner"></div>
						<span id="loader-text"><?php 
        esc_html_e( 'Loading 3D Model...', '3d-product-customizer' );
        ?></span>
					</div>
					<div id="view-controls">
						<button class="control-btn" id="btn-zoom-in"
							title="<?php 
        esc_attr_e( 'Zoom In', '3d-product-customizer' );
        ?>"><i
								data-lucide="zoom-in"></i></button>
						<button class="control-btn" id="btn-zoom-out"
							title="<?php 
        esc_attr_e( 'Zoom Out', '3d-product-customizer' );
        ?>"><i
								data-lucide="zoom-out"></i></button>
						<button class="control-btn" id="btn-reset"
							title="<?php 
        esc_attr_e( 'Reset View', '3d-product-customizer' );
        ?>"><i
								data-lucide="rotate-ccw"></i></button>
						<button class="control-btn" id="btn-fullscreen"
							title="<?php 
        esc_attr_e( 'Fullscreen', '3d-product-customizer' );
        ?>"><i
								data-lucide="maximize"></i></button>
						<button class="control-btn" id="btn-bg"
							title="<?php 
        esc_attr_e( 'Change Background', '3d-product-customizer' );
        ?>"><i
								data-lucide="image"></i></button>
					</div>
					<?php 
        if ( 'simple' === $ar_display_mode ) {
            dprcu_render_ar_button();
        }
        dprcu_maybe_render_watermark();
        ?>
				</div>
				<?php 
        if ( 'detailed' === $ar_display_mode ) {
            dprcu_render_ar_button( 'detailed' );
        }
        /**
         * Fires after the canvas container in viewer mode.
         *
         * @since 3.4.0
         * @param int $product_id The current product ID.
         */
        do_action( 'dprcu_after_viewer_canvas_container', $product_id );
        ?>
				<div id="sidebar" class="psc-hidden" aria-hidden="true">
					<div id="sidebar-content"></div>
					<div id="sidebar-action-bar">
						<div></div>
						<div class="add-to-cart-container"></div>
					</div>
				</div>
			</div>
		</div>
		<?php 
        /**
         * Fires after the viewer canvas is rendered (viewer-only mode).
         *
         * @since 3.4.0
         * @param int $product_id The current product ID.
         */
        do_action( 'dprcu_after_viewer_canvas', $product_id );
    }

    /**
     * Registers GLB/GLTF MIME types so they can be uploaded via the media library.
     *
     * @param array $mimes Allowed MIME types.
     * @return array
     */
    function dprcu_allow_mime_types(  $mimes  ) {
        $mimes['glb'] = 'model/gltf-binary';
        $mimes['gltf'] = 'model/gltf+json';
        return $mimes;
    }

    add_filter( 'upload_mimes', 'dprcu_allow_mime_types' );
    /**
     * Ensures WordPress correctly identifies GLB files during upload validation.
     *
     * @param array  $data     Filetype data.
     * @param string $file     Full path to the file.
     * @param string $filename The name of the file being checked.
     * @param array  $mimes    Allowed MIME types.
     * @return array
     */
    function dprcu_check_filetype_and_ext(
        $data,
        $_file,
        $filename,
        $_mimes
    ) {
        $ext = pathinfo( $filename, PATHINFO_EXTENSION );
        if ( 'glb' === $ext ) {
            $data['ext'] = 'glb';
            $data['type'] = 'model/gltf-binary';
        }
        return $data;
    }

    add_filter(
        'wp_check_filetype_and_ext',
        'dprcu_check_filetype_and_ext',
        10,
        4
    );
    /**
     * Handle review notice dismissal via GET param.
     */
    function dprcu_handle_review_dismiss() {
        if ( isset( $_GET['dprcu_review_dismiss'] ) && '1' === $_GET['dprcu_review_dismiss'] && isset( $_GET['_wpnonce'] ) && wp_verify_nonce( wp_unslash( $_GET['_wpnonce'] ), 'dprcu_review_dismiss' ) && current_user_can( 'manage_options' ) ) {
            update_option( 'dprcu_review_dismissed_at', time() );
            wp_safe_redirect( remove_query_arg( array('dprcu_review_dismiss', '_wpnonce') ) );
            exit;
        }
    }

    add_action( 'admin_init', 'dprcu_handle_review_dismiss' );
    /**
     * Handle support notice dismissal via GET param.
     */
    function dprcu_handle_support_dismiss() {
        if ( isset( $_GET['dprcu_support_dismiss'] ) && '1' === $_GET['dprcu_support_dismiss'] && isset( $_GET['_wpnonce'] ) && wp_verify_nonce( wp_unslash( $_GET['_wpnonce'] ), 'dprcu_support_dismiss' ) && current_user_can( 'manage_options' ) ) {
            update_option( 'dprcu_support_dismissed_at', time() );
            wp_safe_redirect( remove_query_arg( array('dprcu_support_dismiss', '_wpnonce') ) );
            exit;
        }
    }

    add_action( 'admin_init', 'dprcu_handle_support_dismiss' );
    /**
     * Show a review request notice on plugin admin pages every 7 days.
     */
    function dprcu_review_notice() {
        // Only on plugin-related pages
        $screen = get_current_screen();
        if ( $screen && strpos( $screen->id, 'dprcu' ) === false && 'plugins' !== $screen->id && 'dashboard' !== $screen->id ) {
            return;
        }
        if ( !current_user_can( 'manage_options' ) ) {
            return;
        }
        // Record activation time on first admin visit
        $activated_at = get_option( 'dprcu_activated_at' );
        if ( !$activated_at ) {
            update_option( 'dprcu_activated_at', time() );
            return;
        }
        // Wait at least 1 day after activation before asking
        if ( time() - $activated_at < DAY_IN_SECONDS ) {
            return;
        }
        // Respect 7-day dismissal
        $dismissed_at = get_option( 'dprcu_review_dismissed_at' );
        if ( $dismissed_at && time() - $dismissed_at < 7 * DAY_IN_SECONDS ) {
            return;
        }
        $review_url = 'https://wordpress.org/support/plugin/3d-product-customizer/reviews/#new-post';
        $dismiss_url = wp_nonce_url( add_query_arg( 'dprcu_review_dismiss', '1' ), 'dprcu_review_dismiss' );
        ?>
		<div class="notice notice-info">
			<p>
				<?php 
        echo wp_kses_post( __( 'Enjoying <strong>3D Product Customizer & Configurator for WooCommerce</strong>? Please take a moment to rate it on WordPress.org - your support helps us keep improving!', '3d-product-customizer' ) );
        ?>
			</p>
			<p>
				<a href="<?php 
        echo esc_url( $review_url );
        ?>" class="button button-primary" target="_blank"
					rel="noopener noreferrer">
					<?php 
        esc_html_e( 'Leave a Review', '3d-product-customizer' );
        ?>
				</a>
				<a href="<?php 
        echo esc_url( $dismiss_url );
        ?>" style="margin-left:10px;">
					<?php 
        esc_html_e( 'Remind me later', '3d-product-customizer' );
        ?>
				</a>
			</p>
		</div>
		<?php 
    }

    add_action( 'admin_notices', 'dprcu_review_notice' );
    function dprcu_support_notice() {
        $screen = get_current_screen();
        if ( $screen && strpos( $screen->id, 'dprcu' ) === false && 'plugins' !== $screen->id && 'dashboard' !== $screen->id ) {
            return;
        }
        if ( !current_user_can( 'manage_options' ) ) {
            return;
        }
        // Record activation time on first admin visit
        $activated_at = get_option( 'dprcu_activated_at' );
        if ( !$activated_at ) {
            update_option( 'dprcu_activated_at', time() );
            return;
        }
        // Wait at least 1 day after activation before asking
        if ( time() - $activated_at < DAY_IN_SECONDS ) {
            return;
        }
        //ask for contact/help every 2 days
        $dismissed_at = get_option( 'dprcu_support_dismissed_at' );
        if ( $dismissed_at && time() - $dismissed_at < 2 * DAY_IN_SECONDS ) {
            return;
        }
        $dismiss_url = wp_nonce_url( add_query_arg( 'dprcu_support_dismiss', '1' ), 'dprcu_support_dismiss' );
        ?>
		<div class="notice notice-warning">
			<p>
				<?php 
        echo wp_kses_post( __( 'Having trouble or need assistance with', '3d-product-customizer' ) );
        echo ' ';
        ?>
				<span style="font-weight: bold;">
					<?php 
        echo wp_kses_post( __( '3D Product Customizer & Configurator for WooCommerce', '3d-product-customizer' ) );
        ?>
				?</span>
			</p>
			<p>
				<?php 
        echo wp_kses_post( __( 'Visit our <a href="https://web-support.eu/documentation/" target="_blank" rel="noopener noreferrer">documentation</a> or <a href="https://web-support.eu/contact/" target="_blank" rel="noopener noreferrer">contact page</a>.', '3d-product-customizer' ) );
        ?>
				<a href="<?php 
        echo esc_url( $dismiss_url );
        ?>" style="margin-left:10px;">
					<?php 
        esc_html_e( 'Dismiss', '3d-product-customizer' );
        ?>
				</a>
			</p>
		</div>
		<?php 
    }

    add_action( 'admin_notices', 'dprcu_support_notice' );
    add_filter(
        'plugin_row_meta',
        function ( $links, $file ) {
            if ( false === strpos( $file, plugin_basename( __FILE__ ) ) ) {
                return $links;
            }
            $is_premium = dprcu_fs() && dprcu_fs()->can_use_premium_code__premium_only();
            $links[] = '<a href="https://web-support.eu/" target="_blank" rel="noopener noreferrer">' . esc_html__( 'Demo', '3d-product-customizer' ) . '</a>';
            $links[] = '<a href="https://web-support.eu/documentation/" target="_blank" rel="noopener noreferrer">' . esc_html__( 'Docs', '3d-product-customizer' ) . '</a>';
            if ( !$is_premium ) {
                $links[] = '<a href="' . esc_url( dprcu_checkout_url() ) . '" target="_blank" rel="noopener noreferrer" style="font-weight:700;color:#d54e21;">' . esc_html__( 'Buy Premium', '3d-product-customizer' ) . '</a>';
            }
            return $links;
        },
        20,
        2
    );
}
// end else (Freemius auto-deactivation guard)