/home/sylamedg/www/wp-content/plugins/templately/includes/Core/Importer/Utils/FluentImport.php
<?php
/**
 * Fluent Cart Product Importer
 *
 * Imports Fluent Cart products from JSON format with images
 * Replicates the functionality of Fluent Cart's DummyProductService
 *
 * @package Templately\Exporter\Core
 * @since 1.3.4
 */

namespace Templately\Core\Importer\Utils;

use Exception;
use FluentCart\App\Models\Product;
use FluentCart\App\Services\Async\ImageAttachService;
use FluentCart\App\Services\DateTime\DateTime;
use FluentCart\Framework\Support\Arr;
use FluentCart\Framework\Support\Str;

/**
 * FluentImport Class
 *
 * Handles importing Fluent Cart products with their images from JSON format
 * Uses the same approach as Fluent Cart's DummyProductService
 *
 * @since 1.3.4
 */
class FluentImport {

	/**
	 * Import file path
	 *
	 * @var string
	 * @since 1.3.4
	 */
	private $import_path;

	/**
	 * Images directory path
	 *
	 * @var string
	 * @since 1.3.4
	 */
	private $images_dir;

	/**
	 * Import statistics
	 *
	 * @var array
	 * @since 1.3.4
	 */
	private $stats = [
		'products_imported'   => 0,
		'products_failed'     => 0,
		'variations_imported' => 0,
		'images_imported'     => 0,
	];

	/**
	 * Product ID mappings (original ID => new ID)
	 *
	 * @var array
	 * @since 1.3.4
	 */
	private $product_id_mappings = [
		'succeed' => [],
		'failed'  => [],
	];

	/**
	 * Import errors
	 *
	 * @var array
	 * @since 1.3.4
	 */
	private $errors = [];

	/**
	 * Products array for action hooks
	 *
	 * @var array
	 * @since 1.3.4
	 */
	public $products = [];

	/**
	 * Constructor
	 *
	 * @param string $import_path Path to the JSON file to import
	 * @since 1.3.4
	 */
	public function __construct( $import_path ) {
		$this->import_path = $import_path;
		$this->images_dir  = dirname( $import_path ) . '/images';
		$this->require_files();
	}

	/**
	 * Require necessary WordPress files
	 *
	 * @return void
	 * @since 1.3.4
	 */
	private function require_files() {
		if ( ! function_exists( 'media_handle_upload' ) ) {
			require_once ABSPATH . 'wp-admin/includes/image.php';
			require_once ABSPATH . 'wp-admin/includes/file.php';
			require_once ABSPATH . 'wp-admin/includes/media.php';
		}
		if ( ! function_exists( 'wp_create_term' ) ) {
			require_once ABSPATH . 'wp-admin/includes/taxonomy.php';
		}
	}

