<?php

namespace Limb_Chatbot\Includes\Services\Actions;

use Limb_Chatbot\Includes\Data_Objects\Action;
use Limb_Chatbot\Includes\Data_Objects\Action_Callback;
use Limb_Chatbot\Includes\Data_Objects\Action_Submission;
use Limb_Chatbot\Includes\Data_Objects\Message;
use Limb_Chatbot\Includes\Data_Objects\Parameter;
use Limb_Chatbot\Includes\Services\Helper;
use Limb_Chatbot\Includes\Utilities\Chatbot_Utility;
use Limb_Chatbot\Includes\Utilities\Copilot_Utility;

/**
 * Class Action_Submission_Message_Builder
 *
 * Responsible for building action submission result messages.
 * Separates message generation concerns from action service coordination.
 * Follows Single Responsibility Principle by handling only message creation logic.
 *
 * Generates context-aware messages for action execution results:
 * - With extracted variables: Shows user-relevant data (filtered for privacy)
 * - Without variables: Generates beautiful success messages via AI
 *
 * @package Limb_Chatbot\Includes\Services\Actions
 * @since 1.0.0
 */
class Action_Submission_Message_Builder {

	/**
	 * Chatbot utility for AI-powered message generation
	 *
	 * @var Chatbot_Utility|null
	 */
	private ?Chatbot_Utility $chatbot_utility;

	/**
	 * Constructor with dependency injection
	 *
	 * @param Chatbot_Utility|null $chatbot_utility Optional chatbot utility for AI features
	 *
	 * @since 1.0.0
	 */
	public function __construct( ?Chatbot_Utility $chatbot_utility = null ) {
		$this->chatbot_utility = $chatbot_utility;
	}

	/**
	 * Build an action submission result message
	 *
	 * Creates context-aware messages showing the result of action execution.
	 * Adapts message based on whether the action returned extracted variables.
	 * Checks for custom success_message in the last callback's config first.
	 *
	 * @param Action_Submission $submission The action submission result
	 * @param Action|null $action The action (for context and purpose)
	 *
	 * @return Message The action submission result message
	 * @since 1.0.0
	 */
	public function build( Action_Submission $submission, ?Action $action = null ): Message {
		// Check if last callback has a custom success_message
		$custom_success_message = $this->get_last_callback_success_message( $action, $submission );

		if ( ! empty( $custom_success_message ) ) {
			// Use custom success message instead of AI generation
			$message_text = $custom_success_message;
		} else {
			// Determine message based on extracted variables
			if ( ! empty( $submission->get_extracted_vars() ) ) {
				// With extracted vars: Pass them to AI to include in message
				$message_text = $this->generate_extracted_vars_message( $submission, $action );
			} else {
				// Without extracted vars: Generate general success message
				$message_text = $this->generate_action_based_message( $submission, $action );
			}
		}

		return $this->create_message( $message_text, $submission, $action );
	}

	/**
	 * Get success message from last callback's config
	 *
	 * Checks the last callback (highest order) for a custom success_message.
	 * If found, processes it through Variable_Replacer to replace variables.
	 *
	 * @param Action|null $action The action to get callbacks from
	 * @param Action_Submission $submission The action submission for variable context
	 *
	 * @return string|null The processed success message or null if not found
	 * @since 1.0.0
	 */
	private function get_last_callback_success_message( ?Action $action, Action_Submission $submission ): ?string {
		if ( ! $action ) {
			return null;
		}

		// Get all callbacks ordered by execution order (ascending)
		$callbacks = $action->get_callbacks();

		if ( $callbacks->is_empty() ) {
			return null;
		}

		// Get the last callback (highest order value)
		$last_callback = $callbacks->last();

		if ( ! $last_callback instanceof Action_Callback ) {
			return null;
		}

		// Get callback config
		$config = $last_callback->get_config();

		if ( ! is_array( $config ) || empty( $config['success_message'] ) ) {
			return null;
		}

		$success_message = trim( $config['success_message'] );

		if ( empty( $success_message ) ) {
			return null;
		}

		// Process the success message through Variable_Replacer
		// Merge action_data (form parameters) with extracted variables
		$all_variables = array_merge(
			$submission->get_action_data() ?? [],
			$submission->get_extracted_vars() ?? []
		);

		$replacer          = new Variable_Replacer( $all_variables );
		$processed_message = $replacer->replace( $success_message );

		return $processed_message;
	}

