<?php
/**
 * Bulk card importer for Card Oracle.
 *
 * Parses a JSON array or CSV file and inserts each row as a co_cards post,
 * optionally linking it to a chosen reading via the CO_READING_ID meta key.
 *
 * @package CardOracle
 * @since 1.2.6
 */

// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Class CardOracleCardImporter
 *
 * @since 1.2.6
 */
class CardOracleCardImporter {

	/**
	 * Maximum number of rows allowed in a single import.
	 *
	 * @var int
	 */
	const MAX_ROWS = 500;

	/**
	 * Parse a file and insert each entry as a co_cards post.
	 *
	 * @since 1.2.6
	 * @param string $file_path  Server-side path to the uploaded file.
	 * @param int    $reading_id Optional reading ID to link cards to (0 = no link).
	 * @return array {
	 *     @type int    $inserted Count of cards inserted.
	 *     @type int    $skipped  Count of empty rows skipped.
	 *     @type int    $failed   Count of rows that failed to insert.
	 *     @type string[] $errors First few error messages for surface display.
	 * }
	 */
	public function import_from_file( $file_path, $reading_id = 0 ) {
		$result = array(
			'inserted' => 0,
			'skipped'  => 0,
			'failed'   => 0,
			'errors'   => array(),
		);

		if ( ! is_string( $file_path ) || ! is_readable( $file_path ) ) {
			$result['errors'][] = __( 'Uploaded file is not readable.', 'card-oracle' );
			return $result;
		}

		$extension = strtolower( pathinfo( $file_path, PATHINFO_EXTENSION ) );

		switch ( $extension ) {
			case 'json':
				$rows = $this->parse_json( $file_path );
				break;
			case 'csv':
				$rows = $this->parse_csv( $file_path );
				break;
			default:
				$result['errors'][] = sprintf(
					/* translators: %s: file extension */
					__( 'Unsupported file type ".%s". Use .json or .csv.', 'card-oracle' ),
					$extension
				);
				return $result;
		}

		if ( is_wp_error( $rows ) ) {
			$result['errors'][] = $rows->get_error_message();
			return $result;
		}

		if ( count( $rows ) > self::MAX_ROWS ) {
			$result['errors'][] = sprintf(
				/* translators: 1: row count, 2: max rows */
				__( 'File contains %1$d rows; the importer limits each batch to %2$d.', 'card-oracle' ),
				count( $rows ),
				self::MAX_ROWS
			);
			return $result;
		}

		// Only link imported cards to an actual reading post (validate once).
		$link_reading = ( $reading_id > 0 && 'co_readings' === get_post_type( $reading_id ) );

		foreach ( $rows as $index => $row ) {
			$name        = isset( $row['name'] ) ? trim( wp_strip_all_tags( (string) $row['name'] ) ) : '';
			$description = isset( $row['description'] ) ? (string) $row['description'] : '';

			if ( '' === $name ) {
				++$result['skipped'];
				continue;
			}

			$meta = array();
			if ( $link_reading ) {
				$meta[ CO_READING_ID ] = $reading_id;
			}

			$post_args = array(
				'post_title'   => sanitize_text_field( $name ),
				'post_type'    => 'co_cards',
				'post_status'  => 'publish',
				'post_content' => wp_kses_post( $description ),
			);

			if ( ! empty( $meta ) ) {
				$post_args['meta_input'] = $meta;
			}

			$card_id = wp_insert_post( $post_args, true );

			if ( is_wp_error( $card_id ) ) {
				++$result['failed'];
				if ( count( $result['errors'] ) < 5 ) {
					$result['errors'][] = sprintf(
						/* translators: 1: row number, 2: error message */
						__( 'Row %1$d ("%2$s"): %3$s', 'card-oracle' ),
						$index + 1,
						$name,
						$card_id->get_error_message()
					);
				}
				continue;
			}

			++$result['inserted'];
		}

		return $result;
	}

	/**
	 * Decode a JSON file into an array of card rows.
	 *
	 * @since 1.2.6
	 * @param string $file_path Path to the JSON file.
	 * @return array|WP_Error Array of rows on success, WP_Error on failure.
	 */
	private function parse_json( $file_path ) {
		$contents = file_get_contents( $file_path ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
		if ( false === $contents ) {
			return new WP_Error( 'co_import_read_failed', __( 'Could not read JSON file.', 'card-oracle' ) );
		}

		$data = json_decode( $contents, true );
		if ( null === $data ) {
			return new WP_Error( 'co_import_json_invalid', __( 'JSON file is not valid.', 'card-oracle' ) );
		}

		if ( ! is_array( $data ) ) {
			return new WP_Error( 'co_import_json_shape', __( 'JSON root must be an array of card objects.', 'card-oracle' ) );
		}

		// Accept top-level array or { "cards": [...] }.
		if ( isset( $data['cards'] ) && is_array( $data['cards'] ) ) {
			$data = $data['cards'];
		}

		return array_values( $data );
	}

	/**
	 * Parse a CSV file into an array of card rows keyed by header column names.
	 *
	 * @since 1.2.6
	 * @param string $file_path Path to the CSV file.
	 * @return array|WP_Error Array of rows on success, WP_Error on failure.
	 */
	private function parse_csv( $file_path ) {
		$handle = fopen( $file_path, 'r' ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fopen
		if ( false === $handle ) {
			return new WP_Error( 'co_import_read_failed', __( 'Could not read CSV file.', 'card-oracle' ) );
		}

		$header = fgetcsv( $handle );
		if ( false === $header || empty( $header ) ) {
			fclose( $handle ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose
			return new WP_Error( 'co_import_csv_empty', __( 'CSV file is empty or missing a header row.', 'card-oracle' ) );
		}

		$header = array_map(
			static function ( $col ) {
				return strtolower( trim( (string) $col ) );
			},
			$header
		);

		if ( ! in_array( 'name', $header, true ) ) {
			fclose( $handle ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose
			return new WP_Error( 'co_import_csv_no_name', __( 'CSV header must include a "name" column.', 'card-oracle' ) );
		}

		$rows = array();
		$row  = fgetcsv( $handle );
		while ( false !== $row ) {
			$assoc = array();
			foreach ( $header as $i => $col ) {
				$assoc[ $col ] = isset( $row[ $i ] ) ? $row[ $i ] : '';
			}
			$rows[] = $assoc;
			$row    = fgetcsv( $handle );
		}

		fclose( $handle ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose
		return $rows;
	}
}
