<?php
/**
 * Data Source for WooCommerce products.
 *
 * Adapter between wc_get_products() and the export engine.
 * Extracts all product fields (including variables and variations)
 * and normalizes them into flat rows for CSV/XLSX output.
 */

namespace RawTypeOE\Free;

// Prevent direct access.
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Provides product data extraction and normalization for export.
 */
class Data_Source_Products {

	use Data_Source_Trait;

	/**
	 * Get all available columns for products, grouped by category.
	 *
	 * @return array Associative array of field_id => { label, group }.
	 */
	public function get_available_columns() {
		$columns = array(
			// --- Basic product data ---
			'product_id'        => array(
				'label' => __( 'Product ID', 'rawtype-order-exporter-woocommerce' ),
				'group' => 'basic',
			),
			'name'              => array(
				'label' => __( 'Name', 'rawtype-order-exporter-woocommerce' ),
				'group' => 'basic',
			),
			'slug'              => array(
				'label' => __( 'Slug', 'rawtype-order-exporter-woocommerce' ),
				'group' => 'basic',
			),
			'type'              => array(
				'label' => __( 'Type', 'rawtype-order-exporter-woocommerce' ),
				'group' => 'basic',
			),
			'status'            => array(
				'label' => __( 'Status', 'rawtype-order-exporter-woocommerce' ),
				'group' => 'basic',
			),
			'description'       => array(
				'label' => __( 'Description', 'rawtype-order-exporter-woocommerce' ),
				'group' => 'basic',
			),
			'short_description' => array(
				'label' => __( 'Short Description', 'rawtype-order-exporter-woocommerce' ),
				'group' => 'basic',
			),
			'permalink'         => array(
				'label' => __( 'Product URL', 'rawtype-order-exporter-woocommerce' ),
				'group' => 'basic',
			),
			'date_created'      => array(
				'label' => __( 'Date Created', 'rawtype-order-exporter-woocommerce' ),
				'group' => 'basic',
			),
			'date_modified'     => array(
				'label' => __( 'Date Modified', 'rawtype-order-exporter-woocommerce' ),
				'group' => 'basic',
			),

			// --- Pricing ---
			'sku'               => array(
				'label' => __( 'SKU', 'rawtype-order-exporter-woocommerce' ),
				'group' => 'pricing',
			),
			'regular_price'     => array(
				'label' => __( 'Regular Price', 'rawtype-order-exporter-woocommerce' ),
				'group' => 'pricing',
			),
			'sale_price'        => array(
				'label' => __( 'Sale Price', 'rawtype-order-exporter-woocommerce' ),
				'group' => 'pricing',
			),
			'price'             => array(
				'label' => __( 'Current Price', 'rawtype-order-exporter-woocommerce' ),
				'group' => 'pricing',
			),
			'on_sale'           => array(
				'label' => __( 'On Sale', 'rawtype-order-exporter-woocommerce' ),
				'group' => 'pricing',
			),
			'sale_price_from'   => array(
				'label' => __( 'Sale Start Date', 'rawtype-order-exporter-woocommerce' ),
				'group' => 'pricing',
			),
			'sale_price_to'     => array(
				'label' => __( 'Sale End Date', 'rawtype-order-exporter-woocommerce' ),
				'group' => 'pricing',
			),

			// --- Stock ---
			'stock_quantity'    => array(
				'label' => __( 'Stock Quantity', 'rawtype-order-exporter-woocommerce' ),
				'group' => 'stock',
			),
			'stock_status'      => array(
				'label' => __( 'Stock Status', 'rawtype-order-exporter-woocommerce' ),
				'group' => 'stock',
			),
			'manage_stock'      => array(
				'label' => __( 'Manage Stock', 'rawtype-order-exporter-woocommerce' ),
				'group' => 'stock',
			),
			'backorders'        => array(
				'label' => __( 'Backorders', 'rawtype-order-exporter-woocommerce' ),
				'group' => 'stock',
			),
			'low_stock_amount'  => array(
				'label' => __( 'Low Stock Threshold', 'rawtype-order-exporter-woocommerce' ),
				'group' => 'stock',
			),

			// --- Physical ---
			'weight'            => array(
				'label' => __( 'Weight', 'rawtype-order-exporter-woocommerce' ),
				'group' => 'physical',
			),
			'length'            => array(
				'label' => __( 'Length', 'rawtype-order-exporter-woocommerce' ),
				'group' => 'physical',
			),
			'width'             => array(
				'label' => __( 'Width', 'rawtype-order-exporter-woocommerce' ),
				'group' => 'physical',
			),
			'height'            => array(
				'label' => __( 'Height', 'rawtype-order-exporter-woocommerce' ),
				'group' => 'physical',
			),

			// --- Taxonomy ---
			'categories'        => array(
				'label' => __( 'Categories', 'rawtype-order-exporter-woocommerce' ),
				'group' => 'taxonomy',
			),
			'tags'              => array(
				'label' => __( 'Tags', 'rawtype-order-exporter-woocommerce' ),
				'group' => 'taxonomy',
			),

			// --- Media ---
			'image_url'         => array(
				'label' => __( 'Image URL', 'rawtype-order-exporter-woocommerce' ),
				'group' => 'media',
			),
			'gallery_urls'      => array(
				'label' => __( 'Gallery URLs', 'rawtype-order-exporter-woocommerce' ),
				'group' => 'media',
			),

			// --- Attributes ---
			'attributes'        => array(
				'label' => __( 'Attributes', 'rawtype-order-exporter-woocommerce' ),
				'group' => 'attributes',
			),

			// --- Sales ---
			'total_sales'       => array(
				'label' => __( 'Total Sales', 'rawtype-order-exporter-woocommerce' ),
				'group' => 'sales',
			),

			// --- Variation (only in expanded mode) ---
			'variation_id'      => array(
				'label' => __( 'Variation ID', 'rawtype-order-exporter-woocommerce' ),
				'group' => 'variation',
			),
			'variation_attrs'   => array(
				'label' => __( 'Variation Attributes', 'rawtype-order-exporter-woocommerce' ),
				'group' => 'variation',
			),
			'parent_id'         => array(
				'label' => __( 'Parent Product ID', 'rawtype-order-exporter-woocommerce' ),
				'group' => 'variation',
			),
		);

		/**
		 * Filter available product columns.
		 *
		 * @param array $columns Column definitions.
		 */
		return apply_filters( 'rawtype_oe_columns_products_data_source', $columns );
	}