	/**
	 * Import products from JSON
	 *
	 * @return array Structured import result with status, errors, and summary
	 * @since 1.3.4
	 */
	public function import() {
		try {
			// Check if file exists
			if ( ! file_exists( $this->import_path ) ) {
				throw new Exception( 'Import file not found: ' . $this->import_path );
			}

			// Check if images directory exists
			if ( ! file_exists( $this->images_dir ) ) {
				throw new Exception( 'Images directory not found: ' . $this->images_dir );
			}

			// Load JSON
			$json = file_get_contents( $this->import_path );
			if ( $json === false ) {
				throw new Exception( 'Failed to read JSON file' );
			}

			$products = json_decode( $json, true );
			if ( json_last_error() !== JSON_ERROR_NONE ) {
				throw new Exception( 'Failed to parse JSON: ' . json_last_error_msg() );
			}

			if ( empty( $products ) || ! is_array( $products ) ) {
				return $this->format_result( 'success', [], [] );
			}

			// Store products for action hooks (similar to WPImport::$posts)
			$this->products = $this->prepare_products_for_hooks( $products );

			// Trigger Templately-specific import_start action
			do_action( 'templately_import_start', $this );

			// Import each product
			foreach ( $products as $product_data ) {
				// Prepare result array for this product
				$result = [
					'succeed' => $this->product_id_mappings['succeed'],
					'failed'  => $this->product_id_mappings['failed'],
				];

				try {
					$product_id = $this->insert_product( $product_data );
					$this->stats['products_imported']++;

					// Track product ID mapping if original ID exists
					if ( isset( $product_data['id'] ) ) {
						$this->product_id_mappings['succeed'][ $product_data['id'] ] = $product_id;
					}

					// Update result with new mapping
					$result['succeed'] = $this->product_id_mappings['succeed'];

					// Prepare post-like data for action hook
					$post_data = $this->prepare_post_data_for_hook( $product_data, $product_id );

					// Trigger process_post action (similar to WPImport)
					do_action( 'templately_import.process_post', $post_data, $result, $this );

				} catch ( Exception $e ) {
					$this->stats['products_failed']++;
					$error_msg = 'FluentImport: Failed to import product: ' . $e->getMessage();
					$this->errors[] = $error_msg;
					error_log( $error_msg );

					// Track failed product id if original id exists
					if ( isset( $product_data['id'] ) ) {
						$this->product_id_mappings['failed'][] = $product_data['id'];
					}

					// Update result with failed ID
					$result['failed'] = $this->product_id_mappings['failed'];

					// Prepare post-like data for action hook (even for failed imports)
					$post_data = $this->prepare_post_data_for_hook( $product_data, null );

					// Trigger process_post action for failed import
					do_action( 'templately_import.process_post', $post_data, $result, $this );
				}
			}

			return $this->format_result( 'success', $this->errors, $this->product_id_mappings );

		} catch ( Exception $e ) {
			$error_msg = 'Import failed: ' . $e->getMessage();
			return $this->format_result( 'error', [ $error_msg ], [] );
		}
	}

	/**
	 * Format import result in structured JSON format
	 *
	 * @param string $status Status of import (success or error)
	 * @param array  $errors Array of error messages
	 * @param array  $product_mappings Product ID mappings
	 * @return array Formatted result
	 * @since 1.3.4
	 */
	private function format_result( $status, $errors, $product_mappings ) {
		return [
			'status' => $status,
			'errors' => $errors,
			'summary' => [
				'terms' => [
					'succeed' => [],
					'failed'  => [],
				],
				'posts' => [
					'succeed' => $product_mappings['succeed'] ?? [],
					'failed'  => $product_mappings['failed'] ?? [],
				],
			],
		];
	}

	/**
	 * Prepare products array for action hooks
	 * Converts products to a format similar to WPImport::$posts
	 *
	 * @param array $products Products array from JSON
	 * @return array Prepared products array
	 * @since 1.3.4
	 */
	private function prepare_products_for_hooks( $products ) {
		$prepared = [];
		foreach ( $products as $product ) {
			$prepared[] = [
				'post_id'    => $product['id'] ?? 0,
				'post_type'  => 'fluent-products',
				'post_title' => $product['post_title'] ?? '',
			];
		}
		return $prepared;
	}

	/**
	 * Prepare post-like data for action hook
	 * Converts product data to a format similar to WPImport post data
	 *
	 * @param array $product_data Product data from JSON
	 * @param int|null $product_id New product ID (null if failed)
	 * @return array Post-like data array
	 * @since 1.3.4
	 */
	private function prepare_post_data_for_hook( $product_data, $product_id ) {
		return [
			'post_id'    => $product_data['id'] ?? 0,
			'post_type'  => 'fluent-products',
			'post_title' => $product_data['post_title'] ?? '',
			'new_id'     => $product_id,
		];
	}

