<?php
namespace PowerpackElements\Modules\Woocommerce\Widgets;

use PowerpackElements\Base\Powerpack_Widget;
use PowerpackElements\Classes\PP_Helper;
use PowerpackElements\Classes\PP_Woo_Helper;

use Elementor\Controls_Manager;
use Elementor\Group_Control_Border;
use Elementor\Group_Control_Typography;
use Elementor\Group_Control_Image_Size;

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

/**
 * Woo - Product Images widget
 */
class Woo_Product_Images extends Powerpack_Widget {

	/**
	 * Settings used by the WooCommerce gallery size filters.
	 *
	 * Populated once per render so the filter callbacks, which WooCommerce fires
	 * three times for every gallery image, do not re-parse the widget settings.
	 *
	 * @since 3.0.0
	 * @access private
	 *
	 * @var array|null
	 */
	private $gallery_size_settings = null;

	/**
	 * Retrieve Woo - Product Images widget categories.
	 *
	 * @access public
	 *
	 * @return array Widget categories.
	 */
	public function get_categories() {
		return parent::get_woo_builder_categories();
	}

	/**
	 * Retrieve Woo - Product Images widget name.
	 *
	 * @access public
	 *
	 * @return string Widget name.
	 */
	public function get_name() {
		return parent::get_widget_name( 'Woo_Product_Images' );
	}

	/**
	 * Retrieve Woo - Product Images widget title.
	 *
	 * @access public
	 *
	 * @return string Widget title.
	 */
	public function get_title() {
		return parent::get_widget_title( 'Woo_Product_Images' );
	}

	/**
	 * Retrieve Woo - Product Images widget icon.
	 *
	 * @access public
	 *
	 * @return string Widget icon.
	 */
	public function get_icon() {
		return parent::get_widget_icon( 'Woo_Product_Images' );
	}

	/**
	 * Get widget keywords.
	 *
	 * Retrieve the list of keywords the Woo - Product Images widget belongs to.
	 *
	 * @since 1.4.13.4
	 * @access public
	 *
	 * @return array Widget keywords.
	 */
	public function get_keywords() {
		return parent::get_widget_keywords( 'Woo_Product_Images' );
	}

	/**
	 * Retrieve the list of scripts the Woo - Product Images depended on.
	 *
	 * Used to set script dependencies required to run the widget.
	 *
	 * @since 3.0.0
	 * @access public
	 *
	 * @return array Widget scripts dependencies.
	 */
	public function get_script_depends() {
		return [
			'jquery',
			'wc-single-product',
		];
	}

	/**
	 * Retrieve the list of styles the Woo - Product Images depended on.
	 *
	 * Used to set style dependencies required to run the widget.
	 *
	 * @access public
	 *
	 * @return array Widget styles dependencies.
	 */
	public function get_style_depends() {
		return [
			'widget-pp-woo-product-images',
		];
	}

	/**
	 * Whether the widget should be wrapped in Elementor's inner wrapper.
	 *
	 * @since 3.0.0
	 * @access public
	 *
	 * @return bool
	 */
	public function has_widget_inner_wrapper(): bool {
		return ! PP_Helper::is_feature_active( 'e_optimized_markup' );
	}

	/**
	 * Register Woo - Product Images widget controls.
	 *
	 * Adds different input fields to allow the user to change and customize the widget settings.
	 *
	 * @access protected
	 */
	protected function register_controls() {
		/* Content Tab */
		$this->register_content_images_controls();

		/* Style Tab */
		$this->register_style_images_controls();
		$this->register_style_thumbs_controls();
		$this->register_style_sale_flash_controls();
	}