	/**
	 * Fetch products using wc_get_products().
	 *
	 * @param array $args Arguments compatible with wc_get_products().
	 * @return \WC_Product[] Array of WC_Product objects.
	 */
	public function fetch( $args ) {
		return wc_get_products( $args );
	}

	/**
	 * Count products matching args.
	 *
	 * @param array $args Query arguments.
	 * @return int Total count.
	 */
	public function count( $args ) {
		$count_args = array_merge(
			$args,
			array(
				'limit'    => 1,
				'return'   => 'ids',
				'paginate' => true,
			)
		);

		$count_result = wc_get_products( $count_args );

		if ( is_object( $count_result ) && isset( $count_result->total ) ) {
			return absint( $count_result->total );
		}

		return 0;
	}

	/**
	 * Normalize an array of WC_Product objects into flat rows.
	 *
	 * @param \WC_Product[] $products       Products to normalize.
	 * @param array         $columns        Column IDs to include.
	 * @param string        $variation_mode 'flat' or 'expanded'.
	 * @return array Array of associative arrays (rows).
	 */
	public function normalize( $products, $columns, $variation_mode = 'flat' ) {
		$rows          = array();
		$use_expanded  = 'expanded' === $variation_mode;
		$variation_map = $use_expanded ? $this->preload_variations( $products ) : array();

		foreach ( $products as $product ) {
			$row    = $this->extract_product_data( $product, $columns );
			$row    = $this->filter_columns( $row, $columns );
			$rows[] = $row;

			// If variable product and expanded mode, add variations.
			if ( $use_expanded && method_exists( $product, 'is_type' ) && $product->is_type( 'variable' ) ) {
				foreach ( $product->get_children() as $variation_id ) {
					if ( isset( $variation_map[ $variation_id ] ) ) {
						$variation = $variation_map[ $variation_id ];
					} else {
						// Batch miss (e.g. status filtered) — hydrate directly so
						// no variation row is silently dropped.
						$variation = wc_get_product( $variation_id );
					}
					if ( ! $variation ) {
						continue;
					}

					$var_row              = $this->extract_product_data( $variation, $columns );
					$var_row['parent_id'] = $product->get_id();
					$var_row              = $this->filter_columns( $var_row, $columns );
					$rows[]               = $var_row;
				}
			}
		}

		return $rows;
	}

