<?php

namespace Fotogenic_AI;

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

final class Rest_Studio {
	private const SOURCE_ITEM_SCHEMA_VERSION = 2;
	private const MAX_CATEGORY_FILTERS = 50;
	private const STOCK_STATUSES = [ 'instock', 'outofstock', 'onbackorder' ];

    private ProductQueryAdapter $product_query_adapter;

    public function __construct( ProductQueryAdapter $product_query_adapter ) {
        $this->product_query_adapter = $product_query_adapter;
    }

    public function register_routes( Rest_Router $router ): void {
        $router->register_route(
            'fotogenic-ai/v1',
            '/studio/products/selection',
            [
                'methods'             => 'POST',
                'callback'            => [ $this, 'handle_products_selection' ],
                'permission_callback' => [ $router, 'permissions_admin' ],
                'args'                => [
                    'q'           => [ 'type' => 'string', 'default' => '', 'maxLength' => 100 ],
                    'page'        => [ 'type' => 'integer', 'default' => 1, 'minimum' => 1, 'maximum' => 10000 ],
                    'perPage'     => [ 'type' => 'integer', 'default' => 10, 'minimum' => 1, 'maximum' => 50 ],
                    'categories'  => [ 'type' => 'array', 'default' => [], 'maxItems' => 50, 'items' => [ 'type' => 'integer', 'minimum' => 1 ] ],
                    'stock'       => [ 'type' => 'array', 'default' => [], 'maxItems' => 3, 'items' => [ 'type' => 'string', 'enum' => self::STOCK_STATUSES ] ],
                    'selectedIds' => [ 'type' => 'array', 'default' => [], 'maxItems' => 10000, 'items' => [ 'type' => 'integer', 'minimum' => 1 ] ],
                    'excludedIds' => [ 'type' => 'array', 'default' => [], 'maxItems' => 10000, 'items' => [ 'type' => 'integer', 'minimum' => 1 ] ],
                ],
            ]
        );
        $router->register_route(
            'fotogenic-ai/v1',
            '/studio/products',
            [
                'methods'             => 'GET',
                'callback'            => [ $this, 'handle_products_search' ],
                'permission_callback' => [ $router, 'permissions_admin' ],
                'args'                => [
                    'q' => [
                        'type'    => 'string',
                        'default' => '',
                    ],
                    'page' => [
                        'type'    => 'integer',
                        'default' => 1,
                    ],
                    'perPage' => [
                        'type'    => 'integer',
                        'default' => 6,
                    ],
					'categories' => [
						'type'  => 'array',
						'items' => [ 'type' => 'integer' ],
					],
					'stock' => [
						'type'  => 'array',
						'items' => [ 'type' => 'string' ],
					],
                ],
            ]
        );

		$router->register_route(
			'fotogenic-ai/v1',
			'/studio/product-categories',
			[
				'methods'             => 'GET',
				'callback'            => [ $this, 'handle_product_categories' ],
				'permission_callback' => [ $router, 'permissions_admin' ],
				'args'                => [
					'q' => [
						'type'    => 'string',
						'default' => '',
					],
				],
			]
		);

        $router->register_route(
            'fotogenic-ai/v1',
            '/studio/product',
            [
                'methods'             => 'GET',
                'callback'            => [ $this, 'handle_product_context' ],
                'permission_callback' => [ $router, 'permissions_admin' ],
                'args'                => [
                    'productId' => [
                        'type'     => 'integer',
                        'required' => true,
                    ],
                ],
            ]
        );

		$router->register_route(
			'fotogenic-ai/v1',
			'/studio/product-variations',
			[
				'methods'             => 'GET',
				'callback'            => [ $this, 'handle_product_variations' ],
				'permission_callback' => [ $router, 'permissions_admin' ],
				'args'                => [
					'parentProductId' => [
						'type'     => 'integer',
						'required' => true,
					],
					'q' => [
						'type'    => 'string',
						'default' => '',
					],
					'page' => [
						'type'    => 'integer',
						'default' => 1,
					],
					'perPage' => [
						'type'    => 'integer',
						'default' => 8,
					],
					'stock' => [
						'type'    => 'string',
						'default' => '',
					],
					'attributes' => [
						'type'    => 'string',
						'default' => '',
					],
				],
			]
		);

        $router->register_route(
            'fotogenic-ai/v1',
            '/studio/product-summary',
            [
                'methods'             => 'GET',
                'callback'            => [ $this, 'handle_product_summary' ],
                'permission_callback' => [ $router, 'permissions_admin' ],
                'args'                => [
                    'id' => [
                        'type'     => 'integer',
                        'required' => true,
                    ],
                ],
            ]
        );

    }

