<?php
/**
 * File Path: includes/import/csv/class-mbs-child-importer-csv-process.php
 *
 * CSV Importer – Process Child Class
 *
 * Handles the high-level import orchestration, duplicate detection,
 * force-insert logic, and candidate processing that was previously
 * inside the monolithic class-mbs-importer-csv.php.
 *
 * Delegates parsing, mapping, and insertion to the other child classes.
 *
 * @package MyBookShowroom
 * @since   1.0.0
 */

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

/**
 * Child class responsible for the overall import process flow.
 */
class MBS_Child_Importer_CSV_Process { // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedClassFound -- mbs_/MBS_ prefix is correct; scanner not configured with plugin prefix

	/**
	 * Import a CSV file.
	 *
	 * Importing is free (no license gate); the license gates data display, not import.
	 *
	 * @param string $file_path         Absolute path to the uploaded file.
	 * @param array  $column_map        CSV column → MBS field name map from the UI.
	 *                                  When empty, columns are auto-matched by name.
	 * @param array  $force_insert_keys ISBN-13 or title-slug keys approved for
	 *                                  force-insert despite duplicate detection.
	 * @param array  $options           Optional runtime flags:
	 *                                  - images_dir (string): absolute path to a
	 *                                    directory of cover images extracted from a
	 *                                    ZIP bundle. When set, insert_book() will
	 *                                    resolve cover_image_file references from
	 *                                    this directory instead of queuing a URL pass.
	 * @return array{
	 *   success: bool,
	 *   inserted: int,
	 *   skipped: int,
	 *   duplicates: array[],
	 *   errors: string[],
	 *   error: string
	 * }
	 */
	public static function import(
		string $file_path,
		array  $column_map = array(),
		array  $force_insert_keys = array(),
		array  $options = array()
	): array {
		$result = array(
			'success'    => false,
			'inserted'   => 0,
			'skipped'    => 0,
			'duplicates' => array(),
			'errors'     => array(),
			'error'      => '',
		);

		$validation = MBS_Child_Importer_CSV_Helpers::validate_file( $file_path );
		if ( true !== $validation ) {
			$result['error'] = $validation;
			return $result;
		}

		// Auto-detect delimiter — handles comma, semicolon, and tab-separated files.
		$delimiter = MBS_Child_Importer_CSV_Helpers::detect_delimiter( $file_path );

		// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fopen
		$handle = fopen( $file_path, 'r' );
		if ( false === $handle ) {
			$result['error'] = 'Could not open file for reading.';
			return $result;
		}

		$raw_headers = fgetcsv( $handle, 0, $delimiter );
		if ( false === $raw_headers ) {
			fclose( $handle ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose -- WP_Filesystem migration deferred to PC-FILESYS sprint; PHP native file ops are safe here (temp files in get_temp_dir(), never inside wp-content)
			$result['error'] = 'File appears empty or is not a valid CSV.';
			return $result;
		}


		// Strip UTF-8 BOM from the first header field — fgetcsv() does not remove it.
		if ( ! empty( $raw_headers[0] ) && str_starts_with( $raw_headers[0], "\xEF\xBB\xBF" ) ) {
			$raw_headers[0] = substr( $raw_headers[0], 3 );
		}

		$headers   = array_map( 'strtolower', array_map( 'trim', $raw_headers ) );
		$field_map = MBS_Child_Importer_CSV_Map::build_field_map( $headers, $column_map );

		// Resolve images directory from options (ZIP import).
		$images_dir = ! empty( $options['images_dir'] ) ? (string) $options['images_dir'] : '';

		$candidates = array();
		$line       = 1;
		while ( ( $row = fgetcsv( $handle, 0, $delimiter ) ) !== false ) {
			$line++;
			$candidate = MBS_Child_Importer_CSV_Map::map_row( $row, $field_map, $line );
			if ( null !== $candidate ) {
				// Inject images dir so insert_book() can resolve cover_image_file.
				if ( '' !== $images_dir ) {
					$candidate['_images_dir'] = $images_dir;
				}
				$candidates[] = $candidate;
				if ( count( $candidates ) > 5000 ) {
					$result['errors'][] = __( 'Import file exceeds the 5,000-row limit. Please split the file and import in batches.', 'my-book-showroom' );
					break;
				}
			}
		}
		fclose( $handle ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose -- WP_Filesystem migration deferred to PC-FILESYS sprint; PHP native file ops are safe here (temp files in get_temp_dir(), never inside wp-content)

		if ( empty( $candidates ) ) {
			$result['error'] = 'No valid rows found in the file (all rows were missing a title).';
			return $result;
		}

		// Group multi-format rows (same title + author) into one candidate per book.
		// Secondary rows contribute only their format-specific fields via the
		// _extra_formats key on the primary candidate.
		$candidates = MBS_Import_Book_Grouper::merge_to_primary( $candidates );

		return self::process_candidates( $candidates, $force_insert_keys, $result );
	}

	/**
	 * Insert a batch of pre-validated candidate arrays directly (no CSV parsing).
	 *
	 * Used by the mbs_import_candidates AJAX action to insert candidates returned
	 * by the Google Books and Open Library importers after admin confirmation.
	 *
	 * @param array[] $candidates       Pre-built candidate arrays with at least 'title'.
	 * @param array   $force_insert_keys Keys approved for force-insert on duplicate.
	 * @return array Same shape as import().
	 */
	public static function import_candidates(
		array $candidates,
		array $force_insert_keys = array()
	): array {
		$result = array(
			'success'    => false,
			'inserted'   => 0,
			'skipped'    => 0,
			'duplicates' => array(),
			'errors'     => array(),
			'error'      => '',
		);

		$candidates = array_values( array_filter( $candidates, fn( $c ) => ! empty( $c['title'] ) ) );

		if ( empty( $candidates ) ) {
			$result['error'] = 'No valid candidates provided (all were missing titles).';
			return $result;
		}

		// Group multi-format candidates (same title + author) into one primary
		// candidate per book before duplicate detection and insertion.
		$candidates = MBS_Import_Book_Grouper::merge_to_primary( $candidates );

		return self::process_candidates( $candidates, $force_insert_keys, $result );
	}

	/**
	 * Process a list of candidate arrays (duplicate detection + insertion).
	 *
	 * @param array $candidates       Candidate arrays.
	 * @param array $force_insert_keys Keys approved for force-insert.
	 * @param array $result           Result accumulator.
	 * @return array                  Final import result.
	 */
	private static function process_candidates(
		array $candidates,
		array $force_insert_keys,
		array $result
	): array {
		$detection            = MBS_Duplicate_Detector::check( $candidates );
		$clean                = $detection['clean'];
		$detected_dupes       = $detection['duplicates'];
		$result['duplicates'] = $detected_dupes;

		foreach ( $detected_dupes as $dupe ) {
			$key = MBS_Child_Importer_CSV_Helpers::force_key( $dupe['data'] ?? $dupe );
			if ( in_array( $key, $force_insert_keys, true ) ) {
				$clean[] = $dupe['data'] ?? $dupe;
			} else {
				// Duplicate not force-inserted: update passthrough meta on the existing post.
				// This ensures buy links, endorsements, synopsis, and toc from the CSV are
				// applied to the existing book even when a new post is not created.
				if ( ! empty( $dupe['id'] ) ) {
					MBS_Child_Importer_CSV_Insert::update_meta_for_existing(
						(int) $dupe['id'],
						$dupe['data'] ?? $dupe
					);
				}
				$result['skipped']++;
			}
		}

		foreach ( $clean as $candidate ) {
			$insert_result = MBS_Child_Importer_CSV_Insert::insert_book( $candidate );
			if ( true === $insert_result ) {
				$result['inserted']++;
			} else {
				$result['errors'][] = $insert_result;
			}
		}

		$result['success'] = true;
		return $result;
	}
}
