<?php
/**
 * CSV format exporter with UTF-8 BOM.
 *
 * Supports chunked streaming via supports_streaming() / export_chunk() so
 * large datasets are written row-by-row without loading everything into memory.
 *
 * @package Leadite
 */

namespace Leadite\Export;

defined( 'ABSPATH' ) || exit;

class CsvExporter extends Exporter {

	public function format() {
		return 'csv';
	}

	public function mime_type() {
		return 'text/csv; charset=utf-8';
	}

	/**
	 * Whether this exporter supports chunked streaming.
	 *
	 * @return bool
	 */
	public function supports_streaming(): bool {
		return true;
	}

	/**
	 * Non-streaming export (backward-compat entry point).
	 *
	 * Loads all leads at once — kept for code paths that call export() directly.
	 * For large datasets, prefer the streaming path in handle_export().
	 *
	 * @param array $leads Submission row objects.
	 */
	public function export( array $leads ) {
		$output = fopen( 'php://output', 'w' ); // phpcs:ignore WordPress.WP.AlternativeFunctions

		// Prepend UTF-8 BOM so Excel opens it with correct encoding (RTL / Arabic / Hebrew / Russian / Emoji)
		fwrite( $output, "\xEF\xBB\xBF" ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fwrite

		list( $headers, $rows ) = Helper::prepare_export_data( $leads );

		fputcsv( $output, $headers );
		foreach ( $rows as $row ) {
			fputcsv( $output, $row );
		}

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

	/**
	 * Write a single chunk of leads to the output stream.
	 *
	 * Called by handle_export() in the streaming loop. Callers are responsible
	 * for opening php://output themselves and passing in the pre-fetched meta map
	 * and the accumulated headers array (so column order stays consistent across
	 * pages).
	 *
	 * @param resource $output      Open writable stream (php://output).
	 * @param array    $leads       Submission row objects for this chunk.
	 * @param array    $meta_map    Pre-fetched map from LeadsRepository::find_many().
	 * @param array    $headers_map Ordered list of column header strings, built and
	 *                              expanded across chunks (passed by reference).
	 * @param bool     $is_first    True for the very first chunk (writes the BOM and header row).
	 */
	public function export_chunk( $output, array $leads, array $meta_map, array &$headers_map, bool $is_first ): void {
		if ( $is_first ) {
			fwrite( $output, "\xEF\xBB\xBF" ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fwrite
		}

		// Delegate to Helper for consistent field logic; Helper already uses the
		// pre-built meta_map internally for this chunk.
		list( $chunk_headers, $chunk_rows ) = Helper::prepare_export_data_from_map( $leads, $meta_map );

		if ( $is_first ) {
			// Write the header row on the first chunk; capture for future chunks.
			$headers_map = $chunk_headers;
			fputcsv( $output, $headers_map );
		} elseif ( $chunk_headers !== $headers_map ) {
			// Edge case: a later chunk has a different column schema.
			// Strategy: fix the header (defined by first chunk), align this chunk's
			// rows to it — padding missing columns with '' and dropping extras.
			// This preserves column count consistency at the cost of any truly new
			// custom fields that only appear in later pages.
			if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
				// phpcs:ignore WordPress.PHP.DevelopmentFunctions
				if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
					error_log( '[Leadite] CSV streaming: header schema changed at chunk boundary — aligning rows to first-chunk schema.' ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- debug-only diagnostic
				}
			}

			$header_index = array_flip( $headers_map ); // header → column position.
			$aligned_rows = array();
			foreach ( $chunk_rows as $row ) {
				$aligned = array_fill( 0, count( $headers_map ), '' );
				foreach ( $chunk_headers as $col_idx => $col_name ) {
					if ( isset( $header_index[ $col_name ] ) ) {
						$aligned[ $header_index[ $col_name ] ] = $row[ $col_idx ] ?? '';
					}
					// Extra columns not in headers_map are silently dropped.
				}
				$aligned_rows[] = $aligned;
			}
			$chunk_rows = $aligned_rows;
		}

		foreach ( $chunk_rows as $row ) {
			fputcsv( $output, $row );
		}
	}
}