<?php

namespace Limb_Chatbot\Includes\Services\Actions;

use Limb_Chatbot\Includes\Data_Objects\Action;
use Limb_Chatbot\Includes\Data_Objects\Action_Plan;
use Limb_Chatbot\Includes\Data_Objects\Action_Plan_Step;
use Limb_Chatbot\Includes\Data_Objects\Chat;
use Limb_Chatbot\Includes\Data_Objects\Message;
use Limb_Chatbot\Includes\Exceptions\Exception;
use Limb_Chatbot\Includes\Factories\Data_Collecting_Processor_Factory;
use Limb_Chatbot\Includes\Services\Stream_Event_Service;
use Limb_Chatbot\Includes\Utilities\Chatbot_Utility;

/**
 * Class Action_Service
 *
 * Orchestrates action execution and parameter collection workflow.
 * Follows SOLID principles:
 * - Single Responsibility: Coordinates action workflow
 * - Open/Closed: Extensible through dependency injection
 * - Liskov Substitution: Uses interfaces and abstractions
 * - Interface Segregation: Depends on specific services only
 * - Dependency Inversion: Depends on abstractions (injected services)
 *
 * @package Limb_Chatbot\Includes\Services\Actions
 * @since 1.0.0
 */
class Action_Service {

	/**
	 * Chat instance
	 *
	 * @var Chat
	 */
	private Chat $chat;

	/**
	 * Action plan builder
	 *
	 * @var Action_Plan_Builder
	 */
	private Action_Plan_Builder $plan_builder;

	/**
	 * Parameter request message builder
	 *
	 * @var Parameter_Request_Message_Builder
	 */
	private Parameter_Request_Message_Builder $parameter_message_builder;

	private ?Chatbot_Utility $chatbot_utility;

	/**
	 * Constructor with dependency injection
	 *
	 * @param  Chat  $chat  Chat instance
	 * @param  Chatbot_Utility|null  $chatbot_utility  Optional chatbot utility for AI features
	 * @param  Action_Plan_Builder|null  $plan_builder  Plan builder (auto-created if null)
	 * @param  Parameter_Request_Message_Builder|null  $parameter_message_builder  Message builder (auto-created if null)
	 *
	 * @since 1.0.0
	 */
	public function __construct(
		Chat $chat,
		?Chatbot_Utility $chatbot_utility = null,
		?Action_Plan_Builder $plan_builder = null,
		?Parameter_Request_Message_Builder $parameter_message_builder = null
	) {
		$this->chat                      = $chat;
		$this->chatbot_utility           = $chatbot_utility ?? null;
		$this->plan_builder              = $plan_builder ?? new Action_Plan_Builder();
		$this->parameter_message_builder = $parameter_message_builder ?? new Parameter_Request_Message_Builder( $chatbot_utility );
	}

	/**
	 * Dispatch an action and start parameter collection
	 *
	 * @param  Action  $action  The action to dispatch
	 *
	 * @return Message|null The first parameter request message or null
	 * @throws \Exception
	 * @since 1.0.0
	 */
	public function dispatch( Action $action ): ?Message {
		$plan = $this->plan_builder->build( $action, $this->chat );
		$this->save_action_plan( $plan );

		// Return parameter collection message if needed
		$next_step = $plan->next_incomplete_step();
		if ( $next_step && $next_step->get_type() === Action_Plan_Step::TYPE_DATA_COLLECTION ) {
			return $this->parameter_message_builder->build( $plan, $next_step );
		}

		return ( new Action_Executor( $this->chat, $this->chatbot_utility ) )->execute( $plan );
	}

	/**
	 * Save action plan to chat meta
	 *
	 * @param  Action_Plan  $plan  The action plan to save
	 *
	 * @return void
	 * @since 1.0.0
	 */
	private function save_action_plan( Action_Plan $plan ): void {
		$this->chat->update_meta( Chat::KEY_CURRENT_ACTION, $plan );
	}

	/**
	 * Check for ongoing action and handle parameter collection with validation
	 *
	 * @param  Message  $user_message  User's message
	 *
	 * @return Message|null Message if action is ongoing, null otherwise
	 * @throws Exception
	 * @since 1.0.0
	 */
	public function check_ongoing_action( Message $user_message ): ?Message {
		$action_plan = $this->chat->current_action_plan();
		if ( ! $action_plan instanceof Action_Plan ) {
			return null;
		}
		if ( $message = $this->check_for_cancellation( $action_plan, $user_message ) ) {
			return $message;
		}
		
		// Re-fetch action plan as it may have been cleared by check_for_cancellation
		// (when cancellation + text is present, we clear the action to let LLM handle it)
		$action_plan = $this->chat->current_action_plan();
		if ( ! $action_plan instanceof Action_Plan ) {
			return null;
		}
		
		$current_step = $action_plan->next_incomplete_step();
		if ( ! $current_step ) {
			// All parameters collected, execute action
			return ( new Action_Executor( $this->chat, $this->chatbot_utility ) )->execute( $action_plan );
		}
		if ( $current_step->is_data_collect_step() ) {
			$data_collect_processor = ( new Data_Collecting_Processor_Factory() )->make( $current_step, $this->chat, $this->chatbot_utility );

			return $data_collect_processor->process( $action_plan, $current_step, $user_message );
		}

		return null;
	}

	/**
	 * Checks if the user message contains a cancellation request for the given action plan.
	 *
	 * Handles three scenarios:
	 * 1. Only action cancellation: Returns "Cancelled successfully" message directly
	 * 2. Action cancellation + text content: Clears action and returns null to allow LLM to process naturally
	 * 3. No cancellation: Returns null to continue normal flow
	 *
	 * @param  Action_Plan  $action_plan  The action plan currently being executed.
	 * @param  Message  $user_message  The user's incoming message containing potential cancellation data.
	 *
	 * @return Message|null  A confirmation message if only cancellation (no text), null otherwise.
	 */
	private function check_for_cancellation( Action_Plan $action_plan, Message $user_message ) {
		$cancel = false;
		$has_text = false;

		foreach ( $user_message->get_content() as $part ) {
			if ( isset( $part['type'] ) && $part['type'] === Message::CONTENT_TYPE_ACTION_CANCELLATION ) {
				$cancel = true;
			}
			if ( isset( $part['type'] ) && $part['type'] === Message::CONTENT_TYPE_TEXT ) {
				$has_text = true;
			}
		}

		// If no cancellation found, return null
		if ( ! $cancel ) {
			return null;
		}

		// Clear the current action
		$this->chat->update_meta( Chat::KEY_CURRENT_ACTION, null );

		// If there's also text content, return null to let LLM handle it naturally
		if ( $has_text ) {
			return null;
		}

		// Only cancellation, no text - return "Cancelled successfully" message
		$text = __( 'Cancelled successfully', 'limb-chatbot' );
		if ( $this->chatbot_utility && $this->chatbot_utility->get_stream() ) {
			Stream_Event_Service::text( $text );
		}

		$content = [
			[
				'type' => Message::CONTENT_TYPE_TEXT,
				'text' => [ 'value' => $text ],
			]
		];

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