<?php
/**
 * Snapshot Persistence for Cart Recovery
 *
 * Handles persisting cart snapshots to the database with validation,
 * recursion prevention, and identity checking.
 *
 * @package EDD\Pro\CartRecovery\Snapshots
 * @copyright Copyright (c) 2026, Sandhills Development, LLC
 * @license https://opensource.org/licenses/GPL-2.0 GNU Public License
 * @since   3.6.5
 */

declare(strict_types=1);

namespace EDD\Pro\CartRecovery\Snapshots;

// Exit if accessed directly.
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore

use EDD\Pro\CartRecovery\Carts\Repository;
use EDD\Pro\CartRecovery\Traits\Debug;

/**
 * Snapshot Persistence class.
 *
 * Responsible for persisting cart snapshots with proper validation
 * and recursion prevention.
 */
class SnapshotPersistence {
	use Debug;

	/**
	 * Flag to prevent infinite recursion during cart snapshot persistence.
	 *
	 * @var bool
	 */
	private static bool $is_persisting = false;

	/**
	 * Flag to prevent snapshots after a cart has been marked as recovered.
	 * Once recovered, no more tracking should occur for this request.
	 *
	 * @var array Session IDs that have been marked as recovered in this request.
	 */
	private static array $recovered_sessions = array();

	/**
	 * Snapshot builder instance.
	 *
	 * @var SnapshotBuilder
	 */
	private SnapshotBuilder $snapshot_builder;

	/**
	 * Constructor.
	 *
	 * @since 3.6.5
	 * @param SnapshotBuilder $snapshot_builder Snapshot builder instance.
	 */
	public function __construct( SnapshotBuilder $snapshot_builder ) {
		$this->snapshot_builder = $snapshot_builder;
	}

	/**
	 * Persists the current cart snapshot.
	 *
	 * @since 3.6.5
	 * @param array $overrides Optional attribute overrides.
	 * @return array Cart data with cart_id if successful, empty array otherwise.
	 */
	public function persist_snapshot( array $overrides = array() ): array {
		// Prevent infinite recursion.
		if ( self::$is_persisting ) {
			return array();
		}

		if ( ! edd_get_option( 'acr_enabled', false ) ) {
			return array();
		}

		self::$is_persisting = true;

		try {
			$attributes = $this->snapshot_builder->build_server_snapshot();

			if ( empty( $attributes ) ) {
				return array();
			}

			// Check if this session has been marked as recovered in this request.
			// Once recovered, no more snapshots should be taken for this session.
			$session_id = $attributes['session_id'] ?? '';
			if ( ! empty( $session_id ) && in_array( $session_id, self::$recovered_sessions, true ) ) {
				return array();
			}

			if ( ! empty( $overrides ) ) {
				// Handle customer_data override specially - merge into cart_contents.
				if ( isset( $overrides['customer_data'] ) && is_array( $overrides['customer_data'] ) ) {
					$customer_data = $overrides['customer_data'];

					// Ensure cart_contents exists.
					if ( ! isset( $attributes['cart_contents'] ) ) {
						$attributes['cart_contents'] = array();
					}

					// Decode if it's a JSON string.
					$cart_contents = $attributes['cart_contents'];
					if ( is_string( $cart_contents ) ) {
						$cart_contents = json_decode( $cart_contents, true );
						if ( ! is_array( $cart_contents ) ) {
							$cart_contents = array();
						}
					}

					// Merge customer data into cart_contents.
					if ( ! empty( $customer_data['email'] ) ) {
						$attributes['email'] = $customer_data['email'];
					}

					// Add first/last name to customer section.
					$customer_names = array();
					if ( ! empty( $customer_data['first_name'] ) ) {
						$customer_names['first_name'] = $customer_data['first_name'];
					}
					if ( ! empty( $customer_data['last_name'] ) ) {
						$customer_names['last_name'] = $customer_data['last_name'];
					}

					if ( ! empty( $customer_names ) ) {
						$cart_contents['customer']   = $customer_names;
						$attributes['cart_contents'] = $cart_contents;
					}

					// Remove customer_data from overrides so it doesn't get stored at top level.
					unset( $overrides['customer_data'] );
				}

				// Merge remaining overrides.
				$attributes = array_merge( $attributes, $overrides );
			}

			$has_items = isset( $attributes['cart_contents'] ) && is_array( $attributes['cart_contents'] ) && ! empty( $attributes['cart_contents']['items'] );

			// Only bypass empty cart deletion for status-change overrides (recovery, conversion).
			// Identity-only overrides like customer_id should not bypass this check.
			$is_status_override = ! empty( $overrides['status'] );

			// Handle empty carts - delete the recovery cart unless a status override is provided.
			if ( ! $has_items && ! $is_status_override ) {
				$repository    = new Repository();
				$existing_cart = $repository->find_primary_cart( $attributes );

				if ( $existing_cart && ! empty( $existing_cart->id ) ) {
					$repository->delete_cart( (int) $existing_cart->id );
				}

				return array();
			}

			// Handle free carts - skip tracking if the setting is disabled.
			if ( $has_items && ! edd_get_option( 'acr_track_free_carts', false ) ) {
				$cart_total = isset( $attributes['cart_total'] ) ? floatval( $attributes['cart_total'] ) : 0;

				if ( $cart_total <= 0 ) {
					$repository    = new Repository();
					$existing_cart = $repository->find_primary_cart( $attributes );

					if ( $existing_cart && ! empty( $existing_cart->id ) ) {
						$repository->delete_cart( (int) $existing_cart->id );
					}

					return array();
				}
			}

			$has_identity             = ! empty( $attributes['session_id'] ) || ! empty( $attributes['customer_id'] ) || ! empty( $attributes['email'] );
			$has_recoverable_identity = ! empty( $attributes['customer_id'] ) || ! empty( $attributes['email'] );

			if ( ! $has_identity ) {
				// Without identifiers we cannot reconcile the snapshot later.
				if ( ! empty( $overrides ) ) {
					$this->log( 'Snapshot skipped: missing identifiers' );
				}
				return array();
			}

			// Additional validation: require email or customer_id for carts with items.
			// Session ID alone is not sufficient for recovery as it can be lost.
			if ( $has_items && ! $has_recoverable_identity ) {
				return array();
			}

			$repository = new Repository();
			$stored_id  = $repository->upsert( $attributes );

			if ( empty( $stored_id ) ) {
				$this->log( 'Snapshot skipped: upsert failed' );
			}

			// Store the cart ID in session to reliably track this cart across session ID changes.
			// This is critical when a user account is created during checkout, which changes the session ID.
			if ( ! empty( $stored_id ) ) {
				EDD()->session->set( 'acr_cart', (int) $stored_id );
			}

			// Track recovered/converted sessions to prevent additional snapshots in this request.
			if ( ! empty( $stored_id )
				&& isset( $attributes['status'] )
				&& in_array( $attributes['status'], array( 'recovered', 'converted' ), true )
				&& ! empty( $session_id )
				&& ! in_array( $session_id, self::$recovered_sessions, true )
			) {
				self::$recovered_sessions[] = $session_id;
			}

			// Always return array with cart_id when successful, empty array otherwise.
			return ! empty( $stored_id ) ? array( 'cart_id' => $stored_id ) : array();

		} finally {
			// Always reset flag, even if an error occurred.
			self::$is_persisting = false;
		}
	}
}
