<?php
/**
 * Read File Data Service
 *
 * @category   WordPress_Plugin
 * @package    Common
 * @subpackage Services
 * @author     miniOrange <info@miniorange.com>
 * @license    MIT/Expat
 * @link       https://plugins.miniorange.com/woocommerce-api-product-sync-with-woocommerce-rest-apis
 */

namespace MOWCPS\Common\Services\ReadFileData;

use MOWCPS\Common\Constants\Constants;
use MOWCPS\Common\Utils\Utils;

if ( ! defined( 'ABSPATH' ) ) {
	die;
}

/**
 * Read File Data Service Class
 */
class Read_File_Data {

	/**
	 * Reads data from a file (CSV, TSV, TXT, or XLSX) in batches. Can read a specific range of lines, depending on the $start_line and $end_line parameters.
	 *
	 * @param string       $file_directory    The directory path where the file is located.
	 * @param string       $file_name         The name of the file to read.
	 * @param int|false    $start_line        Line number to start reading from (1-based index).
	 * @param int|false    $end_line          Line number to stop reading at (inclusive).
	 * @param bool         $has_header        Whether the file has a header row that should be used as keys.
	 * @param string|false $custom_delimiter  Optional custom delimiter. If false, auto-detects based on file extension.
	 *
	 * @return array Returns a multidimensional array containing parsed file data. If $has_header is true, uses first row as keys. If false, generates keys as 'column_1', 'column_2', etc. Returns an empty array if file type is unsupported or unreadable.
	 */
	public static function read_data_from_file( $file_directory, $file_name, $start_line = false, $end_line = false, $has_header = false, $custom_delimiter = false ) {
		switch ( $custom_delimiter ) {
			case Constants::DELIMITER_COMMA:
				$custom_delimiter = ',';
				break;
			case Constants::DELIMITER_PIPE:
				$custom_delimiter = '|';
				break;
			case Constants::DELIMITER_TAB:
				$custom_delimiter = "\t";
				break;
			case Constants::DELIMITER_SEMICOLON:
				$custom_delimiter = ';';
				break;
			case Constants::DELIMITER_COLON:
				$custom_delimiter = ':';
				break;
			case Constants::DELIMITER_SPACE:
				$custom_delimiter = ' ';
				break;
			case Constants::DELIMITER_TILDE:
				$custom_delimiter = '~';
				break;
			case Constants::DELIMITER_CARET:
				$custom_delimiter = '^';
				break;
			default:
				// fallback if value is unknown
				$custom_delimiter = ',';
		}

		$file_path = $file_directory . DIRECTORY_SEPARATOR . $file_name;

		// Return empty array if file does not exist or is not readable.
		if ( ! file_exists( $file_path ) || ! is_readable( $file_path ) ) {
			return array();
		}

		$file_type = strtolower( pathinfo( $file_name, PATHINFO_EXTENSION ) );
		$data      = array();
		$headers   = array();

		$should_limit_lines = is_numeric( $start_line ) && is_numeric( $end_line );

		// Normalize to start from 1 and cast to int when numeric values are provided.
		if ( $should_limit_lines ) {
			$start_line = max( 1, (int) $start_line );
			$end_line   = (int) $end_line;
		}

		if ( in_array( $file_type, array( Constants::TXT_FILE, Constants::TSV_FILE, Constants::CSV_FILE, Constants::TEXT_FILE ) ) ) {
			// Use custom delimiter if provided, otherwise auto-detect based on file extension using utility function.
			if ( false !== $custom_delimiter ) {
				$delimiter = $custom_delimiter;
			} else {
				$delimiter = Utils::detect_file_delimiter( $file_type );
			}

			$file_pointer = fopen( $file_path, 'r' );
			$current_line = 0;
			$header_count = 0; // Cache header count for performance.

			if ( false !== $file_pointer ) {
				// Remove BOM from the first line if present (read first 3 bytes to check)
				$bom_check = fread( $file_pointer, 3 );
				if ( $bom_check !== "\xEF\xBB\xBF" ) {
					// Not a BOM, rewind to start
					rewind( $file_pointer );
				}
				// If it was a BOM, we've already consumed it, so continue from position 3

				// Always read header row first if specified, regardless of start_line.
				// Headers must come from line 1 of the file, not from the batch range.
				if ( $has_header ) {
					$header_parsed = fgetcsv( $file_pointer, 0, $delimiter );
					if ( false !== $header_parsed ) {
						$headers      = $header_parsed;
						$header_count = count( $headers );
						$current_line = 1; // Header is always line 1.
					}
				}

				// Use fgetcsv() instead of fgets() to properly handle quoted fields with embedded newlines
				// fgetcsv() correctly parses rows (CSV/TSV/TXT) even when they span multiple physical lines
				// Works with any delimiter: comma (CSV), tab (TSV), pipe (TXT), or custom delimiter
				while ( ( $parsed = fgetcsv( $file_pointer, 0, $delimiter ) ) !== false ) {
					++$current_line;

					// Generate column headers if not provided and this is the first data row.
					if ( ! $has_header && empty( $headers ) && ! empty( $parsed ) ) {
						$column_count = count( $parsed );
						$headers      = array();
						for ( $i = 1; $i <= $column_count; $i++ ) {
							$headers[] = 'column_' . $i;
						}
						$header_count = $column_count;
					}

					// Skip rows before the start line.
					if ( $should_limit_lines && $current_line < $start_line ) {
						continue;
					}

					// Stop reading if we've passed the end line (end_line is inclusive, so break after processing it).
					if ( $should_limit_lines && $current_line > $end_line ) {
						break;
					}

					// Create associative array with headers (either from file or auto-generated).
					if ( $header_count > 0 ) {
						$parsed_count = count( $parsed );

						// Normalize parsed array to match header count.
						if ( $parsed_count < $header_count ) {
							// Pad the parsed array with empty strings if it's shorter.
							$parsed = array_pad( $parsed, $header_count, '' );
						} elseif ( $parsed_count > $header_count ) {
							// Truncate the parsed array if it's longer.
							$parsed = array_slice( $parsed, 0, $header_count );
						}

						// Combine headers with parsed data.
						$combined = array_combine( $headers, $parsed );
						if ( false !== $combined ) {
							$data[] = $combined;
						}
					} else {
						$data[] = $parsed;
					}
				}
				fclose( $file_pointer );
			}
		} elseif ( Constants::XLSX_FILE === $file_type ) {
			// Use utility function for XLSX reading.
			$data = Utils::read_xlsx_file( $file_path, $should_limit_lines, $start_line, $end_line );

			// Process data with headers.
			if ( ! empty( $data ) ) {
				if ( $has_header ) {
					// Use first row as headers.
					$headers = array_shift( $data );
				} else {
					// Generate column headers if not provided.
					$column_count = ! empty( $data[0] ) ? count( $data[0] ) : 0;
					$headers      = array();
					for ( $i = 1; $i <= $column_count; $i++ ) {
						$headers[] = 'column_' . $i;
					}
				}

				$header_count   = count( $headers ); // Cache count once.
				$formatted_data = array();

				foreach ( $data as $row ) {
					$row_count = count( $row );

					// Normalize row to match header count.
					if ( $row_count < $header_count ) {
						// Pad the row with empty strings if it's shorter.
						$row = array_pad( $row, $header_count, '' );
					} elseif ( $row_count > $header_count ) {
						// Truncate the row if it's longer.
						$row = array_slice( $row, 0, $header_count );
					}

					// Combine headers with row data.
					$formatted_data[] = array_combine( $headers, $row );
				}
				$data = $formatted_data;
			}
		} else {
			// Unsupported format.
			return array();
		}
		return $data;
	}
}
