<?php

namespace Fotogenic_AI\Compat\WooCommerce;

use Fotogenic_AI\Image_Source_Resolver;
use Fotogenic_AI\Logger;
use Fotogenic_AI\ProductQueryAdapter;
use Fotogenic_AI\Workflow_Item_Identity;

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

final class Product_Query_Adapter_WooCommerce implements ProductQueryAdapter {

	private static bool $search_filter_registered = false;

	public function __construct() {
		self::register_search_filter();
	}

	private static function register_search_filter(): void {
		if ( self::$search_filter_registered ) {
			return;
		}
		add_filter( 'woocommerce_product_data_store_cpt_get_products_query', [ self::class, 'map_search_to_query' ], 10, 2 );
		add_filter( 'posts_where', [ self::class, 'apply_scan_keyset_bounds' ], 10, 2 );
		self::$search_filter_registered = true;
	}

	/**
	 * Apply the private Product scan cursor bounds to the flagged WP_Query only.
	 *
	 * @param string    $where Existing WHERE clause.
	 * @param \WP_Query $query Query instance.
	 * @return string
	 */
	public static function apply_scan_keyset_bounds( $where, $query ): string {
		if ( ! $query instanceof \WP_Query || ! $query->get( 'fotogenic_product_scan' ) ) {
			return (string) $where;
		}

		global $wpdb;
		$after_id     = absint( $query->get( 'fotogenic_after_id' ) );
		$high_water_id = absint( $query->get( 'fotogenic_high_water_id' ) );
		if ( $after_id > 0 ) {
			$where .= $wpdb->prepare( " AND {$wpdb->posts}.ID > %d", $after_id );
		}
		if ( $high_water_id > 0 ) {
			$where .= $wpdb->prepare( " AND {$wpdb->posts}.ID <= %d", $high_water_id );
		}

		return (string) $where;
	}

	/**
	 * Maps the adapter search query to the Product post title query.
	 *
	 * @param array $query      WP_Query args.
	 * @param array $query_vars WC_Product_Query vars passed to wc_get_products().
	 * @return array
	 */
	public static function map_search_to_query( $query, $query_vars ) {
		if ( is_array( $query_vars ) && ! empty( $query_vars['search'] ) && is_string( $query_vars['search'] ) && is_array( $query ) ) {
			$query['s'] = $query_vars['search'];
		}
		return $query;
	}

