<?php
/**
 * REST API: admin builder live-preview endpoints.
 *
 * @package PRECO
 */

namespace PRECO\Includes\Admin\Rest;

use PRECO\Includes\Frontend\Core\PreviewMode;
use PRECO\Includes\Frontend\Core\PreviewRenderer;
use PRECO\Includes\Frontend\Core\RecommendationsEngine;
use PRECO\Includes\Frontend\Core\RecommendationsRenderer;
use WP_REST_Request;
use WP_REST_Response;
use WP_REST_Server;

defined( 'ABSPATH' ) || exit;

/**
 * Backs the admin builder's live iframe preview: draft snapshot storage,
 * on-demand fragment rendering, and the cart/order helpers needed so Cart,
 * Checkout, and Thank You never render blank while editing.
 */
class PreviewRest {

	/**
	 * REST namespace.
	 */
	const PRECO_REST_NAMESPACE = 'preco/v1';

	/**
	 * Constructor.
	 */
	public function __construct() {
		add_action( 'rest_api_init', array( $this, 'register_routes' ) );
	}

	/**
	 * Register routes.
	 *
	 * @return void
	 */
	public function register_routes() {
		register_rest_route(
			self::PRECO_REST_NAMESPACE,
			'/preview/draft',
			array(
				array(
					'methods'             => WP_REST_Server::CREATABLE,
					'callback'            => array( $this, 'save_draft' ),
					'permission_callback' => array( $this, 'permission_check' ),
					'args'                => array(
						'recommendations' => array( 'required' => true ),
					),
				),
				array(
					'methods'             => WP_REST_Server::DELETABLE,
					'callback'            => array( $this, 'clear_draft' ),
					'permission_callback' => array( $this, 'permission_check' ),
				),
			)
		);

		register_rest_route(
			self::PRECO_REST_NAMESPACE,
			'/preview/render',
			array(
				'methods'             => WP_REST_Server::CREATABLE,
				'callback'            => array( $this, 'render_locations' ),
				'permission_callback' => array( $this, 'permission_check' ),
				'args'                => array(
					'page_key'      => array( 'required' => true ),
					'location_keys' => array( 'required' => true ),
				),
			)
		);

		register_rest_route(
			self::PRECO_REST_NAMESPACE,
			'/preview/cart-item',
			array(
				array(
					'methods'             => WP_REST_Server::CREATABLE,
					'callback'            => array( $this, 'ensure_cart_item' ),
					'permission_callback' => array( $this, 'permission_check' ),
				),
				array(
					'methods'             => WP_REST_Server::DELETABLE,
					'callback'            => array( $this, 'remove_cart_item' ),
					'permission_callback' => array( $this, 'permission_check' ),
				),
			)
		);

		register_rest_route(
			self::PRECO_REST_NAMESPACE,
			'/preview/order',
			array(
				'methods'             => WP_REST_Server::CREATABLE,
				'callback'            => array( $this, 'ensure_order' ),
				'permission_callback' => array( $this, 'permission_check' ),
			)
		);

		register_rest_route(
			self::PRECO_REST_NAMESPACE,
			'/preview/sample-product',
			array(
				'methods'             => WP_REST_Server::READABLE,
				'callback'            => array( $this, 'get_sample_product' ),
				'permission_callback' => array( $this, 'permission_check' ),
			)
		);

		register_rest_route(
			self::PRECO_REST_NAMESPACE,
			'/preview/sample-category',
			array(
				'methods'             => WP_REST_Server::READABLE,
				'callback'            => array( $this, 'get_sample_category' ),
				'permission_callback' => array( $this, 'permission_check' ),
			)
		);
	}

	/**
	 * Permission check — same capability gate as the other admin REST controllers.
	 *
	 * @return bool
	 */
	public function permission_check() {
		return current_user_can( 'manage_woocommerce' ) || current_user_can( 'manage_options' );
	}

	/**
	 * Verify the wp_rest nonce, matching the other admin REST controllers.
	 *
	 * @param WP_REST_Request $request Request.
	 * @return bool
	 */
	private function verify_nonce( WP_REST_Request $request ) {
		return (bool) wp_verify_nonce( $request->get_header( 'X-WP-Nonce' ), 'wp_rest' );
	}

