<?php

namespace AIPT\Api\BulkGenerator\Desc\Core;

use AIPT\Utils\PromptFormatter;
use AIPT\Utils\VariableConverter;
use AIPT\Utils\ContentLengthPreset;
use AIPT\Utils\ImagePreserver;

class PromptFactory {

	private array $productData;
	private array $options;
	private array $settings;
	/** @var array<string, string> Placeholder-to-img-tag map populated during mask() calls */
	private array $imageMap = [];
	/** @var array<string, array<string, string>> Per-field placeholder maps to prevent cross-contamination during restore */
	private array $imageMapByField = [];

	public function __construct( array $productData, array $options = array() ) {
		$this->productData = $productData;
		$this->options     = $options;
		$this->settings    = $this->loadSettings();
	}

	private function loadSettings(): array {
		return array(
			'language'               => get_option( 'aipt_language', 'English' ),
			'writing_style'          => get_option( 'aipt_writing_style', 'Professional' ),
			'content_length_preset'  => get_option( 'aipt_content_length_preset', 'long' ),
			'short_length_preset'    => get_option( 'aipt_short_length_preset', 'standard' ),
			'max_tags_count'         => (int) get_option( 'aipt_max_tags_count', 3 ),
			'short_system_prompt'    => get_option( 'aipt_short_system_prompt', '' ),
			'short_user_prompt'      => get_option( 'aipt_short_user_prompt', '' ),
			'full_system_prompt'     => get_option( 'aipt_full_system_prompt', '' ),
			'full_user_prompt'       => get_option( 'aipt_full_user_prompt', '' ),
			'example_short'          => get_option( 'aipt_example_short_description', '' ),
			'example_description'    => get_option( 'aipt_example_description', '' ),
			'example_tags'           => get_option( 'aipt_example_tags', '' ),
			'custom_variables'       => get_option( 'aipt_custom_variables', array() ),
			'brand_context'          => get_option( 'aipt_brand_context', '' ),
		);
	}

	public function buildUnified( array $contentTypes, array $options = array() ): array {

		$this->imageMap        = [];
		$this->imageMapByField = [];
		$options = array_merge( $this->options, $options );

		if ( $this->hasRelevantExamples( $contentTypes ) ) {
			$examplePrompt = $this->buildExampleModePrompt( $contentTypes, $options );
			if ( $examplePrompt !== null ) {
				return $examplePrompt;
			}
		}

		return $this->buildStandardModePrompt( $contentTypes, $options );
	}

	private function hasRelevantExamples( array $contentTypes ): bool {
		return apply_filters( 'aipt_prompt_factory_has_examples', false, $contentTypes, $this->settings );
	}

	private function buildExampleModePrompt( array $contentTypes, array $options ): ?array {
		return apply_filters(
			'aipt_prompt_factory_build_example_prompt',
			null,
			$contentTypes,
			$options,
			$this->productData,
			$this->settings
		);
	}

	private function buildStandardModePrompt( array $contentTypes, array $options ): array {
		$systemParts = array();
		$userParts   = array();

		$systemParts[] = $this->getBaseSystemPrompt();

		$brandContext = $this->getBrandContext();
		if ( ! empty( $brandContext ) ) {
			$systemParts[] = $brandContext;
		}

		$categoryHints = $this->getCategoryHints();
		if ( ! empty( $categoryHints ) ) {
			$systemParts[] = $categoryHints;
		}

		if ( ! empty( $this->settings['short_system_prompt'] ) && in_array( 'short_description', $contentTypes, true ) ) {
			$processed     = $this->replaceVariables( $this->settings['short_system_prompt'], 'short' );
			$systemParts[] = "SHORT DESCRIPTION INSTRUCTIONS:\n" . $processed;
		}

		if ( ! empty( $this->settings['full_system_prompt'] ) && in_array( 'description', $contentTypes, true ) ) {
			$processed     = $this->replaceVariables( $this->settings['full_system_prompt'], 'full' );
			$systemParts[] = "FULL DESCRIPTION INSTRUCTIONS:\n" . $processed;
		}

		$systemParts[] = $this->getOutputFormat( $contentTypes );

		$userParts[] = $this->getProductContext();

		$priceHints = $this->getPricePositioning();
		if ( ! empty( $priceHints ) ) {
			$userParts[] = $priceHints;
		}

		if ( ! empty( $this->settings['short_user_prompt'] ) && in_array( 'short_description', $contentTypes, true ) ) {
			$processed   = $this->replaceVariables( $this->settings['short_user_prompt'], 'short' );
			$userParts[] = "SHORT DESCRIPTION REQUIREMENTS:\n" . $processed;
		}

		if ( ! empty( $this->settings['full_user_prompt'] ) && in_array( 'description', $contentTypes, true ) ) {
			$processed   = $this->replaceVariables( $this->settings['full_user_prompt'], 'full' );
			$userParts[] = "FULL DESCRIPTION REQUIREMENTS:\n" . $processed;
		}

		$existingContent = $this->getExistingContentInstructions( $contentTypes, $options );
		if ( ! empty( $existingContent ) ) {
			$userParts[] = $existingContent;
		}

		if ( ! empty( $this->imageMap ) ) {
			$systemParts[] = $this->getImagePreservationInstruction();
		}

		$userParts[] = $this->getGenerationRequest( $contentTypes );

		return array(
			'system' => PromptFormatter::cleanContent( implode( "\n\n", array_filter( $systemParts ) ) ),
			'user'   => PromptFormatter::cleanContent( implode( "\n\n", array_filter( $userParts ) ) ),
		);
	}

