<?php
/**
 * Suppresses WooCommerce completion emails for virtual orders until invoice is ready.
 *
 * @package PDFDraft_WooCommerce
 */

namespace PDFDraft\Woocommerce\Core;

use PDFDraft\Traits\Hooks;

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

/**
 * Delays WooCommerce order completion emails for virtual orders pending invoice generation.
 */
class VirtualOrderEmailSuppressor {

	use Hooks;

	/**
	 * Meta key indicating the completion email is pending.
	 *
	 * @var string
	 */
	public const META_EMAIL_PENDING = 'pdfdraft_email_pending';

	/**
	 * Cron hook name for sending pending invoice emails.
	 *
	 * @var string
	 */
	private const CRON_HOOK = 'pdfdraft_send_pending_invoice_email';

	/**
	 * Delay in seconds before the cron job fires.
	 *
	 * @var int
	 */
	private const CRON_DELAY = 900;

	/**
	 * In-memory set of order IDs whose completion email must be suppressed in this request.
	 * Using a static flag avoids any WC object-cache or instance-identity issues.
	 *
	 * @var array<int, true>
	 */
	private static array $suppressedInRequest = array();

	/**
	 * Invoice service instance.
	 *
	 * @var OrderInvoiceService
	 */
	private OrderInvoiceService $invoiceService;

	/**
	 * PDF settings option instance.
	 *
	 * @var PdfSettingsOption
	 */
	private PdfSettingsOption $settings;

	/**
	 * Constructor.
	 *
	 * @param OrderInvoiceService $invoiceService Invoice service instance.
	 * @param PdfSettingsOption   $settings       PDF settings option instance.
	 */
	public function __construct( OrderInvoiceService $invoiceService, PdfSettingsOption $settings ) {
		$this->invoiceService = $invoiceService;
		$this->settings       = $settings;
	}

	/**
	 * Register WordPress hooks.
	 *
	 * @return void
	 */
	public function initHooks(): void {
		$this->addAction( 'woocommerce_order_status_completed', array( $this, 'onOrderCompleted' ), 5, 2 );
		$this->addFilter( 'woocommerce_email_enabled_customer_completed_order', array( $this, 'maybeSuppress' ), 10, 2 );
		$this->addAction( self::CRON_HOOK, array( $this, 'onCronSendPendingEmail' ), 10, 1 );
	}

	/**
	 * Handle order completion by suppressing email if invoice is not yet generated.
	 *
	 * @param int            $orderId Order ID.
	 * @param \WC_Order|null $order   Order object or null.
	 * @return void
	 */
	public function onOrderCompleted( int $orderId, $order = null ): void {
		$settings = $this->settings->get();
		$mode     = $settings['general']['invoice_settings']['generation_mode'] ?? 'automatic';
		if ( 'automatic' !== $mode ) {
			return;
		}
		if ( ! $order instanceof \WC_Order ) {
			$order = wc_get_order( $orderId );
		}
		if ( ! $order instanceof \WC_Order ) {
			return;
		}

		// Skip free orders if the setting is enabled — no invoice will be generated,
		// so there's no reason to suppress/delay the completion email.
		$disable_free = (bool) ( $settings['general']['invoice_settings']['disable_free_order_invoices'] ?? false );
		if ( $disable_free && (float) $order->get_total() <= 0 ) {
			return;
		}

		if ( $this->isVirtualDownloadableOrder( $order ) ) {
			// If the invoice was already generated, don't suppress — OrderEmailAttachment
			// will attach the existing file to the WC completion email naturally.
			if ( $this->invoiceService->isGenerated( $orderId ) ) {
				return;
			}
			self::$suppressedInRequest[ $orderId ] = true;
			$order->update_meta_data( self::META_EMAIL_PENDING, 'true' );
			$order->save();
			wp_schedule_single_event( time() + self::CRON_DELAY, self::CRON_HOOK, array( $orderId ) );
		}
	}

	/**
	 * Cron callback to send the pending completion email.
	 *
	 * @param int $orderId Order ID.
	 * @return void
	 */
	public function onCronSendPendingEmail( int $orderId ): void {
		if ( ! self::isEmailPending( $orderId ) ) {
			return;
		}
		self::clearEmailPending( $orderId );
		$this->invoiceService->sendCompletionEmail( $orderId );
	}

	/**
	 * Filter callback to suppress the completion email when invoice is pending.
	 *
	 * @param bool             $enabled Whether the email is enabled.
	 * @param \WC_Order|object $email   Order or email object.
	 * @return bool
	 */
	public function maybeSuppress( bool $enabled, $email ): bool {
		if ( ! $enabled ) {
			return false;
		}

		// Some WC versions/gateways pass the WC_Order directly as $email;
		// others pass the WC_Email object whose ->object property holds the order.
		if ( $email instanceof \WC_Order ) {
			$order = $email;
		} elseif ( is_object( $email ) && isset( $email->object ) && $email->object instanceof \WC_Order ) {
			$order = $email->object;
		} else {
			return true;
		}

		$order_id = $order->get_id();

		if ( isset( self::$suppressedInRequest[ $order_id ] ) ) {
			return false;
		}

		return 'true' !== $order->get_meta( self::META_EMAIL_PENDING );
	}

	/**
	 * Check whether all items in the order are virtual or downloadable.
	 *
	 * @param \WC_Order $order Order object.
	 * @return bool
	 */
	private function isVirtualDownloadableOrder( \WC_Order $order ): bool {
		$items = $order->get_items();
		if ( empty( $items ) ) {
			return false;
		}
		foreach ( $items as $item ) {
			$product = $item->get_product();
			if ( ! $product instanceof \WC_Product ) {
				return false;
			}
			if ( ! $product->is_virtual() && ! $product->is_downloadable() ) {
				return false;
			}
		}
		return true;
	}

	/**
	 * Check whether the completion email is still pending for an order.
	 *
	 * @param int $orderId Order ID.
	 * @return bool
	 */
	public static function isEmailPending( int $orderId ): bool {
		$order = wc_get_order( $orderId );
		if ( ! $order instanceof \WC_Order ) {
			return false;
		}
		return 'true' === $order->get_meta( self::META_EMAIL_PENDING );
	}

	/**
	 * Clear the pending email flag for an order.
	 *
	 * @param int $orderId Order ID.
	 * @return void
	 */
	public static function clearEmailPending( int $orderId ): void {
		unset( self::$suppressedInRequest[ $orderId ] );
		$order = wc_get_order( $orderId );
		if ( ! $order instanceof \WC_Order ) {
			return;
		}
		$order->delete_meta_data( self::META_EMAIL_PENDING );
		$order->save();
	}

	/**
	 * Clear pending-email meta and any scheduled cron for this order.
	 *
	 * @param int $orderId Order ID.
	 * @return void
	 */
	public static function clearPendingEmailAndCron( int $orderId ): void {
		self::clearEmailPending( $orderId );
		wp_clear_scheduled_hook( self::CRON_HOOK, array( $orderId ) );
	}
}
