/**
 * Heartbeat Module for Cart Recovery
 *
 * Sends periodic heartbeat requests to update cart timestamps,
 * preventing active users from being marked as abandoned.
 *
 * Also checks for unsent customer info before each heartbeat.
 *
 * @package EDD\Pro\CartRecovery
 * @since 3.6.5
 */

import * as CustomerInfo from './customer-info';

/**
 * Localized configuration from wp_localize_script
 *
 * @type {Object}
 */
const config = window.eddCartRecovery || {};

/**
 * Heartbeat interval in milliseconds (2 minutes)
 *
 * @type {number}
 */
export const HEARTBEAT_INTERVAL = 2 * 60 * 1000; // 2 minutes

/**
 * Storage key for last heartbeat timestamp
 *
 * @type {string}
 */
const STORAGE_KEY = 'edd_acr_last_heartbeat';

/**
 * Heartbeat state
 *
 * @type {Object}
 */
const state = {
	intervalId: null,
	lastBeat: 0,
	isVisible: true,
	hasCart: false, // Track if we have a cart to beat for
	consecutiveFailures: 0, // Track consecutive failures
};

/**
 * Sends a heartbeat request to the server
 *
 * @return {Promise<void>}
 */
const sendHeartbeat = async () => {
	try {
		// Before sending heartbeat, check if there's unsent customer info.
		// This catches cases where user typed in a field but didn't tab away.
		CustomerInfo.checkForUnsent();

		const now = Date.now();

		// Update last beat timestamp
		state.lastBeat = now;
		try {
			sessionStorage.setItem( STORAGE_KEY, now.toString() );
		} catch ( e ) {
			// Fail silently if sessionStorage is not available
		}

		// Send heartbeat to REST API
		const response = await fetch( `${ config.restEndpoint }/heartbeat`, {
			method: 'POST',
			headers: {
				'Content-Type': 'application/json',
				'X-EDD-Cart-Token': config.token,
				'X-EDD-Cart-Timestamp': config.timestamp,
				'X-WP-Nonce': config.nonce,
			},
			credentials: 'same-origin', // Include cookies for session
		} );

		if ( response.ok ) {
			const data = await response.json().catch( () => ( {} ) );

			// Update token and timestamp for next request.
			if ( data.token ) {
				config.token = data.token;
			}
			if ( data.timestamp ) {
				config.timestamp = data.timestamp;
			}

			// Success - reset failure count and mark that we have a cart
			state.consecutiveFailures = 0;
			state.hasCart = true;
		} else {
			// Check the response for specific errors
			const data = await response.json().catch( () => ( {} ) );

			// If no identifiers or cart not found, we should stop the heartbeat
			if ( response.status === 400 || response.status === 404 ) {
				state.consecutiveFailures++;

				// After 3 consecutive failures, stop the heartbeat to save resources
				if ( state.consecutiveFailures >= 3 ) {
					stopHeartbeat();
					state.hasCart = false;
				}
			} else {
				// Other errors (rate limit, server error, etc.) - don't stop, just log
				console.debug( '[EDD ACR Heartbeat] Request failed:', response.status, data.error );
			}
		}
	} catch ( error ) {
		// Network errors - don't stop the heartbeat, might be temporary
		console.debug( '[EDD ACR Heartbeat] Error:', error );
	}
};

/**
 * Starts the heartbeat timer
 *
 * @return {void}
 */
const startHeartbeat = () => {
	// Clear any existing interval
	if ( state.intervalId ) {
		clearInterval( state.intervalId );
	}

	// Send immediate heartbeat
	sendHeartbeat();

	// Set up recurring interval
	state.intervalId = setInterval( () => {
		// Only send if page is visible
		if ( state.isVisible ) {
			sendHeartbeat();
		}
	}, HEARTBEAT_INTERVAL );
};

/**
 * Stops the heartbeat timer
 *
 * @return {void}
 */
const stopHeartbeat = () => {
	if ( state.intervalId ) {
		clearInterval( state.intervalId );
		state.intervalId = null;
	}
};

/**
 * Handles page visibility changes
 *
 * @return {void}
 */
const handleVisibilityChange = () => {
	if ( document.hidden ) {
		// Page is hidden - mark as not visible
		state.isVisible = false;
	} else {
		// Page is visible again
		state.isVisible = true;

		// Check if we should send an immediate heartbeat
		// (if it's been more than the interval since last beat)
		const now = Date.now();
		const timeSinceLastBeat = now - state.lastBeat;

		if ( timeSinceLastBeat >= HEARTBEAT_INTERVAL ) {
			sendHeartbeat();
		}
	}
};

/**
 * Restarts the heartbeat if it was stopped
 *
 * This is called when cart items are added or email is captured,
 * ensuring we start tracking even if the heartbeat was previously stopped.
 *
 * @return {void}
 */
const restartHeartbeat = () => {

	// Reset failure count
	state.consecutiveFailures = 0;

	// If not running, start it
	if ( ! state.intervalId ) {
		startHeartbeat();
	}
};

/**
 * Initializes the heartbeat system
 *
 * Note: This script is only enqueued when there's an active cart being tracked
 * (checked server-side via EDD()->session->get('acr_cart')), so we can safely
 * start the heartbeat immediately without additional checks.
 *
 * @return {void}
 */
export const init = () => {
	// Don't initialize if already running
	if ( state.intervalId ) {
		return;
	}

	// Try to restore last heartbeat timestamp from storage
	try {
		const stored = sessionStorage.getItem( STORAGE_KEY );
		if ( stored ) {
			state.lastBeat = Number.parseInt( stored, 10 );
		}
	} catch ( e ) {
		console.debug( '[EDD ACR Heartbeat] Error restoring last heartbeat from storage:', e );
	}

	// Set initial visibility state
	state.isVisible = ! document.hidden;

	// Listen for visibility changes
	document.addEventListener( 'visibilitychange', handleVisibilityChange );

	// Listen for cart-related events to restart heartbeat if stopped
	document.addEventListener( 'edd_cart_item_added', restartHeartbeat );

	// Note: edd_email_captured event should be dispatched after successful email
	// validation AJAX in EDD core's checkout. If not available, the heartbeat
	// will still work via edd_cart_item_added.
	document.addEventListener( 'edd_email_captured', restartHeartbeat );

	// Start the heartbeat immediately since this script only loads when there's a cart
	startHeartbeat();
};

/**
 * Cleans up the heartbeat system
 *
 * Exposed for testing purposes.
 *
 * @return {void}
 */
export const cleanup = () => {
	stopHeartbeat();
	document.removeEventListener( 'visibilitychange', handleVisibilityChange );
	document.removeEventListener( 'edd_cart_item_added', restartHeartbeat );
	document.removeEventListener( 'edd_email_captured', restartHeartbeat );
};