    public static function invalidate_latest_cache_on_status_transition( string $new_status, string $old_status, \WP_Post $post ): void {
        if ( 'product' !== ( $post->post_type ?? '' ) ) {
            return;
        }
        if ( 'publish' === $new_status || 'publish' === $old_status ) {
            self::bump_latest_products_cache_version();
            self::bump_products_query_cache_version();
        }
    }

    public static function invalidate_product_and_latest_cache( int $post_id, \WP_Post $post ): void {
        if ( 'product' !== ( $post->post_type ?? '' ) ) {
            return;
        }
        Studio_Cache_Store::flush_all_caches();
    }

    public static function invalidate_product_and_latest_cache_on_delete( int $post_id ): void {
        Studio_Cache_Store::flush_all_caches();
    }

    public static function invalidate_latest_cache( int $post_id ): void {
        $post = get_post( $post_id );
        if ( $post && 'product' === ( $post->post_type ?? '' ) ) {
            Studio_Cache_Store::bump_latest_products_cache_version();
            Studio_Cache_Store::bump_products_query_cache_version();
        }
    }

    /**
     * Public API for cache invalidator: bump latest products list version (keys/ttl unchanged).
     */
    public static function bump_latest_products_cache_version(): void {
        Studio_Cache_Store::bump_latest_products_cache_version();
    }

    /**
     * Public API for cache invalidator: bump products query cache version.
     */
    public static function bump_products_query_cache_version(): void {
        Studio_Cache_Store::bump_products_query_cache_version();
    }

    /**
     * Public API for cache invalidator: bump product summary cache version.
     */
    public static function bump_product_summary_cache_version(): void {
        Studio_Cache_Store::bump_product_summary_cache_version();
    }

    /**
     * Public API for cache invalidator: bump product context cache version.
     */
    public static function bump_product_context_cache_version(): void {
        Studio_Cache_Store::bump_product_context_cache_version();
    }

    /**
     * Flush all Studio product caches by bumping version keys.
     */
    public static function flush_all_caches(): void {
        Studio_Cache_Store::flush_all_caches();
    }

    private const CACHE_GROUP = 'fotogenic_studio';
    private const PRODUCTS_LIST_CACHE_GROUP = 'fotogenic_ai_studio_products';
    private const TRANSIENT_PREFIX = 'fotogenic_ai_studio_';
    private const PRODUCT_SUMMARY_TTL = 900;
    private const PRODUCT_CONTEXT_TTL = 600;
    private const LATEST_PRODUCTS_TTL = 300;
    private const PRODUCTS_LIST_CACHE_TTL_DEFAULT = 1200;

    private static function cache_get( string $key ) {
        $value = wp_cache_get( $key, self::CACHE_GROUP );
        if ( false !== $value ) {
            return $value;
        }
        $transient_key = self::TRANSIENT_PREFIX . str_replace( ':', '_', $key );
        $value = get_transient( $transient_key );
        return false === $value ? false : $value;
    }

    private static function cache_set( string $key, $value, int $ttl_seconds ): void {
        wp_cache_set( $key, $value, self::CACHE_GROUP, $ttl_seconds );
        $transient_key = self::TRANSIENT_PREFIX . str_replace( ':', '_', $key );
        $transient_ttl = $ttl_seconds > 0 ? $ttl_seconds : 10 * YEAR_IN_SECONDS;
        set_transient( $transient_key, $value, $transient_ttl );
    }

