<?php
/**
 * REST API: powers the React rule builder (fields, options, rules CRUD,
 * live preview, and manual re-run).
 *
 * @package SmartCategories
 */

defined( 'ABSPATH' ) || exit;

/**
 * Class SCWC_REST_Controller
 */
class SCWC_REST_Controller {

	/**
	 * Register all routes.
	 */
	public function register_routes() {
		$ns = SCWC_REST_NS;

		register_rest_route(
			$ns,
			'/fields',
			array(
				'methods'             => WP_REST_Server::READABLE,
				'callback'            => array( $this, 'get_fields' ),
				'permission_callback' => array( $this, 'permission' ),
			)
		);

		register_rest_route(
			$ns,
			'/templates',
			array(
				'methods'             => WP_REST_Server::READABLE,
				'callback'            => array( $this, 'get_templates' ),
				'permission_callback' => array( $this, 'permission' ),
			)
		);

		register_rest_route(
			$ns,
			'/options/(?P<taxonomy>[a-z0-9_\-]+)',
			array(
				'methods'             => WP_REST_Server::READABLE,
				'callback'            => array( $this, 'get_options' ),
				'permission_callback' => array( $this, 'permission' ),
				'args'                => array(
					'search'  => array( 'sanitize_callback' => 'sanitize_text_field' ),
					'include' => array( 'sanitize_callback' => 'sanitize_text_field' ),
				),
			)
		);

		register_rest_route(
			$ns,
			'/terms/(?P<id>\d+)/rules',
			array(
				array(
					'methods'             => WP_REST_Server::READABLE,
					'callback'            => array( $this, 'get_rules' ),
					'permission_callback' => array( $this, 'permission' ),
				),
				array(
					'methods'             => WP_REST_Server::CREATABLE,
					'callback'            => array( $this, 'save_rules' ),
					'permission_callback' => array( $this, 'permission' ),
				),
			)
		);

		register_rest_route(
			$ns,
			'/terms/(?P<id>\d+)/run',
			array(
				'methods'             => WP_REST_Server::CREATABLE,
				'callback'            => array( $this, 'run_term' ),
				'permission_callback' => array( $this, 'permission' ),
			)
		);


		register_rest_route(
			$ns,
			'/preview',
			array(
				'methods'             => WP_REST_Server::CREATABLE,
				'callback'            => array( $this, 'preview' ),
				'permission_callback' => array( $this, 'permission' ),
			)
		);

		register_rest_route(
			$ns,
			'/explain',
			array(
				'methods'             => WP_REST_Server::CREATABLE,
				'callback'            => array( $this, 'explain' ),
				'permission_callback' => array( $this, 'permission' ),
			)
		);

		register_rest_route(
			$ns,
			'/products/search',
			array(
				'methods'             => WP_REST_Server::READABLE,
				'callback'            => array( $this, 'search_products' ),
				'permission_callback' => array( $this, 'permission' ),
				'args'                => array(
					'q' => array( 'sanitize_callback' => 'sanitize_text_field' ),
				),
			)
		);

		register_rest_route(
			$ns,
			'/categories',
			array(
				array(
					'methods'             => WP_REST_Server::READABLE,
					'callback'            => array( $this, 'list_categories' ),
					'permission_callback' => array( $this, 'permission' ),
				),
				array(
					'methods'             => WP_REST_Server::CREATABLE,
					'callback'            => array( $this, 'create_category' ),
					'permission_callback' => array( $this, 'permission' ),
				),
			)
		);
	}

	/**
	 * Capability check for all endpoints.
	 *
	 * @return bool
	 */
	public function permission() {
		return current_user_can( 'manage_woocommerce' );
	}

	/**
	 * GET /fields — full field registry, operator labels and groups.
	 *
	 * @return WP_REST_Response
	 */
	public function get_fields() {
		return rest_ensure_response(
			array(
				'groups'    => SCWC_Fields::groups(),
				'fields'    => SCWC_Fields::all(),
				'operators' => SCWC_Fields::operator_labels(),
			)
		);
	}

	/**
	 * GET /templates — built-in one-click rule templates.
	 *
	 * @return WP_REST_Response
	 */
	public function get_templates() {
		return rest_ensure_response( SCWC_Templates::all() );
	}