	/**
	 * Scan published parent Products with a stable high-water mark and ID cursor.
	 *
	 * @param array $args Scan filters and cursor.
	 * @return array{items: array, total: int, nextAfterId: int, done: bool, highWaterId: int, available: bool}
	 */
	public function scan_products( array $args ): array {
		$after_id     = max( 0, absint( $args['after_id'] ?? 0 ) );
		$high_water_id = max( 0, absint( $args['high_water_id'] ?? 0 ) );
		if ( ! $this->woocommerce_available() ) {
			return [
				'items'       => [],
				'total'       => 0,
				'nextAfterId' => $after_id,
				'done'        => true,
				'highWaterId' => $high_water_id,
				'available'   => false,
			];
		}

		$limit  = min( 100, max( 1, absint( $args['limit'] ?? 100 ) ) );
		$search = trim( sanitize_text_field( (string) ( $args['search'] ?? '' ) ) );
		$category = array_values(
			array_unique(
				array_filter( array_map( 'sanitize_title', (array) ( $args['category'] ?? [] ) ) )
			)
		);
		$stock_status = array_values(
			array_unique(
				array_intersect(
					array_map( 'sanitize_key', (array) ( $args['stock_status'] ?? [] ) ),
					[ 'instock', 'outofstock', 'onbackorder' ]
				)
			)
		);

		if ( 0 === $high_water_id ) {
			$high_water_query = new \WP_Query(
				[
					'post_type'              => 'product',
					'post_status'            => 'publish',
					'posts_per_page'         => 1,
					'orderby'                => 'ID',
					'order'                  => 'DESC',
					'fields'                 => 'ids',
					'ignore_sticky_posts'    => true,
					'no_found_rows'          => true,
					'update_post_meta_cache' => false,
					'update_post_term_cache' => false,
				]
			);
			$high_water_id = isset( $high_water_query->posts[0] ) ? absint( $high_water_query->posts[0] ) : 0;
		}

		if ( 0 === $high_water_id || $after_id >= $high_water_id ) {
			return [
				'items'       => [],
				'total'       => 0,
				'nextAfterId' => $after_id,
				'done'        => true,
				'highWaterId' => $high_water_id,
				'available'   => true,
			];
		}

		$query_args = [
			'post_type'                    => 'product',
			'post_status'                  => 'publish',
			'posts_per_page'               => $limit,
			'orderby'                      => 'ID',
			'order'                        => 'ASC',
			'fields'                       => 'ids',
			'ignore_sticky_posts'          => true,
			'no_found_rows'                => false,
			'update_post_meta_cache'       => true,
			'update_post_term_cache'       => true,
			'fotogenic_product_scan'       => true,
			'fotogenic_after_id'           => $after_id,
			'fotogenic_high_water_id'      => $high_water_id,
		];
		if ( '' !== $search ) {
			$exact_id = ctype_digit( $search ) ? absint( $search ) : 0;
			if ( 0 === $exact_id && function_exists( 'wc_get_product_id_by_sku' ) ) {
				$exact_id = absint( wc_get_product_id_by_sku( $search ) );
			}
			if ( $exact_id > 0 ) {
				$exact_product = wc_get_product( $exact_id );
				$query_args['post__in'] = $exact_product instanceof \WC_Product && ! $exact_product instanceof \WC_Product_Variation
					? [ $exact_id ]
					: [ 0 ];
			} else {
				$query_args['s'] = $search;
			}
		}
		if ( ! empty( $category ) ) {
			$query_args['tax_query'] = [ // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query -- Store Queue preparation is bounded and asynchronous.
				[
					'taxonomy'         => 'product_cat',
					'field'            => 'slug',
					'terms'            => $category,
					'include_children' => true,
				],
			];
		}
		if ( ! empty( $stock_status ) ) {
			$query_args['meta_query'] = [ // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Store Queue preparation is bounded and asynchronous.
				[
					'key'     => '_stock_status',
					'value'   => $stock_status,
					'compare' => 'IN',
				],
			];
		}

		$query         = new \WP_Query( $query_args );
		$inspected_ids = array_values( array_filter( array_map( 'absint', (array) $query->posts ) ) );
		$next_after_id = ! empty( $inspected_ids ) ? max( $inspected_ids ) : $after_id;
		$items         = [];
		foreach ( $inspected_ids as $product_id ) {
			$product = wc_get_product( $product_id );
			if ( ! $product instanceof \WC_Product || $product instanceof \WC_Product_Variation ) {
				continue;
			}
			$item = $this->format_item( $product );
			if ( is_array( $item ) ) {
				$items[] = $item;
			}
		}

		return [
			'items'       => $items,
			'total'       => max( count( $inspected_ids ), (int) $query->found_posts ),
			'nextAfterId' => $next_after_id,
			'done'        => count( $inspected_ids ) < $limit || (int) $query->found_posts <= count( $inspected_ids ),
			'highWaterId' => $high_water_id,
			'available'   => true,
		];
	}

