<?php

namespace WarrantyCart\Frontend;

use WarrantyCart\Core\Repository;
use WC_Product;

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Frontend Shortcode Class
 */
class Shortcode {

	/**
	 * The shortcode tag.
	 *
	 * Named once and reused. `has_shortcode()` calls `shortcode_exists()`
	 * first — a plain array lookup — so a mis-cased tag matches nothing
	 * silently, which is how this plugin's frontend assets once failed to load
	 * on the very page using the shortcode.
	 */
	public const TAG = 'warranty_cart';

	/**
	 * Where the chosen display page is stored.
	 */
	public const PAGE_OPTION = 'warranty_cart_page';

	/**
	 * Constructor
	 */
	public function __construct() {
		add_shortcode( self::TAG, array( $this, 'render_shortcode' ) );

		/*
		 * Priority 20, after wpautop.
		 *
		 * The selector is a block of markup with its own layout; running it
		 * through the paragraph filter wraps stray text nodes in <p> tags and
		 * breaks the grid. Appending afterwards hands the theme finished HTML.
		 */
		add_filter( 'the_content', array( $this, 'maybe_append_to_page' ), 20 );
	}

	/**
	 * The page an administrator picked to show the selector on.
	 *
	 * Validated on every read rather than trusted: a stored ID outlives the
	 * page it points at. Somebody trashing the page would otherwise leave the
	 * setup checklist reporting a configured storefront and the frontend
	 * assets enqueueing on a 404.
	 *
	 * @return int Page ID, or 0 when unset or no longer usable.
	 */
	public static function display_page_id(): int {
		$page_id = (int) get_option( self::PAGE_OPTION, 0 );

		if ( $page_id <= 0 || 'publish' !== get_post_status( $page_id ) ) {
			return 0;
		}

		/**
		 * Filter the page the warranty selector is displayed on.
		 *
		 * @param int $page_id Page ID.
		 */
		return (int) apply_filters( 'warranty_cart_display_page_id', $page_id );
	}

	/**
	 * Render the selector on the chosen page without a pasted shortcode.
	 *
	 * Selecting a page in the settings is the supported way to publish the
	 * selector; typing `[warranty_cart]` into a page still works, and a page
	 * that does both must not show it twice — hence the `has_shortcode()`
	 * bail rather than an unconditional append.
	 *
	 * @param string $content Post content.
	 *
	 * @return string
	 */
	public function maybe_append_to_page( string $content ): string {
		/*
		 * `the_content` runs for excerpts, feeds, SEO description generators
		 * and any plugin that formats a snippet. Without the loop and
		 * main-query guards the whole selector ends up in a meta description.
		 */
		if ( ! is_singular() || ! in_the_loop() || ! is_main_query() ) {
			return $content;
		}

		$page_id = self::display_page_id();

		if ( 0 === $page_id || get_the_ID() !== $page_id ) {
			return $content;
		}

		/*
		 * The *raw* post content, not the string passed in.
		 *
		 * `do_shortcode()` runs on `the_content` at priority 11, so by the time
		 * this filter fires the shortcode has already been replaced by its
		 * output and `has_shortcode( $content )` is false. Checking the
		 * argument therefore never detected a pasted shortcode, and a page with
		 * one rendered the selector twice — which is exactly what the page this
		 * plugin creates for you looks like.
		 */
		$raw = get_post_field( 'post_content', $page_id );

		if ( is_string( $raw ) && has_shortcode( $raw, self::TAG ) ) {
			return $content;
		}

		return $content . $this->render_shortcode();
	}