	public function buildSingle( string $contentType, array $options = array() ): array {
		return $this->buildUnified( array( $contentType ), $options );
	}

	/**
	 * Get writing style guidance based on the selected style.
	 * Provides actionable, specific instructions for each tone.
	 * 
	 * Updated in v2.6.0 based on prompt engineering research:
	 * - More actionable and specific guidance
	 * - Includes concrete examples of what to do
	 * - Avoids vague adjectives
	 *
	 * @return string Style-specific writing guidance
	 */
	private function getWritingStyleGuidance(): string {
		$style = strtolower( $this->settings['writing_style'] );

		$styleGuides = array(
			'professional'  => 'Use industry terminology confidently. Write with authority—short sentences, no fluff. State facts directly. Avoid exclamation marks and casual phrases.',
			'casual'        => 'Write like talking to a friend. Use contractions (you\'ll, it\'s, don\'t). Keep sentences conversational. It\'s okay to start sentences with "And" or "But".',
			'luxury'        => 'Understated confidence—never oversell. Focus on craftsmanship, heritage, and materials. Use sensory words (weight, texture, finish). No exclamation marks. Let quality speak.',
			'playful'       => 'Energetic and fun. Use humor where it fits naturally. Short punchy sentences mixed with longer ones. Appeal to joy and excitement. Emojis sparingly if appropriate.',
			'technical'     => 'Lead with specifications and metrics. Use precise numbers and units. Technical terminology without dumbing down. Structure with clear categories.',
			'minimalist'    => 'Every word earns its place. Cut adjectives ruthlessly. One idea per sentence. White space is your friend. Essential information only.',
			'persuasive'    => 'Action verbs: "Get", "Save", "Discover". Address the reader directly (you/your). Mention specific outcomes. Create gentle urgency through benefits, not pressure.',
			'storytelling'  => 'Paint a scene: "Picture yourself..." or "Imagine...". Connect features to daily moments. Use sensory details. Make the reader the hero of the story.',
		);

		return $styleGuides[ $style ] ?? 'Write in a clear, engaging tone suited for online shopping. Be specific and benefit-focused.';
	}

	/**
	 * Get the optimized base system prompt using Markdown + XML hybrid structure.
	 * 
	 * Updated in v2.6.0 based on OpenAI/Gemini 2025 prompt engineering research:
	 * - Uses Markdown headers for structure (# Identity, # Rules)
	 * - Positive instructions instead of "don't do X"
	 * - Feature→Benefit→Meaning transformation guidance
	 * - Burstiness instruction for natural rhythm
	 * - Specificity test as quality gate
	 *
	 * @return string Base system prompt
	 */
	private function getBaseSystemPrompt(): string {
		$style      = $this->settings['writing_style'];
		$language   = $this->settings['language'];
		$styleGuide = $this->getWritingStyleGuidance();

		return "# Identity
You are a product copywriter who transforms features into customer benefits.

# Language & Style
- Language: {$language}
- Tone: {$style}
- {$styleGuide}

# Core Rules
1. Lead with the primary BENEFIT, not the product name or a generic opener
2. Apply the \"So What?\" test to every feature: Feature → What it does → Why the customer cares
3. Be SPECIFIC—use actual details from the product data, not generic phrases like \"high-quality\" or \"premium\"
4. Vary sentence rhythm: mix short punchy sentences (5 words) with detailed ones (15-20 words)
5. Write for scanners: key benefits should be visible in the first line and any headings
6. NEVER include literal prices, price ranges, or currency amounts in the description. Prices change frequently and make descriptions outdated

# Quality Gate
Before finalizing, apply this test: \"Could a competitor use this exact description for their similar product?\"
If yes → add more specific details from the product data (materials, dimensions, unique features).

# Output Format
Return valid JSON. Use HTML tags (<p>, <ul>, <li>, <strong>) for rich text. Never use Markdown syntax in the content.";
	}

