<?php
/**
 * File Path: includes/import/csv/class-mbs-child-importer-csv-insert.php
 *
 * CSV Importer – Insert Child Class
 *
 * Contains the core insert_book() method that was previously inside the
 * monolithic class-mbs-importer-csv.php. Creates the mbs_book post,
 * adds post meta, author/series taxonomies, and saves the format row
 * via MBS_Format_Row_DAO (never raw SQL).
 *
 * F-2 compliant: supports post_content and mbs_import_cover_url.
 *
 * @package MyBookShowroom
 * @since   1.0.0
 */

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

/**
 * Child class responsible for inserting a single book candidate.
 *
 * All insertion logic is isolated here so the parent and other children
 * remain small and maintainable.
 */
class MBS_Child_Importer_CSV_Insert {

	/**
	 * Insert a single pre-validated book candidate.
	 *
	 * Public static so the parent orchestrator and mbs_import_candidates
	 * AJAX handler can call it directly.
	 *
	 * Uses MBS_Format_Row_DAO::save() and WordPress taxonomy APIs — never raw SQL.
	 *
	 * F-2: Reads post_content from candidate (Google Books description).
	 * F-2: Stores cover_url in mbs_import_cover_url postmeta for future sideload.
	 *
	 * @return true|string True on success, error message string on failure.
	 */
	public static function insert_book( array $candidate ): true|string {
		// Build the post.
		$post_data = array(
			'post_type'    => MBS_POST_TYPE,
			'post_status'  => 'publish',
			'post_title'   => sanitize_text_field( $candidate['title'] ),
			// F-2: use post_content when provided (e.g. Google Books description).
			'post_content' => isset( $candidate['post_content'] )
				? wp_kses_post( $candidate['post_content'] )
				: '',
		);

		$post_id = wp_insert_post( $post_data, true );

		if ( is_wp_error( $post_id ) ) {
			return sprintf(
				'Line %d: Could not create post — %s',
				$candidate['_line'] ?? 0,
				$post_id->get_error_message()
			);
		}

		// --- Post meta ---
		if ( ! empty( $candidate['teaser'] ) ) {
			update_post_meta( $post_id, 'mbs_teaser', sanitize_textarea_field( $candidate['teaser'] ) );
		}
		if ( ! empty( $candidate['publisher'] ) ) {
			update_post_meta( $post_id, 'mbs_publisher', sanitize_text_field( $candidate['publisher'] ) );
		}
		if ( ! empty( $candidate['pub_date'] ) ) {
			$date = sanitize_text_field( $candidate['pub_date'] );
			if ( preg_match( '/^\d{4}-\d{2}-\d{2}$/', $date ) ) {
				update_post_meta( $post_id, 'mbs_pub_date', $date );
			}
		}
		if ( ! empty( $candidate['coming_soon'] ) && mbs_is_pro() ) {
			update_post_meta( $post_id, 'mbs_coming_soon', 1 );
		}

		// ── Cover image handling ──────────────────────────────────────────────
		//
		// Three sources, resolved in priority order:
		//   1. cover_image_file + _images_dir → local file from ZIP bundle (sync)
		//   2. cover_url   → remote URL from API importers (deferred sideload pass)
		//   3. cover_image_url → remote URL from CSV column mapping (deferred pass)
		//
		// Local files (case 1) are sideloaded immediately inside insert_book()
		// because they are already on disk; no download is required.  Remote
		// URLs (cases 2 & 3) are written to mbs_import_cover_url postmeta for
		// the deferred MBS_Cover_Sideloader polling pass.

		$cover_sideloaded = false;

		// Case 1 — ZIP bundle: cover_image_file + _images_dir present.
		if ( ! empty( $candidate['cover_image_file'] ) && ! empty( $candidate['_images_dir'] ) ) {
			$img_filename = sanitize_file_name( basename( $candidate['cover_image_file'] ) );
			$img_path     = trailingslashit( $candidate['_images_dir'] ) . $img_filename;

			if ( file_exists( $img_path ) ) {
				// Sideload now — MBS_Cover_Sideloader::sideload_from_local_file()
				// attaches the image and sets the post thumbnail.
				MBS_Cover_Sideloader::sideload_from_local_file( $post_id, $img_path );
				$cover_sideloaded = true;
			}
		}

		// Cases 2 & 3 — Remote URL: queue for the deferred pass.
		if ( ! $cover_sideloaded ) {
			$cover_url = ! empty( $candidate['cover_url'] )
				? $candidate['cover_url']
				: ( $candidate['cover_image_url'] ?? '' );
			if ( ! empty( $cover_url ) ) {
				update_post_meta( $post_id, 'mbs_import_cover_url', esc_url_raw( $cover_url ) );
			}
		}

		// --- Author taxonomy ---
		if ( ! empty( $candidate['author'] ) ) {
			$author_name = sanitize_text_field( $candidate['author'] );
			$term        = term_exists( $author_name, MBS_TAX_AUTHOR );
			if ( ! $term ) {
				$term = wp_insert_term( $author_name, MBS_TAX_AUTHOR );
			}
			if ( ! is_wp_error( $term ) ) {
				$term_id = is_array( $term ) ? (int) $term['term_id'] : (int) $term;
				wp_set_object_terms( $post_id, $term_id, MBS_TAX_AUTHOR, true );
			}
		}

		// --- Series taxonomy ---
		if ( ! empty( $candidate['series'] ) ) {
			$series_name = sanitize_text_field( $candidate['series'] );
			$term        = term_exists( $series_name, MBS_TAX_SERIES );
			if ( ! $term ) {
				wp_insert_term( $series_name, MBS_TAX_SERIES );
			}
			// Attach to post even if term already existed
			wp_set_object_terms( $post_id, $series_name, MBS_TAX_SERIES, true );
		}

		// --- Genre taxonomy (pipe-separated list, same pattern as tags) ---
		// CSV format: "Romance|Historical Fiction" — pipe-delimited so commas
		// inside genre names (e.g. "Biography & Autobiography") are preserved.
		// Falls back to single-value if no pipe present (one genre or legacy data).
		if ( ! empty( $candidate['genre'] ) ) {
			$raw_genres  = sanitize_text_field( $candidate['genre'] );
			$delimiter   = str_contains( $raw_genres, '|' ) ? '|' : '';
			$genre_names = $delimiter
				? array_filter( array_map( 'trim', explode( '|', $raw_genres ) ) )
				: array( trim( $raw_genres ) );

			foreach ( $genre_names as $genre_name ) {
				$term = term_exists( $genre_name, MBS_TAX_GENRE );
				if ( ! $term ) {
					$term = wp_insert_term( $genre_name, MBS_TAX_GENRE );
				}
				if ( ! is_wp_error( $term ) ) {
					$term_id = is_array( $term ) ? (int) $term['term_id'] : (int) $term;
					wp_set_object_terms( $post_id, $term_id, MBS_TAX_GENRE, true );
				}
			}
		}

		// --- Tags taxonomy (pipe-separated list) ---
		// CSV format: "Found Family|Second Chance|Kentucky" — pipe-delimited so
		// commas inside tag names are preserved. Falls back to comma-split if no
		// pipe is present (single tag or legacy data).
		if ( ! empty( $candidate['tags'] ) ) {
			$raw_tags  = sanitize_text_field( $candidate['tags'] );
			$delimiter = str_contains( $raw_tags, '|' ) ? '|' : ',';
			$tag_names = array_filter( array_map( 'trim', explode( $delimiter, $raw_tags ) ) );

			foreach ( $tag_names as $tag_name ) {
				$term = term_exists( $tag_name, MBS_TAX_TAG );
				if ( ! $term ) {
					$term = wp_insert_term( $tag_name, MBS_TAX_TAG );
				}
				if ( ! is_wp_error( $term ) ) {
					$term_id = is_array( $term ) ? (int) $term['term_id'] : (int) $term;
					wp_set_object_terms( $post_id, $term_id, MBS_TAX_TAG, true );
				}
			}
		}


		// --- Format row via MBS_Format_Row_DAO (GP-SQL-1: no raw SQL) ---
		$format_data = array(
			'format'     => ! empty( $candidate['format'] )
				? sanitize_key( $candidate['format'] )
				: 'paperback',
			'sort_order' => 0,
		);

		// E-007: Populate format_label from the CSV candidate. Previously this
		// field was never included, so imported books always had a NULL label
		// regardless of what the CSV contained. Requires D-002 (Session 1-2)
		// to be deployed — the DAO must read the correct column name first.
		if ( ! empty( $candidate['format_label'] ) ) {
			$format_data['format_label'] = sanitize_text_field( $candidate['format_label'] );
		}

		if ( ! empty( $candidate['isbn13'] ) ) {
			$format_data['isbn13'] = $candidate['isbn13'];
		}
		if ( ! empty( $candidate['asin'] ) ) {
			// FV-012: Use normalize_asin() so CSV-imported ASINs are uppercased,
			// stripped of special characters, and validated to exactly 10 chars.
			$normalized_asin = MBS_ISBN_Helper::normalize_asin( $candidate['asin'] );
			if ( false !== $normalized_asin ) {
				$format_data['asin'] = $normalized_asin;
			}
			// Invalid ASINs are silently dropped; the importer's error log
			// captures the raw value via the duplicate-detector path if needed.
		}
		if ( isset( $candidate['price'] ) && '' !== $candidate['price'] ) {
			// FV-015: Pass the active currency so zero-decimal currencies are
			// validated as integers and capped at the correct per-currency max.
			$_import_currency = mbs_get_setting( 'general', 'currency', 'USD' );
			$price            = MBS_Formatting::sanitize_price( $candidate['price'], $_import_currency );
			if ( null !== $price ) {
				$format_data['price'] = $price;
			}
		}
		if ( ! empty( $candidate['sample_text'] ) ) {
			$format_data['sample_text'] = sanitize_textarea_field( mb_substr( $candidate['sample_text'], 0, 3000 ) );
		}
		if ( ! empty( $candidate['page_length'] ) ) {
			$pages = absint( $candidate['page_length'] );
			if ( $pages > 0 && $pages <= 9999 ) {
				$format_data['page_length'] = $pages;
			}
		}
		if ( ! empty( $candidate['series_number'] ) ) {
			$series_num = (float) $candidate['series_number'];
			if ( $series_num > 0 ) {
				$format_data['series_number'] = $series_num;
			}
		}

		// Save the format row.
		$format_result = MBS_Format_Row_DAO::save( $post_id, $format_data );

		// E-003: MBS_Format_Row_DAO::save() returns int|false, never WP_Error.
		if ( ! $format_result ) {
			return sprintf(
				'Line %d: Format row save failed.',
				$candidate['_line'] ?? 0
			);
		}

		// --- Buy links (private passthrough from MBT importer or programmatic callers) ---
		// _buy_links carries a JSON-encoded array of link objects:
		//   [ { "retailer_key": "amazon", "url": "https://...", "is_direct_author": 0 }, ... ]
		// Keys match MBS_Buy_Link_DAO::save() column names.
		// sort_order is assigned by array position so MBT button order is preserved.
		if ( ! empty( $candidate['_buy_links'] ) ) {
			$buy_links = json_decode( $candidate['_buy_links'], true );
			if ( is_array( $buy_links ) ) {
				foreach ( $buy_links as $sort_order => $link ) {
					$url = esc_url_raw( (string) ( $link['url'] ?? '' ) );
					if ( '' === $url ) {
						continue; // Skip links with no URL.
					}
					MBS_Buy_Link_DAO::save( array(
						'post_id'          => $post_id,
						'format_row_id'    => null,
						'retailer_key'     => $link['retailer_key'] ?? null,
						'url'              => $url,
						'is_direct_author' => (int) ( $link['is_direct_author'] ?? 0 ),
						'sort_order'       => (int) $sort_order,
					) );
				}
			}
		}

		return true;
	}
}