	/**
	 * Batch-load all variations for a chunk of variable products.
	 *
	 * Replaces the previous one-query-per-variation pattern, which issued
	 * thousands of database queries on catalogs with large variable products.
	 *
	 * @param \WC_Product[] $products Chunk of parent products.
	 * @return array<int, \WC_Product> Variations keyed by ID.
	 */
	private function preload_variations( $products ) {
		$variation_ids = array();

		foreach ( $products as $product ) {
			if ( method_exists( $product, 'is_type' ) && $product->is_type( 'variable' ) ) {
				foreach ( $product->get_children() as $child_id ) {
					$variation_ids[] = (int) $child_id;
				}
			}
		}

		if ( empty( $variation_ids ) ) {
			return array();
		}

		$map = array();

		// Hydrate in slices so a chunk full of large variable products cannot
		// produce one oversized IN() query.
		foreach ( array_chunk( $variation_ids, 200 ) as $ids_slice ) {
			$variations = wc_get_products(
				array(
					'include' => $ids_slice,
					'type'    => 'variation',
					'limit'   => count( $ids_slice ),
				)
			);

			foreach ( (array) $variations as $variation ) {
				if ( is_object( $variation ) && method_exists( $variation, 'get_id' ) ) {
					$map[ $variation->get_id() ] = $variation;
				}
			}
		}

		return $map;
	}

	/**
	 * Extract data from a single product.
	 *
	 * @param \WC_Product $product Product object.
	 * @param array       $columns Column IDs to include.
	 * @return array Associative array of field => value.
	 */
	private function extract_product_data( $product, $columns ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
		$data = array();

		// Basic fields.
		$data['product_id']        = $product->get_id();
		$data['name']              = $product->get_name();
		$data['slug']              = $product->get_slug();
		$data['type']              = $product->get_type();
		$data['status']            = $product->get_status();
		$data['description']       = $product->get_description();
		$data['short_description'] = $product->get_short_description();
		$data['permalink']         = method_exists( $product, 'get_permalink' ) ? $product->get_permalink() : '';
		$data['date_created']      = $this->format_date( $product->get_date_created() );
		$data['date_modified']     = $this->format_date( $product->get_date_modified() );

		// Pricing.
		$data['sku']           = $product->get_sku();
		$data['regular_price'] = $product->get_regular_price();
		$data['sale_price']    = $product->get_sale_price();
		$data['price']         = $product->get_price();
		$data['on_sale']       = $product->is_on_sale() ? __( 'Yes', 'rawtype-order-exporter-woocommerce' ) : __( 'No', 'rawtype-order-exporter-woocommerce' );

		$sale_from               = $product->get_date_on_sale_from();
		$data['sale_price_from'] = $this->format_date( $sale_from );

		$sale_to               = $product->get_date_on_sale_to();
		$data['sale_price_to'] = $this->format_date( $sale_to );

		// Stock.
		$data['stock_quantity']   = $product->get_stock_quantity();
		$data['stock_status']     = $product->get_stock_status();
		$data['manage_stock']     = $product->get_manage_stock() ? __( 'Yes', 'rawtype-order-exporter-woocommerce' ) : __( 'No', 'rawtype-order-exporter-woocommerce' );
		$data['backorders']       = $product->get_backorders();
		$data['low_stock_amount'] = $product->get_low_stock_amount();

		// Physical dimensions.
		$data['weight'] = $product->get_weight();
		$data['length'] = $product->get_length();
		$data['width']  = $product->get_width();
		$data['height'] = $product->get_height();

		// Taxonomy.
		if ( empty( $columns ) || in_array( 'categories', $columns, true ) ) {
			$data['categories'] = $this->get_taxonomy_terms( $product->get_id(), 'product_cat' );
		}
		if ( empty( $columns ) || in_array( 'tags', $columns, true ) ) {
			$data['tags'] = $this->get_taxonomy_terms( $product->get_id(), 'product_tag' );
		}

		// Media — triggers wp_get_attachment_url() queries.
		if ( empty( $columns ) || in_array( 'image_url', $columns, true ) ) {
			$data['image_url'] = $this->get_image_url( $product );
		}
		if ( empty( $columns ) || in_array( 'gallery_urls', $columns, true ) ) {
			$data['gallery_urls'] = $this->get_gallery_urls( $product );
		}

		// Attributes — triggers get_terms() queries for taxonomy attributes.
		if ( empty( $columns ) || in_array( 'attributes', $columns, true ) ) {
			$data['attributes'] = $this->format_attributes( $product );
		}

		// Sales.
		$data['total_sales'] = $product->get_total_sales();

		// Variation fields.
		$data['variation_id']    = '';
		$data['variation_attrs'] = '';
		$data['parent_id']       = '';

		if ( method_exists( $product, 'is_type' ) && $product->is_type( 'variation' ) ) {
			$data['variation_id']    = $product->get_id();
			$data['variation_attrs'] = $this->format_variation_attributes( $product );
			$data['parent_id']       = method_exists( $product, 'get_parent_id' ) ? $product->get_parent_id() : '';
		}

		$data = $this->append_custom_meta_data( $data, $product, $columns );

		return $data;
	}