	/**
	 * Register the content controls of the widget.
	 *
	 * @since 3.0.0
	 * @access protected
	 *
	 * @return void
	 */
	protected function register_content_images_controls() {

		$this->start_controls_section(
			'section_product_images_content',
			[
				'label' => esc_html__( 'Product Images', 'powerpack' ),
				'tab'   => Controls_Manager::TAB_CONTENT,
			]
		);

		$this->add_control(
			'sale_flash',
			[
				'label'        => esc_html__( 'Sale Flash', 'powerpack' ),
				'type'         => Controls_Manager::SWITCHER,
				'label_on'     => esc_html__( 'Show', 'powerpack' ),
				'label_off'    => esc_html__( 'Hide', 'powerpack' ),
				'render_type'  => 'template',
				'return_value' => 'yes',
				'default'      => 'yes',
			]
		);

		$this->add_group_control(
			Group_Control_Image_Size::get_type(),
			[
				'name'           => 'image',
				'default'        => 'full',
				'fields_options' => $this->get_image_size_fields_options(
					esc_html__( 'Image Size', 'powerpack' ),
					esc_html__( 'Image Dimension', 'powerpack' )
				),
				'separator'      => 'before',
			]
		);

		$this->add_group_control(
			Group_Control_Image_Size::get_type(),
			[
				'name'           => 'thumbnail',
				'default'        => 'thumbnail',
				'fields_options' => $this->get_image_size_fields_options(
					esc_html__( 'Thumbnail Size', 'powerpack' ),
					esc_html__( 'Thumbnail Dimension', 'powerpack' )
				),
				'separator'      => 'before',
			]
		);

		$this->add_group_control(
			Group_Control_Image_Size::get_type(),
			[
				'name'           => 'lightbox',
				'default'        => 'full',
				'fields_options' => $this->get_image_size_fields_options(
					esc_html__( 'Lightbox Image Size', 'powerpack' ),
					esc_html__( 'Lightbox Image Dimension', 'powerpack' )
				),
				'separator'      => 'before',
			]
		);

		$this->end_controls_section();
	}

	/**
	 * Build the fields_options for an Image Size group control.
	 *
	 * Elementor labels every Image Size control "Image Resolution" and every
	 * custom dimension field "Image Dimension", which is ambiguous in a widget
	 * that registers three of them. The group level `label` argument is not used
	 * by this group control, so the labels have to be set per field.
	 *
	 * The sizes are consumed by the WooCommerce gallery filters rather than by
	 * CSS, so the fields also have to force a re-render in the editor.
	 *
	 * @since 3.0.0
	 * @access private
	 *
	 * @param string $size_label      Label for the image size field.
	 * @param string $dimension_label Label for the custom dimension field.
	 * @return array
	 */
	private function get_image_size_fields_options( $size_label, $dimension_label ) {
		return [
			'__all'            => [
				'render_type' => 'template',
			],
			'size'             => [
				'label' => $size_label,
			],
			'custom_dimension' => [
				'label' => $dimension_label,
			],
		];
	}

