<?php
namespace Automattic\WCShipping\Shipment;

use WC_Order;

class ShipmentFromLabelGenerator {

	/*
	 * @var array
	 */
	private $orderItemsMap;

	/*
	 * Create a new ShipmentFromLabelGenerator instance.
	 *
	 * @param WC_Order $order The order object.
	 */
	public function __construct( WC_Order $order ) {
		$this->orderItemsMap = $this->build_order_items_map( $order );
	}

	/*
	 * Generate shipments from labels.
	 *
	 * @param array|null $labels Array of label metadata.
	 * @return array An array with two elements: shipments array and a fallback flag.
	 */
	public function generate_shipments( ?array $labels = null ) {
		if ( null === $labels ) {
			return array(
				'shipments'                 => array(),
				'autogenerated_from_labels' => array(),
			);
		}

		$shipments                 = array();
		$autogenerated_from_labels = array();

		foreach ( $labels as $index => $label ) {
			$result      = $this->generate_shipment_from_label( $label );
			$shipments[] = $result['items'];

			if ( $result['shipment_uses_autogenerated_items'] ) {
				$autogenerated_from_labels[] = $index;
			}
		}

		return array(
			'shipments'                 => $shipments,
			'autogenerated_from_labels' => $autogenerated_from_labels,
		);
	}

	/*
	 * Generate shipment items based on a label.
	 *
	 * If any product mapping fails or if no items are generated,
	 * fall back to including all order items with a fallback flag.
	 *
	 * @param array $label The label data, expected to contain a 'product_ids' key.
	 * @return array An array with the keys 'items' and 'shipment_uses_autogenerated_items'.
	 */
	private function generate_shipment_from_label( array $label ) {
		$product_id_quantities = $this->count_product_id_quantities( $label['product_ids'] );
		$shipmentItems         = array();
		$mappingSuccessful     = true;

		// Process each unique product id found in the label.
		foreach ( $product_id_quantities as $pid => $quantity ) {
			// Check if we can find this product in the order.
			if ( ! isset( $this->orderItemsMap[ $pid ] ) ) {
				$mappingSuccessful = false;
				continue;
			}

			$baseItem             = $this->orderItemsMap[ $pid ];
			$baseItem['quantity'] = $quantity;
			$baseItem['subItems'] = array();

			// If there is more than one occurrence, add subItems.
			if ( $quantity > 1 ) {
				for ( $i = 1; $i < $quantity; $i++ ) {
					$subItem                = $baseItem;
					$subItem['id']          = $pid . '-sub-' . ( $i - 1 );
					$subItem['quantity']    = 1;
					$subItem['parentId']    = $baseItem['id'];
					$subItem['subItems']    = array();
					$baseItem['subItems'][] = $subItem;
				}
			}
			$shipmentItems[] = $baseItem;
		}

		// Fallback: if mapping is not completely successful or no shipment items were created.
		if ( ! $mappingSuccessful || empty( $shipmentItems ) ) {
			$fallbackItems = array();
			foreach ( $this->orderItemsMap as $item ) {
				$fallbackItems[] = $item;
			}
			return array(
				'items'                             => $fallbackItems,
				'shipment_uses_autogenerated_items' => true,
			);
		}

		return array(
			'items'                             => $shipmentItems,
			'shipment_uses_autogenerated_items' => false,
		);
	}

	/**
	 * Group product IDs by occurrence in the label.
	 *
	 * Takes a list of product IDs from a label and groups them by product ID,
	 * tracking the original indices of each occurrence.
	 *
	 * @param array $productIds List of product IDs from the label.
	 * @return array An associative array where keys are product IDs and values are arrays of indices where each product appears.
	 */
	private function count_product_id_quantities( array $productIds ) {
		$product_id_quantities = array_fill_keys( $productIds, 0 );
		foreach ( $productIds as $pid ) {
			++$product_id_quantities[ $pid ];
		}
		return $product_id_quantities;
	}

	/*
	 * Build a lookup map of order items by product ID.
	 *
	 * @param WC_Order $order The order object.
	 * @return array Array with product IDs as keys and order item data as values.
	 */
	private function build_order_items_map( WC_Order $order ) {
		$map = array();
		foreach ( $order->get_items() as $item ) {
			$pid         = $item['product_id'];
			$map[ $pid ] = $item->get_data();
		}
		return $map;
	}
}