<?php
/**
 * File Path: includes/export/class-mbs-book-bible-data-gatherer.php
 *
 * Book Bible Data Gatherer
 *
 * Responsible for assembling all book data required to populate a Book Bible
 * workbook. Extracted from MBS_Book_Bible_Exporter (REF-001) to separate
 * data access from presentation.
 *
 * @package MyBookShowroom
 * @since   1.1.0
 */

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

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

	/**
	 * Gather all book data needed to populate the workbook.
	 *
	 * Calls MBS_Book_Export_Data::get_book_data() for the 27 shared base fields,
	 * then supplements with Book-Bible-specific fields. Also pre-computes
	 * 'completion' and appends it to the returned array.
	 *
	 * @param int $post_id Book post ID.
	 * @return array<string, mixed>
	 * @since 1.1.0
	 */
	public function gather( int $post_id ): array {
		// -- Base fields from shared export helper --------------------------------
		$base           = MBS_Book_Export_Data::get_book_data( $post_id );
		$title          = $base['title'];
		$teaser         = $base['teaser'];
		$publisher      = $base['publisher'];
		$pub_date       = $base['pub_date'];
		$coming_soon    = (bool) $base['coming_soon'];
		$lccn           = $base['lccn'];
		$audience       = $base['audience'];
		$imprint        = $base['imprint'];
		$edition_stmt   = $base['edition_statement'];
		$series_name    = $base['series'];
		$series_number  = $base['series_number'];
		$age_range_from = $base['age_range_from'];
		$age_range_to   = $base['age_range_to'];

		// BISAC: pipe-delimited string returned by get_book_data() -> array
		$bisac_codes = ( '' !== $base['bisac_codes'] )
			? explode( '|', $base['bisac_codes'] ) : [];

		// -- Book-Bible-specific fields (not in MBS_Book_Export_Data) -----------
		$synopsis       = (string) get_post_meta( $post_id, 'mbs_synopsis',      true );
		$tagline        = (string) get_post_meta( $post_id, 'mbs_tagline',        true );
		$copyright_year = (string) get_post_meta( $post_id, 'mbs_copyright_year', true );
		$admin_notes    = (string) get_post_meta( $post_id, '_mbs_admin_notes',   true );

		$decode       = static fn( $v ) => is_array( $v ) ? $v : ( json_decode( (string) $v, true ) ?: [] );
		$thema_codes  = MBS_Classification_DAO::get_book_codes( $post_id, 'thema' ); // Phase 1: read from custom table
		$bowker_codes = MBS_Classification_DAO::get_book_codes( $post_id, 'bowker' ); // R2-003

		// Full author term objects for rich display in Tab 3 and Tab 4
		$author_terms = wp_get_object_terms( $post_id, MBS_TAX_AUTHOR, [ 'fields' => 'all' ] );
		$author_data  = [];
		foreach ( is_array( $author_terms ) ? $author_terms : [] as $term ) {
			$author_data[] = [
				'name'       => $term->name,
				'name_inv'   => (string) get_term_meta( $term->term_id, 'mbs_name_inverted',     true ),
				'birth_year' => (string) get_term_meta( $term->term_id, 'mbs_author_birth_year', true ),
				'bio'        => (string) get_term_meta( $term->term_id, 'mbs_author_bio',        true ),
			];
		}

		// Genre and tag arrays (full arrays, not pipe-delimited strings)
		$genre_terms = wp_get_object_terms( $post_id, MBS_TAX_GENRE, [ 'fields' => 'names' ] );
		$tag_terms   = wp_get_object_terms( $post_id, MBS_TAX_TAG,   [ 'fields' => 'names' ] );
		$genre_terms = is_array( $genre_terms ) ? $genre_terms : [];
		$tag_terms   = is_array( $tag_terms )   ? $tag_terms   : [];

		// KDP keywords: first 7 tags, padded to exactly 7 entries
		$kdp_keywords = array_slice( $tag_terms, 0, 7 );
		while ( count( $kdp_keywords ) < 7 ) {
			$kdp_keywords[] = '';
		}

		// DAO objects (stdClass rows)
		$format_rows  = MBS_Format_Row_DAO::get_by_post( $post_id );
		$buy_links    = MBS_Buy_Link_DAO::get_buy_links_for_book( $post_id );
		$platforms    = MBS_Buy_Link_DAO::get_platforms_for_book( $post_id );
		$contributors = MBS_Contributor_Book_DAO::get_by_post( $post_id );
		$related      = MBS_Related_Products_DAO::get_for_post( $post_id );

		// Description enrichment and BIC derivation for Classification tab.
		$class_dao          = new MBS_Classification_DAO();
		$bisac_descriptions = $class_dao->get_descriptions_batch( 'bisac', $bisac_codes );
		$thema_descriptions = $class_dao->get_descriptions_batch( 'thema', $thema_codes );
		$bic_codes          = $class_dao->get_bic_for_thema_batch( $thema_codes, 10 );
		$bic_descriptions   = ! empty( $bic_codes )
			? $class_dao->get_descriptions_batch( 'bic', array_column( $bic_codes, 'bic_code' ) )
			: array();

		$cover   = $this->get_cover_image( $post_id );
		$pub_ref = $this->compute_publisher_reference(
			$title, $author_data, $series_name, $series_number
		);

		$data = compact(
			'post_id', 'title', 'tagline', 'teaser', 'synopsis',
			'pub_date', 'copyright_year', 'edition_stmt', 'coming_soon',
			'admin_notes', 'lccn', 'imprint', 'publisher',
			'author_data', 'series_name', 'series_number',
			'age_range_from', 'age_range_to',
			'bisac_codes', 'bisac_descriptions',
			'thema_codes', 'thema_descriptions',
			'bic_codes', 'bic_descriptions',
			'bowker_codes', 'audience',
			'genre_terms', 'tag_terms', 'kdp_keywords',
			'format_rows', 'buy_links', 'platforms', 'contributors', 'related', 'cover', 'pub_ref'
		);

		// PUB-only fields for Tab 7 (Copyright Page) and Tab 8 (EPUB OPF Metadata).
		if ( mbs_is_pub() ) {
			$pub_address      = (string) get_option( 'mbs_pub_publisher_address', '' );
			$pub_country      = (string) get_option( 'mbs_pub_country_of_printing', 'United States of America' );
			$disclaimer_global = (string) get_option( 'mbs_pub_copyright_disclaimer', '' );
			$disclaimer_book   = (string) get_post_meta( $post_id, 'mbs_copyright_disclaimer', true );
			$pub_disclaimer    = ( '' !== $disclaimer_book ) ? $disclaimer_book : $disclaimer_global;
			$language          = (string) get_post_meta( $post_id, 'mbs_language', true );
			if ( '' === $language ) {
				$language = (string) get_option( 'mbs_setting_onix_default_language', 'eng' );
			}
			// H-001: Per spec §5h, the UUID identifier is always an amber placeholder
			// in the XML block. MBS never generates or auto-fills a UUID.
			$data['pub_address']   = $pub_address;
			$data['pub_country']   = $pub_country;
			$data['pub_disclaimer'] = $pub_disclaimer;
			$data['language']      = $language;
		}

		// Pre-compute completion so tab writers can use $data['completion'] directly.
		$data['completion'] = $this->get_completion( $data );

		return $data;
	}

	/**
	 * Retrieve cover image bytes, extension, and pixel dimensions.
	 *
	 * Local file first (no network); remote fallback for CDN / S3 attachments.
	 *
	 * @param int $post_id Book post ID.
	 * @return array{bytes: string, ext: string, w: int, h: int}
	 * @since 1.1.0
	 */
	public function get_cover_image( int $post_id ): array {
		$empty    = [ 'bytes' => '', 'ext' => '', 'w' => 0, 'h' => 0 ];
		$thumb_id = get_post_thumbnail_id( $post_id ); // R5-exempt: export data gathering context
		if ( ! $thumb_id ) {
			return $empty;
		}

		// 1. Local file path (same pattern as MBS_Exporter_Zip)
		$local = get_attached_file( $thumb_id );
		if ( $local && file_exists( $local ) ) {
			$ext = strtolower( pathinfo( $local, PATHINFO_EXTENSION ) );
			if ( ! in_array( $ext, [ 'jpg', 'jpeg', 'png' ], true ) ) {
				return $empty;
			}
			$ext  = ( 'jpeg' === $ext ) ? 'jpg' : $ext;
			$size = getimagesize( $local );
			if ( ! $size ) {
				return $empty;
			}
			return [
				'bytes' => file_get_contents( $local ), // phpcs:ignore WordPress.WP.AlternativeFunctions
				'ext'   => $ext,
				'w'     => $size[0],
				'h'     => $size[1],
			];
		}

		// 2. Remote fallback (CDN / S3 / external storage)
		$url = get_the_post_thumbnail_url( $post_id, 'full' ); // R5-exempt: export data gathering context
		if ( ! $url ) {
			return $empty;
		}
		$resp = wp_remote_get( $url, [ 'timeout' => 10 ] );
		if ( is_wp_error( $resp ) || 200 !== wp_remote_retrieve_response_code( $resp ) ) {
			return $empty;
		}
		$body  = wp_remote_retrieve_body( $resp );
		$ctype = wp_remote_retrieve_header( $resp, 'content-type' );
		$ext   = str_contains( $ctype, 'png' ) ? 'png'
			: ( str_contains( $ctype, 'jpeg' ) ? 'jpg' : '' );
		if ( ! $ext ) {
			return $empty;
		}
		$img = imagecreatefromstring( $body );
		if ( ! $img ) {
			return $empty;
		}
		$w = imagesx( $img );
		$h = imagesy( $img );
		imagedestroy( $img );
		return [ 'bytes' => $body, 'ext' => $ext, 'w' => $w, 'h' => $h ];
	}

	/**
	 * Score book completeness across 14 checklist fields.
	 *
	 * @param array $data From gather().
	 * @return array{score: int, total: int, missing: string[]}
	 * @since 1.1.0
	 */
	private function get_completion( array $data ): array {
		$checks = [
			'Cover image'    => '' !== $data['cover']['bytes'],
			'Tagline'        => '' !== $data['tagline'],
			'ISBN (any)'     => ! empty( array_filter(
				$data['format_rows'],
				static fn( $r ) => ! empty( $r->isbn13 )
			) ),
			'LCCN'           => '' !== $data['lccn'],
			'BISAC (>=1)'    => ! empty( $data['bisac_codes'] ),
			'THEMA (>=1)'    => ! empty( $data['thema_codes'] ),
			'Bowker primary' => ! empty( $data['bowker_codes'] ),
			'Teaser'         => '' !== $data['teaser'],
			'Synopsis'       => '' !== $data['synopsis'],
			'Author bio'     => ! empty( $data['author_data'] )
				&& '' !== $data['author_data'][0]['bio'],
			'Pub date'       => '' !== $data['pub_date'],
			'Copyright year' => '' !== $data['copyright_year'],
			'Page count'     => ! empty( array_filter(
				$data['format_rows'],
				static fn( $r ) => ! empty( $r->page_length )
			) ),
			'Buy link'       => ! empty( $data['buy_links'] ),
		];

		$missing = array_keys( array_filter( $checks, static fn( $v ) => ! $v ) );
		return [
			'score'   => count( array_filter( $checks ) ),
			'total'   => 14,
			'missing' => $missing,
		];
	}

	/**
	 * Compute a suggested publisher reference from title/author/series initials.
	 *
	 * Delegates stop-word and position logic to MBS_Exporter_Zip public statics.
	 * Always displayed as amber in the workbook -- user should verify before use.
	 *
	 * @param string $title       Book title.
	 * @param array  $author_data From gather().
	 * @param string $series      Series name.
	 * @param string $pos         Series position string.
	 * @return string
	 * @since 1.1.0
	 */
	private function compute_publisher_reference(
		string $title, array $author_data, string $series, string $pos ): string {
		$ai      = $author_data
			? MBS_Exporter_Zip::make_initials( $author_data[0]['name'] ) : '';
		$si      = $series ? MBS_Exporter_Zip::make_initials( $series ) : '';
		$pos_str = $pos    ? MBS_Exporter_Zip::normalize_series_position( $pos ) : '';
		$ti      = MBS_Exporter_Zip::make_initials( $title );
		return implode( '_', array_filter( [ $ai, $si . $pos_str, $ti ] ) );
	}
}