    /**
     * Preview one selection page without creating a queue, pricing proof, or run.
     * Explicit additions precede the filtered catalogue, with no duplicates.
     *
     * @return \WP_REST_Response|\WP_Error
     */
    public function handle_products_selection( \WP_REST_Request $request ) {
        if ( ! Plugin::instance()->compat()->is_woocommerce_active() ) {
            return Messages::error( 'woocommerce_not_active', 503 );
        }
        $page       = min( 10000, max( 1, (int) $request->get_param( 'page' ) ) );
        $per_page   = min( 50, max( 1, (int) $request->get_param( 'perPage' ) ) );
        $offset     = ( $page - 1 ) * $per_page;
        $selected   = array_values( array_unique( array_filter( array_map( 'absint', (array) $request->get_param( 'selectedIds' ) ) ) ) );
        $excluded   = array_values( array_unique( array_filter( array_map( 'absint', (array) $request->get_param( 'excludedIds' ) ) ) ) );
        $selected   = array_values( array_diff( $selected, $excluded ) );
        $categories = (array) $request->get_param( 'categories' );
        $slugs      = $this->normalize_product_categories( $categories );
        if ( ! empty( $categories ) && count( $slugs ) !== count( array_unique( $categories ) ) ) {
            return new \WP_Error( 'fotogenic_ai_product_category_unavailable', __( 'A selected category is unavailable. Return to Select to update it.', 'fotogenic-ai' ), [ 'status' => 409 ] );
        }
        $items = [];
        foreach ( array_slice( $selected, $offset, $per_page ) as $product_id ) {
            $item = $this->product_query_adapter->get_product_summary( $product_id );
            if ( is_wp_error( $item ) ) {
                return $item;
            }
            if ( ! is_array( $item ) ) {
                return Messages::error( 'not_found', 404 );
            }
            $items[] = $item;
        }
        $remaining = $per_page - count( $items );
        $result = $this->product_query_adapter->list_products(
            [
                'status'       => 'publish',
                'limit'        => max( 1, $remaining ),
                'offset'       => max( 0, $offset - count( $selected ) ),
                'orderby'      => 'ID',
                'order'        => 'ASC',
                'search'       => substr( sanitize_text_field( (string) $request->get_param( 'q' ) ), 0, 100 ),
                'category'     => $slugs,
                'stock_status' => (array) $request->get_param( 'stock' ),
                'exclude'      => array_values( array_unique( array_merge( $selected, $excluded ) ) ),
            ]
        );
        if ( $remaining > 0 ) {
            $items = array_merge( $items, $result['items'] );
        }
        $authorized = $this->filter_authorized_product_items( $items );
        if ( count( $authorized ) !== count( $items ) ) {
            return Messages::error( 'forbidden', 403 );
        }
        $total = count( $selected ) + (int) $result['totalItems'];
        $response = rest_ensure_response(
            [
                'items'      => $authorized,
                'page'       => $page,
                'perPage'    => $per_page,
                'totalItems' => $total,
                'totalPages' => max( 1, (int) ceil( $total / $per_page ) ),
            ]
        );
        $response->header( 'Cache-Control', 'no-store, no-cache, must-revalidate' );
        return $response;
    }