	/**
	 * GET /options/{taxonomy} — searchable term options for taxonomy value pickers.
	 *
	 * @param WP_REST_Request $request Request.
	 * @return WP_REST_Response|WP_Error
	 */
	public function get_options( $request ) {
		$taxonomy = $request['taxonomy'];
		if ( ! taxonomy_exists( $taxonomy ) ) {
			return new WP_Error( 'scwc_invalid_taxonomy', __( 'Invalid taxonomy.', 'smart-categories-for-woocommerce' ), array( 'status' => 400 ) );
		}

		$args = array(
			'taxonomy'   => $taxonomy,
			'hide_empty' => false,
			'number'     => 30,
			'orderby'    => 'name',
			'order'      => 'ASC',
		);

		$include = $request->get_param( 'include' );
		if ( ! empty( $include ) ) {
			// Resolve specific term IDs to labels (used when loading saved values).
			$args['include'] = array_map( 'absint', array_filter( explode( ',', $include ) ) );
			$args['number']  = 0;
			unset( $args['orderby'] );
		} else {
			$search = $request->get_param( 'search' );
			if ( ! empty( $search ) ) {
				$args['search'] = $search;
			}
		}

		$terms   = get_terms( $args );
		$options = array();
		if ( ! is_wp_error( $terms ) ) {
			foreach ( $terms as $term ) {
				$options[] = array(
					'value' => (int) $term->term_id,
					'label' => $term->name,
				);
			}
		}

		return rest_ensure_response( $options );
	}

	/**
	 * GET /terms/{id}/rules — enabled flag + rule tree + managed count.
	 *
	 * @param WP_REST_Request $request Request.
	 * @return WP_REST_Response
	 */
	public function get_rules( $request ) {
		$term_id = (int) $request['id'];
		return rest_ensure_response(
			array(
				'enabled'       => SCWC_Term_Rules::is_enabled( $term_id ),
				'rules'         => SCWC_Term_Rules::get_rules( $term_id ),
				'sort'          => SCWC_Term_Rules::get_sort( $term_id ),
				'managed_count' => SCWC_Assigner::managed_count( $term_id ),
			)
		);
	}

	/**
	 * POST /terms/{id}/rules — validate + persist, then queue a re-evaluation.
	 *
	 * @param WP_REST_Request $request Request.
	 * @return WP_REST_Response|WP_Error
	 */
	public function save_rules( $request ) {
		$term_id = (int) $request['id'];
		$term    = get_term( $term_id );
		if ( ! $term || is_wp_error( $term ) || ! in_array( $term->taxonomy, SCWC_Term_Rules::taxonomies(), true ) ) {
			return new WP_Error( 'scwc_invalid_term', __( 'Category not found.', 'smart-categories-for-woocommerce' ), array( 'status' => 404 ) );
		}

		$body    = $request->get_json_params();
		$enabled = ! empty( $body['enabled'] );
		$tree    = isset( $body['rules'] ) ? SCWC_Term_Rules::sanitize_tree( $body['rules'] ) : SCWC_Term_Rules::default_tree();

		SCWC_Term_Rules::save_rules( $term_id, $tree );
		SCWC_Term_Rules::set_enabled( $term_id, $enabled );

		if ( isset( $body['sort'] ) && is_array( $body['sort'] ) ) {
			SCWC_Term_Rules::save_sort( $term_id, $body['sort'] );
		}

		// Re-evaluate so membership reflects the new rules — synchronously for
		// typical catalogs so the count updates right away (async for huge ones).
		$ran = false;
		if ( $enabled ) {
			$ran = SCWC_Scheduler::sync_or_schedule( $term_id );
		}

		return rest_ensure_response(
			array(
				'saved'         => true,
				'enabled'       => $enabled,
				'queued'        => $enabled && ! $ran,
				'available'     => SCWC_Scheduler::available(),
				'managed_count' => SCWC_Assigner::managed_count( $term_id ),
			)
		);
	}

	/**
	 * POST /terms/{id}/run — manually queue a full re-evaluation.
	 *
	 * @param WP_REST_Request $request Request.
	 * @return WP_REST_Response
	 */
	public function run_term( $request ) {
		$term_id = (int) $request['id'];
		// Interactive "Refresh now": re-evaluate synchronously so the returned
		// count is immediately accurate (falls back to async for huge catalogs).
		$ran = SCWC_Scheduler::sync_or_schedule( $term_id );
		return rest_ensure_response(
			array(
				'queued'        => ! $ran,
				'managed_count' => SCWC_Assigner::managed_count( $term_id ),
			)
		);
	}