	/**
	 * Register the images style controls of the widget.
	 *
	 * @since 3.0.0
	 * @access protected
	 *
	 * @return void
	 */
	protected function register_style_images_controls() {

		$this->start_controls_section(
			'section_product_gallery_style',
			[
				'label' => esc_html__( 'Images', 'powerpack' ),
				'tab'   => Controls_Manager::TAB_STYLE,
			]
		);

		$this->add_control(
			'wc_style_warning',
			[
				'type'            => Controls_Manager::RAW_HTML,
				'raw'             => esc_html__( 'The style of this widget is often affected by your theme and plugins. If you experience any such issue, try to switch to a basic theme and deactivate related plugins.', 'powerpack' ),
				'content_classes' => 'elementor-panel-alert elementor-panel-alert-info',
			]
		);

		$this->add_group_control(
			Group_Control_Border::get_type(),
			[
				'name'     => 'image_border',
				'selector' => '.woocommerce {{WRAPPER}} .woocommerce-product-gallery__trigger + .woocommerce-product-gallery__wrapper,
				.woocommerce {{WRAPPER}} .flex-viewport, .woocommerce {{WRAPPER}} .flex-control-thumbs img',
			]
		);

		$this->add_responsive_control(
			'image_border_radius',
			[
				'label'      => esc_html__( 'Border Radius', 'powerpack' ),
				'type'       => Controls_Manager::DIMENSIONS,
				'size_units' => [ 'px', '%', 'em', 'rem', 'custom' ],
				'selectors'  => [
					'.woocommerce {{WRAPPER}} .woocommerce-product-gallery__trigger + .woocommerce-product-gallery__wrapper,
					.woocommerce {{WRAPPER}} .flex-viewport' => 'border-radius: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}}',
				],
			]
		);

		$this->add_responsive_control(
			'spacing',
			[
				'label'      => esc_html__( 'Spacing', 'powerpack' ),
				'type'       => Controls_Manager::SLIDER,
				'size_units' => [ 'px', 'em', 'rem', 'custom' ],
				'selectors'  => [
					'.woocommerce {{WRAPPER}} .flex-viewport:not(:last-child)' => 'margin-bottom: {{SIZE}}{{UNIT}}',
				],
			]
		);

		$this->end_controls_section();
	}

	/**
	 * Register the thumbnails style controls of the widget.
	 *
	 * @since 3.0.0
	 * @access protected
	 *
	 * @return void
	 */
	protected function register_style_thumbs_controls() {

		$this->start_controls_section(
			'section_product_thumbs_style',
			[
				'label' => esc_html__( 'Thumbnails', 'powerpack' ),
				'tab'   => Controls_Manager::TAB_STYLE,
			]
		);

		$this->add_group_control(
			Group_Control_Border::get_type(),
			[
				'name'     => 'thumbs_border',
				'selector' => '.woocommerce {{WRAPPER}} .flex-control-thumbs img',
			]
		);

		$this->add_responsive_control(
			'thumbs_border_radius',
			[
				'label'      => esc_html__( 'Border Radius', 'powerpack' ),
				'type'       => Controls_Manager::DIMENSIONS,
				'size_units' => [ 'px', '%', 'em', 'rem', 'custom' ],
				'selectors'  => [
					'.woocommerce {{WRAPPER}} .flex-control-thumbs img' => 'border-radius: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}}',
				],
			]
		);

		$this->add_responsive_control(
			'spacing_thumbs',
			[
				'label'      => esc_html__( 'Spacing', 'powerpack' ),
				'type'       => Controls_Manager::SLIDER,
				'size_units' => [ 'px', 'em', 'rem', 'custom' ],
				'selectors'  => [
					'.woocommerce {{WRAPPER}} .flex-control-thumbs li' => 'padding-right: calc({{SIZE}}{{UNIT}} / 2); padding-left: calc({{SIZE}}{{UNIT}} / 2); padding-bottom: {{SIZE}}{{UNIT}}',
					'.woocommerce {{WRAPPER}} .flex-control-thumbs' => 'margin-right: calc(-{{SIZE}}{{UNIT}} / 2); margin-left: calc(-{{SIZE}}{{UNIT}} / 2)',
				],
			]
		);

		$this->end_controls_section();
	}

	/**
	 * Register the sale flash style controls of the widget.
	 *
	 * @since 3.0.0
	 * @access protected
	 *
	 * @return void
	 */
	protected function register_style_sale_flash_controls() {

		$this->start_controls_section(
			'section_sale_flash_style',
			[
				'label'     => esc_html__( 'Sale Flash', 'powerpack' ),
				'tab'       => Controls_Manager::TAB_STYLE,
				'condition' => [
					'sale_flash' => 'yes',
				],
			]
		);

		$this->add_responsive_control(
			'sale_margin',
			[
				'label'      => esc_html__( 'Margin', 'powerpack' ),
				'type'       => Controls_Manager::DIMENSIONS,
				'size_units' => [ 'px', '%', 'em', 'rem', 'vw', 'custom' ],
				'default'    => [
					'top'    => '10',
					'bottom' => '10',
					'left'   => '10',
					'right'  => '10',
					'unit'   => 'px',
				],
				'selectors'  => [
					'.woocommerce {{WRAPPER}} span.onsale' => 'margin: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};',
				],
			]
		);

		$this->add_responsive_control(
			'sale_padding',
			[
				'label'      => esc_html__( 'Padding', 'powerpack' ),
				'type'       => Controls_Manager::DIMENSIONS,
				'size_units' => [ 'px', '%', 'em', 'rem', 'vw', 'custom' ],
				'default'    => [
					'top'    => '0',
					'bottom' => '0',
					'left'   => '0',
					'right'  => '0',
					'unit'   => 'px',
				],
				'selectors'  => [
					'.woocommerce {{WRAPPER}} span.onsale' => 'padding: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};',
				],
			]
		);

		$this->add_control(
			'sale_badge_color',
			[
				'label'     => esc_html__( 'Color', 'powerpack' ),
				'type'      => Controls_Manager::COLOR,
				'default'   => '',
				'selectors' => [
					'.woocommerce {{WRAPPER}} span.onsale' => 'color: {{VALUE}}',
				],
			]
		);

		$this->add_control(
			'sale_badge_bg_color',
			[
				'label'     => esc_html__( 'Background Color', 'powerpack' ),
				'type'      => Controls_Manager::COLOR,
				'default'   => '',
				'selectors' => [
					'.woocommerce {{WRAPPER}} span.onsale' => 'background-color: {{VALUE}}',
				],
			]
		);

		$this->add_group_control(
			Group_Control_Typography::get_type(),
			[
				'name'     => 'sale_badge_typography',
				'label'    => esc_html__( 'Typography', 'powerpack' ),
				'selector' => '.woocommerce {{WRAPPER}} span.onsale',
			]
		);

		$this->add_responsive_control(
			'sale_badge_size',
			[
				'label'      => esc_html__( 'Size', 'powerpack' ),
				'type'       => Controls_Manager::SLIDER,
				'size_units' => [ 'px', 'em', 'rem', 'custom' ],
				'range'      => [
					'px' => [
						'min' => 20,
						'max' => 200,
					],
					'em' => [
						'min' => 1,
						'max' => 10,
					],
				],
				'default'    => [
					'size' => 2,
					'unit' => 'em',
				],
				'selectors'  => [
					'.woocommerce {{WRAPPER}} span.onsale' => 'min-height: {{SIZE}}{{UNIT}}; min-width: {{SIZE}}{{UNIT}}; line-height: {{SIZE}}{{UNIT}};',
				],
			]
		);

		$this->end_controls_section();
	}

	/**
	 * Get WooCommerce gallery image size
	 *
	 * @see woocommerce/includes/wc-template-functions.php
	 *
	 * @since 2.10.14
	 *
	 * @param string|array $size Size passed by WooCommerce.
	 * @return string|array Image size name, or an array of width and height.
	 */
	public function get_woo_gallery_image_size( $size ) {
		return $this->get_gallery_size( 'image', $size );
	}

	/**
	 * Get WooCommerce gallery thumbnail size
	 *
	 * @see woocommerce/includes/wc-template-functions.php
	 *
	 * @since 2.10.14
	 *
	 * @param string|array $size Size passed by WooCommerce.
	 * @return string|array Image size name, or an array of width and height.
	 */
	public function get_woo_gallery_thumbnail_size( $size ) {
		return $this->get_gallery_size( 'thumbnail', $size );
	}

	/**
	 * Get WooCommerce gallery lightbox size
	 *
	 * @see woocommerce/includes/wc-template-functions.php
	 *
	 * @since 2.10.14
	 *
	 * @param string|array $size Size passed by WooCommerce.
	 * @return string|array Image size name, or an array of width and height.
	 */
	public function get_woo_gallery_full_size( $size ) {
		return $this->get_gallery_size( 'lightbox', $size );
	}

	/**
	 * Resolve an Image Size group control to a value WooCommerce can consume.
	 *
	 * Returns the selected image 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 private
	 *
	 * @param string       $name    Image Size group control name.
	 * @param string|array $default Size to fall back to.
	 * @return string|array
	 */
	private function get_gallery_size( $name, $default ) {
		$settings = $this->get_gallery_size_settings();
		$size     = ! empty( $settings[ $name . '_size' ] ) ? $settings[ $name . '_size' ] : '';

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

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

		$dimension = ! empty( $settings[ $name . '_custom_dimension' ] ) ? $settings[ $name . '_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 ];
	}

	/**
	 * Get the settings used by the gallery size filters.
	 *
	 * @since 3.0.0
	 * @access private
	 *
	 * @return array
	 */
	private function get_gallery_size_settings() {
		if ( null === $this->gallery_size_settings ) {
			$this->gallery_size_settings = $this->get_settings_for_display();
		}

		return $this->gallery_size_settings;
	}

	/**
	 * Get the product to render the gallery for.
	 *
	 * Falls back to the most recent published product in the editor so the
	 * widget previews on layouts that are not a product page.
	 *
	 * @since 3.0.0
	 * @access protected
	 *
	 * @return \WC_Product|false
	 */
	protected function get_gallery_product() {
		$product = wc_get_product();

		if ( $product instanceof \WC_Product ) {
			return $product;
		}

		if ( ! PP_Helper::is_edit_mode() ) {
			return false;
		}

		$product_id = PP_Woo_Helper::get_instance()->woo_get_last_product_id();

		if ( ! $product_id ) {
			return false;
		}

		$product = wc_get_product( $product_id );

		return $product instanceof \WC_Product ? $product : false;
	}

	/**
	 * Enqueue the WooCommerce gallery assets when WooCommerce has not.
	 *
	 * WooCommerce only loads the gallery scripts on a product page served by a
	 * classic theme, so a builder template, a block theme or a library preview
	 * would otherwise render a gallery with no slider, zoom or lightbox.
	 *
	 * The handles are enqueued unconditionally because Elementor may already
	 * have enqueued `wc-single-product` from get_script_depends() before the
	 * widget renders. wp_enqueue_script() is a no-op for a handle that is
	 * already queued, and the theme support checks keep this in step with what
	 * WooCommerce itself would load.
	 *
	 * @since 3.0.0
	 * @access protected
	 *
	 * @return void
	 */
	protected function load_assets_dependencies() {
		if ( current_theme_supports( 'wc-product-gallery-zoom' ) ) {
			wp_enqueue_script( 'zoom' );
		}

		if ( current_theme_supports( 'wc-product-gallery-slider' ) ) {
			wp_enqueue_script( 'flexslider' );
		}

		if ( current_theme_supports( 'wc-product-gallery-lightbox' ) ) {
			wp_enqueue_script( 'photoswipe-ui-default' );
			wp_enqueue_style( 'photoswipe' );
			wp_enqueue_style( 'photoswipe-default-skin' );

			if ( false === has_action( 'wp_footer', 'woocommerce_photoswipe' ) ) {
				add_action( 'wp_footer', 'woocommerce_photoswipe' );
			}
		}

		wp_enqueue_script( 'wc-single-product' );
	}

	/**
	 * Render the sale flash and the product gallery.
	 *
	 * Shared by the editor and the frontend so both apply the same settings and
	 * emit the same markup.
	 *
	 * @since 3.0.0
	 * @access protected
	 *
	 * @param array $settings Widget settings.
	 * @return void
	 */
	protected function render_gallery( $settings ) {
		$this->load_assets_dependencies();

		$this->gallery_size_settings = $settings;

		add_filter( 'woocommerce_gallery_image_size', [ $this, 'get_woo_gallery_image_size' ] );
		add_filter( 'woocommerce_gallery_thumbnail_size', [ $this, 'get_woo_gallery_thumbnail_size' ] );
		add_filter( 'woocommerce_gallery_full_size', [ $this, 'get_woo_gallery_full_size' ] );

		if ( 'yes' === $settings['sale_flash'] ) {
			wc_get_template( 'loop/sale-flash.php' );
		}

		wc_get_template( 'single-product/product-image.php' );

		remove_filter( 'woocommerce_gallery_image_size', [ $this, 'get_woo_gallery_image_size' ] );
		remove_filter( 'woocommerce_gallery_thumbnail_size', [ $this, 'get_woo_gallery_thumbnail_size' ] );
		remove_filter( 'woocommerce_gallery_full_size', [ $this, 'get_woo_gallery_full_size' ] );

		$this->gallery_size_settings = null;
	}

	/**
	 * Initialise the WooCommerce gallery for this widget instance.
	 *
	 * WooCommerce binds the gallery on document ready, which has already fired
	 * by the time the editor re-renders a widget, so the init is triggered
	 * manually and scoped to this instance.
	 *
	 * @since 3.0.0
	 * @access protected
	 *
	 * @return void
	 */
	protected function render_gallery_init_script() {
		?>
		<script>
			( function( $ ) {
				if ( 'function' !== typeof $.fn.wc_product_gallery ) {
					return;
				}

				$( '.elementor-element-<?php echo esc_js( $this->get_id() ); ?> .woocommerce-product-gallery' ).each( function() {
					$( this ).wc_product_gallery();
				} );
			} )( jQuery );
		</script>
		<?php
	}

	/**
	 * Render Woo - Product Images widget output on the frontend.
	 *
	 * @access protected
	 *
	 * @return void
	 */
	protected function render() {
		$settings = $this->get_settings_for_display();

		do_action( 'pp_woo_builder_widget_before_render', $this );

		global $product;

		$product = $this->get_gallery_product();

		if ( $product instanceof \WC_Product ) {
			$this->render_gallery( $settings );

			// On render widget from Editor - trigger the init manually.
			if ( PP_Helper::is_edit_mode() ) {
				$this->render_gallery_init_script();
			}
		} elseif ( PP_Helper::is_edit_mode() ) {
			$this->render_editor_placeholder(
				[
					'body' => esc_html__( 'No product found to preview the gallery. Publish a product, or set a preview product in the template settings.', 'powerpack' ),
				]
			);
		}

		do_action( 'pp_woo_builder_widget_after_render', $this );
	}

	/**
	 * Render the widget plain content.
	 *
	 * Intentionally empty - the gallery is built from WooCommerce templates that
	 * must not run while Elementor saves the document.
	 *
	 * @since 3.0.0
	 * @access public
	 *
	 * @return void
	 */
	public function render_plain_content() {}
}