	/**
	 * Persist the current user's unsaved builder state for use by the live
	 * preview iframe, in place of the saved wp_options snapshot.
	 *
	 * @param WP_REST_Request $request Request.
	 * @return WP_REST_Response
	 */
	public function save_draft( WP_REST_Request $request ) {
		if ( ! $this->verify_nonce( $request ) ) {
			return new WP_REST_Response( array( 'message' => 'Invalid nonce.' ), 403 );
		}

		$recommendations = $request->get_param( 'recommendations' );
		if ( ! is_array( $recommendations ) ) {
			return new WP_REST_Response( array( 'message' => 'Invalid recommendations payload.' ), 400 );
		}

		PreviewMode::save_draft_snapshot( RestSanitizer::sanitize_recursive( $recommendations ) );

		return new WP_REST_Response( array( 'saved' => true ), 200 );
	}

	/**
	 * Clear the current user's draft snapshot.
	 *
	 * @param WP_REST_Request $request Request.
	 * @return WP_REST_Response
	 */
	public function clear_draft( WP_REST_Request $request ) {
		if ( ! $this->verify_nonce( $request ) ) {
			return new WP_REST_Response( array( 'message' => 'Invalid nonce.' ), 403 );
		}

		PreviewMode::clear_draft_snapshot();

		return new WP_REST_Response( array( 'cleared' => true ), 200 );
	}

	/**
	 * Render one or more locations on a page using the current draft +
	 * sample products, for the builder to patch straight into the live
	 * iframe without a full page reload.
	 *
	 * @param WP_REST_Request $request Request.
	 * @return WP_REST_Response
	 */
	public function render_locations( WP_REST_Request $request ) {
		if ( ! $this->verify_nonce( $request ) ) {
			return new WP_REST_Response( array( 'message' => 'Invalid nonce.' ), 403 );
		}

		$page_key      = (string) $request->get_param( 'page_key' );
		$location_keys = $request->get_param( 'location_keys' );
		if ( '' === $page_key || ! is_array( $location_keys ) || empty( $location_keys ) ) {
			return new WP_REST_Response( array( 'message' => 'Invalid page_key/location_keys.' ), 400 );
		}

		$context          = $this->build_preview_context( $page_key, $request );
		$preview_renderer = new PreviewRenderer( new RecommendationsRenderer(), new RecommendationsEngine() );

		$html = array();
		foreach ( $location_keys as $location_key ) {
			$location_key = sanitize_key( (string) $location_key );
			if ( '' === $location_key ) {
				continue;
			}
			ob_start();
			$preview_renderer->render_location( $page_key, $location_key, $context );
			$html[ $location_key ] = ob_get_clean();
		}

		return new WP_REST_Response( array( 'html' => $html ), 200 );
	}

	/**
	 * Build the rule-evaluation context for a fast-patch preview render.
	 *
	 * Unlike a full iframe (re)load — a real page request where
	 * get_the_ID()/WC()->cart/get_queried_object() resolve naturally against
	 * the actual URL — this endpoint is called out of band on every debounced
	 * edit (no main query for a page URL ever runs here), so the identifying
	 * subject (which product/category/order the iframe currently shows) has
	 * to be supplied by the client. The client already resolved it once via
	 * getPreviewSampleProduct()/getPreviewSampleCategory()/ensurePreviewOrder()
	 * before navigating the iframe there in the first place — see the
	 * `resolveUrl` implementations under locations/*\/index.js and
	 * LivePreviewFrame's `previewSubjectRef`.
	 *
	 * @param string          $page_key Page key.
	 * @param WP_REST_Request $request  Request.
	 * @return array
	 */
	private function build_preview_context( $page_key, WP_REST_Request $request ) {
		$context = array( 'page' => $page_key );

		switch ( $page_key ) {
			case 'product-page':
				$product_id = absint( $request->get_param( 'product_id' ) );
				if ( $product_id ) {
					$context['product_id'] = $product_id;
				}
				break;

			case 'product-archive':
				$taxonomy  = sanitize_key( (string) $request->get_param( 'taxonomy' ) );
				$term_slug = sanitize_title( (string) $request->get_param( 'term_slug' ) );
				if ( $taxonomy && $term_slug ) {
					$context['queried_term_taxonomy'] = $taxonomy;
					$context['queried_term_slug']     = $term_slug;
				}
				break;

			case 'cart':
			case 'checkout':
				$context['exclude_ids'] = $this->cart_product_ids();
				break;

			case 'thank-you':
				$order_id               = absint( $request->get_param( 'order_id' ) );
				$context['order_id']    = $order_id;
				$context['exclude_ids'] = $this->order_product_ids( $order_id );
				break;
		}

		return $context;
	}

