<?php
/**
 * Builds the preview diff CSV for export (SPEC §5/T-303).
 *
 * @package EmberPrice
 */

namespace EmberPrice\Pricing;

/**
 * Pure (no WordPress dependency, same reasoning as Ingestion\CsvParser):
 * `;` as the field delimiter and `,` as the decimal separator (es-AR),
 * since a `,`-decimal number inside a `,`-delimited CSV would silently
 * split into two columns -- this is exactly why real es-AR supplier lists
 * use `;` too (see Ingestion\CsvParser's delimiter detection). Every cell
 * is also checked for CSV/formula injection (CLAUDE.md security rule):
 * a value starting with `=`, `+`, `-` or `@` is prefixed with a leading
 * `'` so Excel/Sheets treat it as literal text instead of a formula.
 */
final class DiffCsvExporter {

	/**
	 * Leading characters a spreadsheet app would interpret as the start of
	 * a formula.
	 */
	private const DANGEROUS_PREFIXES = array( '=', '+', '-', '@' );

	/**
	 * Builds the CSV.
	 *
	 * @param array<int, array{code: string, name: string, old_price: ?float, new_price: ?float, flags: string[]}> $rows Rows to export.
	 */
	public function export( array $rows ): string {
		$handle = fopen( 'php://memory', 'r+' ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fopen -- deliberately WordPress-free; in-memory stream, not a file on disk.

		fputcsv( $handle, array( 'Código', 'Producto', 'Precio anterior', 'Precio nuevo', '% cambio', 'Alertas' ), ';' );

		foreach ( $rows as $row ) {
			fputcsv(
				$handle,
				array(
					$this->sanitize_cell( (string) $row['code'] ),
					$this->sanitize_cell( (string) $row['name'] ),
					$this->format_price( $row['old_price'] ?? null ),
					$this->format_price( $row['new_price'] ?? null ),
					$this->format_percent( $row['old_price'] ?? null, $row['new_price'] ?? null ),
					$this->sanitize_cell( implode( ', ', $row['flags'] ?? array() ) ),
				),
				';'
			);
		}

		rewind( $handle );
		$csv = stream_get_contents( $handle );
		fclose( $handle ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose -- see fopen() above.

		return false !== $csv ? $csv : '';
	}

	/**
	 * Neutralizes a value that would otherwise be read as a formula.
	 *
	 * @param string $value Raw cell value.
	 */
	private function sanitize_cell( string $value ): string {
		if ( '' !== $value && in_array( $value[0], self::DANGEROUS_PREFIXES, true ) ) {
			return "'" . $value;
		}

		return $value;
	}

	/**
	 * Formats a price es-AR style (`.` thousands, `,` decimal), or '' if unknown.
	 *
	 * @param float|null $value Price, if known.
	 */
	private function format_price( ?float $value ): string {
		return null === $value ? '' : number_format( $value, 2, ',', '.' );
	}

	/**
	 * Formats the percentage change from old to new price, or '' if either
	 * is unknown or the old price is zero (division by zero).
	 *
	 * @param float|null $old_price Price before this update.
	 * @param float|null $new_price Price after this update.
	 */
	private function format_percent( ?float $old_price, ?float $new_price ): string {
		if ( null === $old_price || null === $new_price || 0.0 === $old_price ) {
			return '';
		}

		return number_format( ( ( $new_price - $old_price ) / $old_price ) * 100, 2, ',', '.' ) . '%';
	}
}