	/**
	 * Insert a single product
	 * Replicates DummyProductService::insert() method
	 *
	 * @param array $product Product data array
	 * @return int The ID of the created product
	 * @throws Exception If product insertion fails
	 * @since 1.3.4
	 */
	private function insert_product( $product ) {
		$product_name = Str::slug( $product['post_title'], '-', null );
		$now          = DateTime::gmtNow();
		$created_date = $now->format( 'Y-m-d H:i:s' );
		$product_name_suffix = $now->format( 'd-m-Y-H-i-s' );

		$data = [
			'post_author'           => get_current_user_id(),
			'post_date'             => $created_date,
			'post_date_gmt'         => $created_date,
			'post_content_filtered' => '',
			'post_status'           => 'publish',
			'post_type'             => 'fluent-products',
			'comment_status'        => 'open',
			'ping_status'           => 'closed',
			'post_password'         => '',
			'post_name'             => $product_name . '-' . $product_name_suffix,
			'to_ping'               => '',
			'pinged'                => '',
			'post_modified'         => $created_date,
			'post_modified_gmt'     => $created_date,
			'post_parent'           => 0,
			'menu_order'            => 0,
			'post_mime_type'        => '',
			'guid'                  => get_site_url() . '/?items=' . $product_name . '-' . $product_name_suffix,
		];

		$product      = array_merge( $product, $data );
		$detail       = Arr::get( $product, 'detail', [] );
		$variant_data = Arr::get( $product, 'variants', [] );
		$gallery_images = Arr::get( $product, 'gallery', [] );
		$categories   = Arr::get( $product, 'categories', [] );

		// Create product using Fluent Cart's ORM
		$product_model = Product::query()->create(
			Arr::except( $product, [ 'detail', 'variants', 'gallery', 'categories' ] )
		);

		if ( ! $product_model ) {
			throw new Exception( 'Failed to create product' );
		}

		// Attach categories
		$this->attach_terms( $categories, $product_model->ID );

		// Attach gallery images
		if ( ! empty( $gallery_images ) ) {
			$this->attach_images_to_product( $product_model->toArray(), $gallery_images );

			// Set featured image from first gallery image
			$gallery_image_with_id = get_post_meta( $product_model->ID, 'fluent-products-gallery-image', true );
			if ( isset( $gallery_image_with_id[0] ) ) {
				set_post_thumbnail( $product_model->ID, Arr::get( $gallery_image_with_id, '0.id' ) );
			}
		}

		// Create variants
		if ( ! empty( $variant_data ) ) {
			$variants = $product_model->variants()->createMany( $variant_data );
			$this->stats['variations_imported'] += $variants->count();

			// Attach images to variants
			foreach ( $variants as $index => $variant ) {
				$images = Arr::get( $variant_data, $index . '.images', [] );
				if ( is_array( $images ) && count( $images ) ) {
					$this->attach_images_to_variant( $variant->id, $images );
				}
			}

			// Create product detail with default variation
			if ( ! empty( $detail ) ) {
				$detail['post_id']              = $product_model->ID;
				$detail['default_variation_id'] = $variants->first()->id;
				$product_model->detail()->create( $detail );
			}
		} else {
			// Create product detail without default variation
			if ( ! empty( $detail ) ) {
				$detail['post_id'] = $product_model->ID;
				$product_model->detail()->create( $detail );
			}
		}

		return $product_model->ID;
	}

	/**
	 * Attach terms to product
	 * Replicates DummyProductService::attachTerms() method
	 *
	 * @param array|string $categories Categories to attach
	 * @param int          $post_id Post ID
	 * @return void
	 * @since 1.3.4
	 */
	private function attach_terms( $categories, $post_id ) {
		if ( is_string( $categories ) ) {
			$categories = explode( ',', $categories );
		}

		$term_ids = [];

		if ( is_array( $categories ) ) {
			foreach ( $categories as $category ) {
				$term = wp_create_term( $category, 'product-categories' );
				if ( ! is_wp_error( $term ) ) {
					$term_ids[] = $term['term_id'];
				}
			}
		}

		if ( ! empty( $term_ids ) ) {
			wp_set_post_terms( $post_id, $term_ids, 'product-categories' );
		}
	}

