<?php
namespace WPO\IPS\EDI\Syntaxes\Ubl\Handlers;

use WPO\IPS\EDI\Syntaxes\Ubl\Abstracts\AbstractUblHandler;

if ( ! defined( 'ABSPATH' ) ) {
	exit; // Exit if accessed directly
}

class LineHandler extends AbstractUblHandler {

	/**
	 * Handle the data and return the formatted output.
	 *
	 * @param array $data    The data to be handled.
	 * @param array $options Additional options for handling.
	 * @return array
	 */
	public function handle( array $data, array $options = array() ): array {
		$root_element         = $this->document->get_root_element();
		$quantity_role		  = $this->document->get_quantity_role();
		$include_coupon_lines = apply_filters( 'wpo_ips_edi_ubl_discount_as_line', false, $this );
		$items                = $this->document->order->get_items( array( 'line_item', 'fee', 'shipping' ) );
		$currency             = $this->document->order->get_currency();

		// Build the tax totals array
		foreach ( $items as $item_id => $item ) {
			// Resolve tax meta for this line
			$meta       = $this->resolve_item_tax_meta( $item );
			$category   = strtoupper( (string) ( $meta['category'] ?? '' ) );
			$scheme     = (string) ( $meta['scheme'] ?? 'VAT' );
			$percentage = $meta['percentage'] ?? null;

			$tax_category = array(
				array(
					'name'  => 'cbc:ID',
					'value' => $category,
				),
			);

			// For VAT category O ("Not subject to VAT"), do NOT emit Percent.
			if ( 'O' !== $category && null !== $percentage && '' !== $percentage ) {
				$tax_category[] = array(
					'name'  => 'cbc:Percent',
					'value' => $this->format_decimal( $percentage, 1 ),
				);
			}

			$tax_category[] = array(
				'name'  => 'cac:TaxScheme',
				'value' => array(
					array(
						'name'  => 'cbc:ID',
						'value' => $scheme,
					),
				),
			);

			// Price parts
			$parts = $this->compute_item_price_parts( $item, (bool) $include_coupon_lines );

			// Round gross/net units first (numeric), then derive discount, then recompute net.
			$gross_unit_f = (float) $this->format_decimal( $parts['gross_unit'], 2 );
			$net_unit_f   = (float) $this->format_decimal( $parts['net_unit'],   2 );

			$unit_discount_f = $gross_unit_f - $net_unit_f;
			if ( $unit_discount_f < 0 ) {
				$unit_discount_f = 0.0;
			}

			$unit_discount_f = (float) $this->format_decimal( $unit_discount_f, 2 );

			// Recompute net from gross - discount to guarantee equality in XML.
			$net_unit_f = $gross_unit_f - $unit_discount_f;

			$gross_unit    = $this->format_decimal( $gross_unit_f, 2 );
			$net_unit      = $this->format_decimal( $net_unit_f,   2 );
			$unit_discount = $this->format_decimal( $unit_discount_f, 2 );

			$price_value = array(
				array(
					'name'       => 'cbc:PriceAmount',
					'value'      => abs( $net_unit ), // unit price always positive (Credit Notes as well)
					'attributes' => array(
						'currencyID' => $currency,
					),
				),
				array(
					'name'       => 'cbc:BaseQuantity',
					'value'      => 1,
					'attributes' => array(
						'unitCode' => 'C62',
					),
				),
			);

			/**
			* NOTE ABOUT DISABLING cac:Price/cac:AllowanceCharge FOR PEPPOL
			*
			* We intentionally do not emit price-level AllowanceCharge elements for Peppol BIS invoices.
			*
			* Reason: While BT-147 (Item price discount) and BT-148 (Gross price) are optional in EN16931,
			* the Peppol validator interprets cac:Price/cac:AllowanceCharge as a line-level allowance and
			* applies BR-24 and BR-42 rules. This caused the validator to:
			*
			*   - subtract the discount again from the already-net PriceAmount, and
			*   - require a reason code (BT-139/BT-140) for each price allowance,
			*
			* resulting in validation failures and incorrect recalculated line totals.
			*
			* To avoid double-discounting and maintain correct monetary values, we emit only:
			*
			*   PriceAmount (net unit price) + LineExtensionAmount (net line amount)
			*
			* This is fully compliant with Peppol BIS Billing 3.0 since BT-147/BT-148 are optional.
			*
			* If explicit gross/discount information is needed in the future, this can be reintroduced.
			*/
			// Only show AllowanceCharge when there is a discount at price level (already reflected in net price)
			// if ( $unit_discount > 0 ) {
			// 	$price_value[] = array(
			// 		'name'  => 'cac:AllowanceCharge',
			// 		'value' => array(
			// 			array(
			// 				'name'  => 'cbc:ChargeIndicator',
			// 				'value' => 'false',
			// 			),
			// 			array(
			// 				'name'       => 'cbc:Amount',
			// 				'value'      => $unit_discount,
			// 				'attributes' => array(
			// 					'currencyID' => $currency,
			// 				),
			// 			),
			// 			array(
			// 				'name'       => 'cbc:BaseAmount',
			// 				'value'      => $gross_unit,
			// 				'attributes' => array(
			// 					'currencyID' => $currency,
			// 				),
			// 			),
			// 		),
			// 	);
			// }

			// Build base Item node
			$item_value = array(
				array(
					'name'  => 'cbc:Name',
					'value' => wpo_ips_edi_sanitize_string( $item->get_name() ),
				),
				array(
					'name'  => 'cac:ClassifiedTaxCategory',
					'value' => $tax_category,
				),
			);

			// Optionally append AdditionalItemProperty from meta
			if ( wpo_ips_edi_include_item_meta() ) {
				$meta_rows = $this->get_item_meta( $item );

				if ( ! empty( $meta_rows ) ) {
					foreach ( $meta_rows as $row ) {
						$item_value[] = array(
							'name'  => 'cac:AdditionalItemProperty',
							'value' => array(
								array(
									'name'  => 'cbc:Name',
									'value' => $row['name'],
								),
								array(
									'name'  => 'cbc:Value',
									'value' => $row['value'],
								),
							),
						);
					}
				}
			}

			$quantity_value = $parts['qty'];

			// For credit notes: quantity must carry the sign, price stays positive
			if ( 'Credited' === $quantity_role && $parts['net_total'] < 0 ) {
				$quantity_value = -abs( $quantity_value );
			}

			// Use Woo’s net_total for the line extension amount.
			$net_line_total = $this->format_decimal( $parts['net_total'], 2 );

			$line = array(
				'name'  => "cac:{$root_element}Line",
				'value' => array(
					array(
						'name'  => 'cbc:ID',
						'value' => $item_id,
					),
					array(
						'name'       => "cbc:{$quantity_role}Quantity",
						'value'      => $quantity_value,
						'attributes' => array(
							'unitCode' => 'C62', // https://docs.peppol.eu/pracc/catalogue/1.0/codelist/UNECERec20/
						),
					),
					array(
						'name'       => 'cbc:LineExtensionAmount',
						'value'      => $net_line_total,
						'attributes' => array(
							'currencyID' => $currency,
						),
					),
					array(
						'name'  => 'cac:Item',
						'value' => $item_value,
					),
					array(
						'name'  => 'cac:Price',
						'value' => $price_value,
					),
				),
			);

			$data[] = apply_filters( 'wpo_ips_edi_ubl_line', $line, $data, $options, $item, $this );
		}

		// Append coupon lines as negative lines
		if ( $include_coupon_lines ) {
			$coupons = $this->document->order->get_items( 'coupon' );

			if ( empty( $coupons ) ) {
				return $data;
			}

			foreach ( $coupons as $order_item_id => $coupon_item ) {
				$_line = $this->build_coupon_line( $coupon_item, $order_item_id, $currency );
				if ( $_line ) {
					$data[] = $_line;
				}
			}
		}

		return $data;
	}

