<?php
/**
 * PowerPack WooCommerce Skin Single Product - Default.
 *
 * @package PowerPack
 */

namespace PowerpackElements\Modules\Woocommerce\Skins;

use Elementor\Skin_Base;

if ( ! defined( 'ABSPATH' ) ) {
	exit; // Exit if accessed directly.
}

/**
 * Class Single_Product_Base
 *
 * @property Products $parent
 */
abstract class Single_Product_Base extends Skin_Base {

	/**
	 * Path of the layout template for the current skin.
	 *
	 * get_id() returns skin-1 ... skin-4, which is exactly how the layout
	 * templates are named, so every skin resolves its own template without
	 * needing to override anything.
	 *
	 * @since 3.0.0
	 * @access protected
	 *
	 * @return string Absolute path to the layout template.
	 */
	protected function get_template_path() {
		return POWERPACK_ELEMENTS_PATH . 'modules/woocommerce/templates/single-product-' . $this->get_id() . '.php';
	}

	/**
	 * Resolve the Image Size group control to a value WooCommerce can consume.
	 *
	 * Returns the selected size name, or - when the "Custom" size is selected -
	 * an array of width and height. Either dimension may be 0, which WordPress
	 * treats as "match the original aspect ratio".
	 *
	 * @since 3.0.0
	 * @access protected
	 *
	 * @param array $settings Widget settings.
	 * @return string|array
	 */
	protected function get_image_size( $settings ) {
		$default = 'medium';
		$size    = ! empty( $settings['image_size_size'] ) ? $settings['image_size_size'] : '';

		if ( '' === $size ) {
			return $default;
		}

		if ( 'custom' !== $size ) {
			return $size;
		}

		$dimension = ! empty( $settings['image_size_custom_dimension'] ) ? $settings['image_size_custom_dimension'] : [];
		$width     = ! empty( $dimension['width'] ) ? absint( $dimension['width'] ) : 0;
		$height    = ! empty( $dimension['height'] ) ? absint( $dimension['height'] ) : 0;

		if ( ! $width && ! $height ) {
			return $default;
		}

		return [ $width, $height ];
	}

	/**
	 * Resolve the chosen product, or false when it must not be rendered.
	 *
	 * post_status matters as much as post_type here. The picker only ever
	 * offered published products, but unpublishing one is exactly how a store
	 * pulls it from sale, and wc_get_product() returns drafts, private and
	 * trashed products just the same - so the widget kept showing the title,
	 * price, description and a working add to cart button for a product that
	 * was meant to be gone. The pp_single_product_id filter can supply any ID
	 * at all, which is the other reason this cannot be trusted unchecked.
	 *
	 * @since 3.0.0
	 * @access protected
	 *
	 * @param array $settings Widget settings.
	 * @return \WC_Product|false
	 */
	protected function get_product( $settings ) {
		$product_id = $settings['product_id'];

		/**
		 * Filters the product the widget renders.
		 *
		 * @since 2.7.0
		 *
		 * @param string $product_id ID of the product chosen in the widget settings.
		 */
		$product_id = apply_filters( 'pp_single_product_id', $product_id );

		$product_post = $product_id ? get_post( absint( $product_id ) ) : null;

		if ( $product_post instanceof \WP_Post
			&& in_array( $product_post->post_type, [ 'product', 'product_variation' ], true )
			&& 'publish' === $product_post->post_status ) {
			return wc_get_product( $product_post );
		}

		return false;
	}

