<?php

namespace AIPT\Api\TaxonomyGenerator;

use AIPT\Api\BulkGenerator\Common\ProductDataFormatter;

class TaxonomyDataFormatter {
	/**
	 * @var TaxonomyDiscoveryService
	 */
	private $taxonomyDiscoveryService;

	public function __construct( ?TaxonomyDiscoveryService $taxonomyDiscoveryService = null ) {
		$this->taxonomyDiscoveryService = $taxonomyDiscoveryService ?: new TaxonomyDiscoveryService();
	}

	/**
	 * Build term context using selected content sources and sampled products.
	 *
	 * @param int    $term_id  Term ID.
	 * @param string $taxonomy Taxonomy name.
	 * @param array  $sources  Selected source keys.
	 * @param array  $options  Runtime options.
	 *
	 * @return array
	 */
	public function formatTermContext( int $term_id, string $taxonomy, array $sources, array $options ): array {
		$term_id  = absint( $term_id );
		$taxonomy = (string) sanitize_key( $taxonomy );

		if ( '' === $taxonomy ) {
			return [
				'term_id'       => $term_id,
				'term_name'     => '',
				'term_slug'     => '',
				'taxonomy'      => '',
				'child_terms'   => [],
				'product_count' => 0,
				'has_products'  => false,
			];
		}

		$term = $this->taxonomyDiscoveryService->getTermById( $term_id, $taxonomy );

		if ( ! $term ) {
			return [
				'term_id'       => $term_id,
				'term_name'     => '',
				'term_slug'     => '',
				'taxonomy'      => $taxonomy,
				'child_terms'   => [],
				'product_count' => 0,
				'has_products'  => false,
			];
		}

		$normalized_sources = $this->normalizeSources( $sources );
		$child_terms        = $this->getChildTerms( $term_id, $taxonomy );

		$context = [
			'term_id'       => (int) $term['term_id'],
			'term_name'     => (string) $term['name'],
			'term_slug'     => (string) $term['slug'],
			'taxonomy'      => $taxonomy,
			'child_terms'   => $child_terms,
			'product_count' => (int) $term['count'],
			'has_products'  => ( (int) $term['count'] ) > 0,
		];

		if ( $this->hasSource( $normalized_sources, 'term_description' ) ) {
			$context['existing_description'] = (string) $term['description'];
		}

		if ( $this->hasSource( $normalized_sources, 'parent_context' ) ) {
			$context['breadcrumb'] = (string) $term['breadcrumb'];
			$context['parent_term'] = '';

			if ( ! empty( $term['parent'] ) ) {
				$parent_term = $this->taxonomyDiscoveryService->getTermById( (int) $term['parent'], $taxonomy );
				if ( is_array( $parent_term ) ) {
					$context['parent_term'] = $this->arrayString( $parent_term, 'name' );
				}
			}
		}

		$product_sources = [
			'product_names',
			'product_descriptions',
			'product_attributes',
			'product_prices',
			'product_tags',
		];

		$has_product_source = false;
		foreach ( $product_sources as $product_source ) {
			if ( $this->hasSource( $normalized_sources, $product_source ) ) {
				$has_product_source = true;
				break;
			}
		}

		if ( ! $context['has_products'] || ! $has_product_source ) {
			return $context;
		}

		$max_samples = $this->resolveMaxSamples( $options );
		$products    = $this->getSampledProducts( $taxonomy, $this->arrayString( $term, 'slug' ), $max_samples );

		if ( empty( $products ) ) {

			return $context;
		}

		$context['product_summary'] = $this->buildProductSummary( $products, $normalized_sources );

		return $context;
	}

	/**
	 * @param array $sources Selected source list.
	 *
	 * @return string[]
	 */
	private function normalizeSources( array $sources ): array {
		$normalized = [];

		foreach ( $sources as $source ) {
			if ( ! is_string( $source ) ) {
				continue;
			}

			$sanitized = sanitize_key( $source );
			if ( '' === $sanitized ) {
				continue;
			}

			$normalized[] = $sanitized;
		}

		return array_values( array_unique( $normalized ) );
	}

	/**
	 * @param array  $sources Source list.
	 * @param string $source  Source key.
	 *
	 * @return bool
	 */
	private function hasSource( array $sources, string $source ): bool {
		return in_array( $source, $sources, true );
	}

	/**
	 * @param array $options Runtime options.
	 *
	 * @return int
	 */
	private function resolveMaxSamples( array $options ): int {
		$max_samples = isset( $options['maxProductSamples'] ) ? absint( $options['maxProductSamples'] ) : 10;

		if ( $max_samples < 1 ) {
			$max_samples = 1;
		}

		return min( 20, $max_samples );
	}

	/**
	 * @param int    $term_id  Term ID.
	 * @param string $taxonomy Taxonomy name.
	 *
	 * @return string[]
	 */
	private function getChildTerms( int $term_id, string $taxonomy ): array {
		$children = get_terms(
			[
				'taxonomy'   => $taxonomy,
				'hide_empty' => false,
				'parent'     => $term_id,
				'fields'     => 'names',
			]
		);

		if ( is_wp_error( $children ) || empty( $children ) ) {
			return [];
		}

		return array_values( array_filter( array_map( 'strval', $children ) ) );
	}