	/**
	 * POST /preview — evaluate a (draft) rule tree live and return a count + sample.
	 *
	 * @param WP_REST_Request $request Request.
	 * @return WP_REST_Response
	 */
	public function preview( $request ) {
		$body = $request->get_json_params();
		$tree = isset( $body['rules'] ) ? SCWC_Term_Rules::sanitize_tree( $body['rules'] ) : SCWC_Term_Rules::default_tree();

		if ( empty( $tree['children'] ) ) {
			return rest_ensure_response(
				array(
					'matched'   => 0,
					'scanned'   => 0,
					'truncated' => false,
					'sample'    => array(),
				)
			);
		}

		$scan_cap     = (int) apply_filters( 'scwc_preview_scan_cap', 2000 );
		$sample_limit = (int) apply_filters( 'scwc_preview_sample_limit', 24 );
		$per_page     = 200;
		$statuses     = apply_filters( 'scwc_evaluated_post_statuses', array( 'publish' ) );

		$paged     = 1;
		$scanned   = 0;
		$matched   = 0;
		$truncated = false;
		$sample    = array();

		do {
			$query = new WP_Query(
				array(
					'post_type'              => 'product',
					'post_status'            => $statuses,
					'fields'                 => 'ids',
					'posts_per_page'         => $per_page,
					'paged'                  => $paged,
					'orderby'                => 'ID',
					'order'                  => 'ASC',
					'no_found_rows'          => true,
					'update_post_meta_cache' => false,
					'update_post_term_cache' => false,
				)
			);
			$ids = $query->posts;

			foreach ( $ids as $product_id ) {
				++$scanned;
				$product = wc_get_product( $product_id );
				if ( ! $product ) {
					continue;
				}
				if ( SCWC_Rule_Engine::matches( $product, $tree ) ) {
					++$matched;
					if ( count( $sample ) < $sample_limit ) {
						$sample[] = array(
							'id'    => $product->get_id(),
							'name'  => $product->get_name(),
							'price' => html_entity_decode( wp_strip_all_tags( wc_price( (float) $product->get_price() ) ), ENT_QUOTES, 'UTF-8' ),
							'thumb' => get_the_post_thumbnail_url( $product->get_id(), 'thumbnail' ),
							'edit'  => get_edit_post_link( $product->get_id(), 'raw' ),
						);
					}
				}
				if ( $scanned >= $scan_cap ) {
					$truncated = true;
					break 2;
				}
			}
			++$paged;
		} while ( count( $ids ) >= $per_page );

		return rest_ensure_response(
			array(
				'matched'   => $matched,
				'scanned'   => $scanned,
				'truncated' => $truncated,
				'sample'    => $sample,
			)
		);
	}


	/**
	 * POST /explain — per-condition pass/fail trace for one product against a
	 * (draft) rule tree. Answers "why did / didn't this product match?".
	 *
	 * @param WP_REST_Request $request Request.
	 * @return WP_REST_Response|WP_Error
	 */
	public function explain( $request ) {
		$body       = $request->get_json_params();
		$product_id = isset( $body['product_id'] ) ? (int) $body['product_id'] : 0;
		if ( ! $product_id ) {
			return new WP_Error( 'scwc_no_product', __( 'Select a product.', 'smart-categories-for-woocommerce' ), array( 'status' => 400 ) );
		}
		$product = wc_get_product( $product_id );
		if ( ! $product ) {
			return new WP_Error( 'scwc_bad_product', __( 'Product not found.', 'smart-categories-for-woocommerce' ), array( 'status' => 404 ) );
		}

		$tree = isset( $body['rules'] ) ? SCWC_Term_Rules::sanitize_tree( $body['rules'] ) : SCWC_Term_Rules::default_tree();

		return rest_ensure_response(
			array(
				'matched' => SCWC_Rule_Engine::matches( $product, $tree ),
				'trace'   => SCWC_Rule_Engine::explain( $product, $tree ),
				'product' => array(
					'id'    => $product_id,
					'name'  => $product->get_name(),
					'thumb' => get_the_post_thumbnail_url( $product_id, 'thumbnail' ),
				),
			)
		);
	}