	/**
	 * Render woo default template.
	 *
	 * @since 2.7.0
	 * @access public
	 *
	 * @param \WC_Product|false|null $product Product to render. Resolved from the
	 *                                        widget settings when not supplied,
	 *                                        which fires pp_single_product_id a
	 *                                        second time - render() passes the
	 *                                        product it already resolved.
	 * @return void
	 */
	public function render_woo_single_product_template( $product = null ) {
		$settings = $this->parent->get_settings_for_display();

		global $post;

		if ( null === $product ) {
			$product = $this->get_product( $settings );
		}

		if ( ! $product ) {
			return;
		}

		/*
		 * wc_get_product() overwrites the global, and nothing here used to put
		 * it back. Anything rendered afterwards that reads global $product
		 * without setting it first - another PP Woo widget, a wc_get_template()
		 * call, a theme hook - then saw this widget's product and showed its
		 * price and stock in place of its own.
		 */
		$previous_product   = $GLOBALS['product'] ?? null;
		$GLOBALS['product'] = $product;

		// The layout templates and partials read all four of these.
		$product_id   = $product->get_id();
		$product_data = $product->get_data();
		$image_size   = $this->get_image_size( $settings );

		$post = get_post( $product_id, OBJECT );
		setup_postdata( $post );

		/**
		 * Fires before the single product markup is rendered.
		 *
		 * @since 2.7.0
		 */
		do_action( 'pp_before_product' );

		include $this->get_template_path();

		/**
		 * Fires after the single product markup is rendered.
		 *
		 * @since 2.7.0
		 */
		do_action( 'pp_after_product' );
		wp_reset_postdata();

		$GLOBALS['product'] = $previous_product;
	}

	/**
	 * Render wrapper start.
	 *
	 * @since 2.7.0
	 * @access public
	 *
	 * @return void
	 */
	public function render_wrapper_start() {
		$this->parent->add_render_attribute(
			'wrapper',
			[
				'class' => [
					'woocommerce',
					'pp-woocommerce',
					'pp-single-product',
					'pp-single-product-' . $this->get_id(),
				],
			]
		);

		?>
		<div <?php $this->parent->print_render_attribute_string( 'wrapper' ); ?>>
		<div class="summary entry-summary">
		<?php
	}

	/**
	 * Whether anything inside the product content wrapper will render.
	 *
	 * Every element in .product-content is behind its own switcher, so with
	 * all of them off the wrapper was still emitted - an empty box carrying
	 * the widget's content padding and, on skins 1 and 2, a 70% width. Used
	 * to skip the element rather than emit it empty.
	 *
	 * @since 3.0.0
	 * @access protected
	 *
	 * @param array $settings Widget settings.
	 * @return bool
	 */
	protected function has_product_content( $settings ) {
		$toggles = [
			'product_title',
			'product_rating',
			'product_price',
			'product_short_description',
			'show_sku',
			'show_taxonomy',
		];

		foreach ( $toggles as $toggle ) {
			if ( 'yes' === $settings[ $toggle ] ) {
				return true;
			}
		}

		return in_array( $settings['button_type'], [ 'cart', 'custom' ], true );
	}

	/**
	 * Render wrapper end.
	 *
	 * @since 2.7.0
	 * @access public
	 *
	 * @return void
	 */
	public function render_wrapper_end() {
		echo '</div></div>';
	}

	/**
	 * Render Content.
	 *
	 * @since 2.7.0
	 * @access public
	 *
	 * @return void
	 */
	public function render() {
		$settings = $this->parent->get_settings_for_display();

		if ( ! $settings['product_id'] ) {
			$placeholder = sprintf(
				/* translators: %s: widget title */
				esc_html__( 'Click here to edit the "%s" settings and choose a product from the dropdown list.', 'powerpack' ),
				esc_html( $this->parent->get_title() )
			);

			$this->parent->render_editor_placeholder(
				[
					'title' => esc_html( $this->parent->get_title() ),
					'body'  => $placeholder,
				]
			);

			/*
			 * Without this the wrappers were emitted anyway: the placeholder
			 * suppresses itself outside the editor, so a widget with no product
			 * chosen left a pair of empty divs on the front end - still laid
			 * out, still padded, showing as an unexplained gap.
			 */
			return;
		}

		$product = $this->get_product( $settings );

		if ( ! $product ) {
			/*
			 * Chosen product is gone, unpublished or trashed. Emitting the
			 * wrappers regardless left an empty box on the front end, still
			 * laid out and still padded - and the status check above makes that
			 * reachable simply by unpublishing a product.
			 */
			$this->parent->render_editor_placeholder(
				[
					'title' => esc_html( $this->parent->get_title() ),
					'body'  => esc_html__( 'The selected product is no longer available. Choose another product in the widget settings.', 'powerpack' ),
				]
			);

			return;
		}

		$this->render_wrapper_start();
			$this->render_woo_single_product_template( $product );
		$this->render_wrapper_end();
	}
}
