<?php
/**
 * Maps WooCommerce objects to flat row arrays for Google Sheets.
 *
 * @package StoreSheet
 */

declare( strict_types=1 );

namespace StoreSheet\Sync;

defined( 'ABSPATH' ) || exit;

/**
 * Converts WC product/order/coupon objects into flat row arrays
 * matching the column order defined in SpreadsheetManager.
 */
class RowMapper {

	// -------------------------------------------------------------------------
	// Products
	// -------------------------------------------------------------------------

	/**
	 * @since 1.2.0 Appends a 21st `image_alt` column (featured image's alt text).
	 * @param \WC_Product $product
	 * @return array<int, string|int|float|bool>
	 */
	public function product_to_row( \WC_Product $product ): array {
		$categories = implode( '|', $this->get_term_slugs( $product->get_id(), 'product_cat' ) );
		$tags       = implode( '|', $this->get_term_slugs( $product->get_id(), 'product_tag' ) );
		$now        = current_time( 'Y-m-d H:i:s' );

		$settings   = get_option( 'storesheet_settings', array() );
		$image_url  = ( ! isset( $settings['sync_product_image'] ) || $settings['sync_product_image'] )
			? $this->get_image_url( $product )
			: '';
		$image_alt  = ( ! isset( $settings['sync_product_image'] ) || $settings['sync_product_image'] )
			? $this->get_image_alt( $product )
			: '';
		$attributes = ( ! isset( $settings['sync_product_attributes'] ) || $settings['sync_product_attributes'] )
			? $this->get_attributes_string( $product )
			: '';

		$row = array(
			$product->get_id(),
			$product->get_sku(),
			$product->get_name(),
			$product->get_status(),
			$product->get_regular_price(),
			$product->get_sale_price(),
			$product->get_stock_quantity() ?? '',
			$product->get_stock_status(),
			$product->get_manage_stock() ? 'TRUE' : 'FALSE',
			$categories,
			$tags,
			$product->get_weight(),
			$this->format_date( $product->get_date_created() ),
			$this->format_date( $product->get_date_modified() ),
			$now,   // _ss_synced_at
			'FALSE', // _ss_locked
			'',     // margin_% (formula column — left blank)
			'',     // link (formula column — left blank)
			$image_url,  // image_url (B2)
			$attributes, // attributes (B2)
			$image_alt,  // image_alt (B2)
		);

		/**
		 * Filter a product's flat Sheets row before it is written.
		 *
		 * @since 1.1.0
		 * @param array<int,mixed> $row     Positional cell values.
		 * @param \WC_Product      $product Source product.
		 */
		return apply_filters( 'storesheet_product_row', $row, $product );
	}

	// -------------------------------------------------------------------------
	// Orders
	// -------------------------------------------------------------------------

	/**
	 * @since 1.2.0 Appends a 17th `line_items` column ("SKU:qty | SKU:qty").
	 * @since 1.2.0 Appends 18th `shipping_address` and 19th `coupons` columns.
	 * @param \WC_Order $order
	 * @return array<int, string|int|float>
	 */
	public function order_to_row( \WC_Order $order ): array {
		$now        = current_time( 'Y-m-d H:i:s' );
		$items      = array();
		$line_items = array();
		foreach ( $order->get_items() as $item ) {
			$items[] = $item->get_name() . ' x' . $item->get_quantity();

			$product = $item instanceof \WC_Order_Item_Product ? $item->get_product() : null;
			$sku     = $product instanceof \WC_Product ? $product->get_sku() : '';
			if ( '' !== $sku ) {
				$line_items[] = $sku . ':' . $item->get_quantity();
			}
		}

		$shipping_parts   = array_filter(
			array(
				$order->get_shipping_address_1(),
				$order->get_shipping_address_2(),
				$order->get_shipping_city(),
				$order->get_shipping_state(),
				$order->get_shipping_postcode(),
				$order->get_shipping_country(),
			),
			static function ( string $part ): bool {
				return '' !== $part;
			}
		);
		$shipping_address = implode( ', ', $shipping_parts );
		$coupons          = implode( ', ', $order->get_coupon_codes() );

		$row = array(
			$order->get_id(),
			$order->get_order_number(),
			$order->get_status(),
			$this->format_date( $order->get_date_created() ),
			$this->format_date( $order->get_date_paid() ),
			trim( $order->get_billing_first_name() . ' ' . $order->get_billing_last_name() ),
			$order->get_billing_email(),
			$order->get_total(),
			$order->get_subtotal(),
			$order->get_total_tax(),
			$order->get_shipping_total(),
			$order->get_payment_method_title(),
			implode( ', ', $items ),
			'', // item_count — formula column
			$order->get_customer_note(),
			$now, // _ss_synced_at
			implode( ' | ', $line_items ), // line_items: "SKU:qty | SKU:qty"
			$shipping_address, // shipping_address
			$coupons,          // coupons
		);

		/**
		 * Filter an order's flat Sheets row before it is written.
		 *
		 * @since 1.1.0
		 * @param array<int,mixed> $row   Positional cell values.
		 * @param \WC_Order        $order Source order.
		 */
		return apply_filters( 'storesheet_order_row', $row, $order );
	}