	/**
	 * Generate message for action with extracted variables
	 *
	 * Sends extracted variables to AI to generate beautiful messages that include them.
	 * Example: "Congrats, your voucher code is: SAVE20XYZ"
	 *
	 * @param Action_Submission $submission The action submission
	 * @param Action|null $action The action for context
	 *
	 * @return string The generated message text with extracted variables included
	 * @since 1.0.0
	 */
	private function generate_extracted_vars_message( Action_Submission $submission, ?Action $action = null ): string {
		$extracted_vars = $submission->get_extracted_vars();

		// Try AI-powered message generation with extracted variables context
		if ( ! empty( $this->chatbot_utility ) ) {
			try {
				$action_context = $this->get_action_context( $action );

				// Use Copilot utility to generate the success message with extracted vars context
				$copilot = new Copilot_Utility(
					$this->chatbot_utility->get_ai_model(),
					$this->chatbot_utility->get_config()
				);

				// Format the extracted variables for the AI prompt
				$details = $this->format_extracted_vars_for_prompt( $extracted_vars );

				// Pass context including the extracted variables and success status
				$is_success = $submission->is_success();
				$ai_message = $copilot->generate_action_success_message_with_context( $action_context, $details, $is_success, $this->chatbot_utility->get_stream() );

				if ( $ai_message instanceof Message ) {
					return $ai_message->extract_text();
				}

				return $this->get_fallback_message();
			} catch ( \Exception $e ) {
				Helper::log( 'Failed to generate AI result message with context: ' . $e->getMessage() );

				return $this->get_fallback_message();
			}
		}

		// Fallback if AI generation fails or unavailable
		return $this->get_fallback_message();
	}

	/**
	 * Get user-friendly action context/purpose
	 *
	 * Converts action name to readable context (e.g., "generate_voucher" → "Voucher Generation").
	 *
	 * @param Action|null $action The action object
	 *
	 * @return string User-friendly action context
	 * @since 1.0.0
	 */
	private function get_action_context( ?Action $action ): string {
		// Convert snake_case to Title Case with spaces
		$name = $action->name ?? '';
		$name = str_replace( [ '_', '-' ], ' ', $name );

		return ucwords( $name );
	}

