<?php
/**
 * File Path: includes/import/class-mbs-catalog-media-importer.php
 *
 * XLSX Catalog Media Importer
 *
 * Handles all media sideloading for the XLSX catalog import pipeline.
 * Extracted from MBS_Catalog_XLSX_Importer to separate concerns:
 * cover images, banners, contributor headshots, and downloadable files
 * are all resolved and sideloaded here via MBS_Import_Media_Sideloader.
 *
 * Entry point: sideload_all() -- called by MBS_Catalog_XLSX_Importer
 * after each book is hydrated.
 *
 * @package MyBookShowroom
 * @since   1.3.5
 */

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

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

	/**
	 * Sideload all media for a single book.
	 *
	 * Delegates to the four specialised sideload methods and merges their
	 * attachment ID arrays into a single flat list.
	 *
	 * @param int    $post_id     Book post ID.
	 * @param array  $book_object Sanitized MBS Book Object.
	 * @param string $images_dir  Absolute path to ZIP-extracted images directory.
	 * @return int[] Merged attachment IDs from all sideload operations.
	 * @since 1.3.5
	 */
	public static function sideload_all( int $post_id, array $book_object, string $images_dir ): array {
		return array_merge(
			self::sideload_media( $post_id, $book_object, $images_dir ),
			self::sideload_term_banners( $post_id, $book_object, $images_dir ),
			self::sideload_contributor_profiles( $post_id, $book_object, $images_dir ),
			self::sideload_downloadable_files( $post_id, $book_object, $images_dir )
		);
	}

	/**
	 * Sideload cover and media files from the extracted images directory.
	 *
	 * Corrections:
	 * - PLAN-C-003: Uses the single ::sideload() entry point -- no separate path/url methods.
	 * - PLAN-R2-C-002: Media type strings corrected -- 'trailer_video' / 'audio_sample'.
	 *   Audio sample requires a format_row_id (it's a per-format media type).
	 *   At book level, audio is sideloaded to the first format row if it exists.
	 *
	 * @param int    $post_id     Book post ID.
	 * @param array  $book_object Sanitized MBS Book Object.
	 * @param string $images_dir  Absolute path to extracted images directory.
	 * @return int[] Attachment IDs of successfully sideloaded media files.
	 * @since 1.3.0
	 */
	public static function sideload_media( int $post_id, array $book_object, string $images_dir ): array {
		$attachment_ids = array();
		$images_dir = trailingslashit( $images_dir );
		$imgs       = $book_object['images'] ?? array();

		// Helper: try subfolder path first (MBS catalog ZIP layout: covers/, banners/,
		// trailers/, audio/), then flat fallback (legacy ZIPs and manual XLSX imports).
		$resolve = static function ( string $dir, string $sub, string $file ): string {
			if ( '' === $file ) { return ''; }
			$bn = basename( $file );
			$subfolder_path = $dir . trailingslashit( $sub ) . $bn;
			if ( file_exists( $subfolder_path ) ) { return $subfolder_path; }
			$flat_path = $dir . $bn;
			return file_exists( $flat_path ) ? $flat_path : '';
		};

		// Cover image: file takes precedence over URL (PLAN-C-003: sideload() API).
		if ( ! empty( $imgs['cover_file'] ) ) {
			$local = $resolve( $images_dir, 'covers', $imgs['cover_file'] );
			if ( '' !== $local ) {
				$_sid = MBS_Import_Media_Sideloader::sideload( $post_id, $local, 'cover' );
				if ( ! is_wp_error( $_sid ) ) { $attachment_ids[] = $_sid; }
			}
		} elseif ( ! empty( $imgs['cover_url'] ) ) {
			$_sid = MBS_Import_Media_Sideloader::sideload( $post_id, $imgs['cover_url'], 'cover' );
			if ( ! is_wp_error( $_sid ) ) { $attachment_ids[] = $_sid; }
		}

		// Banner image.
		if ( ! empty( $imgs['banner_file'] ) ) {
			$local = $resolve( $images_dir, 'banners', $imgs['banner_file'] );
			if ( '' !== $local ) {
				$_sid = MBS_Import_Media_Sideloader::sideload( $post_id, $local, 'banner' );
				if ( ! is_wp_error( $_sid ) ) { $attachment_ids[] = $_sid; }
			}
		} elseif ( ! empty( $imgs['banner_url'] ) ) {
			$_sid = MBS_Import_Media_Sideloader::sideload( $post_id, $imgs['banner_url'], 'banner' );
			if ( ! is_wp_error( $_sid ) ) { $attachment_ids[] = $_sid; }
		}

		// Trailer video -- book-level media type (PLAN-R2-C-002: 'trailer_video').
		if ( ! empty( $imgs['trailer_file'] ) ) {
			$local = $resolve( $images_dir, 'trailers', $imgs['trailer_file'] );
			if ( '' !== $local ) {
				$_sid = MBS_Import_Media_Sideloader::sideload( $post_id, $local, 'trailer_video' );
				if ( ! is_wp_error( $_sid ) ) { $attachment_ids[] = $_sid; }
			}
		} elseif ( ! empty( $imgs['trailer_url'] ) ) {
			$_sid = MBS_Import_Media_Sideloader::sideload( $post_id, $imgs['trailer_url'], 'trailer_video' );
			if ( ! is_wp_error( $_sid ) ) { $attachment_ids[] = $_sid; }
		}

		// Book-level spinning video (mbs_spinning_video_id postmeta -- Gap 1 closed Task 28).
		if ( ! empty( $imgs['spinning_video_file'] ) ) {
			$local = $resolve( $images_dir, 'spinning-videos', $imgs['spinning_video_file'] );
			if ( '' !== $local ) {
				$_sid = MBS_Import_Media_Sideloader::sideload( $post_id, $local, 'spinning_video' );
				if ( ! is_wp_error( $_sid ) ) { $attachment_ids[] = $_sid; }
			}
		} elseif ( ! empty( $imgs['spinning_video_url'] ) ) {
			$_sid = MBS_Import_Media_Sideloader::sideload( $post_id, $imgs['spinning_video_url'], 'spinning_video' );
			if ( ! is_wp_error( $_sid ) ) { $attachment_ids[] = $_sid; }
		}

		// Per-format covers and audio samples.
		// R3-M-003: Resolve format_row_id via MBS_Format_Row_DAO::get_by_post() after
		// hydration. Match by isbn13 to map each format row from the book object to
		// its DB-assigned format_row_id.
		$format_rows = $book_object['format_rows'] ?? array();
		if ( ! empty( $format_rows ) ) {
			$db_rows       = MBS_Format_Row_DAO::get_by_post( $post_id );
			$isbn13_to_fid = array();
			foreach ( $db_rows as $db_row ) {
				if ( ! empty( $db_row->isbn13 ) ) {
					$isbn13_to_fid[ $db_row->isbn13 ] = (int) $db_row->id;
				}
			}
			foreach ( $format_rows as $fr ) {
				$isbn13        = $fr['isbn13']           ?? '';
				$audio_file    = $fr['audio_file']       ?? '';
				$audio_url     = $fr['audio_sample_url'] ?? '';
				$cover_file    = $fr['cover_file']       ?? '';
				$format_row_id = $isbn13_to_fid[ $isbn13 ] ?? null;

				// Per-format cover (Tab 2 col 21 -- filename written by XLSX exporter
				// during ZIP export; found in covers/ subfolder of the extract root).
				if ( ! empty( $cover_file ) ) {
					$local = $resolve( $images_dir, 'covers', $cover_file );
					if ( '' !== $local ) {
						$_sid = MBS_Import_Media_Sideloader::sideload( $post_id, $local, 'cover', $format_row_id );
						if ( ! is_wp_error( $_sid ) ) { $attachment_ids[] = $_sid; }
					}
				}

				// Per-format audio sample -- audiobook format only.
				// Guard: reject audio sideload for any non-audiobook format row.
				// Audio samples are an audiobook-only asset. ZIPs produced by older
				// exporter versions (or hand-crafted XLSXs) may populate col 22 for
				// all format rows of a book, causing the same MP3 to be sideloaded
				// once per format row. The format key guard prevents this (ZIP-IMPORT-FIX-6).
				if ( 'audiobook' === ( $fr['format'] ?? '' ) ) {
					if ( ! empty( $audio_file ) ) {
						$local = $resolve( $images_dir, 'audio', $audio_file );
						if ( '' !== $local ) {
							$_sid = MBS_Import_Media_Sideloader::sideload( $post_id, $local, 'audio_sample', $format_row_id );
							if ( ! is_wp_error( $_sid ) ) { $attachment_ids[] = $_sid; }
						}
					} elseif ( ! empty( $audio_url ) ) {
						$_sid = MBS_Import_Media_Sideloader::sideload( $post_id, $audio_url, 'audio_sample', $format_row_id );
						if ( ! is_wp_error( $_sid ) ) { $attachment_ids[] = $_sid; }
					}
				}
			}
		}

		return $attachment_ids;
	}

	/**
	 * Sideload term banner images for series, collection, and universe terms.
	 *
	 * Reads banner filenames from Tab 1 (term_banners key) and overflow
	 * memberships from Tab 7 (extra_memberships banner_file key). Resolves
	 * files from the banners/ subfolder of the extracted ZIP, sideloads via
	 * MBS_Import_Media_Sideloader (type 'term_image'), then writes term_meta.
	 *
	 * @param int    $post_id     Book post ID.
	 * @param array  $book_object Sanitized MBS Book Object.
	 * @param string $images_dir  Absolute path to extracted images directory.
	 * @return int[] Attachment IDs of successfully sideloaded banner images.
	 * @since 1.3.2
	 */
	public static function sideload_term_banners( int $post_id, array $book_object, string $images_dir ): array {
		$attachment_ids = array();
		$images_dir = trailingslashit( $images_dir );
		$resolve    = static function ( string $dir, string $file ): string {
			if ( '' === $file ) { return ''; }
			$bn = basename( $file );
			$sub = $dir . 'banners/' . $bn;
			if ( file_exists( $sub ) ) { return $sub; }
			$flat = $dir . $bn;
			return file_exists( $flat ) ? $flat : '';
		};

		$term_cfg = array(
			'series'     => array( 'tax' => MBS_TAX_SERIES,     'meta' => 'mbs_series_banner_id',     'banner_key' => 'series_banner' ),
			'collection' => array( 'tax' => MBS_TAX_COLLECTION, 'meta' => 'mbs_collection_banner_id', 'banner_key' => 'collection_banner' ),
			'universe'   => array( 'tax' => MBS_TAX_UNIVERSE,   'meta' => 'mbs_universe_banner_id',   'banner_key' => 'universe_banner' ),
		);

		// Primary term banners from Tab 1.
		foreach ( $term_cfg as $type => $cfg ) {
			$banner_file = $book_object['term_banners'][ $cfg['banner_key'] ] ?? '';
			if ( '' === $banner_file ) { continue; }
			$term_name   = $book_object['taxonomies'][ $cfg['tax'] ][0] ?? '';
			if ( '' === $term_name ) { continue; }
			$term = get_term_by( 'name', $term_name, $cfg['tax'] );
			if ( ! $term instanceof WP_Term ) { continue; }
			// Idempotency: skip if this term already has a banner set (prevents re-sideload
			// when multiple books share the same series/universe/collection banner).
			if ( (int) get_term_meta( $term->term_id, $cfg['meta'], true ) > 0 ) { continue; }
			$local = $resolve( $images_dir, $banner_file );
			if ( '' === $local ) { continue; }
			$att_id = MBS_Import_Media_Sideloader::sideload( $post_id, $local, 'term_image' );
			if ( ! is_wp_error( $att_id ) ) {
				update_term_meta( $term->term_id, $cfg['meta'], $att_id );
				$attachment_ids[] = $att_id;
			}
		}

		// Overflow term banners from Tab 7 extra_memberships.
		foreach ( $book_object['extra_memberships'] ?? array() as $m ) {
			$banner_file = $m['banner_file']     ?? '';
			$type        = $m['membership_type'] ?? '';
			$term_name   = $m['term_name']       ?? '';
			if ( '' === $banner_file || '' === $term_name || ! isset( $term_cfg[ $type ] ) ) { continue; }
			$cfg  = $term_cfg[ $type ];
			$term = get_term_by( 'name', $term_name, $cfg['tax'] );
			if ( ! $term instanceof WP_Term ) { continue; }
			// Idempotency: skip if already set (overflow memberships share banners across books).
			if ( (int) get_term_meta( $term->term_id, $cfg['meta'], true ) > 0 ) { continue; }
			$local = $resolve( $images_dir, $banner_file );
			if ( '' === $local ) { continue; }
			$att_id = MBS_Import_Media_Sideloader::sideload( $post_id, $local, 'term_image' );
			if ( ! is_wp_error( $att_id ) ) {
				update_term_meta( $term->term_id, $cfg['meta'], $att_id );
				$attachment_ids[] = $att_id;
			}
		}

		return $attachment_ids;
	}

	/**
	 * Sideload author headshots and write bio/website term meta.
	 *
	 * Iterates contributors with author profile data (from Tab 5 cols G-I).
	 * Resolves the headshot file from author-headshots/ subfolder, sideloads
	 * via MBS_Import_Media_Sideloader (type 'term_image'), writes
	 * mbs_author_photo_id term meta. Bio and website URL are written
	 * unconditionally when present (no media sideload needed).
	 *
	 * @param int    $post_id     Book post ID.
	 * @param array  $book_object Sanitized MBS Book Object.
	 * @param string $images_dir  Absolute path to extracted images directory.
	 * @return int[] Attachment IDs of successfully sideloaded headshot images.
	 * @since 1.3.2
	 */
	public static function sideload_contributor_profiles( int $post_id, array $book_object, string $images_dir ): array {
		$attachment_ids = array();
		$images_dir = trailingslashit( $images_dir );
		$seen_names = array(); // Process each author once per import run.

		foreach ( $book_object['contributors'] ?? array() as $c ) {
			$name          = $c['contributor_name']    ?? '';
			$headshot_file = $c['author_headshot_file'] ?? '';
			$bio           = $c['author_bio']          ?? '';
			$website_url   = $c['author_website_url']  ?? '';

			if ( '' === $name || isset( $seen_names[ $name ] ) ) { continue; }
			if ( '' === $headshot_file && '' === $bio && '' === $website_url ) { continue; }
			$seen_names[ $name ] = true;

			$term = get_term_by( 'name', $name, MBS_TAX_AUTHOR );
			if ( ! $term instanceof WP_Term ) { continue; }

			// Headshot -- resolve from author-headshots/ subfolder.
			if ( '' !== $headshot_file ) {
				$bn    = basename( $headshot_file );
				$local = '';
				$sub   = $images_dir . 'author-headshots/' . $bn;
				if ( file_exists( $sub ) ) {
					$local = $sub;
				} elseif ( file_exists( $images_dir . $bn ) ) {
					$local = $images_dir . $bn;
				}
				// Idempotency: skip if a headshot has already been set for this author
				// (prevents duplicate attachments on re-import or multi-book runs).
				if ( '' !== $local && (int) get_term_meta( $term->term_id, 'mbs_author_photo_id', true ) === 0 ) {
					$att_id = MBS_Import_Media_Sideloader::sideload( $post_id, $local, 'term_image' );
					if ( ! is_wp_error( $att_id ) ) {
						update_term_meta( $term->term_id, 'mbs_author_photo_id', $att_id );
						$attachment_ids[] = $att_id;
					}
				}
			}

			// Author bio and website -- no file, write directly.
			if ( '' !== $bio ) {
				update_term_meta( $term->term_id, 'mbs_author_bio', $bio );
			}
			if ( '' !== $website_url ) {
				update_term_meta( $term->term_id, 'mbs_author_website_url', $website_url );
			}
		}

		return $attachment_ids;
	}

	/**
	 * Sideload book club downloadable files from the extracted ZIP files/ subfolder.
	 *
	 * Reads file records from book_object['book_club']['files'], resolves each
	 * from {images_dir}/files/{basename}, sideloads via MBS_Import_Media_Sideloader
	 * (type 'downloadable_file'), then writes the full file list to the DAO via
	 * MBS_Book_Club_DAO::replace_files(). No PRO gate -- always imported.
	 *
	 * @param int    $post_id     Book post ID.
	 * @param array  $book_object Sanitized MBS Book Object.
	 * @param string $images_dir  Absolute path to extracted ZIP root directory.
	 * @return int[] Attachment IDs of successfully sideloaded files.
	 * @since 1.3.3
	 */
	public static function sideload_downloadable_files( int $post_id, array $book_object, string $images_dir ): array {
		$attachment_ids = array();
		$bc_files = $book_object['book_club']['files'] ?? array();
		if ( empty( $bc_files ) ) {
			return $attachment_ids;
		}

		$images_dir = trailingslashit( $images_dir );
		$dao_files  = array();

		foreach ( $bc_files as $item ) {
			$filename = (string) ( $item['file']  ?? '' );
			$label    = (string) ( $item['label'] ?? '' );
			if ( '' === $filename ) { continue; }

			// Prefer files/ subfolder, fall back to flat layout.
			$bn    = basename( $filename );
			$local = $images_dir . 'files/' . $bn;
			if ( ! file_exists( $local ) ) {
				$local = $images_dir . $bn;  // flat fallback
			}
			if ( ! file_exists( $local ) ) {
				continue;
			}

			$att_id = MBS_Import_Media_Sideloader::sideload( $post_id, $local, 'downloadable_file' );
			if ( is_wp_error( $att_id ) ) {
				continue;
			}
			$attachment_ids[] = $att_id;
			$dao_files[]      = array( 'id' => $att_id, 'label' => $label );
		}

		if ( ! empty( $dao_files ) ) {
			MBS_Book_Club_DAO::replace_files( $post_id, $dao_files );
		}

		return $attachment_ids;
	}
}