	/**
	 * Render the warranty cart shortcode
	 *
	 * @param array<string, mixed> $atts Shortcode attributes.
	 *
	 * @return string
	 */
	public function render_shortcode( array $atts = array() ): string {
		/**
		 * Filter shortcode attributes
		 *
		 * @param array $atts Shortcode attributes.
		 */
		$atts = apply_filters( 'warranty_cart_shortcode_atts', $atts );

		// The stored layout is the default; a `layout` attribute on an
		// individual shortcode still overrides it.
		$layout = \get_option( 'warranty_cart_selector_layout', 'grid' );

		$atts = shortcode_atts(
			array(
				'layout' => in_array( $layout, array( 'grid', 'list' ), true ) ? $layout : 'grid',
			),
			$atts
		);

		// Get warranty products
		$products = $this->get_warranty_products();

		if ( empty( $products ) ) {
			/**
			 * Filter empty products message
			 *
			 * @param string $message Empty products message.
			 */
			$message = apply_filters( 'warranty_cart_no_products_message', '<p>' . esc_html__( 'No warranty products found.', 'warranty-cart' ) . '</p>' );

			/*
			 * In the selector's own container, like every other state.
			 *
			 * This returned a bare paragraph, so a store with no warranty
			 * products published yet — which is every store on the day it
			 * installs the plugin — got unstyled body text dropped into the
			 * page with none of the plugin's spacing or typography around it.
			 */
			return sprintf(
				'<div class="warranty-selector warranty-selector--empty">%s</div>',
				$message
			);
		}

		$output = $this->render_product_selector( $products, $atts['layout'] );

		/**
		 * Filter shortcode output
		 *
		 * @param string $output   Shortcode output.
		 * @param array  $products Warranty products.
		 * @param array  $atts     Shortcode attributes.
		 */
		return apply_filters( 'warranty_cart_shortcode_output', $output, $products, $atts );
	}

	/**
	 * Get warranty products
	 *
	 * Retrieves enabled warranty products from the database.
	 *
	 * @return array Array of warranty product objects.
	 */
	private function get_warranty_products(): array {
		/**
		 * Filter warranty products query arguments
		 *
		 * @param array $args Query arguments.
		 */
		$args = apply_filters(
			'warranty_cart_shortcode_products_query',
			array(
				'limit'      => - 1,
				'meta_query' => array(
					array(
						'key'     => '_warranty_cart_product_status',
						'value'   => 'enabled',
						'compare' => '=',
					),
					array(
						'key'     => '_warranty_cart_product_type',
						'value'   => array( 'main_warranty', 'additional_service' ),
						'compare' => 'IN',
					),
				),
			)
		);

		$products = warranty_cart()->repository->get_warranty_products( $args );

		/**
		 * Filter warranty products for shortcode
		 *
		 * @param array $products Warranty products.
		 * @param array $args     Query arguments.
		 */
		return apply_filters( 'warranty_cart_shortcode_products', $products, $args );
	}

	/**
	 * A warranty product's type, for bucketing into warranties and services.
	 *
	 * Reading only one of the two type keys made the selector render its empty
	 * frame for products created through the admin. That is now decided in one
	 * place — see `Repository::product_type()` — and this stays as the
	 * name the rest of the class already calls.
	 *
	 * @param mixed $product A product, or whatever the query returned.
	 *
	 * @return string The type, or an empty string.
	 */
	private static function product_type( $product ): string {
		return Repository::product_type( $product );
	}

	/**
	 * Render product selector UI
	 *
	 * Generates the HTML for the warranty product selector interface.
	 *
	 * @param array  $products Products to display.
	 * @param string $layout   Layout type (grid or list).
	 *
	 * @return string HTML content for the product selector.
	 */
	private function render_product_selector( array $products, string $layout ): string {
		$main_warranties = array_filter(
			$products,
			static function ( $product ) {
				return 'main_warranty' === self::product_type( $product );
			}
		);

		$services = array_filter(
			$products,
			static function ( $product ) {
				return 'additional_service' === self::product_type( $product );
			}
		);

		return wc_get_template_html(
			'frontend/warranty-selector.php',
			array(
				'main_warranties' => $main_warranties,
				'services'        => $services,
				'layout'          => $layout,
			),
			'warranty-cart',
			WARRANTY_CART_PATH . 'templates/'
		);
	}
}