	/**
	 * Get brand/store context to provide global information to the AI.
	 * Uses XML tags for clear data isolation.
	 *
	 * @return string Brand context section or empty string if not set.
	 */
	private function getBrandContext(): string {
		$brandContext = trim( $this->settings['brand_context'] ?? '' );

		if ( empty( $brandContext ) ) {
			return '';
		}

		return "<brand_context>
{$brandContext}
</brand_context>

Apply this brand voice and context to all generated content.";
	}

	/**
	 * Category hints removed in v2.6.0.
	 * 
	 * Reason: Product categories are infinitely diverse (flowers, antiques, 
	 * musical instruments, pet supplies, handmade gifts, industrial equipment, etc.)
	 * Hardcoded category hints cannot cover this diversity and add unnecessary
	 * complexity. The LLM already understands category context from the product data.
	 *
	 * @return string Always empty - kept for backward compatibility
	 */
	private function getCategoryHints(): string {
		return '';
	}

	/**
	 * Price positioning hints removed in v2.6.0.
	 * 
	 * Reason: Product data already contains price information (regular_price, sale_price).
	 * The LLM can interpret this data naturally. Artificial "weave savings naturally" 
	 * instructions often produce worse results than letting the LLM handle it.
	 *
	 * @return string Always empty - kept for backward compatibility
	 */
	private function getPricePositioning(): string {
		return '';
	}

	/**
	 * Get the product context wrapped in XML tags for clear data isolation.
	 * 
	 * Updated in v2.6.0 based on prompt engineering research:
	 * - XML tags prevent LLM from interpreting product data as instructions
	 * - Cleaner structure improves LLM comprehension
	 * - Gemini and OpenAI both recommend this pattern
	 *
	 * @return string Product context with XML wrapper
	 */
	private function getProductContext(): string {
		$name = $this->productData['name'] ?? '';
		$sku  = $this->productData['sku'] ?? '';
		$id   = $this->productData['id'] ?? 0;

		$lines = array();
		$lines[] = '<product_data>';
		$lines[] = "Name: {$name}";
		
		if ( ! empty( $sku ) ) {
			$lines[] = "SKU: {$sku}";
		}

		$categories = $this->productData['categories_string'] ?? '';
		if ( ! empty( $categories ) ) {
			$lines[] = "Category: {$categories}";
		}

		$attributes = $this->productData['formatted_attributes'] ?? '';
		if ( is_array( $attributes ) ) {
			$attributes = implode( ', ', $attributes );
		}
		if ( ! empty( $attributes ) ) {
			$lines[] = "Specifications: {$attributes}";
		}

		$tags = $this->productData['tags_string'] ?? '';
		if ( ! empty( $tags ) ) {
			$lines[] = "Tags: {$tags}";
		}

		if ( ! empty( $this->productData['custom_variables'] ) ) {
			foreach ( $this->productData['custom_variables'] as $varName => $value ) {
				if ( ! empty( $value ) ) {
					$lines[] = "{$varName}: {$value}";
				}
			}
		}
		
		$lines[] = '</product_data>';

		return implode( "\n", $lines );
	}