	/**
	 * Flatten an order into one row per line item for the Order Items tab.
	 * Order-level context repeats on every item row (self-describing / pivotable).
	 * Push-only.
	 *
	 * @since 1.2.0
	 * @param \WC_Order $order
	 * @return array<int,array{0:int,1:array<int,mixed>}> List of [ item_id, row ].
	 */
	public function order_items_to_rows( \WC_Order $order ): array {
		$now  = current_time( 'Y-m-d H:i:s' );
		$rows = array();

		foreach ( $order->get_items() as $item ) {
			if ( ! $item instanceof \WC_Order_Item_Product ) {
				continue;
			}
			$product = $item->get_product();
			$sku     = $product instanceof \WC_Product ? $product->get_sku() : '';

			$row = array(
				$order->get_id(),
				$order->get_order_number(),
				$order->get_status(),
				$this->format_date( $order->get_date_created() ),
				$order->get_billing_email(),
				$item->get_id(),
				$item->get_product_id(),
				$item->get_name(),
				$sku,
				$item->get_quantity(),
				$item->get_subtotal(),
				$item->get_total(),
				$item->get_total_tax(),
				$now,
			);

			/**
			 * Filter an order line item's flat Sheets row before it is written.
			 *
			 * @since 1.2.0
			 * @param array<int,mixed>       $row   Positional cell values.
			 * @param \WC_Order              $order Source order.
			 * @param \WC_Order_Item_Product $item  Source line item.
			 */
			$row = apply_filters( 'storesheet_order_item_row', $row, $order, $item );

			$rows[] = array( (int) $item->get_id(), $row );
		}

		return $rows;
	}

	// -------------------------------------------------------------------------
	// Coupons
	// -------------------------------------------------------------------------

	/**
	 * @param \WC_Coupon $coupon
	 * @return array<int, string|int|float>
	 */
	public function coupon_to_row( \WC_Coupon $coupon ): array {
		$now    = current_time( 'Y-m-d H:i:s' );
		$expiry = $coupon->get_date_expires() ? $coupon->get_date_expires()->date( 'Y-m-d' ) : '';

		$row = array(
			$coupon->get_id(),
			$coupon->get_code(),
			$coupon->get_discount_type(),
			$coupon->get_amount(),
			$expiry,
			$coupon->get_individual_use() ? 'TRUE' : 'FALSE',
			$coupon->get_usage_limit() ?: '',
			$coupon->get_usage_limit_per_user() ?: '',
			$coupon->get_minimum_amount(),
			$coupon->get_maximum_amount(),
			$coupon->get_usage_count(),
			$coupon->get_description(),
			'',   // _ss_action — always blank when syncing FROM WC
			$now, // _ss_synced_at
		);

		/**
		 * Filter a coupon's flat Sheets row before it is written.
		 *
		 * @since 1.1.0
		 * @param array<int,mixed> $row    Positional cell values.
		 * @param \WC_Coupon       $coupon Source coupon.
		 */
		return apply_filters( 'storesheet_coupon_row', $row, $coupon );
	}

	// -------------------------------------------------------------------------
	// Helpers
	// -------------------------------------------------------------------------

	/**
	 * @param \WC_DateTime|null $date
	 * @return string
	 */
	private function format_date( ?\WC_DateTime $date ): string {
		return $date ? $date->date( 'Y-m-d H:i:s' ) : '';
	}

	/**
	 * @param int    $post_id
	 * @param string $taxonomy
	 * @return string[]
	 */
	private function get_term_slugs( int $post_id, string $taxonomy ): array {
		$terms = get_the_terms( $post_id, $taxonomy );
		if ( ! is_array( $terms ) ) {
			return array();
		}
		return array_column( $terms, 'slug' );
	}

	/**
	 * Featured image URL (full size), or empty string when the product has none.
	 *
	 * @since 1.2.0
	 * @param \WC_Product $product
	 * @return string
	 */
	private function get_image_url( \WC_Product $product ): string {
		$image_id = $product->get_image_id();
		if ( ! $image_id ) {
			return '';
		}
		$url = wp_get_attachment_image_url( (int) $image_id, 'full' );
		return is_string( $url ) ? $url : '';
	}

	/**
	 * Featured image's alt text, or empty string when the product has none.
	 *
	 * @since 1.2.0
	 * @param \WC_Product $product
	 * @return string
	 */
	private function get_image_alt( \WC_Product $product ): string {
		$image_id = $product->get_image_id();
		if ( ! $image_id ) {
			return '';
		}
		return (string) get_post_meta( (int) $image_id, '_wp_attachment_image_alt', true );
	}

	/**
	 * Flatten a product's attributes to "Label: opt1, opt2 | Label2: opt3".
	 * Attributes with no value are skipped. Formula/CSV injection is neutralized
	 * at write time by GoogleSheetsClient, so no escaping is done here.
	 *
	 * Options are read directly from the attribute (taxonomy term names, or the
	 * custom attribute's own options) and joined with ', ' — WC_Product::get_attribute()
	 * is not used here because it joins non-taxonomy attribute options with
	 * WC_DELIMITER ('|'), which would collide with the ' | ' separator used
	 * between attributes below.
	 *
	 * @since 1.2.0
	 * @param \WC_Product $product
	 * @return string
	 */
	private function get_attributes_string( \WC_Product $product ): string {
		$parts = array();
		foreach ( $product->get_attributes() as $attribute ) {
			if ( ! $attribute instanceof \WC_Product_Attribute ) {
				continue;
			}

			$options = $attribute->is_taxonomy()
				? wc_get_product_terms( $product->get_id(), $attribute->get_name(), array( 'fields' => 'names' ) )
				: $attribute->get_options();

			if ( empty( $options ) ) {
				continue;
			}

			$parts[] = wc_attribute_label( $attribute->get_name(), $product ) . ': ' . implode( ', ', $options );
		}
		return implode( ' | ', $parts );
	}
}
