<?php
/**
 * File Path: includes/import/class-mbs-importer-bulk-isbn.php
 *
 * Multi-Source Bulk ISBN Importer — Story 13-3 (PRO)
 *
 * Accepts a batch of ISBN-13 values and resolves each one against multiple
 * data sources in cascade order:
 *
 *   1. Google Books  — MBS_Importer_Google_Books::fetch()
 *   2. Open Library  — MBS_Importer_Open_Library::fetch()
 *   3. Amazon PA-API — MBS_Importer_Amazon::fetch() (if PA-API credentials saved)
 *
 * Each ISBN is resolved by the first source that returns a match. ISBNs that
 * are not found in any source are collected in the `not_found` list.
 *
 * Returns candidates in the same format used by all MBS importers so the
 * Tab 5 confirmation UI and MBS_Importer_CSV::import_candidates() can handle
 * the results without modification.
 *
 * Architecture:
 *   fetch()  — fetches and returns candidates. Does not insert.
 *   Tab 5 JS presents the candidate list for admin confirmation.
 *   Tab 5 AJAX handler calls MBS_Importer_CSV::import_candidates().
 *
 * PRO-gated: returns an error result immediately for FREE / unregistered installs.
 *
 * Limits:
 *   MAX_ISBNS   — maximum number of ISBNs accepted per call (default 100).
 *   BATCH_SIZE  — number of ISBNs passed to each per-source call (default 50,
 *                 matching the per-source caps in Google Books and Open Library).
 *
 * @package    My_Book_Showroom
 * @subpackage Includes/Import
 * @since      1.0.0
 */

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

/**
 * Class MBS_Importer_Bulk_ISBN
 */
class MBS_Importer_Bulk_ISBN { // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedClassFound -- mbs_/MBS_ prefix is correct; scanner not configured with plugin prefix

	/** Maximum ISBNs accepted per fetch call. */
	const MAX_ISBNS = 100;

	/**
	 * Batch size passed to each source importer.
	 * Matches the per-source cap in Google Books and Open Library.
	 */
	const BATCH_SIZE = 50;

	/**
	 * Cascade order for source importers.
	 * Each entry is an array: [ class_name, method_name ].
	 * Sources are tried in order; the first hit for each ISBN wins.
	 */
	private static function source_cascade(): array {
		$sources = array(
			array( 'MBS_Importer_Google_Books', 'fetch' ),
			array( 'MBS_Importer_Open_Library', 'fetch' ),
		);

		// Include Amazon PA-API only if credentials have been configured.
		if ( self::amazon_configured() ) {
			$sources[] = array( 'MBS_Importer_Amazon', 'fetch' );
		}

		return $sources;
	}