	/**
	 * Format extracted variables for prompt inclusion
	 *
	 * Intelligently converts any extracted variables structure into human-readable format for AI prompt.
	 * Adapts to different input types and structures without hardcoded keys.
	 * Recursively processes nested objects and arrays.
	 *
	 * Examples:
	 * - Flat array: ['voucher_code' => 'ABC123'] → "Voucher Code: ABC123"
	 * - Nested structure: ['data' => [...]] or ['result' => [...]] → Extracts and formats content
	 * - Mixed: ['success' => true, 'data' => [...]] → Skips meta fields, formats meaningful data
	 *
	 * @param array $extracted_vars Action submission extracted variables (any structure)
	 *
	 * @return string Formatted variables for prompt
	 * @since 1.0.0
	 */
	private function format_extracted_vars_for_prompt( array $extracted_vars ): string {
		$no_additional_details = __( 'No additional details', 'limb-chatbot' );
		if ( empty( $extracted_vars ) ) {
			return $no_additional_details;
		}

		$parts = $this->extract_meaningful_data( $extracted_vars );

		if ( empty( $parts ) ) {
			return $no_additional_details;
		}

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

	/**
	 * Extract meaningful data from extracted variables
	 *
	 * Recursively processes the input structure, filtering out meta-fields (success, message, timestamp, etc.)
	 * and extracting the actual meaningful data for user-facing messages.
	 *
	 * @param array $data The data structure to process
	 * @param int $depth Current recursion depth
	 *
	 * @return array Array of formatted "Key: Value" strings
	 * @since 1.0.0
	 */
	private function extract_meaningful_data( array $data, int $depth = 0 ): array {
		// Prevent infinite recursion
		if ( $depth > 3 ) {
			return [];
		}

		$parts = [];

		foreach ( $data as $key => $value ) {
			// Handle null values
			if ( $value === null ) {
				continue;
			}

			// Handle boolean values
			if ( is_bool( $value ) ) {
				$formatted_key = $this->format_variable_name( $key );
				$bool_value    = $value ? 'Yes' : 'No';
				$parts[]       = "$formatted_key: $bool_value";
				continue;
			}

			// Handle nested arrays and objects
			if ( is_array( $value ) ) {
				$nested_parts = $this->extract_meaningful_data( $value, $depth + 1 );

				if ( ! empty( $nested_parts ) ) {
					$formatted_key = $this->format_variable_name( $key );
					// If nested data is simple, inline it; if complex, group it
					if ( count( $nested_parts ) === 1 ) {
						// Single item: flatten it
						$parts[] = "$formatted_key: " . $nested_parts[0];
					} else {
						// Multiple items: group them
						$parts[] = "$formatted_key: " . implode( "\n", $nested_parts );
					}
				}
				continue;
			}

			// Handle scalar values
			$formatted_key   = $this->format_variable_name( $key );
			$sanitized_value = $this->sanitize_display_value( $value );
			$parts[]         = "$formatted_key: $sanitized_value";
		}

		return $parts;
	}

	/**
	 * Format variable name for display
	 *
	 * Converts "voucher_code" to "Voucher Code", "order_id" to "Order ID", etc.
	 *
	 * @param string $key The variable key
	 *
	 * @return string Formatted display name
	 * @since 1.0.0
	 */
	private function format_variable_name( string $key ): string {
		// Convert snake_case to Title Case
		$name = str_replace( [ '_', '-' ], ' ', $key );

		return ucwords( $name );
	}

	/**
	 * Sanitize value for display
	 *
	 * Ensures values are safe to display.
	 * Truncates very long values.
	 *
	 * @param mixed $value The value to sanitize
	 *
	 * @return string Sanitized display value
	 * @since 1.0.0
	 */
	private function sanitize_display_value( $value ): string {
		// Convert to string
		$value = (string) $value;

		// Truncate long values
		if ( strlen( $value ) > 100 ) {
			$value = substr( $value, 0, 97 ) . '...';
		}

		// Escape for safe display
		return esc_html( $value );
	}

	/**
	 * Generate fallback message when something went wrong
	 *
	 * @return string Fallback success message
	 * @since 1.0.0
	 */
	private function get_fallback_message() {
		return __( 'Submitted successfully! 🎉', 'limb-chatbot' );
	}

	/**
	 * Generate beautiful message for action without extracted variables
	 *
	 * Creates a context-aware message based on action purpose.
	 * Uses AI to generate personalized messages if available.
	 *
	 * @param Action_Submission $submission The action submission
	 * @param Action|null $action The action for context
	 *
	 * @return string The generated message text
	 * @since 1.0.0
	 */
	private function generate_action_based_message( Action_Submission $submission, ?Action $action = null ): string {
		// Try AI-powered message generation if available
		if ( ! empty( $this->chatbot_utility ) ) {
			try {
				$action_context = $this->get_action_context( $action );

				// Use Copilot utility to generate the success message
				$copilot = new Copilot_Utility(
					$this->chatbot_utility->get_ai_model(),
					$this->chatbot_utility->get_config()
				);

				// Pass success status to AI for context-aware message generation
				$is_success = $submission->is_success();
				$ai_message = $copilot->generate_action_success_message( $action_context, $is_success, $this->chatbot_utility->get_stream() );

				if ( $ai_message instanceof Message ) {
					return $ai_message->extract_text();
				}

				return $this->get_fallback_message();
			} catch ( \Exception $e ) {
				Helper::log( 'Failed to generate AI result message: ' . $e->getMessage() );

				return $this->get_fallback_message();
			}
		}

		return $this->get_fallback_message();
	}

	/**
	 * Create the actual message object
	 *
	 * @param string $message_text The message text
	 * @param Action_Submission $submission The action submission
	 * @param Action|null $action The action (for checking callback settings)
	 *
	 * @return Message The constructed message
	 * @since 1.0.0
	 */
	private function create_message( string $message_text, Action_Submission $submission, ?Action $action = null ): Message {
		// Check if last callback wants to show collected data
		$show_collected_data = $this->should_show_collected_data( $action );

		// Get action data and convert vector search IDs to titles for frontend display
		// Only include data_fields if show_collected_data is true
		$data_fields = $show_collected_data ? $this->prepare_data_fields_for_frontend( $submission->get_action_data(), $action ) : null;

		$submission_content = [
			'type'                                  => Message::CONTENT_TYPE_ACTION_SUBMISSION,
			Message::CONTENT_TYPE_ACTION_SUBMISSION => array(
				'message'     => $message_text,
				'data_fields' => $data_fields,
				'success'     => $submission->is_success()
			),
		];

		$message = Message::make( [
			'role'    => Message::ROLE_ASSISTANT,
			'content' => [
				$submission_content,
			]
		] );

		$message->set_is_action_message( true );

		return $message;
	}

	/**
	 * Check if collected data should be shown based on last callback's setting
	 *
	 * @param Action|null $action The action to get callbacks from
	 *
	 * @return bool True if collected data should be shown, false otherwise (defaults to true)
	 * @since 1.0.0
	 */
	private function should_show_collected_data( ?Action $action ): bool {
		if ( ! $action ) {
			// Default to showing collected data if no action
			return true;
		}

		// Get all callbacks ordered by execution order (ascending)
		$callbacks = $action->get_callbacks();

		if ( $callbacks->is_empty() ) {
			// Default to showing collected data if no callbacks
			return true;
		}

		// Get the last callback (highest order value)
		$last_callback = $callbacks->last();

		if ( ! $last_callback instanceof Action_Callback ) {
			// Default to showing collected data if last callback is invalid
			return true;
		}

		// Get callback config
		$config = $last_callback->get_config();

		if ( ! is_array( $config ) || ! isset( $config['show_collected_data'] ) ) {
			// Default to showing collected data if setting is not defined
			return true;
		}

		// Return the setting value (defaults to true if not explicitly false/0)
		return $config['show_collected_data'] !== false && $config['show_collected_data'] !== 0;
	}

	/**
	 * Prepare data fields for frontend display
	 *
	 * Converts vector search parameter values from IDs to post titles
	 * for better user experience. Backend callbacks still use IDs.
	 *
	 * @param array|null $action_data Raw action data with parameter values
	 * @param Action|null $action Action object to get parameter definitions
	 *
	 * @return array|null Prepared data fields with titles instead of IDs for vector search params
	 * @since 1.0.0
	 */
	private function prepare_data_fields_for_frontend( ?array $action_data, ?Action $action ): ?array {
		if ( empty( $action_data ) || ! $action ) {
			return $action_data;
		}

		// Get action parameters to identify vector search fields
		$parameters = $action->get_parameters();
		if ( $parameters->is_empty() ) {
			return $action_data;
		}

		$prepared_data = $action_data;

		// Iterate through parameters to find vector search types
		foreach ( $parameters as $parameter ) {
			if ( ! $parameter instanceof Parameter ) {
				continue;
			}

			if ( $parameter->get_type() === Parameter::TYPE_BUTTONS ) {
				$options = $parameter->get_config_value( 'options' );
				if ( ! is_array( $options ) ) {
					continue;
				}
				if ( isset( $prepared_data[ $parameter->get_name() ] ) ) {
					$selected_value = $prepared_data[ $parameter->get_name() ];
					foreach ( $options as $option ) {
						if ( is_array( $option ) && isset( $option['value'] ) && $option['value'] === $selected_value ) {
							if ( isset( $option['label'] ) ) {
								$prepared_data[ $parameter->get_name() ] = $option['label'];
							}
							break;
						}
					}
				}
			}

			// Check if this is a vector search parameter
			if ( $parameter->get_type() !== Parameter::TYPE_VECTOR_SEARCH ) {
				continue;
			}

			$param_name = $parameter->get_name();

			// Check if this parameter has a value in action data
			if ( ! isset( $prepared_data[ $param_name ] ) ) {
				continue;
			}

			$post_id = $prepared_data[ $param_name ];

			// Get the post title for this ID
			if ( is_numeric( $post_id ) ) {
				$post = get_post( (int) $post_id );
				if ( $post ) {
					// Replace ID with post title for frontend display
					$prepared_data[ $param_name ] = $post->post_title;
				}
			}
		}

		return $prepared_data;
	}
}