	/**
	 * Get taxonomy term names for a product.
	 *
	 * @param int    $product_id Product ID.
	 * @param string $taxonomy   Taxonomy slug.
	 * @return string Comma-separated term names.
	 */
	private function get_taxonomy_terms( $product_id, $taxonomy ) {
		$terms = get_the_terms( $product_id, $taxonomy );

		if ( ! is_array( $terms ) || empty( $terms ) ) {
			return '';
		}

		$names = array();
		foreach ( $terms as $term ) {
			if ( isset( $term->name ) ) {
				$names[] = $term->name;
			}
		}

		return implode( ', ', $names );
	}

	/**
	 * Get the main image URL for a product.
	 *
	 * @param \WC_Product $product Product object.
	 * @return string Image URL or empty string.
	 */
	private function get_image_url( $product ) {
		$image_id = $product->get_image_id();

		if ( ! $image_id ) {
			return '';
		}

		$url = wp_get_attachment_url( $image_id );

		return $url ? $url : '';
	}

	/**
	 * Get gallery image URLs for a product.
	 *
	 * @param \WC_Product $product Product object.
	 * @return string Comma-separated image URLs.
	 */
	private function get_gallery_urls( $product ) {
		$gallery_ids = $product->get_gallery_image_ids();

		if ( empty( $gallery_ids ) || ! is_array( $gallery_ids ) ) {
			return '';
		}

		$urls = array();
		foreach ( $gallery_ids as $id ) {
			$url = wp_get_attachment_url( $id );
			if ( $url ) {
				$urls[] = $url;
			}
		}

		return implode( ', ', $urls );
	}

	/**
	 * Format product attributes as "Name=Value1, Value2 | Name2=Value3".
	 *
	 * @param \WC_Product $product Product object.
	 * @return string Formatted attributes string.
	 */
	private function format_attributes( $product ) {
		$attributes = $product->get_attributes();

		if ( empty( $attributes ) ) {
			return '';
		}

		$parts = array();
		foreach ( $attributes as $attribute ) {
			$name = '';
			$vals = array();

			if ( is_object( $attribute ) && method_exists( $attribute, 'get_name' ) ) {
				$name = $attribute->get_name();

				if ( method_exists( $attribute, 'is_taxonomy' ) && $attribute->is_taxonomy() ) {
					$vals = $this->get_attribute_taxonomy_values( $attribute );
				} elseif ( method_exists( $attribute, 'get_options' ) ) {
					$vals = $attribute->get_options();
				}
			} elseif ( is_array( $attribute ) ) {
				$name = isset( $attribute['name'] ) ? $attribute['name'] : '';
				$vals = isset( $attribute['value'] ) ? array( $attribute['value'] ) : array();
			}

			if ( ! empty( $name ) && ! empty( $vals ) ) {
				$parts[] = $name . '=' . implode( ', ', $vals );
			}
		}

		return implode( ' | ', $parts );
	}

	/**
	 * Get taxonomy attribute values.
	 *
	 * @param object $attribute Product attribute object.
	 * @return array Term names.
	 */
	private function get_attribute_taxonomy_values( $attribute ) {
		$options = method_exists( $attribute, 'get_options' ) ? $attribute->get_options() : array();

		if ( empty( $options ) ) {
			return array();
		}

		$taxonomy = method_exists( $attribute, 'get_taxonomy' ) ? $attribute->get_taxonomy() : '';

		if ( empty( $taxonomy ) ) {
			return $options;
		}

		$terms = get_terms(
			array(
				'taxonomy'   => $taxonomy,
				'include'    => $options,
				'hide_empty' => false,
			)
		);

		if ( ! is_array( $terms ) || is_wp_error( $terms ) ) {
			return $options;
		}

		$names = array();
		foreach ( $terms as $term ) {
			$names[] = $term->name;
		}

		return $names;
	}

	/**
	 * Format variation-specific attributes.
	 *
	 * @param \WC_Product $product Variation product.
	 * @return string Formatted variation attributes.
	 */
	private function format_variation_attributes( $product ) {
		if ( ! method_exists( $product, 'get_variation_attributes' ) ) {
			return '';
		}

		$attrs = $product->get_variation_attributes();

		if ( empty( $attrs ) || ! is_array( $attrs ) ) {
			return '';
		}

		$parts = array();
		foreach ( $attrs as $name => $value ) {
			// Remove 'attribute_' prefix and 'pa_' taxonomy prefix.
			$clean_name = str_replace( array( 'attribute_pa_', 'attribute_' ), '', $name );
			$clean_name = ucfirst( $clean_name );

			if ( ! empty( $value ) ) {
				$parts[] = $clean_name . ': ' . $value;
			}
		}

		return implode( ', ', $parts );
	}
}