	public function list_products( array $args ): array {
		if ( ! $this->woocommerce_available() ) {
			return $this->empty_list();
		}

		$defaults = [
			'status'   => 'publish',
			'limit'    => 6,
			'page'     => 1,
			'return'   => 'objects',
			'orderby'  => 'date',
			'order'    => 'DESC',
			'paginate' => true,
			'search'   => '',
			'category' => [],
			'stock_status' => [],
		];
		$r       = array_merge( $defaults, $args );
		$excluded = array_values( array_unique( array_filter( array_map( 'absint', (array) ( $r['exclude'] ?? [] ) ) ) ) );
		$offset   = isset( $r['offset'] ) ? max( 0, (int) $r['offset'] ) : null;
		$search  = trim( (string) $r['search'] );
		$exact_id = '' !== $search && ctype_digit( $search ) ? absint( $search ) : 0;
		if ( 0 === $exact_id && '' !== $search && function_exists( 'wc_get_product_id_by_sku' ) ) {
			$exact_id = absint( wc_get_product_id_by_sku( $search ) );
		}
		if ( $exact_id > 0 ) {
			$product = $this->normalize_list_product( wc_get_product( $exact_id ) );
			$item    = $product instanceof \WC_Product && ! in_array( $product->get_id(), $excluded, true ) && $this->product_matches_filters( $product, (array) $r['category'], (array) $r['stock_status'] )
				? $this->format_item( $product )
				: null;
			return is_array( $item )
				? [ 'items' => ( $offset > 0 || ( null === $offset && (int) $r['page'] > 1 ) ) ? [] : [ $item ], 'totalItems' => 1, 'totalPages' => 1 ]
				: $this->empty_list();
		}

		$wc_args = [
			'status'   => $r['status'],
			'limit'    => min( 50, max( 1, (int) $r['limit'] ) ),
			'page'     => max( 1, (int) $r['page'] ),
			'return'   => 'objects',
			'orderby'  => $r['orderby'],
			'order'    => $r['order'],
			'paginate' => true,
		];
		if ( ! empty( $excluded ) ) {
			$wc_args['exclude'] = $excluded;
		}
		if ( null !== $offset ) {
			$wc_args['offset'] = $offset;
		}
		if ( function_exists( 'wc_get_product_types' ) ) {
			$product_types = array_values( array_diff( array_keys( (array) wc_get_product_types() ), [ 'variation' ] ) );
			if ( ! empty( $product_types ) ) {
				$wc_args['type'] = $product_types;
			}
		}
		if ( '' !== $search ) {
			$wc_args['search'] = $search;
		}
		if ( ! empty( $r['category'] ) ) {
			$wc_args['category'] = array_values( array_filter( array_map( 'sanitize_title', (array) $r['category'] ) ) );
		}
		if ( ! empty( $r['stock_status'] ) ) {
			$wc_args['stock_status'] = array_values(
				array_intersect(
					array_map( 'sanitize_key', (array) $r['stock_status'] ),
					[ 'instock', 'outofstock', 'onbackorder' ]
				)
			);
		}

		$result   = wc_get_products( $wc_args );
		$products = is_object( $result ) && isset( $result->products ) ? (array) $result->products : (array) $result;
		$items    = [];
		foreach ( $products as $product ) {
			$product = $this->normalize_list_product( $product );
			if ( ! $product instanceof \WC_Product ) {
				continue;
			}
			$item = $this->format_item( $product );
			if ( is_array( $item ) ) {
				$items[] = $item;
			}
		}

		$total_items = is_object( $result ) && isset( $result->total ) ? (int) $result->total : count( $items );
		$total_pages = is_object( $result ) && isset( $result->max_num_pages ) ? (int) $result->max_num_pages : 1;
		return [
			'items'      => $items,
			'totalItems' => max( count( $items ), $total_items ),
			'totalPages' => max( 1, $total_pages ),
		];
	}