	/**
	 * Product IDs currently in the session cart, mirroring
	 * CartPageRecommendations/CheckoutPageRecommendations::build_context().
	 * WC()->cart resolves correctly here (unlike get_the_ID()/is_product()
	 * etc.) since the WooCommerce session hydrates from the request cookie
	 * regardless of whether this is a REST call or a real page load.
	 *
	 * @return int[]
	 */
	private function cart_product_ids() {
		$ids = array();
		if ( function_exists( 'WC' ) && WC() && WC()->cart && method_exists( WC()->cart, 'get_cart' ) ) {
			foreach ( WC()->cart->get_cart() as $item ) {
				if ( isset( $item['product_id'] ) ) {
					$ids[] = absint( $item['product_id'] );
				}
			}
		}
		return array_values( array_unique( array_filter( $ids ) ) );
	}

	/**
	 * Product IDs on an order's line items, mirroring
	 * OrderPageRecommendations::build_context()'s exclude_ids.
	 *
	 * @param int $order_id Order id.
	 * @return int[]
	 */
	private function order_product_ids( $order_id ) {
		if ( ! $order_id || ! function_exists( 'wc_get_order' ) ) {
			return array();
		}
		$order = wc_get_order( $order_id );
		if ( ! $order || ! method_exists( $order, 'get_items' ) ) {
			return array();
		}
		$ids = array();
		foreach ( $order->get_items() as $item ) {
			if ( $item && method_exists( $item, 'get_product_id' ) ) {
				$ids[] = absint( $item->get_product_id() );
			}
		}
		return array_values( array_unique( array_filter( $ids ) ) );
	}

	/**
	 * Ensure the current session's cart has at least one item, so /cart/ and
	 * /checkout/ render normally (WooCommerce redirects /checkout/ away
	 * entirely when the cart is empty) instead of showing their empty state.
	 *
	 * @param WP_REST_Request $request Request.
	 * @return WP_REST_Response
	 */
	public function ensure_cart_item( WP_REST_Request $request ) {
		if ( ! $this->verify_nonce( $request ) ) {
			return new WP_REST_Response( array( 'message' => 'Invalid nonce.' ), 403 );
		}
		if ( ! function_exists( 'WC' ) ) {
			return new WP_REST_Response(
				array(
					'added'  => false,
					'reason' => 'no_woocommerce',
				),
				200
			);
		}

		if ( null === WC()->cart ) {
			wc_load_cart();
		}

		if ( WC()->cart && ! WC()->cart->is_empty() ) {
			return new WP_REST_Response(
				array(
					'added'  => false,
					'reason' => 'cart_not_empty',
				),
				200
			);
		}

		$sample_ids = PreviewMode::get_sample_product_ids( 1 );
		if ( empty( $sample_ids ) ) {
			return new WP_REST_Response(
				array(
					'added'  => false,
					'reason' => 'no_products',
				),
				200
			);
		}

		$cart_item_key = WC()->cart->add_to_cart( $sample_ids[0], 1, 0, array(), array( PreviewMode::CART_ITEM_FLAG => true ) );

		return new WP_REST_Response( array( 'added' => (bool) $cart_item_key ), 200 );
	}

