<?php
defined( 'ABSPATH' ) || exit;

/**
 * Product-category term query for the Product Categories widget.
 *
 * Mirrors Jalpi_Woo_Query's role for products: it turns sanitized widget
 * content into get_terms() args, and returns either real WP_Term objects or
 * Jalpi_Woo_Term_Placeholder stand-ins when WooCommerce is inactive — so the
 * editor and frontend always render a populated grid.
 *
 * Kept separate from the element so any future AJAX/carousel handler can build
 * the identical term set without duplicating the filter logic.
 */
class Jalpi_Woo_Terms {

	const TAXONOMY = 'product_cat';

	/**
	 * Build get_terms() args from a widget's content array.
	 *
	 * @param array $content Sanitized node content.
	 */
	public static function build_args( array $content ): array {
		$source = (string) ( $content['source'] ?? 'all' );
		$source = in_array( $source, [ 'all', 'selected', 'parent', 'child', 'featured' ], true ) ? $source : 'all';

		$number = max( 0, min( 100, (int) ( $content['number'] ?? 12 ) ) );

		$args = [
			'taxonomy'   => self::TAXONOMY,
			'hide_empty' => ! empty( $content['hideEmpty'] ),
			'number'     => $number ?: 0, // 0 = no limit
		];

		// ── Order ────────────────────────────────────────────────────────────
		$orderby = (string) ( $content['orderBy'] ?? 'name' );
		$order   = strtoupper( (string) ( $content['order'] ?? 'ASC' ) );
		$args['order'] = 'DESC' === $order ? 'DESC' : 'ASC';

		switch ( $orderby ) {
			case 'id':
				$args['orderby'] = 'term_id';
				break;
			case 'count':
				$args['orderby'] = 'count';
				break;
			case 'menu_order':
				// WooCommerce stores category order in its own meta key; without
				// it get_terms() would silently fall back to name order.
				$args['orderby']  = 'meta_value_num';
				$args['meta_key'] = 'order'; // phpcs:ignore WordPress.DB.SlowDBQuery
				break;
			case 'slug':
				$args['orderby'] = 'slug';
				break;
			case 'name':
			default:
				$args['orderby'] = 'name';
				break;
		}

		// ── Source ───────────────────────────────────────────────────────────
		switch ( $source ) {
			case 'parent':
				// Top-level categories only.
				$args['parent'] = 0;
				break;

			case 'child':
				// Children of a specific parent; falls back to "any child" when
				// no parent is given (child_of => 0 would mean "all", so guard).
				$parent = (int) ( $content['parentCategory'] ?? 0 );
				if ( $parent > 0 ) {
					$args['child_of'] = $parent;
				} else {
					$args['exclude_tree'] = self::top_level_ids();
				}
				break;

			case 'selected':
				$ids = self::ids( $content['includeCategories'] ?? '' );
				// An empty include list must match nothing, not everything.
				$args['include'] = $ids ?: [ 0 ];
				// Preserve the author's chosen order for an explicit selection.
				if ( $ids ) {
					$args['orderby'] = 'include';
					unset( $args['meta_key'] );
				}
				break;

			case 'featured':
				$featured = self::featured_ids();
				$args['include'] = $featured ?: [ 0 ];
				break;

			case 'all':
			default:
				break;
		}

		// ── Include / exclude by ID ──────────────────────────────────────────
		// Intersect rather than overwrite so a source-defined include survives.
		$include = self::ids( $content['categoryIds'] ?? '' );
		if ( $include ) {
			$args['include'] = isset( $args['include'] )
				? ( array_values( array_intersect( (array) $args['include'], $include ) ) ?: [ 0 ] )
				: $include;
		}

		$exclude = self::ids( $content['excludeCategories'] ?? '' );
		if ( $exclude ) {
			// Set only when the user lists categories to exclude in the widget.
			// phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_exclude -- opt-in widget setting, empty by default
			$args['exclude'] = $exclude;
		}

		/**
		 * Filter the Product Categories term-query args.
		 *
		 * @param array $args    get_terms() args.
		 * @param array $content The widget's content array.
		 */
		return apply_filters( 'jalpi_product_categories_args', $args, $content );
	}

	/**
	 * Run the term query.
	 *
	 * @return array<int,WP_Term|Jalpi_Woo_Term_Placeholder>
	 */
	public static function run( array $args, int $fallback_count = 6 ): array {
		if ( ! self::woo_active() ) {
			return Jalpi_Woo_Term_Placeholder::make( $fallback_count );
		}

		$terms = get_terms( $args );
		if ( is_wp_error( $terms ) || ! is_array( $terms ) ) {
			return [];
		}
		return $terms;
	}

	public static function woo_active(): bool {
		return class_exists( 'WooCommerce' ) && taxonomy_exists( self::TAXONOMY );
	}

	/**
	 * Category thumbnail URL for a term, at the requested size.
	 * Falls back to WooCommerce's own placeholder when the term has no image.
	 */
	public static function image_url( $term, string $size = 'woocommerce_thumbnail' ): string {
		if ( $term instanceof Jalpi_Woo_Term_Placeholder ) {
			return $term->get_image_url();
		}

		$thumb_id = (int) get_term_meta( $term->term_id, 'thumbnail_id', true );
		if ( $thumb_id ) {
			$src = wp_get_attachment_image_url( $thumb_id, $size );
			if ( $src ) {
				return $src;
			}
		}

		return function_exists( 'wc_placeholder_img_src' ) ? wc_placeholder_img_src( $size ) : '';
	}