	/** @return array|\WP_Error */
	public function list_product_variations( int $parent_product_id, array $args ) {
		if ( ! $this->woocommerce_available() ) {
			return new \WP_Error( 'woocommerce_not_active', __( 'WooCommerce is not active.', 'fotogenic-ai' ), [ 'status' => 503 ] );
		}

		$parent_target = Workflow_Item_Identity::resolve_target( 'product', $parent_product_id );
		if ( is_wp_error( $parent_target ) ) {
			return $parent_target;
		}
		if ( 'product' !== (string) ( $parent_target['targetType'] ?? '' ) ) {
			return new \WP_Error( 'fotogenic_ai_invalid_product_parent', __( 'Choose a parent Product before selecting variations.', 'fotogenic-ai' ), [ 'status' => 400 ] );
		}

		$parent = wc_get_product( $parent_product_id );
		if ( ! $parent instanceof \WC_Product ) {
			return new \WP_Error( 'fotogenic_ai_invalid_product', __( 'Product not found.', 'fotogenic-ai' ), [ 'status' => 404 ] );
		}
		if ( ! $parent->is_type( 'variable' ) ) {
			return $this->empty_list();
		}

		$page     = max( 1, absint( $args['page'] ?? 1 ) );
		$limit    = min( 50, max( 1, absint( $args['limit'] ?? 8 ) ) );
		$search   = trim( sanitize_text_field( (string) ( $args['search'] ?? '' ) ) );
		$stock    = sanitize_key( (string) ( $args['stock'] ?? '' ) );
		$stock    = in_array( $stock, [ 'instock', 'outofstock', 'onbackorder' ], true ) ? $stock : '';
		$attribute_options = [];
		if ( method_exists( $parent, 'get_variation_attributes' ) ) {
			foreach ( (array) $parent->get_variation_attributes() as $name => $values ) {
				$key = sanitize_title( (string) $name );
				if ( '' === $key ) {
					continue;
				}
				$attribute_options[ $key ] = [
					'key'     => $key,
					'label'   => sanitize_text_field( function_exists( 'wc_attribute_label' ) ? (string) wc_attribute_label( (string) $name, $parent ) : (string) $name ),
					'options' => array_values( array_unique( array_filter( array_map( 'sanitize_text_field', (array) $values ) ) ) ),
				];
			}
		}
		$attribute_filters = [];
		foreach ( (array) ( $args['attributes'] ?? [] ) as $name => $value ) {
			$key   = sanitize_title( (string) $name );
			$value = sanitize_text_field( (string) $value );
			if ( '' === $value ) {
				continue;
			}
			if ( ! isset( $attribute_options[ $key ] ) || ! in_array( $value, $attribute_options[ $key ]['options'], true ) ) {
				return new \WP_Error( 'fotogenic_ai_invalid_variation_filter', __( 'The selected variation filter is unavailable.', 'fotogenic-ai' ), [ 'status' => 400 ] );
			}
			$attribute_filters[ $key ] = $value;
		}
		$query_args = [
			'post_type'              => 'product_variation',
			'post_parent'            => $parent_product_id,
			'post_status'            => [ 'publish', 'private' ],
			'posts_per_page'         => $limit,
			'paged'                  => $page,
			'orderby'                => [ 'menu_order' => 'ASC', 'ID' => 'ASC' ],
			'fields'                 => 'ids',
			'perm'                   => 'editable',
			'ignore_sticky_posts'    => true,
			'no_found_rows'          => false,
			'update_post_meta_cache' => true,
			'update_post_term_cache' => false,
		];

		if ( '' !== $search ) {
			$exact_variation_id = ctype_digit( $search ) ? absint( $search ) : 0;
			if ( 0 === $exact_variation_id && function_exists( 'wc_get_product_id_by_sku' ) ) {
				$exact_variation_id = absint( wc_get_product_id_by_sku( $search ) );
			}
			if ( $exact_variation_id > 0 ) {
				$exact = wc_get_product( $exact_variation_id );
				$query_args['post__in'] = $exact instanceof \WC_Product_Variation && $parent_product_id === (int) $exact->get_parent_id()
					? [ $exact_variation_id ]
					: [ 0 ];
			} else {
				$query_args['s'] = $search;
			}
		}
		$meta_query = [];
		if ( '' !== $stock ) {
			$meta_query[] = [
				'key'   => '_stock_status',
				'value' => $stock,
			];
		}
		foreach ( $attribute_filters as $name => $value ) {
			$meta_query[] = [
				'key'   => 'attribute_' . $name,
				'value' => $value,
			];
		}
		if ( ! empty( $meta_query ) ) {
			$query_args['meta_query'] = $meta_query; // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Exact parent variations are bounded and paginated.
		}

		$query = new \WP_Query( $query_args );
		$items = [];
		foreach ( (array) $query->posts as $variation_id ) {
			$variation = wc_get_product( absint( $variation_id ) );
			if (
				! $variation instanceof \WC_Product_Variation
				|| $parent_product_id !== (int) $variation->get_parent_id()
			) {
				continue;
			}
			$item = $this->format_item( $variation );
			if ( is_array( $item ) ) {
				$items[] = $item;
			}
		}

		return [
			'items'            => $items,
			'attributeOptions' => array_values( $attribute_options ),
			'totalItems'       => max( count( $items ), (int) $query->found_posts ),
			'totalPages'       => max( 1, (int) $query->max_num_pages ),
		];
	}

