<?php
/**
 * Parses supplier CSV price lists into a matrix of rows.
 *
 * @package EmberPrice
 */

namespace EmberPrice\Ingestion;

/**
 * Detects delimiter (`;`, `,`, tab) and encoding (normalizing to UTF-8),
 * and locates the real header row via {@see HeaderRowDetector}. Pure PHP,
 * no WordPress dependency.
 */
final class CsvParser {

	private const CANDIDATE_DELIMITERS = array( ';', ',', "\t" );

	/**
	 * Used to locate the real header row.
	 *
	 * @var HeaderRowDetector
	 */
	private HeaderRowDetector $header_row_detector;

	/**
	 * Constructor.
	 *
	 * @param HeaderRowDetector|null $header_row_detector Defaults to a new instance.
	 */
	public function __construct( ?HeaderRowDetector $header_row_detector = null ) {
		$this->header_row_detector = $header_row_detector ?? new HeaderRowDetector();
	}

	/**
	 * Result shape: `rows` (string matrix), `header_row` (index or null),
	 * `delimiter` (the detected one).
	 *
	 * @param string $path Path to the CSV file to parse.
	 *
	 * @return array{rows: array<int, string[]>, header_row: int|null, delimiter: string}
	 */
	public function parse( string $path ): array {
		// No WP_Filesystem: this class is deliberately WordPress-free so it
		// can run in the Unit test suite (which doesn't load WordPress).
		$content   = (string) file_get_contents( $path ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
		$content   = $this->normalize_encoding( $content );
		$delimiter = $this->detect_delimiter( $content );
		$rows      = $this->split_rows( $content, $delimiter );

		return array(
			'rows'       => $rows,
			'header_row' => $this->header_row_detector->detect( $rows ),
			'delimiter'  => $delimiter,
		);
	}

	/**
	 * Converts UTF-8/Latin-1/Windows-1252 content to UTF-8, stripping a
	 * leading BOM if present.
	 *
	 * @param string $content Raw file content.
	 */
	private function normalize_encoding( string $content ): string {
		$content = (string) preg_replace( '/^\xEF\xBB\xBF/', '', $content );

		$encoding = mb_detect_encoding( $content, array( 'UTF-8', 'ISO-8859-1', 'Windows-1252' ), true );

		if ( false === $encoding || 'UTF-8' === $encoding ) {
			return $content;
		}

		$converted = mb_convert_encoding( $content, 'UTF-8', $encoding );

		return false !== $converted ? $converted : $content;
	}

	/**
	 * Picks whichever candidate delimiter appears most often across the
	 * first few lines.
	 *
	 * @param string $content UTF-8 file content.
	 */
	private function detect_delimiter( string $content ): string {
		$lines       = preg_split( '/\r\n|\r|\n/', $content );
		$first_lines = array_slice( false !== $lines ? $lines : array(), 0, 5 );

		$best_delimiter = ',';
		$best_count     = 0;

		foreach ( self::CANDIDATE_DELIMITERS as $delimiter ) {
			$count = 0;

			foreach ( $first_lines as $line ) {
				$count += substr_count( $line, $delimiter );
			}

			if ( $count > $best_count ) {
				$best_count     = $count;
				$best_delimiter = $delimiter;
			}
		}

		return $best_delimiter;
	}

	/**
	 * Splits CSV content into a matrix of trimmed string cells.
	 *
	 * @param string $content   UTF-8 file content.
	 * @param string $delimiter The delimiter to split on.
	 *
	 * @return array<int, string[]>
	 */
	private function split_rows( string $content, string $delimiter ): array {
		// No WP_Filesystem: this is an in-memory stream, not a file on disk,
		// and this class is deliberately WordPress-free (see parse()).
		$rows   = array();
		$handle = fopen( 'php://memory', 'r+' ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fopen

		if ( false === $handle ) {
			return $rows;
		}

		fwrite( $handle, $content ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fwrite
		rewind( $handle );

		// phpcs:ignore Generic.CodeAnalysis.AssignmentInCondition.FoundInWhileCondition -- standard fgetcsv() read-until-EOF idiom.
		while ( false !== ( $row = fgetcsv( $handle, 0, $delimiter ) ) ) {
			$rows[] = array_map(
				static fn( $cell ) => trim( (string) $cell ),
				$row
			);
		}

		fclose( $handle ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose

		return $rows;
	}
}