	/**
	 * @param string $taxonomy Taxonomy name.
	 * @param string $slug     Term slug.
	 * @param int    $limit    Sample limit.
	 *
	 * @return array
	 */
	private function getSampledProducts( string $taxonomy, string $slug, int $limit ): array {
		if ( '' === $slug || strpos( $taxonomy, 'pa_' ) === 0 ) {
			return [];
		}

		$args = [
			'limit'   => $limit,
			'orderby' => 'date',
			'order'   => 'DESC',
			'return'  => 'objects',
			'status'  => [ 'publish' ],
		];

		if ( 'product_cat' === $taxonomy ) {
			$args['category'] = [ $slug ];
		} elseif ( 'product_tag' === $taxonomy ) {
			$args['tag'] = [ $slug ];
		} else {
			$args['tax_query'] = [
				[
					'taxonomy' => $taxonomy,
					'field'    => 'slug',
					'terms'    => [ $slug ],
				],
			];
		}

		$products = wc_get_products( $args );

		if ( ! is_array( $products ) || empty( $products ) ) {
			return [];
		}

		return $products;
	}

	/**
	 * @param array $products Sampled WC_Product objects.
	 * @param array $sources  Selected source keys.
	 *
	 * @return array
	 */
	private function buildProductSummary( array $products, array $sources ): array {
		$summary              = [];
		$names                = [];
		$descriptions         = [];
		$price_values         = [];
		$collected_attributes = [];
		$collected_tags       = [];

		foreach ( $products as $product ) {
			if ( ! $product || ! is_object( $product ) || ! method_exists( $product, 'get_name' ) ) {
				continue;
			}

			$formatted_product = ProductDataFormatter::getFormattedProductData( $product );

			if ( $this->hasSource( $sources, 'product_names' ) ) {
				$name = isset( $formatted_product['name'] ) ? trim( (string) $formatted_product['name'] ) : '';
				if ( '' !== $name ) {
					$names[] = $name;
				}
			}

			if ( $this->hasSource( $sources, 'product_descriptions' ) ) {
				$description = method_exists( $product, 'get_short_description' ) ? (string) $product->get_short_description() : '';
				if ( '' === trim( $description ) && method_exists( $product, 'get_description' ) ) {
					$description = (string) $product->get_description();
				}

				$description = trim( wp_strip_all_tags( $description ) );
				if ( '' !== $description ) {
					$descriptions[] = $description;
				}
			}

			if ( $this->hasSource( $sources, 'product_prices' ) && method_exists( $product, 'get_price' ) ) {
				$raw_price = $product->get_price();
				if ( '' !== $raw_price && null !== $raw_price ) {
					$price_values[] = (float) $raw_price;
				}
			}

			if ( $this->hasSource( $sources, 'product_attributes' ) && isset( $formatted_product['attributes'] ) && is_array( $formatted_product['attributes'] ) ) {
				$this->collectAttributes( $collected_attributes, $formatted_product['attributes'] );
			}

			if ( $this->hasSource( $sources, 'product_tags' ) && method_exists( $product, 'get_tag_ids' ) ) {
				$tag_ids = $product->get_tag_ids();
				foreach ( $tag_ids as $tag_id ) {
					$tag_term = get_term( $tag_id, 'product_tag' );
					if ( $tag_term && ! is_wp_error( $tag_term ) ) {
						$collected_tags[] = $tag_term->name;
					}
				}
			}
		}

		if ( $this->hasSource( $sources, 'product_names' ) ) {
			$summary['names'] = array_values( array_unique( $names ) );
		}

		if ( $this->hasSource( $sources, 'product_descriptions' ) ) {
			$summary['descriptions'] = array_values( array_unique( $descriptions ) );
		}

		if ( $this->hasSource( $sources, 'product_prices' ) ) {
			if ( empty( $price_values ) ) {
				$summary['price_range'] = [
					'min' => null,
					'max' => null,
				];
			} else {
				$summary['price_range'] = [
					'min' => round( min( $price_values ), 2 ),
					'max' => round( max( $price_values ), 2 ),
				];
			}
		}

		if ( $this->hasSource( $sources, 'product_attributes' ) ) {
			$summary['attributes'] = $this->formatCollectedAttributes( $collected_attributes );
		}

		if ( $this->hasSource( $sources, 'product_tags' ) ) {
			$summary['tags'] = array_values( array_unique( $collected_tags ) );
		}

		return $summary;
	}

	/**
	 * @param array $collected Reference map of attribute name => values.
	 * @param array $attributes Product attributes.
	 */
	private function collectAttributes( array &$collected, array $attributes ): void {
		foreach ( $attributes as $name => $values ) {
			$attribute_name = trim( (string) $name );

			if ( '' === $attribute_name || strpos( strtolower( $attribute_name ), 'pa_' ) === 0 ) {
				continue;
			}

			if ( ! isset( $collected[ $attribute_name ] ) ) {
				$collected[ $attribute_name ] = [];
			}

			$raw_value = is_array( $values ) ? implode( ', ', $values ) : (string) $values;
			$parts     = array_filter( array_map( 'trim', explode( ',', $raw_value ) ) );

			foreach ( $parts as $part ) {
				if ( '' !== $part ) {
					$collected[ $attribute_name ][] = $part;
				}
			}
		}
	}

	/**
	 * @param array $collected Collected attribute map.
	 *
	 * @return string[]
	 */
	private function formatCollectedAttributes( array $collected ): array {
		if ( empty( $collected ) ) {
			return [];
		}

		$formatted = [];
		foreach ( $collected as $name => $values ) {
			$unique_values = array_values( array_unique( array_filter( array_map( 'strval', (array) $values ) ) ) );
			if ( empty( $unique_values ) ) {
				continue;
			}

			$formatted[] = $name . ': ' . implode( ', ', $unique_values );
		}

		return $formatted;
	}

	/**
	 * @param array  $data Array data source.
	 * @param string $key  Array key.
	 *
	 * @return string
	 */
	private function arrayString( array $data, string $key ): string {
		if ( ! isset( $data[ $key ] ) || null === $data[ $key ] ) {
			return '';
		}

		return is_string( $data[ $key ] ) ? $data[ $key ] : (string) $data[ $key ];
	}
}