	/** @return array|null|\WP_Error */
	public function get_product_summary( int $product_id ) {
		if ( ! $this->woocommerce_available() ) {
			return new \WP_Error( 'woocommerce_not_active', __( 'WooCommerce is not active.', 'fotogenic-ai' ), [ 'status' => 503 ] );
		}
		$target = Workflow_Item_Identity::resolve_target( 'product', $product_id );
		if ( is_wp_error( $target ) ) {
			return $target;
		}
		$product = wc_get_product( $product_id );
		if ( ! $product instanceof \WC_Product ) {
			return null;
		}

		$state  = $this->source_state( $product );
		$target = $this->apply_target_eligibility( $target, $state );
		return [
			'id'                   => (int) $product->get_id(),
			'label'                => \Fotogenic_AI\fotogenic_ai_plain_text_label( (string) $product->get_name() ),
			'sku'                  => (string) $product->get_sku(),
			'imageUrl'             => $state['imageUrl'],
			'sourceAttachmentId'   => $state['sourceAttachmentId'],
			'status'               => (string) $product->get_status(),
			'stockStatus'          => $state['stockStatus'],
			'eligible'             => $state['eligible'],
			'eligibilityReason'    => $state['eligibilityReason'],
			'hasVariations'        => $product->is_type( 'variable' ),
			'target'               => $target,
			'sourceAvailability'   => $state['sourceAvailability'],
		];
	}

	/** @return array|\WP_Error */
	public function get_product_context( int $product_id ) {
		if ( ! $this->woocommerce_available() ) {
			return new \WP_Error( 'woocommerce_not_active', __( 'WooCommerce is not active.', 'fotogenic-ai' ), [ 'status' => 503 ] );
		}
		$target = Workflow_Item_Identity::resolve_target( 'product', $product_id );
		if ( is_wp_error( $target ) ) {
			return $target;
		}
		$product = wc_get_product( $product_id );
		if ( ! $product instanceof \WC_Product ) {
			return new \WP_Error( 'fotogenic_ai_invalid_product', __( 'Product not found.', 'fotogenic-ai' ), [ 'status' => 404 ] );
		}

		$state           = $this->source_state( $product );
		$target          = $this->apply_target_eligibility( $target, $state );
		$featured        = $this->format_context_image( $state['featuredId'], $state['attachmentEligibility'] );
		$gallery         = [];
		foreach ( $state['galleryIds'] as $image_id ) {
			$gallery[] = $this->format_context_image( $image_id, $state['attachmentEligibility'] );
		}

		return [
			'product'        => [
				'id'                 => $product_id,
				'name'               => \Fotogenic_AI\fotogenic_ai_plain_text_label( (string) $product->get_name() ),
				'sku'                => (string) $product->get_sku(),
				'viewUrl'            => (string) get_permalink( $product_id ),
				'target'             => $target,
				'eligible'           => $state['eligible'],
				'eligibilityReason'  => $state['eligibilityReason'],
				'sourceAvailability' => $state['sourceAvailability'],
			],
			'images'         => [
				'featured' => $featured,
				'gallery'  => $gallery,
			],
		];
	}

	/**
	 * Plain array only; no WC_Product, WP_Post, or WP_Error instances.
	 */
	private function format_item( \WC_Product $product ): ?array {
		$target = Workflow_Item_Identity::resolve_target( 'product', (int) $product->get_id() );
		if ( is_wp_error( $target ) ) {
			return null;
		}
		$state      = $this->source_state( $product );
		$target     = $this->apply_target_eligibility( $target, $state );
		$attributes = [];
		$category_ids = method_exists( $product, 'get_category_ids' )
			? array_values( array_unique( array_filter( array_map( 'absint', (array) $product->get_category_ids( 'edit' ) ) ) ) )
			: [];
		$category_names = [];
		$category_ancestors = [];
		foreach ( $category_ids as $category_id ) {
			$category_ancestors = array_merge( $category_ancestors, get_ancestors( $category_id, 'product_cat', 'taxonomy' ) );
		}
		$category_ancestors = array_values( array_unique( array_map( 'absint', $category_ancestors ) ) );
		if ( ! empty( $category_ids ) ) {
			$terms = get_terms(
				[
					'taxonomy'   => 'product_cat',
					'include'    => $category_ids,
					'hide_empty' => false,
				]
			);
			if ( ! is_wp_error( $terms ) ) {
				$category_names = array_values(
					array_filter(
						array_map(
							static fn( $term ): string => sanitize_text_field( (string) $term->name ),
							$terms
						)
					)
				);
			}
		}
		$variation_count = $product->is_type( 'variable' ) && method_exists( $product, 'get_children' )
			? count( array_filter( array_map( 'absint', (array) $product->get_children() ) ) )
			: 0;
		if ( $product instanceof \WC_Product_Variation ) {
			foreach ( (array) $product->get_variation_attributes() as $name => $value ) {
				$label = function_exists( 'wc_attribute_label' ) ? wc_attribute_label( str_replace( 'attribute_', '', (string) $name ), $product ) : (string) $name;
				$attributes[] = [
					'name'  => sanitize_text_field( (string) $label ),
					'value' => sanitize_text_field( (string) $value ),
				];
			}
		}

		return [
			'id'                   => (int) $product->get_id(),
			'label'                => \Fotogenic_AI\fotogenic_ai_plain_text_label( (string) $product->get_name() ),
			'sku'                  => (string) $product->get_sku(),
			'imageUrl'             => $state['imageUrl'],
			'sourceAttachmentId'   => $state['sourceAttachmentId'],
			'stockStatus'          => $state['stockStatus'],
			'stockQuantity'        => method_exists( $product, 'get_stock_quantity' ) ? $product->get_stock_quantity( 'edit' ) : null,
			'attributes'           => $attributes,
			'categories'           => $category_ids,
			'categoryAncestors'    => $category_ancestors,
			'categoryNames'        => $category_names,
			'eligible'             => $state['eligible'],
			'eligibilityReason'    => $state['eligibilityReason'],
			'hasVariations'        => $product->is_type( 'variable' ),
			'variationCount'       => $variation_count,
			'target'               => $target,
			'sourceAvailability'   => $state['sourceAvailability'],
		];
	}