    public function handle_products_search( \WP_REST_Request $request ) {
        $query = sanitize_text_field( (string) $request->get_param( 'q' ) );
        $query = strlen( $query ) > 100 ? substr( $query, 0, 100 ) : $query;
        $query = self::normalize_products_search_query( $query );
        $page = max( 1, absint( $request->get_param( 'page' ) ) );
		$per_page = absint( $request->get_param( 'perPage' ) );
		if ( $per_page <= 0 ) {
			$per_page = 6;
		}
        $per_page = min( 50, max( 1, $per_page ) );
		$categories = array_slice(
			array_values(
				array_unique(
					array_filter( array_map( 'absint', (array) $request->get_param( 'categories' ) ) )
				)
			),
			0,
			self::MAX_CATEGORY_FILTERS
		);
		$stock = array_values(
			array_unique(
				array_intersect(
					array_map( 'sanitize_key', (array) $request->get_param( 'stock' ) ),
					self::STOCK_STATUSES
				)
			)
		);

        if ( ! Plugin::instance()->compat()->is_woocommerce_active() ) {
            return $this->products_search_response( [
				'items'      => [],
				'page'       => $page,
				'perPage'    => $per_page,
				'totalPages' => 1,
				'totalItems' => 0,
            ] );
        }

        $normalized_q = preg_replace( '/\s+/', ' ', trim( $query ) );
        $query_cache_version = self::get_products_query_cache_version();
		$user_id      = get_current_user_id();
		$filter_hash = hash( 'sha256', wp_json_encode( [ $categories, $stock ] ) );
		$cache_key    = get_current_blog_id() . ':source-items:v' . self::SOURCE_ITEM_SCHEMA_VERSION . ':user:' . $user_id . ':products:v' . $query_cache_version . ':' . $page . ':' . $per_page . ':' . $normalized_q . ':' . $filter_hash;
        $ttl          = (int) apply_filters( 'fotogenic_ai_studio_products_cache_ttl', self::PRODUCTS_LIST_CACHE_TTL_DEFAULT );
        $cached        = wp_cache_get( $cache_key, self::PRODUCTS_LIST_CACHE_GROUP );
		if ( false !== $cached && is_array( $cached ) && isset( $cached['items'], $cached['totalItems'], $cached['totalPages'] ) ) {
			return $this->products_search_response( $this->reauthorize_products_response( $cached, $per_page ) );
        }

		if ( '' === $query && empty( $categories ) && empty( $stock ) ) {
            $result = $this->fetch_latest_products_cached( $page, $per_page );
			$items       = isset( $result['items'] ) && is_array( $result['items'] ) ? $this->filter_authorized_product_items( $result['items'] ) : [];
            $total_items = isset( $result['totalItems'] ) ? (int) $result['totalItems'] : 0;
            $total_pages = isset( $result['totalPages'] ) ? max( 1, (int) $result['totalPages'] ) : 1;
            $response = [
				'items'      => $items,
				'page'       => $page,
				'perPage'    => $per_page,
				'totalPages' => $total_pages,
				'totalItems' => $total_items,
            ];
            wp_cache_set( $cache_key, $response, self::PRODUCTS_LIST_CACHE_GROUP, $ttl );
            return $this->products_search_response( $response );
        }

		$result = $this->product_query_adapter->list_products( [
            'status'   => 'publish',
            'limit'    => $per_page,
            'page'     => $page,
            'orderby'  => 'date',
            'order'    => 'DESC',
            'paginate' => true,
			'search'       => $query,
			'category'     => $this->normalize_product_categories( $categories ),
			'stock_status' => $stock,
        ] );
		$items       = isset( $result['items'] ) && is_array( $result['items'] ) ? $this->filter_authorized_product_items( $result['items'] ) : [];
        $total_items = isset( $result['totalItems'] ) ? (int) $result['totalItems'] : 0;
        $total_pages = isset( $result['totalPages'] ) ? max( 1, (int) $result['totalPages'] ) : 1;
        $response = [
			'items'      => $items,
			'page'       => $page,
			'perPage'    => $per_page,
			'totalPages' => $total_pages,
			'totalItems' => $total_items,
        ];
        wp_cache_set( $cache_key, $response, self::PRODUCTS_LIST_CACHE_GROUP, $ttl );
        return $this->products_search_response( $response );
    }

	/** @return \WP_REST_Response|\WP_Error */
	public function handle_product_categories( \WP_REST_Request $request ) {
		if ( ! Plugin::instance()->compat()->is_woocommerce_active() ) {
			return Messages::error( 'woocommerce_not_active', 503 );
		}

		$search = sanitize_text_field( (string) $request->get_param( 'q' ) );
		$terms  = get_terms(
			[
				'taxonomy'   => 'product_cat',
				'hide_empty' => false,
				'search'     => $search,
				'number'     => 100,
				'orderby'    => 'name',
				'order'      => 'ASC',
			]
		);
		if ( is_wp_error( $terms ) ) {
			return $terms;
		}

		$items = array_map(
			static fn( \WP_Term $term ): array => [
				'id'    => (int) $term->term_id,
				'name'  => (string) $term->name,
				'count' => (int) $term->count,
			],
			$terms
		);

		return rest_ensure_response(
			[
				'items' => array_values( $items ),
			]
		);
	}

