<?php
/**
 * Taxonomy Discovery Service
 *
 * Discovers WooCommerce product taxonomies and their terms with hierarchy support.
 *
 * @package AIPT\Api\TaxonomyGenerator
 */

namespace AIPT\Api\TaxonomyGenerator;

/**
 * Service for discovering and retrieving WooCommerce product taxonomies and terms.
 *
 * Handles dynamic discovery of product taxonomies, term retrieval with hierarchy,
 * breadcrumb generation, and metadata enrichment.
 */
class TaxonomyDiscoveryService {

	/**
	 * Internal taxonomies to exclude from discovery.
	 *
	 * @var string[]
	 */
	private const EXCLUDED_TAXONOMIES = [
		'product_type',
		'product_visibility',
		'product_shipping_class',
	];

	/**
	 * Maximum depth for breadcrumb parent traversal to prevent infinite loops.
	 *
	 * @var int
	 */
	private const MAX_BREADCRUMB_DEPTH = 100;

	/**
	 * Discovers all product taxonomies excluding internal and attribute taxonomies.
	 *
	 * Returns an array of taxonomy metadata including name, label, hierarchy, and term count.
	 *
	 * @return array[] Array of taxonomy objects with keys: name, label, hierarchical, count
	 */
	public function discoverTaxonomies(): array {
		$taxonomies = get_object_taxonomies( 'product', 'objects' );

		if ( empty( $taxonomies ) ) {
			return [];
		}

		$result = [];

		foreach ( $taxonomies as $taxonomy ) {

			if ( in_array( $taxonomy->name, self::EXCLUDED_TAXONOMIES, true ) ) {
				continue;
			}

			if ( strpos( $taxonomy->name, 'pa_' ) === 0 ) {
				continue;
			}

			$term_count = wp_count_terms(
				[
					'taxonomy'  => $taxonomy->name,
					'hide_empty' => false,
				]
			);

			if ( is_wp_error( $term_count ) || (int) $term_count === 0 ) {
				continue;
			}

			$result[] = [
				'name'          => $taxonomy->name,
				'label'         => $taxonomy->label,
				'hierarchical'  => (bool) $taxonomy->hierarchical,
				'count'         => (int) $term_count,
			];
		}

		return $result;
	}

	/**
	 * Retrieves terms for a given taxonomy with optional filtering and hierarchy.
	 *
	 * Supports pagination, search, and hierarchical tree building for hierarchical taxonomies.
	 *
	 * @param string $taxonomy Taxonomy name (e.g., 'product_cat', 'product_tag').
	 * @param array  $args     Optional arguments:
	 *                         - search (string): Search term name/slug
	 *                         - page (int): Page number (1-indexed)
	 *                         - per_page (int): Terms per page
	 *                         - hide_empty (bool): Hide empty terms
	 *
	 * @return array[] Array of term objects with hierarchy and breadcrumbs
	 */
	public function getTerms( string $taxonomy, array $args = [] ): array {

		$taxonomies = get_object_taxonomies( 'product' );
		if ( ! in_array( $taxonomy, $taxonomies, true ) ) {
			return [ 'terms' => [], 'total' => 0, 'page' => 1, 'per_page' => 0 ];
		}

		if ( in_array( $taxonomy, self::EXCLUDED_TAXONOMIES, true ) ) {
			return [ 'terms' => [], 'total' => 0, 'page' => 1, 'per_page' => 0 ];
		}

		if ( strpos( $taxonomy, 'pa_' ) === 0 ) {
			return [ 'terms' => [], 'total' => 0, 'page' => 1, 'per_page' => 0 ];
		}

		$get_terms_args = [
			'taxonomy'   => $taxonomy,
			'hide_empty' => isset( $args['hide_empty'] ) ? (bool) $args['hide_empty'] : false,
		];

		if ( ! empty( $args['search'] ) ) {
			$get_terms_args['search'] = sanitize_text_field( $args['search'] );
		}

		$per_page = ! empty( $args['per_page'] ) ? (int) $args['per_page'] : 50;
		$per_page = min( max( $per_page, 1 ), 5000 );
		$get_terms_args['number'] = $per_page;

		$page = ! empty( $args['page'] ) ? max( 1, (int) $args['page'] ) : 1;
		if ( $page > 1 ) {
			$get_terms_args['offset'] = ( $page - 1 ) * $per_page;
		}

		$count_args = [
			'taxonomy'   => $taxonomy,
			'hide_empty' => $get_terms_args['hide_empty'],
		];
		if ( isset( $get_terms_args['search'] ) ) {
			$count_args['search'] = $get_terms_args['search'];
		}
		$total = wp_count_terms( $count_args );
		$total = is_wp_error( $total ) ? 0 : (int) $total;

		$terms = get_terms( $get_terms_args );

		if ( is_wp_error( $terms ) || empty( $terms ) ) {
			return [
				'terms'    => [],
				'total'    => $total,
				'page'     => $page,
				'per_page' => $per_page,
			];
		}

		$taxonomy_obj    = get_taxonomy( $taxonomy );
		$is_hierarchical = $taxonomy_obj && $taxonomy_obj->hierarchical;

		$result = [];
		foreach ( $terms as $term ) {
			$term_data = [
				'term_id'         => (int) $term->term_id,
				'name'            => $term->name,
				'slug'            => $term->slug,
				'description'     => $term->description,
				'count'           => (int) $term->count,
				'parent'          => (int) $term->parent,
				'has_description' => ! empty( $term->description ),
				'breadcrumb'      => '',
				'children'        => [],
			];

			if ( $is_hierarchical ) {
				$term_data['breadcrumb'] = $this->buildBreadcrumb( $term->term_id, $taxonomy );
			}

			$result[] = $term_data;
		}

		return [
			'terms'    => $result,
			'total'    => $total,
			'page'     => $page,
			'per_page' => $per_page,
		];
	}