	/**
	 * @return array{featuredId:int,galleryIds:int[],sourceAttachmentId:int,imageUrl:string,stockStatus:string,eligible:bool,eligibilityReason:string,ineligibilityCode:string,sourceAvailability:array,attachmentEligibility:array<int,array>}
	 */
	private function source_state( \WC_Product $product ): array {
		$is_variation = $product instanceof \WC_Product_Variation || $product->is_type( 'variation' );
		$featured_id  = (int) $product->get_image_id( 'edit' );
		$gallery_ids  = $is_variation
			? []
			: array_values( array_unique( array_filter( array_map( 'absint', (array) $product->get_gallery_image_ids( 'edit' ) ) ) ) );
		$attachment_ids = array_values( array_unique( array_filter( array_merge( [ $featured_id ], $gallery_ids ) ) ) );
		$eligibility    = [];
		foreach ( $attachment_ids as $attachment_id ) {
			$eligibility[ $attachment_id ] = Image_Source_Resolver::instance()->get_workflow_attachment_eligibility( $attachment_id );
		}

		$eligible_featured = $featured_id > 0 && true === ( $eligibility[ $featured_id ]['eligible'] ?? false );
		$eligible_gallery  = array_values(
			array_filter(
				$gallery_ids,
				static fn( int $attachment_id ): bool => true === ( $eligibility[ $attachment_id ]['eligible'] ?? false )
			)
		);
		$eligible_ids      = array_values( array_unique( array_merge( $eligible_featured ? [ $featured_id ] : [], $eligible_gallery ) ) );
		$stock_status      = (string) $product->get_stock_status();
		$eligible          = ! empty( $eligible_ids );
		$reason            = '';
		$reason_code       = '';
		if ( $is_variation && 'outofstock' === $stock_status ) {
			$eligible    = false;
			$reason      = __( 'This variation is out of stock.', 'fotogenic-ai' );
			$reason_code = 'out_of_stock';
		} elseif ( empty( $attachment_ids ) ) {
			$reason      = $is_variation
				? __( 'Add a variation image in WooCommerce to select it.', 'fotogenic-ai' )
				: __( 'This Product has no eligible featured or gallery image.', 'fotogenic-ai' );
			$reason_code = 'missing_image';
		} elseif ( empty( $eligible_ids ) ) {
			$first       = reset( $eligibility );
			$reason      = is_array( $first ) && is_string( $first['eligibilityReason'] ?? null )
				? (string) $first['eligibilityReason']
				: __( 'This Product has no eligible workflow image.', 'fotogenic-ai' );
			$reason_code = 'invalid_image';
		}

		$preview_id = (int) ( $eligible_ids[0] ?? $attachment_ids[0] ?? 0 );
		$preview    = $preview_id > 0 ? \Fotogenic_AI\Media_Saver::get_attachment_view_url( $preview_id, 'thumbnail' ) : '';
		return [
			'featuredId'            => $featured_id,
			'galleryIds'            => $gallery_ids,
			'sourceAttachmentId'    => $preview_id,
			'imageUrl'              => $preview ? (string) $preview : '',
			'stockStatus'           => $stock_status,
			'eligible'              => $eligible,
			'eligibilityReason'     => $reason,
			'ineligibilityCode'     => $reason_code,
			'sourceAvailability'    => [
				'featuredCount' => $eligible_featured ? 1 : 0,
				'galleryCount'  => count( $eligible_gallery ),
				'totalCount'    => count( $eligible_ids ),
			],
			'attachmentEligibility' => $eligibility,
		];
	}

