<?php
/**
 * Cart Recovery — Cart Snapshot DTO
 *
 * Immutable snapshot of a visitor's WooCommerce cart at the moment we
 * observed activity (add_to_cart / qty change / removal / pre-checkout).
 * The snapshot is persisted in the `niwa_ai_cart_recovery` row so the
 * recovery mailer can reconstruct the exact contents even if the live
 * cart has since changed or expired.
 *
 * The hash over `items_hash` is the canonical "has this cart changed"
 * signal — used by the repository to avoid pointless upserts / to drive
 * the 7-day-per-cart cooldown.
 *
 * @package Niwa_AI
 * @since   0.12.0
 */

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

final class Niwa_AI_Cart_Recovery_Cart_Snapshot {

	/**
	 * @param array<int, Niwa_AI_Cart_Recovery_Line_Item> $items
	 */
	public function __construct(
		public readonly array $items,
		public readonly float $subtotal,
		public readonly float $total,
		public readonly string $currency,
		public readonly string $items_hash,
		public readonly int $captured_at
	) {}

	public function is_empty(): bool {
		return array() === $this->items;
	}

	public function item_count(): int {
		$total = 0;
		foreach ( $this->items as $item ) {
			$total += $item->qty;
		}
		return $total;
	}

	/**
	 * Stable JSON serialization used for persistence + hashing.
	 * Key order mirrors line-item DTO for deterministic hashing across
	 * requests. `JSON_UNESCAPED_UNICODE` so Serbian product names survive
	 * without escape noise.
	 */
	public function to_json(): string {
		$payload = array(
			'items'       => array_map(
				static fn( Niwa_AI_Cart_Recovery_Line_Item $item ) => $item->to_array(),
				$this->items
			),
			'subtotal'    => $this->subtotal,
			'total'       => $this->total,
			'currency'    => $this->currency,
			'items_hash'  => $this->items_hash,
			'captured_at' => $this->captured_at,
		);

		$encoded = wp_json_encode( $payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES );
		return is_string( $encoded ) ? $encoded : '{}';
	}

	/**
	 * Reconstruct a snapshot from its persisted JSON form.
	 *
	 * Returns `null` for malformed input so callers can treat a bad row
	 * as "no snapshot" without throwing.
	 */
	public static function from_json( string $json ): ?self {
		$decoded = json_decode( $json, true );
		if ( ! is_array( $decoded ) ) {
			return null;
		}

		$items = array();
		if ( isset( $decoded['items'] ) && is_array( $decoded['items'] ) ) {
			foreach ( $decoded['items'] as $raw ) {
				if ( is_array( $raw ) ) {
					$items[] = Niwa_AI_Cart_Recovery_Line_Item::from_array( $raw );
				}
			}
		}

		return new self(
			$items,
			(float) ( $decoded['subtotal'] ?? 0.0 ),
			(float) ( $decoded['total'] ?? 0.0 ),
			(string) ( $decoded['currency'] ?? '' ),
			(string) ( $decoded['items_hash'] ?? '' ),
			(int) ( $decoded['captured_at'] ?? 0 )
		);
	}
}