	/**
	 * Create the Line array for a single coupon item.
	 *
	 * @param \WC_Order_Item_Coupon $coupon_item
	 * @param int                   $fallback_id
	 * @param string                $currency
	 * @return array|null
	 */
	protected function build_coupon_line( \WC_Order_Item_Coupon $coupon_item, int $fallback_id, string $currency ): ?array {
		if ( ! is_object( $coupon_item ) || ! method_exists( $coupon_item, 'get_discount' ) ) {
			return null;
		}

		$code              = method_exists( $coupon_item, 'get_code' ) ? $coupon_item->get_code() : '';
		$discount_excl_tax = (float) $coupon_item->get_discount();
		$net_total         = -1.0 * (float) $this->format_decimal( $discount_excl_tax, 2 );

		if ( 0.0 === $net_total ) {
			return null;
		}

		$coupon_post_id = ( function_exists( 'wc_get_coupon_id_by_code' ) && $code )
			? (int) wc_get_coupon_id_by_code( $code )
			: 0;

		$label_template = apply_filters(
			'wpo_ips_edi_ubl_coupon_line_label',
			/* Translators: %s is the coupon code applied to the discount. */
			__( 'Discount %s', 'woocommerce-pdf-invoices-packing-slips' ),
			$this
		);

		$line_label = apply_filters(
			'wpo_ips_edi_ubl_coupon_line_name',
			sprintf( $label_template, $code ),
			$coupon_item,
			$this
		);

		$zero_meta = $this->get_zero_tax_meta( $this->document->order );
		$category  = strtoupper( (string) ( $zero_meta['category'] ?? 'Z' ) );
		$scheme    = (string) ( $zero_meta['scheme'] ?? 'VAT' );

		$tax_category = array(
			array(
				'name'  => 'cbc:ID',
				'value' => $category,
			),
		);

		// For coupons with category O ("Not subject to VAT"), do not emit Percent.
		if ( 'O' !== $category ) {
			$tax_category[] = array(
				'name'  => 'cbc:Percent',
				'value' => '0.0',
			);
		}

		$tax_category[] = array(
			'name'  => 'cac:TaxScheme',
			'value' => array(
				array(
					'name'  => 'cbc:ID',
					'value' => $scheme,
				),
			),
		);

		$root_element  = $this->document->get_root_element();
		$quantity_role = $this->document->get_quantity_role();

		$line = array(
			'name'  => "cac:{$root_element}Line",
			'value' => array(
				array(
					'name'  => 'cbc:ID',
					'value' => $coupon_post_id > 0 ? $coupon_post_id : $fallback_id,
				),
				array(
					'name'       => "cbc:{$quantity_role}Quantity",
					'value'      => 1,
					'attributes' => array(
						'unitCode' => 'C62',
					),
				),
				array(
					'name'       => 'cbc:LineExtensionAmount',
					'value'      => $this->format_decimal( $net_total ),
					'attributes' => array(
						'currencyID' => $currency,
					),
				),
				array(
					'name'  => 'cac:Item',
					'value' => array(
						array(
							'name'  => 'cbc:Name',
							'value' => wpo_ips_edi_sanitize_string( $line_label ),
						),
						array(
							'name' => 'cac:ClassifiedTaxCategory',
							'value' => $tax_category,
						),
					),
				),
				array(
					'name'  => 'cac:Price',
					'value' => array(
						array(
							'name'       => 'cbc:PriceAmount',
							'value'      => $this->format_decimal( $net_total ), // unit price (negative)
							'attributes' => array(
								'currencyID' => $currency,
							),
						),
						array(
							'name'       => 'cbc:BaseQuantity',
							'value'      => 1,
							'attributes' => array(
								'unitCode' => 'C62',
							),
						),
					),
				),
			),
		);

		return apply_filters( 'wpo_ips_edi_ubl_coupon_line', $line, $coupon_item, $this );
	}

}
