<?php
/**
 * Product CSV exporter.
 *
 * @package Affilun\Application\Product\Csv
 */

declare(strict_types=1);

namespace Affilun\Application\Product\Csv;

use Affilun\Application\Product\ProductManualTaxonomy;
use Affilun\Infrastructure\Persistence\ProductListQuery;
use Affilun\Infrastructure\Persistence\ProductRecord;
use Affilun\Infrastructure\Persistence\ProductRepository;

/**
 * Exports saved products to a spreadsheet-safe portability CSV.
 */
final class ProductCsvExporter {

	/**
	 * CSV header order shared with the importer.
	 *
	 * @var list<string>
	 */
	public const HEADERS = array(
		'id',
		'provider',
		'provider_item_id',
		'canonical_hash',
		'title',
		'brand',
		'description',
		'image_primary_url',
		'detail_url',
		'affiliate_url',
		'price_amount',
		'price_currency',
		'price_label',
		'availability_status',
		'search_keywords',
		'search_keywords_source',
		'sync_state',
		'lifecycle_state',
		'categories',
		'tags',
	);

	/**
	 * Product repository.
	 */
	private ProductRepository $products;

	/**
	 * Manual category/tag helper.
	 */
	private ProductManualTaxonomy $taxonomy;

	/**
	 * @param ProductRepository     $products Product repository.
	 * @param ProductManualTaxonomy $taxonomy Manual taxonomy helper.
	 */
	public function __construct( ProductRepository $products, ProductManualTaxonomy $taxonomy ) {
		$this->products = $products;
		$this->taxonomy = $taxonomy;
	}

	/**
	 * Export all saved products as CSV.
	 */
	public function exportAll(): string {
		$lines  = array( self::csvLine( self::HEADERS ) );
		$page   = 1;
		$result = null;

		do {
			$result = $this->products->findPaginated(
				new ProductListQuery(
					$page,
					50,
					null,
					null,
					null,
					null,
					null,
					ProductListQuery::SORT_KEY_CREATED_AT,
					ProductListQuery::SORT_ORDER_ASC
				)
			);

			foreach ( $result->items() as $record ) {
				$lines[] = self::csvLine( $this->recordToRow( $record ) );
			}

			++$page;
		} while ( $result !== null && $page <= $result->totalPages() );

		return implode( "\r\n", $lines ) . "\r\n";
	}

	/**
	 * Convert a product record into an export row.
	 *
	 * @param ProductRecord $record Product record.
	 *
	 * @return list<string>
	 */
	private function recordToRow( ProductRecord $record ): array {
		$id         = $record->id();
		$termsId    = $id ?? 0;
		$categories = $termsId > 0 ? $this->taxonomy->categoriesForProduct( $termsId ) : array();
		$tags       = $termsId > 0 ? $this->taxonomy->tagsForProduct( $termsId ) : array();

		return array(
			$id === null ? '' : (string) $id,
			$record->provider(),
			$record->providerItemId(),
			$record->canonicalHash(),
			$record->title(),
			$record->brand() ?? '',
			$record->description() ?? '',
			$record->imagePrimaryUrl() ?? '',
			$record->detailUrl(),
			$record->affiliateUrl() ?? '',
			$record->priceAmount() ?? '',
			$record->priceCurrency() ?? '',
			$record->priceLabel() ?? '',
			$record->availabilityStatus() ?? '',
			$record->searchKeywords() ?? '',
			$record->searchKeywordsSource() ?? '',
			$record->syncState(),
			$record->lifecycleState(),
			ProductManualTaxonomy::termsToDisplayString( $categories ),
			ProductManualTaxonomy::termsToDisplayString( $tags ),
		);
	}

	/**
	 * Build one RFC-4180-ish CSV line with spreadsheet formula protection.
	 *
	 * @param list<string> $cells Cells.
	 */
	private static function csvLine( array $cells ): string {
		$escaped = array();
		foreach ( $cells as $cell ) {
			$value     = self::escapeFormulaCell( $cell );
			$escaped[] = '"' . str_replace( '"', '""', $value ) . '"';
		}

		return implode( ',', $escaped );
	}

	/**
	 * Prefix risky spreadsheet-formula values with an apostrophe.
	 *
	 * @param string $value Raw value.
	 */
	private static function escapeFormulaCell( string $value ): string {
		$value = str_replace( "\0", '', $value );
		if ( $value === '' ) {
			return '';
		}

		$first = $value[0];
		return in_array( $first, array( '=', '+', '-', '@', "\t", "\r", "\n" ), true )
			? "'" . $value
			: $value;
	}
}