	/**
	 * Attach images to product
	 * Uses ImageAttachService similar to DummyProductService
	 *
	 * @param array $product Product array
	 * @param array $images Array of image paths
	 * @return void
	 * @since 1.3.4
	 */
	private function attach_images_to_product( array $product, array $images ) {
		if ( empty( $images ) ) {
			return;
		}

		$gallery = [];

		foreach ( $images as $image_path ) {
			$value = $this->add_image_from_path( $image_path, $product['post_title'] );
			if ( ! empty( $value ) ) {
				$gallery[] = $value;
				$this->stats['images_imported']++;
			}
		}

		if ( ! empty( $gallery ) ) {
			update_post_meta( $product['ID'], 'fluent-products-gallery-image', $gallery );
		}
	}

	/**
	 * Attach images to variant
	 * Uses ImageAttachService similar to DummyProductService
	 *
	 * @param int   $variant_id Variant ID
	 * @param array $images Array of image paths
	 * @return void
	 * @since 1.3.4
	 */
	private function attach_images_to_variant( $variant_id, array $images ) {
		if ( empty( $images ) ) {
			return;
		}

		// Use ProductVariation model to access media() relationship
		$variant = \FluentCart\App\Models\ProductVariation::query()->find( $variant_id );
		if ( empty( $variant ) ) {
			return;
		}

		$meta_value = [];
		foreach ( $images as $image_path ) {
			$value = $this->add_image_from_path( $image_path, $variant['variation_title'] );
			if ( ! empty( $value ) ) {
				$meta_value[] = $value;
				$this->stats['images_imported']++;
			}
		}

		if ( ! empty( $meta_value ) ) {
			// Use media() relationship like ImageAttachService does
			// meta_value should be an array, not serialized
			$media = [
				'meta_value'  => $meta_value,
				'object_id'   => $variant['id'],
				'object_type' => 'product_variant_info',
				'meta_key'    => 'product_thumbnail',
			];
			$variant->media()->create( $media );
		}
	}

	/**
	 * Add image from local path
	 * Similar to ImageAttachService::addImageFromUrl() but for local files
	 *
	 * @param string $image_path Relative image path (e.g., 'images/photo.jpg')
	 * @param string $title Image title
	 * @return array Image data array with id, title, and url
	 * @since 1.3.4
	 */
	private function add_image_from_path( $image_path, $title ) {
		// Convert relative path to absolute
		$full_path = dirname( $this->import_path ) . '/' . $image_path;

		if ( ! file_exists( $full_path ) ) {
			error_log( 'FluentImport: Image file not found: ' . $full_path );
			return [];
		}

		// Check if image already exists in media library
		$filename = basename( $full_path );
		global $wpdb;
		$existing = $wpdb->get_var(
			$wpdb->prepare(
				"SELECT ID FROM {$wpdb->posts} WHERE post_type = 'attachment' AND guid LIKE %s",
				'%' . $wpdb->esc_like( $filename )
			)
		);

		if ( $existing ) {
			$media = wp_prepare_attachment_for_js( $existing );
			return [
				'id'    => $existing,
				'title' => $media['title'],
				'url'   => $media['url'],
			];
		}

		// Upload image to WordPress media library
		$upload_dir = wp_upload_dir();
		$dest_file  = $upload_dir['path'] . '/' . $filename;

		// Copy file to uploads directory
		if ( ! copy( $full_path, $dest_file ) ) {
			error_log( 'FluentImport: Failed to copy image: ' . $full_path );
			return [];
		}

		// Create attachment
		$attachment_data = [
			'post_mime_type' => mime_content_type( $dest_file ),
			'post_title'     => sanitize_file_name( pathinfo( $filename, PATHINFO_FILENAME ) ),
			'post_content'   => '',
			'post_status'    => 'inherit',
		];

		$attachment_id = wp_insert_attachment( $attachment_data, $dest_file );
		if ( is_wp_error( $attachment_id ) ) {
			error_log( 'FluentImport: Failed to create attachment: ' . $attachment_id->get_error_message() );
			return [];
		}

		// Generate attachment metadata
		$attachment_metadata = wp_generate_attachment_metadata( $attachment_id, $dest_file );
		wp_update_attachment_metadata( $attachment_id, $attachment_metadata );

		$media = wp_prepare_attachment_for_js( $attachment_id );
		return [
			'id'    => $attachment_id,
			'title' => $media['title'],
			'url'   => $media['url'],
		];
	}
}