	/** @return string[] */
	private function normalize_product_categories( array $category_ids ): array {
		if ( empty( $category_ids ) ) {
			return [];
		}
		$terms = get_terms(
			[
				'taxonomy'   => 'product_cat',
				'include'    => $category_ids,
				'fields'     => 'slugs',
				'hide_empty' => false,
			]
		);

		return is_wp_error( $terms ) ? [] : array_values( array_map( 'sanitize_text_field', $terms ) );
	}

    /**
     * Ensure products search always returns a response with a non-empty JSON body.
     */
    private function products_search_response( array $data ): \WP_REST_Response {
		if ( ! isset( $data['items'], $data['page'], $data['perPage'], $data['totalPages'], $data['totalItems'] ) ) {
			$data = [ 'items' => [], 'page' => 1, 'perPage' => 6, 'totalPages' => 1, 'totalItems' => 0 ];
		}
        $data = self::fa_rest_json_sanitize( $data );
        $response = new \WP_REST_Response( $data, 200 );
        $response->header( 'Content-Type', 'application/json; charset=' . get_option( 'blog_charset' ) );
        $response->header( 'Cache-Control', 'no-store, no-cache, must-revalidate' );
        return $response;
    }

    private static function normalize_products_search_query( string $query ): string {
        return preg_replace( '/[\x{2018}\x{2019}\x{02BC}]/u', "'", $query ) ?? $query;
    }

	public function handle_product_variations( \WP_REST_Request $request ) {
		$parent_product_id = absint( $request->get_param( 'parentProductId' ) );
		if ( $parent_product_id <= 0 ) {
			return Messages::error( 'missing_product', 400 );
		}
		$authorized = $this->authorize_product_target( $parent_product_id, 'product' );
		if ( is_wp_error( $authorized ) ) {
			return $authorized;
		}

		$query = sanitize_text_field( (string) $request->get_param( 'q' ) );
		$query = strlen( $query ) > 100 ? substr( $query, 0, 100 ) : $query;
		$query = self::normalize_products_search_query( $query );
		$page  = max( 1, absint( $request->get_param( 'page' ) ) );
		$per_page = min( 50, max( 1, absint( $request->get_param( 'perPage' ) ) ?: 8 ) );
		$stock = sanitize_key( (string) $request->get_param( 'stock' ) );
		$stock = in_array( $stock, [ 'instock', 'outofstock', 'onbackorder' ], true ) ? $stock : '';
		$attributes = json_decode( (string) $request->get_param( 'attributes' ), true );
		$attributes = is_array( $attributes ) ? array_slice( $attributes, 0, 4, true ) : [];

		$result = $this->product_query_adapter->list_product_variations(
			$parent_product_id,
			[
				'search' => $query,
				'page'   => $page,
				'limit'  => $per_page,
				'stock'  => $stock,
				'attributes' => $attributes,
			]
		);
		if ( is_wp_error( $result ) ) {
			return $result;
		}
		$items = isset( $result['items'] ) && is_array( $result['items'] )
			? $this->filter_authorized_product_items( $result['items'], $parent_product_id )
			: [];

		return $this->products_search_response(
			[
				'items'           => $items,
				'attributeOptions' => isset( $result['attributeOptions'] ) && is_array( $result['attributeOptions'] )
					? array_values( $result['attributeOptions'] )
					: [],
				'parentProductId' => $parent_product_id,
				'page'            => $page,
				'perPage'         => $per_page,
				'totalPages'      => max( 1, (int) ( $result['totalPages'] ?? 1 ) ),
				'totalItems'      => max( count( $items ), (int) ( $result['totalItems'] ?? 0 ) ),
			]
		);
	}

