<?php
/**
 * Setup Controller for Warranty Cart Admin REST API
 *
 * Backs the setup wizard's checklist.
 *
 * @package WarrantyCart
 */

namespace WarrantyCart\REST\Controllers;

use WarrantyCart\Frontend\Shortcode;
use WarrantyCart\REST\Controller;
use WP_REST_Request;
use WP_REST_Response;
use WP_REST_Server;

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

/**
 * Setup class
 */
class Setup extends Controller {

	/**
	 * Where the "wizard has been completed or dismissed" flag lives.
	 *
	 * An option rather than user meta: setup describes the *store*, not the
	 * person looking at it. A second administrator should not be walked through
	 * a wizard for work their colleague already did.
	 */
	private const DONE_OPTION = 'warranty_cart_setup_complete';

	/**
	 * Set the first time the settings screen is saved.
	 *
	 * A fact about what the shop has done, rather than about what the plugin
	 * wrote to the database on its own behalf.
	 */
	public const SETTINGS_SAVED_OPTION = 'warranty_cart_settings_saved';

	/**
	 * Constructor
	 */
	public function __construct() {
		$this->rest_base = 'admin';
	}

	/**
	 * Register routes
	 *
	 * @return void
	 */
	public function register_routes(): void {
		register_rest_route(
			$this->namespace,
			'/' . $this->rest_base . '/setup-status',
			array(
				array(
					'methods'             => WP_REST_Server::READABLE,
					'callback'            => array( $this, 'get_status' ),
					'permission_callback' => array( $this, 'check_admin_permission' ),
				),
				array(
					'methods'             => WP_REST_Server::CREATABLE,
					'callback'            => array( $this, 'update_status' ),
					'permission_callback' => array( $this, 'check_admin_permission' ),
					'args'                => array(
						'complete' => array(
							'required'          => false,
							'type'              => 'boolean',
							'description'       => __( 'Mark setup finished or dismissed.', 'warranty-cart' ),
							'sanitize_callback' => 'rest_sanitize_boolean',
						),
					),
				),
			)
		);
	}

	/**
	 * The setup checklist, computed from the store rather than remembered.
	 *
	 * Every step is derived from real state on each request. A wizard that
	 * stores "step 2 done" drifts the moment somebody deletes the thing step 2
	 * created, and then cheerfully reports a store is configured when it is
	 * not.
	 *
	 * @param WP_REST_Request $request REST request object.
	 * @return WP_REST_Response
	 */
	public function get_status( WP_REST_Request $request ): WP_REST_Response {
		$products = $this->warranty_product_count();
		$fields   = get_option( 'warranty_checkout_fields', array() );
		$page     = $this->shortcode_page_id();

		$steps = array(
			'products'  => $products > 0,
			'fields'    => is_array( $fields ) && ! empty( $fields ),
			'shortcode' => $page > 0,

			/*
			 * Whether the shop has ever saved its settings.
			 *
			 * This tested `warranty_cart_settings` for being a non-empty array.
			 * Activation writes that option with two keys nothing reads, so the
			 * step was green from the moment the plugin switched on, whatever
			 * the store had or had not configured — a checklist item that could
			 * not fail and therefore said nothing.
			 *
			 * Saving the settings screen is a real event, and the settings
			 * controller records it.
			 */
			'settings'  => (bool) get_option( self::SETTINGS_SAVED_OPTION, false ),
		);

		return $this->success_response(
			array(
				'steps'          => $steps,
				'complete'       => ! in_array( false, $steps, true ),
				'dismissed'      => (bool) get_option( self::DONE_OPTION, false ),
				'product_count'  => $products,
				'field_count'    => is_array( $fields ) ? count( $fields ) : 0,
				'shortcode_page' => $page,
				'woocommerce'    => class_exists( 'WooCommerce' ),
			)
		);
	}

	/**
	 * Record that the wizard has been finished or dismissed.
	 *
	 * @param WP_REST_Request $request REST request object.
	 * @return WP_REST_Response
	 */
	public function update_status( WP_REST_Request $request ): WP_REST_Response {
		$complete = (bool) $request->get_param( 'complete' );

		update_option( self::DONE_OPTION, $complete, false );

		return $this->success_response( array( 'dismissed' => $complete ) );
	}

	/**
	 * How many published warranty products the store has.
	 *
	 * Counts on `_warranty_cart_product_type`, which is what
	 * Admin\Product\Meta writes. Note that Frontend\Shortcode reads a different
	 * key, `_warranty_product_type` — the two are not synced, and that split is
	 * a known issue rather than something this count should paper over. Using
	 * the key the product editor writes means the checklist agrees with what an
	 * administrator just did.
	 *
	 * @return int
	 */
	private function warranty_product_count(): int {
		$ids = get_posts(
			array(
				'post_type'    => 'product',
				'post_status'  => 'publish',
				'numberposts'  => 1,
				'fields'       => 'ids',
				'meta_key'     => '_warranty_cart_product_type', // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
				'meta_compare' => 'EXISTS',
			)
		);

		if ( empty( $ids ) ) {
			return 0;
		}

		// A second, unbounded query only when there is at least one, so an
		// empty store costs one cheap lookup rather than a full count.
		return count(
			get_posts(
				array(
					'post_type'    => 'product',
					'post_status'  => 'publish',
					'numberposts'  => -1,
					'fields'       => 'ids',
					'meta_key'     => '_warranty_cart_product_type', // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
					'meta_compare' => 'EXISTS',
				)
			)
		);
	}

	/**
	 * The page the selector appears on, if any.
	 *
	 * The page chosen in the settings wins: that is the supported route, and
	 * it needs no shortcode in the content for the selector to render. Only
	 * when nothing is chosen does this fall back to scanning for a pasted
	 * shortcode, which is what stores configured before the picker existed
	 * will have.
	 *
	 * The scan matches on the registered tag exactly. `has_shortcode()` calls
	 * `shortcode_exists()` first, which is a plain array lookup, so a
	 * mis-cased tag silently matches nothing — the same mistake that once
	 * stopped this plugin's frontend assets loading at all.
	 *
	 * @return int Page ID, or 0.
	 */
	private function shortcode_page_id(): int {
		$selected = Shortcode::display_page_id();

		if ( $selected > 0 ) {
			return $selected;
		}

		$pages = get_posts(
			array(
				'post_type'   => 'page',
				'post_status' => 'publish',
				'numberposts' => 50,
				'fields'      => 'ids',
				's'           => Shortcode::TAG,
			)
		);

		foreach ( $pages as $id ) {
			$content = get_post_field( 'post_content', $id );

			if ( $content && has_shortcode( $content, Shortcode::TAG ) ) {
				return (int) $id;
			}
		}

		return 0;
	}
}
