<?php
/**
 * Feed generation orchestrator.
 *
 * @package Kramar
 * @since   1.0.0
 */

declare( strict_types=1 );

namespace Kramar\Modules\Marketing;

// Prevent direct access.
defined( 'ABSPATH' ) || exit;

/**
 * Orchestrates the feed build pipeline:
 * query product IDs → expand variations → map fields → stream XML.
 *
 * @since 1.0.0
 */
final class Feed_Generator {

	/**
	 * Constructor.
	 *
	 * @since 1.0.0
	 *
	 * @param Feed_Config $cfg Module configuration.
	 */
	public function __construct( private Feed_Config $cfg ) {}

	/**
	 * Generate the feed XML file.
	 *
	 * On error, logs via error_log() and returns — never throws to the calling cron.
	 *
	 * @since 1.0.0
	 */
	public function generate(): void {
		$token = $this->cfg->get_token();
		if ( '' === $token ) {
			// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
			error_log( 'Kramar Google Shopping Feed: no token configured, skipping generation.' );
			return;
		}

		$query   = new Product_Query( $this->cfg );
		$handler = new Variation_Handler( $this->cfg );
		$mapper  = new Product_Data( $this->cfg );
		$writer  = new Google_Xml_Writer( $this->cfg->get_feed_path() );

		try {
			$writer->open();
		} catch ( \RuntimeException $e ) {
			// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
			error_log( 'Kramar Google Shopping Feed: ' . $e->getMessage() );
			return;
		}

		$product_ids = $query->get_ids();

		$generated        = 0;
		$skipped_no_image = 0;
		$skipped_no_price = 0;

		foreach ( $product_ids as $product_id ) {
			$product = wc_get_product( $product_id );
			if ( ! $product instanceof \WC_Product ) {
				continue;
			}

			$items = $handler->get_items( $product );

			foreach ( $items as $item ) {
				$fields = $mapper->map( $item );
				// Skip products without an image — Google rejects imageless items.
				if ( '' === ( $fields['g:image_link'] ?? '' ) ) {
					++$skipped_no_image;
					continue;
				}
				// Skip priceless items — a Google Shopping item without <g:price>
				// is rejected (e.g. variations that never had a price set).
				if ( '' === ( $fields['g:price'] ?? '' ) ) {
					++$skipped_no_price;
					continue;
				}
				$writer->write_item( $fields );
				++$generated;
			}

			// Free memory per product iteration.
			unset( $product, $items );
		}

		$writer->close();

		// Record generation stats for the admin summary.
		update_option(
			'kramar_google_feed_stats',
			[
				'generated'        => $generated,
				'skipped_no_image' => $skipped_no_image,
				'skipped_no_price' => $skipped_no_price,
				'generated_at'     => time(),
			],
			false
		);

		// Remove orphaned feed files left behind by a previous (rotated) token,
		// which would otherwise stay publicly fetchable in the uploads dir.
		$this->cleanup_orphan_feeds();
	}

	/**
	 * Delete generated feed files that do not belong to the current token.
	 *
	 * @since 1.0.0
	 */
	private function cleanup_orphan_feeds(): void {
		$current = $this->cfg->get_feed_path();
		$files   = glob( $this->cfg->get_feed_dir() . '/google-feed-*.xml' );

		if ( ! is_array( $files ) ) {
			return;
		}

		foreach ( $files as $file ) {
			if ( $file !== $current ) {
				wp_delete_file( $file );
			}
		}
	}
}