	/**
	 * Remove any cart items tagged as preview placeholders.
	 *
	 * @param WP_REST_Request $request Request.
	 * @return WP_REST_Response
	 */
	public function remove_cart_item( WP_REST_Request $request ) {
		if ( ! $this->verify_nonce( $request ) ) {
			return new WP_REST_Response( array( 'message' => 'Invalid nonce.' ), 403 );
		}
		if ( ! function_exists( 'WC' ) || null === WC()->cart ) {
			return new WP_REST_Response( array( 'removed' => 0 ), 200 );
		}

		$removed = 0;
		foreach ( WC()->cart->get_cart() as $cart_item_key => $cart_item ) {
			if ( ! empty( $cart_item[ PreviewMode::CART_ITEM_FLAG ] ) ) {
				WC()->cart->remove_cart_item( $cart_item_key );
				++$removed;
			}
		}

		return new WP_REST_Response( array( 'removed' => $removed ), 200 );
	}

	/**
	 * The current user's most recent order's Thank You URL, so that page can
	 * be previewed without requiring a fresh checkout. Falls back to creating
	 * a minimal sample order (see PreviewMode::create_sample_order()) when
	 * the current user has none yet.
	 *
	 * @param WP_REST_Request $request Request.
	 * @return WP_REST_Response
	 */
	public function ensure_order( WP_REST_Request $request ) {
		if ( ! $this->verify_nonce( $request ) ) {
			return new WP_REST_Response( array( 'message' => 'Invalid nonce.' ), 403 );
		}
		if ( ! function_exists( 'wc_get_orders' ) ) {
			return new WP_REST_Response( array( 'found' => false ), 200 );
		}

		$orders = wc_get_orders(
			array(
				'customer_id' => get_current_user_id(),
				'limit'       => 1,
				'orderby'     => 'date',
				'order'       => 'DESC',
			)
		);

		$order = ( ! empty( $orders ) && is_a( $orders[0], 'WC_Order' ) ) ? $orders[0] : PreviewMode::create_sample_order();

		if ( ! $order ) {
			return new WP_REST_Response( array( 'found' => false ), 200 );
		}

		return new WP_REST_Response(
			array(
				'found'    => true,
				'url'      => $order->get_checkout_order_received_url(),
				'order_id' => $order->get_id(),
			),
			200
		);
	}

	/**
	 * A real product to use as the Product Page preview subject.
	 *
	 * @param WP_REST_Request $request Request.
	 * @return WP_REST_Response
	 */
	public function get_sample_product( WP_REST_Request $request ) {
		if ( ! $this->verify_nonce( $request ) ) {
			return new WP_REST_Response( array( 'message' => 'Invalid nonce.' ), 403 );
		}

		$products = PreviewMode::get_sample_products( 1 );
		if ( empty( $products ) ) {
			return new WP_REST_Response( array( 'found' => false ), 200 );
		}

		$product = $products[0];

		return new WP_REST_Response(
			array(
				'found'      => true,
				'product_id' => $product->get_id(),
				'permalink'  => get_permalink( $product->get_id() ),
			),
			200
		);
	}

	/**
	 * A populated product category to use as the Product Archive preview subject.
	 *
	 * @param WP_REST_Request $request Request.
	 * @return WP_REST_Response
	 */
	public function get_sample_category( WP_REST_Request $request ) {
		if ( ! $this->verify_nonce( $request ) ) {
			return new WP_REST_Response( array( 'message' => 'Invalid nonce.' ), 403 );
		}
		if ( ! taxonomy_exists( 'product_cat' ) ) {
			return new WP_REST_Response( array( 'found' => false ), 200 );
		}

		$terms = get_terms(
			array(
				'taxonomy'   => 'product_cat',
				'hide_empty' => true,
				'number'     => 1,
			)
		);

		if ( is_wp_error( $terms ) || empty( $terms ) ) {
			return new WP_REST_Response( array( 'found' => false ), 200 );
		}

		$term = reset( $terms );

		return new WP_REST_Response(
			array(
				'found'     => true,
				'url'       => get_term_link( $term ),
				'taxonomy'  => $term->taxonomy,
				'term_slug' => $term->slug,
			),
			200
		);
	}
}
