<?php
/**
 * File Path: includes/import/class-mbs-importer-open-library.php
 *
 * Open Library Importer (PRO)
 *
 * Fetches book metadata from the Open Library Books API by ISBN-13. Used as
 * a fallback when Google Books returns no results for a given ISBN. Returns
 * candidates for admin confirmation before insertion.
 *
 * Architecture: same two-step pattern as Google Books importer.
 *   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().
 *
 * @package MyBookShowroom
 * @since   1.0.0
 */

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

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

	/** Maximum ISBNs per fetch call (spec: Story 7-4). */
	const MAX_ISBNS = 50;

	/** Open Library Books API base URL. */
	const API_BASE = 'https://openlibrary.org/api/books';

	/** HTTP request timeout in seconds. */
	const REQUEST_TIMEOUT = 15;

	/**
	 * Fetch book metadata from Open Library for a list of ISBN-13 values.
	 *
	 * Uses the batch `bibkeys` endpoint: one HTTP request for up to 50 ISBNs.
	 * Returns a candidate list for Tab 5 confirmation; does NOT insert.
	 *
	 * @param string[] $isbns Raw ISBN-13 values (may be unnormalised).
	 * @return array{
	 *   success: bool,
	 *   candidates: array[],
	 *   not_found: string[],
	 *   errors: string[],
	 *   error: string
	 * }
	 */
	public static function fetch( array $isbns ): array {
		$result = array(
			'success'    => false,
			'candidates' => array(),
			'not_found'  => array(),
			'errors'     => array(),
			'error'      => '',
		);

		// Enforce the 50-ISBN limit and normalize.
		$isbns = array_slice( $isbns, 0, self::MAX_ISBNS );
		$isbns = array_filter( array_map( 'trim', $isbns ) );

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

		// Normalise all ISBNs before building the API request.
		$normalized_map = array(); // normalized_isbn → raw_isbn for error reporting
		foreach ( $isbns as $raw ) {
			$normalized = MBS_ISBN_Helper::normalize_isbn( $raw );
			if ( $normalized ) {
				$normalized_map[ $normalized ] = $raw;
			} else {
				$result['errors'][] = $raw . ': Invalid ISBN-13.';
			}
		}

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

		// Open Library batch endpoint: comma-separated bibkeys in one request.
		// NEW-H2: Single batch request — no sleep() needed or used.
		$bibkeys = implode( ',', array_map(
			fn( $isbn ) => 'ISBN:' . $isbn,
			array_keys( $normalized_map )
		) );

		$url = add_query_arg(
			array(
				'bibkeys' => $bibkeys,
				'format'  => 'json',
				'jscmd'   => 'data',
			),
			self::API_BASE
		);

		$response = wp_remote_get( $url, array( 'timeout' => self::REQUEST_TIMEOUT ) );

		if ( is_wp_error( $response ) ) {
			$result['error'] = 'Open Library API request failed: ' . $response->get_error_message();
			return $result;
		}

		$http_code = wp_remote_retrieve_response_code( $response );
		if ( 200 !== $http_code ) {
			$result['error'] = 'Open Library API returned HTTP ' . $http_code . '.';
			return $result;
		}

		$body = json_decode( wp_remote_retrieve_body( $response ), true );

		if ( ! is_array( $body ) ) {
			$result['error'] = 'Open Library API returned an unexpected response format.';
			return $result;
		}

		// Map each returned volume to a candidate.
		foreach ( $normalized_map as $normalized => $raw ) {
			$key = 'ISBN:' . $normalized;
			if ( ! isset( $body[ $key ] ) ) {
				$result['not_found'][] = (string) $normalized;
				continue;
			}

			$candidate = self::map_volume( $body[ $key ], $normalized );
			if ( null !== $candidate ) {
				$result['candidates'][] = $candidate;
			} else {
				$result['not_found'][] = (string) $normalized;
			}
		}

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

	// =========================================================================
	// Private helpers
	// =========================================================================

	/**
	 * Map an Open Library volume object to a canonical MBS candidate array.
	 *
	 * The shape returned must match MBS_Importer_CSV::insert_book() expectations
	 * so that all importers share one insert code path (NEW-C1/NEW-C3).
	 *
	 * @param array  $volume     Volume data object from the Open Library API.
	 * @param string $normalized Normalised ISBN-13.
	 * @return array|null Candidate array, or null if title is absent.
	 */
	private static function map_volume( array $volume, string $normalized ): ?array {
		$title = trim( $volume['title'] ?? '' );
		if ( empty( $title ) ) {
			return null;
		}

		// Author: Open Library returns [{name: "..."}, ...].
		$author = '';
		if ( ! empty( $volume['authors'][0]['name'] ) ) {
			$author = trim( $volume['authors'][0]['name'] );
		}

		// Publisher: [{name: "..."}, ...].
		$publisher = '';
		if ( ! empty( $volume['publishers'][0]['name'] ) ) {
			$publisher = trim( $volume['publishers'][0]['name'] );
		} elseif ( is_string( $volume['publishers'][0] ?? null ) ) {
			$publisher = trim( $volume['publishers'][0] );
		}

		// Description: can be a string or {"type": "...", "value": "..."}.
		$description = '';
		if ( is_string( $volume['description'] ?? null ) ) {
			$description = $volume['description'];
		} elseif ( is_array( $volume['description'] ?? null ) && isset( $volume['description']['value'] ) ) {
			$description = $volume['description']['value'];
		}

		// Cover URL.
		$cover_url = '';
		if ( ! empty( $volume['cover']['large'] ) ) {
			$cover_url = $volume['cover']['large'];
		} elseif ( ! empty( $volume['cover']['medium'] ) ) {
			$cover_url = $volume['cover']['medium'];
		} elseif ( ! empty( $volume['cover']['small'] ) ) {
			$cover_url = $volume['cover']['small'];
		}

		// Page count.
		$page_length = '';
		if ( ! empty( $volume['number_of_pages'] ) ) {
			$page_length = (string) (int) $volume['number_of_pages'];
		}

		// Publication date.
		$pub_date = '';
		$raw_date = trim( $volume['publish_date'] ?? '' );
		if ( ! empty( $raw_date ) ) {
			$ts = strtotime( $raw_date );
			if ( $ts ) {
				$pub_date = gmdate( 'Y-m-d', $ts );
			}
		}

		return array(
			'_source'      => 'open_library',
			'title'        => $title,
			// author key matches insert_book() taxonomy path (not _mbs_primary_author).
			'author'       => $author,
			'isbn13'       => $normalized,
			'publisher'    => $publisher,
			'pub_date'     => $pub_date,
			'format'       => 'paperback',  // Open Library does not provide format data.
			'page_length'  => $page_length,
			// description → post_content per insert_book() F-2 path.
			'post_content' => sanitize_textarea_field( $description ),
			// cover_url → mbs_import_cover_url postmeta (Sprint 9 sideload).
			'cover_url'    => esc_url_raw( $cover_url ),
		);
	}
}