	/**
	 * Get the output format specification.
	 * 
	 * Updated in v2.6.0:
	 * - Uses XML-style section markers for clarity
	 * - More explicit JSON structure examples
	 * - Length instructions integrated clearly
	 *
	 * @param array $contentTypes Content types to generate.
	 * @return string Output format instructions.
	 */
	private function getOutputFormat( array $contentTypes ): string {
		$maxTags = $this->settings['max_tags_count'];

		$allowedTags = get_option( 'aipt_allowed_html_tags', array() );
		if ( ! is_array( $allowedTags ) ) {
			$allowedTags = array();
		}
		$tagList = ! empty( $allowedTags ) ? implode( ', ', array_map( function( $t ) { return '<' . $t . '>'; }, $allowedTags ) ) : '<p>, <ul>, <li>, <strong>';

		$lines = array();
		$lines[] = '<output_format>';
		$lines[] = "Return a valid JSON object. For rich text use ONLY these HTML tags: {$tagList}. Never use Markdown. Add a newline after each closing block tag (</p>, </ul>, </li>) for readability.";
		$lines[] = '';
		$lines[] = 'Structure:';
		$lines[] = '{';

		$jsonParts = array();

		if ( in_array( 'short_description', $contentTypes, true ) ) {
			$jsonParts[] = '  "short_description": "<p>Your hook text here</p>"';
		}

		if ( in_array( 'description', $contentTypes, true ) ) {
			$jsonParts[] = '  "description": "<p>Opening...</p><ul><li>Feature 1</li></ul><p>Closing...</p>"';
		}

		if ( in_array( 'tags', $contentTypes, true ) ) {
			$jsonParts[] = "  \"tags\": \"tag1, tag2, tag3\" (exactly {$maxTags} comma-separated)";
		}

		$lines[] = implode( ",\n", $jsonParts );
		$lines[] = '}';
		$lines[] = '';
		$lines[] = 'Return ONLY the JSON object—no additional text before or after.';
		$lines[] = '</output_format>';

		if ( in_array( 'short_description', $contentTypes, true ) ) {
			$lines[] = '';
			$lines[] = '<short_description_length>';
			$lines[] = ContentLengthPreset::buildShortInstructions( $this->settings['short_length_preset'] );
			$lines[] = '</short_description_length>';
		}

		if ( in_array( 'description', $contentTypes, true ) ) {
			$lines[] = '';
			$lines[] = '<full_description_length>';
			$lines[] = ContentLengthPreset::buildFullInstructions( $this->settings['content_length_preset'] );
			$lines[] = '</full_description_length>';
		}

		return implode( "\n", $lines );
	}

	private function getExistingContentInstructions( array $contentTypes, array $options ): string {
		$instructions = array();

		$improve = ! empty( $options['improveExistingDescription'] );
		$keep    = ! empty( $options['keepExistingDescription'] );

		if ( $improve ) {
			$preserveContentImages = $this->shouldPreserveContentImages( $options );
			if ( in_array( 'short_description', $contentTypes, true ) && ! empty( $this->productData['short_description'] ) ) {
				$raw = $this->productData['short_description'];
				if ( $preserveContentImages ) {
					$raw = $this->maskProductImages( $raw, 'short_description' );
				}
				$existing       = wp_strip_all_tags( $raw );
				$instructions[] = "EXISTING SHORT DESCRIPTION TO IMPROVE:\n\"{$existing}\"\n\nEnhance clarity and impact while keeping the core message. Make it more benefit-focused.";
			}

			if ( in_array( 'description', $contentTypes, true ) && ! empty( $this->productData['description'] ) ) {
				$raw = $this->productData['description'];
				if ( $preserveContentImages ) {
					$raw = $this->maskProductImages( $raw, 'description' );
				}
				$existing       = wp_strip_all_tags( $raw );
				$instructions[] = "EXISTING FULL DESCRIPTION TO IMPROVE:\n\"{$existing}\"\n\nEnhance structure, clarity, and scannability. Transform features into benefits. Maintain brand voice.";
			}
		}

		if ( $keep && in_array( 'tags', $contentTypes, true ) && ! empty( $this->productData['tags_string'] ) ) {
			$additionalCount = $options['additionalTagsCount'] ?? $this->settings['max_tags_count'];
			$instructions[]  = "EXISTING TAGS: {$this->productData['tags_string']}\n\nGenerate {$additionalCount} NEW complementary tags. No duplicates.";
		}

		return implode( "\n\n", $instructions );
	}

	private function shouldPreserveContentImages( array $options ): bool {
		if ( array_key_exists( 'preserve_content_images', $options ) ) {
			return (bool) $options['preserve_content_images'];
		}

		return (bool) get_option( 'aipt_preserve_content_images', true );
	}

