<?php
/**
 * File Path: includes/api/class-mbs-onix-parser-21.php
 *
 * ONIX 2.1 Product Parser -- My Book Showroom
 *
 * Receives a parsed SimpleXMLElement root from MBS_Onix_Parser and
 * normalises each flat <product> element into a product array using
 * the same schema as MBS_Onix_Parser_30.
 *
 * ONIX 2.1 key structural differences from 3.0:
 *   - All elements are direct children of <product> (flat, not nested composites).
 *   - Title in <title><titletype>01</titletype><titletext>...</titletext></title>.
 *   - Series in <series><titleofseries>...</titleofseries></series>.
 *   - Text content in <othertext><texttype>...</texttype><text>...</text></othertext>.
 *   - Related products in <relatedproduct> directly on <product> (no wrapper).
 *   - Publisher in <publisher><publishername>...</publishername></publisher> on <product>.
 *   - Publication date in <publicationdate>YYYYMMDD</publicationdate> (no role wrapper).
 *   - <publishingstatus> parsed directly (valid in ONIX 2.1 r2003+); flat element on <product>.
 *
 * @package    My_Book_Showroom
 * @subpackage Includes/Api
 * @since      1.2.0
 */

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

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

	/**
	 * Extract normalised product arrays from an ONIX 2.1 document root.
	 *
	 * @param SimpleXMLElement $xml   Parsed root element.
	 * @param array            &$stats Import stats; errors appended on failure.
	 * @return array[] Array of normalised product arrays.
	 */
	public function parse_products( SimpleXMLElement $xml, array &$stats ): array {
		// Short tags: <product>; reference tags: <Product>.
		$product_elements = $xml->product ?? $xml->Product ?? array();
		$products         = array();

		foreach ( $product_elements as $element ) {
			$product = $this->normalise_product( $element );
			if ( null !== $product ) {
				$products[] = $product;
			}
		}

		if ( empty( $products ) ) {
			$stats['errors'][] = __( 'No valid product records found in the ONIX 2.1 file.', 'my-book-showroom' );
		}

		return $products;
	}

	// -------------------------------------------------------------------------
	// Normalisation
	// -------------------------------------------------------------------------

	/**
	 * Extract a normalised product array from a single SimpleXML product element.
	 *
	 * Returns null if the element has no valid ISBN-13 (13 stripped digits).
	 *
	 * @param SimpleXMLElement $el
	 * @return array|null
	 */
	private function normalise_product( SimpleXMLElement $el ): ?array {

		// -- ISBN-13 (ProductIDType 15) -------------------------------------------
		$isbn        = '';
		$identifiers = $el->productidentifier ?? $el->ProductIdentifier ?? array();
		foreach ( $identifiers as $id ) {
			$type = (string) ( $id->productidtype ?? $id->ProductIDType ?? '' );
			if ( '15' === $type ) {
				$isbn = (string) ( $id->idvalue ?? $id->IDValue ?? '' );
				break;
			}
		}
		$isbn = MBS_ISBN_Helper::normalize_isbn( $isbn );
		if ( false === $isbn ) {
			return null; // No usable ISBN.
		}

		// -- ProductForm + ProductFormDetail (flat in 2.1) ------------------------
		$product_form   = (string) ( $el->productform ?? $el->ProductForm ?? '' );
		$product_detail = (string) ( $el->productformdetail ?? $el->ProductFormDetail ?? '' );

		// -- Title + Subtitle (2.1: <title><titletype>01</titletype><titletext>) --
		$title    = '';
		$subtitle = '';
		$titles   = $el->title ?? $el->Title ?? array();
		foreach ( $titles as $t ) {
			$ttype = (string) ( $t->titletype ?? $t->TitleType ?? '' );
			if ( '01' === $ttype || '' === $ttype ) {
				$title    = (string) ( $t->titletext ?? $t->TitleText ?? '' );
				$subtitle = (string) ( $t->subtitle ?? $t->Subtitle ?? '' );
				break;
			}
		}
		// Fallback: bare <titletext> directly on <product> (some 2.1 variants).
		if ( '' === $title ) {
			$title = (string) ( $el->titletext ?? $el->TitleText ?? '' );
		}

		// -- Author KeyNames (A01) + Narrator (E07) (flat in 2.1) ----------------
		$author_key_names = '';
		$narrator         = '';
		$contributors     = $el->contributor ?? $el->Contributor ?? array();
		foreach ( $contributors as $contrib ) {
			$role = (string) ( $contrib->contributorrole ?? $contrib->ContributorRole ?? '' );
			if ( 'A01' === $role && '' === $author_key_names ) {
				$author_key_names = (string) ( $contrib->keynames ?? $contrib->KeyNames ?? '' );
			}
			if ( 'E07' === $role && '' === $narrator ) {
				$narrator = (string) ( $contrib->personnameinverted ?? $contrib->PersonNameInverted
					?? $contrib->keynames ?? $contrib->KeyNames ?? '' );
				if ( '' === $narrator ) {
					$nb       = (string) ( $contrib->namesbeforekey ?? $contrib->NamesBeforeKey ?? '' );
					$kn       = (string) ( $contrib->keynames ?? $contrib->KeyNames ?? '' );
					$narrator = trim( $nb . ' ' . $kn );
				}
			}
		}

		// Contributor statement / BiographicalNote (b044) — first A01 contributor.
		$contributor_statement = '';
		$contributors          = $el->contributor ?? $el->Contributor ?? array();
		foreach ( $contributors as $contrib ) {
			$role = (string) ( $contrib->contributorrole ?? $contrib->ContributorRole ?? '' );
			if ( 'A01' === $role ) {
				$contributor_statement = (string) ( $contrib->b044 ?? $contrib->biographicalnote ?? $contrib->BiographicalNote ?? '' );
				break;
			}
		}

		// -- Sibling ISBNs from RelatedProduct (relation code 06) (flat in 2.1) --
		// In 2.1 <relatedproduct> is a direct child of <product> -- no <relatedmaterial> wrapper.
		$sibling_isbns    = array();
		$related_products = $el->relatedproduct ?? $el->RelatedProduct ?? array();
		foreach ( $related_products as $rp ) {
			$rel_code = (string) ( $rp->productrelationcode ?? $rp->ProductRelationCode ?? '' );
			if ( '06' !== $rel_code ) {
				continue;
			}
			$rp_ids = $rp->productidentifier ?? $rp->ProductIdentifier ?? array();
			foreach ( $rp_ids as $rpid ) {
				$rpid_type = (string) ( $rpid->productidtype ?? $rpid->ProductIDType ?? '' );
				if ( '15' === $rpid_type ) {
					$sib = MBS_ISBN_Helper::normalize_isbn( (string) ( $rpid->idvalue ?? $rpid->IDValue ?? '' ) );
					if ( false !== $sib ) {
						$sibling_isbns[] = $sib;
					}
				}
			}
		}

		// -- Text content (OtherText in 2.1, replaces CollateralDetail/TextContent) --
		// TextType codes: 01=Main description, 02=Short desc, 03=Long description,
		//                 04=Table of contents, 13=Review quote
		$description = '';
		$short_desc  = '';
		$toc         = '';
		$other_texts = $el->othertext ?? $el->OtherText ?? array();
		foreach ( $other_texts as $ot ) {
			$text_type = (string) ( $ot->texttype ?? $ot->TextType ?? '' );
			$text_val  = (string) ( $ot->text ?? $ot->Text ?? '' );
			// 2.1 TextType 01/03 both map to long description; 02 = short description.
			if ( '' === $description && in_array( $text_type, array( '01', '03' ), true ) ) {
				$description = $text_val;
			}
			if ( '02' === $text_type ) { $short_desc = $text_val; }
			if ( '04' === $text_type ) { $toc        = $text_val; }
		}

		// -- PublishingStatus (flat <publishingstatus> in 2.1) --------------------
		$pub_status = (string) ( $el->publishingstatus ?? $el->PublishingStatus ?? '' );

		// -- Publication date (flat <publicationdate>YYYYMMDD</publicationdate>) ---
		$pub_date_raw = (string) ( $el->publicationdate ?? $el->PublicationDate ?? '' );
		$pub_date     = $pub_date_raw; // Kept as-is; reconciler normalises dates.

		// -- Publisher name (flat <publisher><publishername>) ----------------------
		$pub_name   = '';
		$publishers = $el->publisher ?? $el->Publisher ?? array();
		foreach ( $publishers as $pub ) {
			$pub_role = (string) ( $pub->publishingrole ?? $pub->PublishingRole ?? '' );
			if ( '01' === $pub_role || '' === $pub_role ) {
				$pub_name = (string) ( $pub->publishername ?? $pub->PublisherName ?? '' );
				if ( '' !== $pub_name ) { break; }
			}
		}

		// SalesRights (flat in 2.1: <salesrights><j101>/<rightscountry>) — build territorial_rights_v2.
		$territorial_rights_v2 = '';
		$sales_rights_els      = $el->salesrights ?? $el->SalesRights ?? array();
		foreach ( $sales_rights_els as $sr ) {
			$sr_type       = (string) ( $sr->j101 ?? $sr->salesrightstype ?? $sr->SalesRightsType ?? '' ); // SalesRightsType
			$country_str   = (string) ( $sr->rightscountry ?? $sr->RightsCountry ?? '' ); // Space-separated ISO codes
			$country_array = array_values( array_filter( explode( ' ', $country_str ) ) );
			$v2 = array( 'type' => 'world', 'countries' => array() );
			if ( '01' === $sr_type && empty( $country_array ) ) {
				$v2['type'] = 'world';
			} elseif ( '01' === $sr_type && ! empty( $country_array ) ) {
				$v2['type']      = 'include';
				$v2['countries'] = $country_array;
			} elseif ( in_array( $sr_type, array( '02', '03', '04', '05', '06' ), true ) ) {
				$v2['type']      = 'exclude';
				$v2['countries'] = $country_array;
			}
			$territorial_rights_v2 = wp_json_encode( $v2 );
			break; // First SalesRights record only.
		}

		// -- Language (flat <language><languagerole>01</languagerole><languagecode>) --
		$language = '';
		$langs    = $el->language ?? $el->Language ?? array();
		foreach ( $langs as $lang ) {
			$lang_role = (string) ( $lang->languagerole ?? $lang->LanguageRole ?? '' );
			if ( '01' === $lang_role || '' === $lang_role ) {
				$language = (string) ( $lang->languagecode ?? $lang->LanguageCode ?? '' );
				if ( '' !== $language ) { break; }
			}
		}
		// Fallback: bare <languagecode> on <product>.
		if ( '' === $language ) {
			$language = (string) ( $el->languagecode ?? $el->LanguageCode ?? '' );
		}

		// -- BISAC subject codes (SubjectSchemeIdentifier 10) ---------------------
		$bisac_codes = array();
		$subjects    = $el->subject ?? $el->Subject ?? array();
		foreach ( $subjects as $subj ) {
			$scheme = (string) ( $subj->subjectschemeidentifier ?? $subj->SubjectSchemeIdentifier ?? '' );
			if ( '10' === $scheme ) {
				$code = sanitize_text_field( (string) ( $subj->subjectcode ?? $subj->SubjectCode ?? '' ) );
				if ( '' !== $code ) {
					$bisac_codes[] = $code;
				}
			}
		}

		// -- Thema subject codes (SubjectSchemeIdentifier 93) ---------------------
		$thema_codes = array();
		foreach ( $subjects as $subj ) {
			$scheme = (string) ( $subj->subjectschemeidentifier ?? $subj->SubjectSchemeIdentifier ?? '' );
			if ( '93' === $scheme ) {
				$tcode = sanitize_text_field( (string) ( $subj->subjectcode ?? $subj->SubjectCode ?? '' ) );
				if ( '' !== $tcode ) {
					$thema_codes[] = $tcode;
				}
			}
		}

		// -- Price (SupplyDetail > Price, PriceTypeCode 01 in 2.1) ----------------
		$price    = '';
		$currency = '';
		$supply   = $el->supplydetail ?? $el->SupplyDetail ?? null;
		if ( null !== $supply ) {
			// In 2.1 there may be multiple <supplydetail> elements; iterate.
			foreach ( $el->supplydetail ?? $el->SupplyDetail ?? array() as $sd ) {
				$prices = $sd->price ?? $sd->Price ?? array();
				foreach ( $prices as $pr ) {
					// 2.1 uses <pricetypecode> (not <pricetype>).
					$pr_type = (string) ( $pr->pricetypecode ?? $pr->PriceTypeCode
						?? $pr->pricetype ?? $pr->PriceType ?? '' );
					if ( '01' === $pr_type ) {
						$price    = (string) ( $pr->priceamount ?? $pr->PriceAmount ?? '' );
						$currency = (string) ( $pr->currencycode ?? $pr->CurrencyCode ?? '' );
						break 2;
					}
				}
			}
		}

		// -- Extent: page count + duration (flat in 2.1) --------------------------
		$page_length = 0;
		$duration_hh = 0;
		$duration_mm = 0;
		$extents     = $el->extent ?? $el->Extent ?? array();
		foreach ( $extents as $ext ) {
			$ext_type  = (string) ( $ext->extenttype  ?? $ext->ExtentType  ?? '' );
			$ext_unit  = (string) ( $ext->extentunit  ?? $ext->ExtentUnit  ?? '' );
			$ext_value = (string) ( $ext->extentvalue ?? $ext->ExtentValue ?? '' );
			if ( '00' === $ext_type && '03' === $ext_unit ) {
				$page_length = absint( $ext_value );
			} elseif ( '09' === $ext_type && '15' === $ext_unit && 4 === strlen( $ext_value ) ) {
				$duration_hh = (int) substr( $ext_value, 0, 2 );
				$duration_mm = (int) substr( $ext_value, 2, 2 );
			}
		}
		// Fallback for page count: bare <numberofpages> on <product>.
		if ( 0 === $page_length ) {
			$np = (string) ( $el->numberofpages ?? $el->NumberOfPages ?? '' );
			if ( '' !== $np ) {
				$page_length = absint( $np );
			}
		}

		// -- Audience code (flat <audiencecode> in 2.1) ---------------------------
		$audience_code = (string) ( $el->audiencecode ?? $el->AudienceCode ?? '' );

		// -- Series (flat <series><titleofseries>...</titleofseries> in 2.1) ------
		// GAP-007: extract series name + number for reconciler term lookup.
		$series_name   = '';
		$series_number = '';
		$series_els    = $el->series ?? $el->Series ?? array();
		foreach ( $series_els as $series_el ) {
			$sn = (string) ( $series_el->titleofseries ?? $series_el->TitleOfSeries ?? '' );
			if ( '' !== $sn ) {
				$series_name   = $sn;
				$series_number = (string) ( $series_el->b019 ?? $series_el->numberwithinSeries ?? $series_el->NumberWithinSeries ?? '' ); // NumberWithinSeries
				break;
			}
		}

		// -- Edition fields (flat in 2.1) -----------------------------------------
		$edition_statement = (string) ( $el->editionstatement ?? $el->EditionStatement ?? '' );
		$edition_number    = absint( $el->editionnumber ?? $el->EditionNumber ?? 0 );
		$edition_type      = '';
		$edition_type_raw  = (string) ( $el->editiontypecode ?? $el->EditionTypeCode ?? '' );
		if ( MBS_ONIX_Lists::is_valid_edition_type( strtoupper( $edition_type_raw ) ) ) {
			$edition_type = strtoupper( $edition_type_raw );
		}

		// -- Prize composites (flat -- direct children of <product> in 2.1) -----------
		$prizes    = array();
		$prize_els = $el->prize ?? $el->Prize ?? array();
		foreach ( $prize_els as $prz ) {
			$prize_name = sanitize_text_field( (string) ( $prz->prizename ?? $prz->PrizeName ?? '' ) );
			if ( '' === $prize_name ) {
				continue;
			}
			$prize_year = sanitize_text_field( (string) ( $prz->prizeyear ?? $prz->PrizeYear ?? '' ) );
			$prize_code = sanitize_text_field( (string) ( $prz->prizecode ?? $prz->PrizeCode ?? '' ) );
			$prizes[]   = array(
				'name' => $prize_name,
				'year' => $prize_year,
				'code' => $prize_code,
			);
		}

		// -- Assemble and sanitize ------------------------------------------------
		return array(
			'isbn'              => $isbn,
			'sibling_isbns'     => $sibling_isbns,
			'title'             => sanitize_text_field( $title ),
			'subtitle'          => sanitize_text_field( $subtitle ),
			'author_key_names'  => sanitize_text_field( $author_key_names ),
			'product_form'      => sanitize_text_field( $product_form ),
			'product_detail'    => sanitize_text_field( $product_detail ),
			'description'       => wp_kses_post( $description ),
			'short_desc'        => sanitize_text_field( $short_desc ),
			'toc'               => sanitize_text_field( $toc ),
			'pub_status'        => sanitize_text_field( $pub_status ),
			'pub_date'          => sanitize_text_field( $pub_date ),
			'publisher'         => sanitize_text_field( $pub_name ),
			'language'          => sanitize_text_field( $language ),
			'bisac_codes'       => $bisac_codes,
			'thema_codes'       => $thema_codes,
			'price'             => sanitize_text_field( $price ),
			'currency'          => sanitize_text_field( $currency ),
			'page_length'       => $page_length,
			'duration_hh'       => $duration_hh,
			'duration_mm'       => $duration_mm,
			'narrator'          => sanitize_text_field( $narrator ),
			'series_name'       => sanitize_text_field( $series_name ),
			'series_number'     => sanitize_text_field( $series_number ),
			'audience_code'     => sanitize_text_field( $audience_code ),
			'edition_statement' => sanitize_text_field( $edition_statement ),
			'edition_number'    => $edition_number,
			'edition_type'      => sanitize_text_field( $edition_type ),
			'prizes'                => $prizes,
			'contributor_statement' => sanitize_text_field( $contributor_statement ),
			'territorial_rights_v2' => $territorial_rights_v2,
		);
	}
}