    /**
     * Recursively sanitize payload for JSON: UTF-8 strings, stdClass→array, no objects/resources.
     * Leaves ints/floats/bools/null untouched. Use before rest_ensure_response for studio/products.
     *
     * @param mixed $payload Data to sanitize.
     * @return mixed Sanitized copy (arrays and scalars only).
     */
    private static function fa_rest_json_sanitize( $payload ) {
        if ( is_array( $payload ) ) {
            $out = [];
            foreach ( $payload as $k => $v ) {
                $out[ $k ] = self::fa_rest_json_sanitize( $v );
            }
            return $out;
        }
        if ( $payload instanceof \stdClass ) {
            return self::fa_rest_json_sanitize( (array) $payload );
        }
        if ( is_object( $payload ) || is_resource( $payload ) ) {
            return '';
        }
        if ( is_string( $payload ) ) {
            return wp_check_invalid_utf8( $payload, true );
        }
        if ( is_int( $payload ) || is_float( $payload ) || is_bool( $payload ) || $payload === null ) {
            return $payload;
        }
        return $payload;
    }

    public function handle_product_context( \WP_REST_Request $request ) {
        $product_id = absint( $request->get_param( 'productId' ) );
        if ( $product_id <= 0 ) {
            return Messages::error( 'missing_product', 400 );
        }
		$authorized = $this->authorize_product_target( $product_id );
		if ( is_wp_error( $authorized ) ) {
			return $authorized;
		}
        $context_cache_version = self::get_product_context_cache_version();
		$cache_key = 'studio_product_context:v' . $context_cache_version . ':user:' . get_current_user_id() . ':' . $product_id;
        $payload = self::cache_get( $cache_key );
		if (
			false === $payload
			|| ! is_array( $payload )
			|| ! $this->target_snapshot_matches( $authorized, $payload['product']['target'] ?? null )
		) {
            $payload = $this->product_query_adapter->get_product_context( $product_id );
            if ( is_wp_error( $payload ) ) {
				return $payload;
            }
            self::cache_set( $cache_key, $payload, self::PRODUCT_CONTEXT_TTL );
        }
		return rest_ensure_response( $payload );
    }

    public function handle_product_summary( \WP_REST_Request $request ) {
        $product_id = absint( $request->get_param( 'id' ) );
        if ( $product_id <= 0 ) {
            return Messages::error( 'missing_product', 400 );
        }
		$authorized = $this->authorize_product_target( $product_id );
		if ( is_wp_error( $authorized ) ) {
			return $authorized;
		}

        $summary_cache_version = self::get_product_summary_cache_version();
		$cache_key = 'source-items:v' . self::SOURCE_ITEM_SCHEMA_VERSION . ':product-summary:v' . $summary_cache_version . ':user:' . get_current_user_id() . ':' . $product_id;
        $cached = self::cache_get( $cache_key );
		if ( false !== $cached && is_array( $cached ) && $this->target_snapshot_matches( $authorized, $cached['target'] ?? null ) ) {
			return rest_ensure_response( $cached );
        }

        $summary = $this->product_query_adapter->get_product_summary( $product_id );
        if ( is_wp_error( $summary ) ) {
			return $summary;
        }
        if ( null === $summary ) {
            return Messages::error( 'missing_product', 404 );
        }

        self::cache_set( $cache_key, $summary, self::PRODUCT_SUMMARY_TTL );

		return rest_ensure_response( $summary );
    }

    private static function get_latest_products_cache_version(): int {
        $version = self::cache_get( 'latest_products_cache_version' );
        return is_numeric( $version ) ? (int) $version : 0;
    }

    private static function get_products_query_cache_version(): int {
        $version = self::cache_get( 'products_query_cache_version' );
        return is_numeric( $version ) ? (int) $version : 0;
    }

    private static function get_product_summary_cache_version(): int {
        $version = self::cache_get( 'product_summary_cache_version' );
        return is_numeric( $version ) ? (int) $version : 0;
    }

    private static function get_product_context_cache_version(): int {
        $version = self::cache_get( 'product_context_cache_version' );
        return is_numeric( $version ) ? (int) $version : 0;
    }