	/**
	 * Fetch book metadata for a batch of ISBN-13 values from all sources.
	 *
	 * Each ISBN is resolved by the first source that returns a candidate.
	 * ISBNs that yield no match across all sources go into `not_found`.
	 *
	 * @param string[] $isbns Raw ISBN-13 values (may be unnormalised).
	 * @return array{
	 *   success: bool,
	 *   candidates: array[],
	 *   not_found: string[],
	 *   errors: string[],
	 *   error: string,
	 *   source_summary: array<string, int>
	 * }
	 */
	public static function fetch( array $isbns ): array {
		$result = array(
			'success'        => false,
			'candidates'     => array(),
			'not_found'      => array(),
			'errors'         => array(),
			'error'          => '',
			'source_summary' => array(), // e.g. [ 'Google Books' => 12, 'Open Library' => 3 ]
		);

		// Enforce maximum and normalise.
		$isbns = array_slice( $isbns, 0, self::MAX_ISBNS );
		$isbns = array_values( array_filter( array_map( 'trim', $isbns ) ) );

		if ( empty( $isbns ) ) {
			$result['error'] = 'No ISBN values provided.';
			return $result;
		}

		// Normalize all ISBNs up front; collect invalid ones immediately.
		$valid_map = array(); // normalized_isbn → raw_isbn
		foreach ( $isbns as $raw ) {
			$normalized = MBS_ISBN_Helper::normalize_isbn( $raw );
			if ( $normalized ) {
				// Deduplicate: last occurrence of a given ISBN wins.
				$valid_map[ $normalized ] = $raw;
			} else {
				$result['errors'][] = $raw . ': Invalid ISBN-13 — skipped.';
			}
		}

		if ( empty( $valid_map ) ) {
			$result['error'] = 'No valid ISBN-13 values after normalization.';
			return $result;
		}

		// Track which ISBNs still need resolving.
		$unresolved = array_keys( $valid_map ); // array of normalized ISBNs

		// Work through the source cascade.
		foreach ( self::source_cascade() as $source ) {
			list( $class, $method ) = $source;

			if ( empty( $unresolved ) ) {
				break;
			}

			if ( ! class_exists( $class ) || ! method_exists( $class, $method ) ) {
				$result['errors'][] = "Source {$class} not available — skipped.";
				continue;
			}

			$source_label = self::source_label( $class );

			// Split unresolved ISBNs into batches of BATCH_SIZE.
			$batches = array_chunk( $unresolved, self::BATCH_SIZE );

			foreach ( $batches as $batch ) {
				$source_result = call_user_func( array( $class, $method ), $batch );

				if ( empty( $source_result['candidates'] ) ) {
					// Collect any per-source errors for transparency.
					if ( ! empty( $source_result['errors'] ) ) {
						foreach ( $source_result['errors'] as $err ) {
							$result['errors'][] = "[{$source_label}] " . $err;
						}
					}
					continue;
				}

				// Merge in any candidates returned; mark those ISBNs as resolved.
				foreach ( $source_result['candidates'] as $candidate ) {
					$isbn = isset( $candidate['isbn13'] )
						? MBS_ISBN_Helper::normalize_isbn( $candidate['isbn13'] )
						: '';

					$result['candidates'][] = array_merge(
						$candidate,
						array( '_import_source' => $source_label )
					);

					// Remove from unresolved list.
					if ( $isbn ) {
						$unresolved = array_values( array_filter(
							$unresolved,
							static fn( $i ) => $i !== $isbn
						) );
					}

					// Tally by source.
					$result['source_summary'][ $source_label ] =
						( $result['source_summary'][ $source_label ] ?? 0 ) + 1;
				}

				// Carry over per-source errors.
				if ( ! empty( $source_result['errors'] ) ) {
					foreach ( $source_result['errors'] as $err ) {
						$result['errors'][] = "[{$source_label}] " . $err;
					}
				}
			}
		}

		// Any ISBN still unresolved after all sources = not found.
		$result['not_found'] = array_values( array_map(
			static fn( $isbn ) => $valid_map[ $isbn ] ?? $isbn,
			$unresolved
		) );

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

	// -------------------------------------------------------------------------
	// Helpers
	// -------------------------------------------------------------------------

	/**
	 * Return a human-readable label for a source class name.
	 *
	 * @param string $class Class name, e.g. 'MBS_Importer_Google_Books'.
	 * @return string
	 */
	private static function source_label( string $class ): string {
		$labels = array(
			'MBS_Importer_Google_Books' => 'Google Books',
			'MBS_Importer_Open_Library' => 'Open Library',
			'MBS_Importer_Amazon'       => 'Amazon PA-API',
		);
		return $labels[ $class ] ?? $class;
	}

	/**
	 * Return true when Amazon PA-API credentials are configured.
	 *
	 * @return bool
	 */
	private static function amazon_configured(): bool {
		$access_key = get_option( 'mbs_setting_general_amazon_access_key', '' );
		$secret_key = get_option( 'mbs_setting_general_amazon_secret_key', '' );
		$partner_tag = get_option( 'mbs_setting_general_amazon_partner_tag', '' );
		return ( '' !== $access_key && '' !== $secret_key && '' !== $partner_tag );
	}
}