	private function format_context_image( int $attachment_id, array $eligibility ): array {
		$state     = isset( $eligibility[ $attachment_id ] ) && is_array( $eligibility[ $attachment_id ] )
			? $eligibility[ $attachment_id ]
			: [ 'eligible' => false, 'eligibilityReason' => '' ];
		$full_url  = $attachment_id > 0 ? \Fotogenic_AI\Media_Saver::get_attachment_view_url( $attachment_id, 'full' ) : '';
		$thumb_url = $attachment_id > 0 ? \Fotogenic_AI\Media_Saver::get_attachment_view_url( $attachment_id, 'thumbnail' ) : '';
		return [
			'attachmentId'      => $attachment_id,
			'fullUrl'           => $full_url ? (string) $full_url : '',
			'thumbUrl'          => $thumb_url ? (string) $thumb_url : '',
			'bytes'             => (int) ( $state['bytes'] ?? 0 ),
			'eligible'          => true === ( $state['eligible'] ?? false ),
			'eligibilityReason' => (string) ( $state['eligibilityReason'] ?? '' ),
		];
	}

	private function apply_target_eligibility( array $target, array $state ): array {
		$target['eligible']            = true === $state['eligible'];
		$target['ineligibilityReason'] = (string) $state['ineligibilityCode'];
		return $target;
	}

	/** @param mixed $product Product candidate. */
	private function normalize_list_product( $product ): ?\WC_Product {
		if ( ! $product instanceof \WC_Product ) {
			return null;
		}
		if ( $product instanceof \WC_Product_Variation || $product->is_type( 'variation' ) ) {
			$parent_id = (int) $product->get_parent_id();
			$product   = $parent_id > 0 ? wc_get_product( $parent_id ) : null;
		}
		if ( ! $product instanceof \WC_Product || 'publish' !== (string) $product->get_status( 'edit' ) ) {
			return null;
		}
		// phpcs:ignore WordPress.WP.Capabilities.Unknown -- WooCommerce maps this exact Product meta capability.
		return current_user_can( 'edit_product', (int) $product->get_id() ) ? $product : null;
	}

	private function product_matches_filters( \WC_Product $product, array $categories, array $stock_statuses ): bool {
		$stock_statuses = array_values(
			array_intersect(
				array_map( 'sanitize_key', $stock_statuses ),
				[ 'instock', 'outofstock', 'onbackorder' ]
			)
		);
		if ( ! empty( $stock_statuses ) && ! in_array( (string) $product->get_stock_status(), $stock_statuses, true ) ) {
			return false;
		}
		$categories = array_values( array_filter( array_map( 'sanitize_title', $categories ) ) );
		if ( empty( $categories ) ) {
			return true;
		}
		$category_ids = method_exists( $product, 'get_category_ids' ) ? (array) $product->get_category_ids( 'edit' ) : [];
		$terms = get_terms(
			[
				'taxonomy'   => 'product_cat',
				'include'    => array_values( array_filter( array_map( 'absint', $category_ids ) ) ),
				'fields'     => 'slugs',
				'hide_empty' => false,
			]
		);
		return ! is_wp_error( $terms ) && ! empty( array_intersect( $categories, array_map( 'sanitize_title', (array) $terms ) ) );
	}

	private function woocommerce_available(): bool {
		return function_exists( 'Fotogenic_AI\fotogenic_ai_woocommerce_available' )
			&& \Fotogenic_AI\fotogenic_ai_woocommerce_available();
	}

	/** @return array{items:array,totalItems:int,totalPages:int} */
	private function empty_list(): array {
		return [
			'items'      => [],
			'totalItems' => 0,
			'totalPages' => 1,
		];
	}
}