	/** @return array|\WP_Error */
	private function authorize_product_target( int $product_id, string $expected_target_type = '' ) {
		$target = Workflow_Item_Identity::resolve_target( 'product', $product_id );
		if ( is_wp_error( $target ) ) {
			return $target;
		}
		if ( '' !== $expected_target_type && $expected_target_type !== (string) ( $target['targetType'] ?? '' ) ) {
			return new \WP_Error(
				'fotogenic_ai_invalid_product_parent',
				__( 'Choose a parent Product before selecting variations.', 'fotogenic-ai' ),
				[ 'status' => 400 ]
			);
		}
		return $target;
	}

	/** @param mixed $candidate Candidate value. */
	private function target_snapshot_matches( array $authorized, $candidate ): bool {
		if ( ! is_array( $candidate ) ) {
			return false;
		}
		return (string) ( $authorized['targetType'] ?? '' ) === (string) ( $candidate['targetType'] ?? '' )
			&& (int) ( $authorized['targetId'] ?? 0 ) === (int) ( $candidate['targetId'] ?? 0 )
			&& (int) ( $authorized['parentProductId'] ?? 0 ) === (int) ( $candidate['parentProductId'] ?? 0 )
			&& (int) ( $authorized['variationId'] ?? 0 ) === (int) ( $candidate['variationId'] ?? 0 );
	}

	/**
	 * Reauthorize every Product or variation projection at the REST boundary.
	 *
	 * @param array[] $items             Product records.
	 * @param int     $parent_product_id Optional required direct parent for variation rows.
	 * @return array[]
	 */
	private function filter_authorized_product_items( array $items, int $parent_product_id = 0 ): array {
		$authorized_items = [];
		foreach ( $items as $item ) {
			if ( ! is_array( $item ) ) {
				continue;
			}
			$item_id = absint( $item['id'] ?? 0 );
			$target  = $item_id > 0 ? $this->authorize_product_target( $item_id ) : null;
			if ( ! is_array( $target ) || ! $this->target_snapshot_matches( $target, $item['target'] ?? null ) ) {
				continue;
			}
			if (
				$parent_product_id > 0
				&& (
					'variation' !== (string) $target['targetType']
					|| $parent_product_id !== (int) $target['parentProductId']
				)
			) {
				continue;
			}
			$authorized_items[] = $item;
		}
		return $authorized_items;
	}

	private function reauthorize_products_response( array $response, int $per_page ): array {
		$before = isset( $response['items'] ) && is_array( $response['items'] ) ? count( $response['items'] ) : 0;
		$response['items'] = $this->filter_authorized_product_items( (array) ( $response['items'] ?? [] ) );
		$removed = max( 0, $before - count( $response['items'] ) );
		$response['totalItems'] = max( count( $response['items'] ), (int) ( $response['totalItems'] ?? 0 ) - $removed );
		$response['totalPages'] = max( 1, (int) ceil( $response['totalItems'] / max( 1, $per_page ) ) );
		return $response;
	}

    private function fetch_latest_products_cached( int $page, int $per_page ): array {
        $version = self::get_latest_products_cache_version();
		$cache_key = 'source-items:v' . self::SOURCE_ITEM_SCHEMA_VERSION . ':latest:user:' . get_current_user_id() . ':' . $version . ':' . $page . ':' . $per_page;
        $cached = self::cache_get( $cache_key );
        if ( false !== $cached && is_array( $cached ) && isset( $cached['items'], $cached['totalItems'], $cached['totalPages'] ) ) {
			$cached['items'] = $this->filter_authorized_product_items( (array) $cached['items'] );
			return $cached;
        }

        $result = $this->product_query_adapter->list_products( [
            'status'   => 'publish',
            'limit'    => $per_page,
            'page'     => $page,
            'orderby'  => 'date',
            'order'    => 'DESC',
            'paginate' => true,
            'search'   => '',
        ] );
		$result['items'] = isset( $result['items'] ) && is_array( $result['items'] )
			? $this->filter_authorized_product_items( $result['items'] )
			: [];
        self::cache_set( $cache_key, $result, self::LATEST_PRODUCTS_TTL );
        return $result;
    }
}