	/**
	 * GET /products/search?q= — product picker for the explain tool.
	 *
	 * @param WP_REST_Request $request Request.
	 * @return WP_REST_Response
	 */
	public function search_products( $request ) {
		$q    = (string) $request->get_param( 'q' );
		$args = array(
			'post_type'      => 'product',
			'post_status'    => 'publish',
			'posts_per_page' => 15,
			'fields'         => 'ids',
			'orderby'        => 'title',
			'order'          => 'ASC',
		);
		if ( '' !== $q ) {
			$args['s'] = $q;
		}

		$ids = ( new WP_Query( $args ) )->posts;
		$out = array();
		foreach ( $ids as $id ) {
			$product = wc_get_product( $id );
			if ( ! $product ) {
				continue;
			}
			$out[] = array(
				'id'    => (int) $id,
				'name'  => $product->get_name(),
				'sku'   => $product->get_sku(),
				'thumb' => get_the_post_thumbnail_url( $id, 'thumbnail' ),
			);
		}
		return rest_ensure_response( $out );
	}

	/**
	 * GET /categories — overview of all smart-enabled categories.
	 *
	 * @return WP_REST_Response
	 */
	public function list_categories() {
		$out = array();
		foreach ( SCWC_Term_Rules::get_smart_term_ids() as $term_id ) {
			// Smart terms live in BOTH product_cat and product_tag. Resolve the real
			// taxonomy instead of forcing product_cat, or smart tags silently vanish
			// from the overview and their edit links break.
			$term = get_term( $term_id );
			if ( ! $term || is_wp_error( $term ) ) {
				continue;
			}
			$out[] = array(
				'id'            => (int) $term_id,
				'name'          => $term->name,
				'taxonomy'      => $term->taxonomy,
				'managed_count' => SCWC_Assigner::managed_count( $term_id ),
				'edit'          => get_edit_term_link( $term_id, $term->taxonomy ),
			);
		}
		return rest_ensure_response( $out );
	}

	/**
	 * POST /categories — Shopify-style: create a new product category together
	 * with its smart rules in a single call, then queue population.
	 *
	 * @param WP_REST_Request $request Request.
	 * @return WP_REST_Response|WP_Error
	 */
	public function create_category( $request ) {
		$body = $request->get_json_params();
		$name = isset( $body['name'] ) ? sanitize_text_field( $body['name'] ) : '';

		if ( '' === $name ) {
			return new WP_Error( 'scwc_no_name', __( 'Category name is required.', 'smart-categories-for-woocommerce' ), array( 'status' => 400 ) );
		}

		$args = array();
		if ( ! empty( $body['parent'] ) ) {
			$args['parent'] = (int) $body['parent'];
		}
		if ( ! empty( $body['slug'] ) ) {
			$args['slug'] = sanitize_title( $body['slug'] );
		}
		if ( ! empty( $body['description'] ) ) {
			$args['description'] = sanitize_textarea_field( $body['description'] );
		}

		$result = wp_insert_term( $name, SCWC_Term_Rules::TAXONOMY, $args );
		if ( is_wp_error( $result ) ) {
			return new WP_Error( 'scwc_insert_failed', $result->get_error_message(), array( 'status' => 400 ) );
		}

		$term_id = (int) $result['term_id'];
		$enabled = isset( $body['enabled'] ) ? (bool) $body['enabled'] : true;
		$tree    = isset( $body['rules'] ) ? SCWC_Term_Rules::sanitize_tree( $body['rules'] ) : SCWC_Term_Rules::default_tree();

		SCWC_Term_Rules::save_rules( $term_id, $tree );
		SCWC_Term_Rules::set_enabled( $term_id, $enabled );

		if ( isset( $body['sort'] ) && is_array( $body['sort'] ) ) {
			SCWC_Term_Rules::save_sort( $term_id, $body['sort'] );
		}

		$ran = false;
		if ( $enabled ) {
			// Populate immediately for typical catalogs so the new category does
			// not show a misleading "0 products" right after creation.
			$ran = SCWC_Scheduler::sync_or_schedule( $term_id );
		}

		$view = get_term_link( $term_id, SCWC_Term_Rules::TAXONOMY );

		return rest_ensure_response(
			array(
				'id'            => $term_id,
				'name'          => $name,
				'edit'          => get_edit_term_link( $term_id, SCWC_Term_Rules::TAXONOMY ),
				'view'          => is_wp_error( $view ) ? '' : $view,
				'managed_count' => SCWC_Assigner::managed_count( $term_id ),
				'queued'        => $enabled && ! $ran,
			)
		);
	}
}