	/**
	 * Mask image tags in product content and update both the shared and per-field image maps.
	 *
	 * The shared imageMap drives the AI preservation instruction (all placeholders),
	 * while imageMapByField enables field-specific restoration to prevent cross-contamination.
	 *
	 * @param string $content Raw HTML content that may contain <img> tags.
	 * @param string $field   Content field name (e.g. 'short_description', 'description').
	 * @return string Content with image tags replaced by AIPT placeholders.
	 */
	private function maskProductImages( string $content, string $field ): string {
		$masked = ImagePreserver::mask( $content, count( $this->imageMap ) );

		if ( ! is_array( $masked )
			|| ! isset( $masked['content'], $masked['imageMap'] )
			|| ! is_string( $masked['content'] )
			|| ! is_array( $masked['imageMap'] ) ) {
			return $content;
		}

		if ( ! empty( $masked['imageMap'] ) ) {
			$this->imageMap                  = array_merge( $this->imageMap, $masked['imageMap'] );
			$this->imageMapByField[ $field ] = $masked['imageMap'];
		}

		return $masked['content'];
	}

	/**
	 * Build the AI instruction that tells the model to preserve image placeholders verbatim.
	 *
	 * @return string System prompt addendum.
	 */
	private function getImagePreservationInstruction(): string {
		return "# Image Preservation\n\n" .
			"The text contains image placeholders like ##AIPT_IMG_0##, ##AIPT_IMG_1##.\n" .
			"You MUST preserve every placeholder EXACTLY as shown, in appropriate positions within your output.\n" .
			"Do not translate, modify, or remove these placeholders.";
	}

	/**
	 * Expose the full placeholder-to-img-tag map populated during prompt building.
	 * Used for AI preservation instructions (all placeholders across all fields).
	 *
	 * @return array<string, string>
	 */
	public function getImageMap(): array {
		return $this->imageMap;
	}

	/**
	 * Expose the placeholder-to-img-tag map for a specific content field.
	 * Use this for restoration to prevent images from one field leaking into another.
	 *
	 * @param string $field Content field name (e.g. 'short_description', 'description').
	 * @return array<string, string>
	 */
	public function getImageMapForField( string $field ): array {
		return $this->imageMapByField[ $field ] ?? array();
	}

	/**
	 * Get the generation request with clear task specification.
	 * 
	 * Updated in v2.6.0:
	 * - Uses XML task tag for clear instruction boundary
	 * - Includes reminder about specificity and benefit focus
	 * - Reinforces quality gate
	 *
	 * @param array $contentTypes Content types to generate.
	 * @return string Generation request.
	 */
	private function getGenerationRequest( array $contentTypes ): string {
		$typeNames = array(
			'short_description' => 'short description',
			'description'       => 'full description',
			'tags'              => 'product tags',
		);

		$requested = array();
		foreach ( $contentTypes as $type ) {
			if ( isset( $typeNames[ $type ] ) ) {
				$requested[] = $typeNames[ $type ];
			}
		}

		$typeList = implode( ' + ', $requested );

		return "<task>
Generate: {$typeList}

Using the product data above:
1. Transform each specification into a customer benefit
2. Use specific details—avoid generic phrases that could fit any product
3. Match the requested tone and length requirements
</task>";
	}

	private function replaceVariables( string $template, string $type ): string {
		$variables = array(
			'{language}'         => $this->settings['language'],
			'{style}'            => $this->settings['writing_style'],
			'{title}'            => $this->productData['name'] ?? '',
			'{SKU}'              => $this->productData['sku'] ?? '',
			'{ATTRIBUTES}'       => $this->productData['formatted_attributes'] ?? '',
			'{CATEGORIES}'       => $this->productData['categories_string'] ?? '',
			'{TAGS}'             => $this->productData['tags_string'] ?? '',
			'{PRICE}'            => $this->productData['price_display'] ?? '',
			'{REGULAR_PRICE}'    => $this->productData['regular_price_display'] ?? '',
			'{SALE_PRICE}'       => $this->productData['sale_price_display'] ?? '',
		);

		if ( ! empty( $this->settings['custom_variables'] ) && ! empty( $this->productData['custom_variables'] ) ) {
			foreach ( $this->settings['custom_variables'] as $varDef ) {
				$varName = $varDef['name'] ?? '';
				if ( ! empty( $varName ) && isset( $this->productData['custom_variables'][ $varName ] ) ) {
					$variables[ '{' . $varName . '}' ] = $this->productData['custom_variables'][ $varName ];
				}
			}
		}

		$safeVariables = VariableConverter::convertVariables( $variables );

		return str_replace( array_keys( $safeVariables ), array_values( $safeVariables ), $template );
	}

	public function getProductData(): array {
		return $this->productData;
	}

	public function getSettings(): array {
		return $this->settings;
	}
}