	/**
	 * Retrieves a single term by ID with full metadata.
	 *
	 * @param int    $term_id   Term ID.
	 * @param string $taxonomy  Taxonomy name.
	 *
	 * @return array|null Term object with metadata, or null if not found
	 */
	public function getTermById( int $term_id, string $taxonomy ): ?array {

		$taxonomies = get_object_taxonomies( 'product' );
		if ( ! in_array( $taxonomy, $taxonomies, true ) ) {
			return null;
		}

		if ( in_array( $taxonomy, self::EXCLUDED_TAXONOMIES, true ) ) {
			return null;
		}

		if ( strpos( $taxonomy, 'pa_' ) === 0 ) {
			return null;
		}

		$term = get_term( $term_id, $taxonomy );

		if ( is_wp_error( $term ) || ! $term ) {
			return null;
		}

		$taxonomy_obj = get_taxonomy( $taxonomy );
		$is_hierarchical = $taxonomy_obj && $taxonomy_obj->hierarchical;

		$term_data = [
			'term_id'         => (int) $term->term_id,
			'name'            => $term->name,
			'slug'            => $term->slug,
			'description'     => $term->description,
			'count'           => (int) $term->count,
			'parent'          => (int) $term->parent,
			'has_description' => ! empty( $term->description ),
			'breadcrumb'      => '',
			'children'        => [],
		];

		if ( $is_hierarchical ) {
			$term_data['breadcrumb'] = $this->buildBreadcrumb( $term->term_id, $taxonomy );
		}

		return $term_data;
	}

	/**
	 * Builds a breadcrumb string for a term by walking up the parent chain.
	 *
	 * Example: 'Women > Clothing > Dresses'
	 *
	 * @param int    $term_id   Term ID.
	 * @param string $taxonomy  Taxonomy name.
	 *
	 * @return string Breadcrumb string with ' > ' separator
	 */
	private function buildBreadcrumb( int $term_id, string $taxonomy ): string {
		$breadcrumb = [];
		$current_id = $term_id;
		$visited    = [];

		while ( $current_id > 0 ) {

			if ( isset( $visited[ $current_id ] ) || count( $visited ) >= self::MAX_BREADCRUMB_DEPTH ) {
				break;
			}

			$visited[ $current_id ] = true;

			$term = get_term( $current_id, $taxonomy );

			if ( is_wp_error( $term ) || ! $term ) {
				break;
			}

			array_unshift( $breadcrumb, $term->name );
			$current_id = (int) $term->parent;
		}

		return implode( ' > ', $breadcrumb );
	}

}