	/** Full <img> markup where available, so srcset/sizes are preserved. */
	public static function image_html( $term, string $size, bool $lazy, string $class ): string {
		if ( $term instanceof Jalpi_Woo_Term_Placeholder ) {
			return '<img class="' . esc_attr( $class ) . '" src="' . esc_url( $term->get_image_url() ) . '"'
				. ' alt="' . esc_attr( $term->name ) . '" loading="' . ( $lazy ? 'lazy' : 'eager' ) . '" />';
		}

		$thumb_id = (int) get_term_meta( $term->term_id, 'thumbnail_id', true );
		if ( $thumb_id ) {
			$html = wp_get_attachment_image( $thumb_id, $size, false, [
				'class'   => $class,
				'loading' => $lazy ? 'lazy' : 'eager',
				'alt'     => $term->name,
			] );
			if ( $html ) {
				return $html;
			}
		}

		$src = self::image_url( $term, $size );
		if ( ! $src ) {
			return '';
		}
		return '<img class="' . esc_attr( $class ) . '" src="' . esc_url( $src ) . '"'
			. ' alt="' . esc_attr( $term->name ) . '" loading="' . ( $lazy ? 'lazy' : 'eager' ) . '" />';
	}

	// ── Helpers ──────────────────────────────────────────────────────────────

	/** Comma/space-separated list → positive int term IDs. */
	private static function ids( $raw ): array {
		$parts = is_array( $raw )
			? $raw
			: ( preg_split( '/[\s,]+/', (string) $raw, -1, PREG_SPLIT_NO_EMPTY ) ?: [] );
		return array_values( array_filter( array_map( 'absint', $parts ) ) );
	}

	/** All top-level category IDs — used to exclude them in "child" mode. */
	private static function top_level_ids(): array {
		if ( ! self::woo_active() ) {
			return [];
		}
		$terms = get_terms( [
			'taxonomy'   => self::TAXONOMY,
			'parent'     => 0,
			'hide_empty' => false,
			'fields'     => 'ids',
		] );
		return is_wp_error( $terms ) ? [] : array_map( 'absint', (array) $terms );
	}

	/**
	 * "Featured" categories. WooCommerce has no core featured flag for terms,
	 * so this reads a conventional `featured` term meta (set by several themes
	 * and by Jalpi kits) and returns [] when none are marked.
	 */
	private static function featured_ids(): array {
		if ( ! self::woo_active() ) {
			return [];
		}
		$terms = get_terms( [
			'taxonomy'   => self::TAXONOMY,
			'hide_empty' => false,
			'fields'     => 'ids',
			'meta_query' => [ // phpcs:ignore WordPress.DB.SlowDBQuery
				[
					'key'     => 'featured',
					'value'   => '1',
					'compare' => '=',
				],
			],
		] );
		return is_wp_error( $terms ) ? [] : array_map( 'absint', (array) $terms );
	}
}

/**
 * Stand-in category used when WooCommerce is not active.
 *
 * Exposes the same public properties the element reads from a WP_Term
 * (term_id, name, slug, count, description) so the card renderer needs no
 * WooCommerce-specific branching.
 */
class Jalpi_Woo_Term_Placeholder {

	public int    $term_id = 0;
	public string $name;
	public string $slug;
	public int    $count;
	public string $description;

	private int $index;

	public function __construct( int $index, string $name, int $count ) {
		$this->index       = $index;
		$this->name        = $name;
		$this->slug        = sanitize_title( $name );
		$this->count       = $count;
		$this->description = __( 'Sample category description shown while WooCommerce is unavailable.', 'jalpi-page-builder' );
	}

	/** @return array<int,self> */
	public static function make( int $count ): array {
		$count  = max( 1, min( 24, $count ) );
		$names  = [
			'Clothing', 'Accessories', 'Footwear', 'Bags',
			'Electronics', 'Home & Living', 'Beauty', 'Outdoors',
		];
		$counts = [ 24, 12, 18, 9, 31, 15, 7, 21 ];

		$out = [];
		for ( $i = 0; $i < $count; $i++ ) {
			$out[] = new self( $i + 1, $names[ $i % count( $names ) ], $counts[ $i % count( $counts ) ] );
		}
		return $out;
	}

	/** Inline SVG placeholder — no HTTP request, no missing-asset flash. */
	public function get_image_url(): string {
		$hues = [ '#eef2f7', '#f3eef7', '#eef7f2', '#f7f3ee' ];
		$hue  = $hues[ $this->index % count( $hues ) ];
		$svg  = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 400">'
			. '<rect width="400" height="400" fill="' . $hue . '"/>'
			. '<path d="M120 160h70l18 26h72v94H120z" fill="#c3ccd8"/></svg>';
		return 'data:image/svg+xml;base64,' . base64_encode( $svg ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions
	}
}
