<?php
/**
 * Cart Recovery — Snapshot Builder
 *
 * Pure(ish) builder that converts the live `WC()->cart` contents into an
 * immutable `Niwa_AI_Cart_Recovery_Cart_Snapshot` DTO. Kept free of DB
 * writes and email logic so snapshot assembly is testable in isolation
 * and can be called from any cart hook (add/remove/qty-change/thankyou).
 *
 * The builder is defensive: if WooCommerce is not loaded, the cart is
 * null, or any line item can't resolve to a product it returns an empty
 * snapshot rather than throwing. Callers are expected to treat an empty
 * snapshot as "nothing to track".
 *
 * @package Niwa_AI
 * @since   0.12.0
 */

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

class Niwa_AI_Cart_Recovery_Snapshot_Builder {

	/**
	 * Build a snapshot from the currently active WooCommerce cart.
	 *
	 * @return Niwa_AI_Cart_Recovery_Cart_Snapshot
	 */
	public function build_from_current_cart(): Niwa_AI_Cart_Recovery_Cart_Snapshot {
		if ( ! function_exists( 'WC' ) ) {
			return $this->empty_snapshot();
		}

		$wc = WC();
		if ( ! is_object( $wc ) || empty( $wc->cart ) || ! is_object( $wc->cart ) ) {
			return $this->empty_snapshot();
		}

		$cart_contents = $wc->cart->get_cart();
		if ( ! is_array( $cart_contents ) || empty( $cart_contents ) ) {
			return $this->empty_snapshot();
		}

		$items = array();
		foreach ( $cart_contents as $line ) {
			$item = $this->build_line_item( $line );
			if ( null !== $item ) {
				$items[] = $item;
			}
		}

		if ( empty( $items ) ) {
			return $this->empty_snapshot();
		}

		$subtotal = (float) $wc->cart->get_subtotal();
		$total    = (float) $wc->cart->get_total( 'edit' );
		$currency = function_exists( 'get_woocommerce_currency' ) ? (string) get_woocommerce_currency() : '';

		return new Niwa_AI_Cart_Recovery_Cart_Snapshot(
			$items,
			$subtotal,
			$total,
			$currency,
			$this->compute_items_hash( $items ),
			time()
		);
	}

	/**
	 * Compute a stable content hash over the line items. Uses the
	 * canonical array form from the DTO so the hash is independent of
	 * insertion order of WC cart keys (which are random hashes).
	 *
	 * @param array<int, Niwa_AI_Cart_Recovery_Line_Item> $items Items.
	 */
	public function compute_items_hash( array $items ): string {
		$rows = array_map(
			static fn( Niwa_AI_Cart_Recovery_Line_Item $item ) => $item->to_array(),
			$items
		);

		usort(
			$rows,
			static function ( array $a, array $b ): int {
				$key_a = ( $a['product_id'] ?? 0 ) . ':' . ( $a['variation_id'] ?? 0 );
				$key_b = ( $b['product_id'] ?? 0 ) . ':' . ( $b['variation_id'] ?? 0 );
				return strcmp( $key_a, $key_b );
			}
		);

		$json = wp_json_encode( $rows, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES );
		return is_string( $json ) ? md5( $json ) : '';
	}

	private function empty_snapshot(): Niwa_AI_Cart_Recovery_Cart_Snapshot {
		return new Niwa_AI_Cart_Recovery_Cart_Snapshot(
			array(),
			0.0,
			0.0,
			function_exists( 'get_woocommerce_currency' ) ? (string) get_woocommerce_currency() : '',
			'',
			time()
		);
	}

	/**
	 * Convert a single WC cart line into a readonly DTO, or null when the
	 * underlying product can't be resolved (deleted / trashed mid-session).
	 *
	 * @param array<string, mixed> $line WC cart item.
	 */
	private function build_line_item( array $line ): ?Niwa_AI_Cart_Recovery_Line_Item {
		$product_id   = (int) ( $line['product_id'] ?? 0 );
		$variation_id = (int) ( $line['variation_id'] ?? 0 );
		$qty          = max( 0, (int) ( $line['quantity'] ?? 0 ) );

		if ( $product_id <= 0 || $qty <= 0 ) {
			return null;
		}

		$product = null;
		if ( function_exists( 'wc_get_product' ) ) {
			$candidate_id = $variation_id > 0 ? $variation_id : $product_id;
			$product      = wc_get_product( $candidate_id );
		}

		$name      = '';
		$unit      = 0.0;
		$image     = '';
		$permalink = '';

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

			if ( method_exists( $product, 'get_price' ) ) {
				$unit = (float) $product->get_price();
			}

			if ( method_exists( $product, 'get_permalink' ) ) {
				$permalink = (string) $product->get_permalink();
			}

			if ( method_exists( $product, 'get_image_id' ) ) {
				$image_id = (int) $product->get_image_id();
				if ( $image_id > 0 && function_exists( 'wp_get_attachment_image_url' ) ) {
					$resolved = wp_get_attachment_image_url( $image_id, 'medium' );
					if ( is_string( $resolved ) ) {
						$image = $resolved;
					}
				}
			}
		}

		if ( isset( $line['line_total'] ) ) {
			$line_total = (float) $line['line_total'];
		} elseif ( isset( $line['line_subtotal'] ) ) {
			$line_total = (float) $line['line_subtotal'];
		} else {
			$line_total = $unit * $qty;
		}

		return new Niwa_AI_Cart_Recovery_Line_Item(
			$product_id,
			$variation_id,
			$name,
			$qty,
			$unit,
			$line_total,
			$image,
			$permalink
		);
	}
}
