<?php

namespace Limb_Chatbot\Includes\Services\Actions;

use Exception;
use Limb_Chatbot\Includes\Data_Objects\Action_Callback;
use Limb_Chatbot\Includes\Data_Objects\Action_Plan;
use Limb_Chatbot\Includes\Data_Objects\Action_Submission;
use Limb_Chatbot\Includes\Factories\Action_Callback_Executor_Factory;
use Limb_Chatbot\Includes\Interfaces\Action_Callback_Executor_Interface;
use Limb_Chatbot\Includes\Interfaces\Data_Accessible_Interface;
use Limb_Chatbot\Includes\Services\Actions\Executors\Abstract_Callback_Executor;
use Limb_Chatbot\Includes\Services\Actions\Executors\Callback_Executor_Response;
use Limb_Chatbot\Includes\Services\Collection;

/**
 * Class Action_Callback_Chain_Executor
 *
 * Executor that chains callbacks and stores results in submission.
 * Handles data extraction for Data_Accessible executors.
 *
 * @package Limb_Chatbot\Includes\Services\Actions
 * @since 1.0.0
 */
class Action_Callback_Chain_Executor {

	private Action_Submission $action_submission;

	public function __construct( Action_Submission $action_submission ) {
		$this->action_submission = $action_submission;
	}

	/**
	 * Execute all callbacks for an action submission
	 *
	 * @param  Collection  $callbacks  Callbacks collection
	 *
	 * @return Action_Submission
	 * @throws Exception
	 */
	public function execute_chain( Collection $callbacks, Action_Plan $action_plan): Action_Submission {
		$results        = [];
		$extracted_vars = $this->action_submission->get_action_data();

		foreach ( $callbacks as $callback ) {
			if ( ! $callback instanceof Action_Callback || ! $callback->is_active() ) {
				continue;
			}
			$context  = new Action_Callback_Execution_Context( $this->action_submission, $extracted_vars, $action_plan->get_parameters() );
			$executor = ( new Action_Callback_Executor_Factory() )->make( $callback->get_type() );
			try {
				$response = $executor->execute( $callback, $context );
				// Check if execution was successful
				if ( $response->is_failed() ) {
					throw new Exception( $response->get_error_message() ?? 'Callback execution failed' );
				}
				// Extract variables if executor is data accessible
				if ( $executor instanceof Data_Accessible_Interface ) {
					$extracted = $executor->extract_response_variables( $callback, $response );
					$response->set_extracted_data( $extracted );
					$extracted_vars = array_merge( $extracted_vars, $extracted );
					// Update context with new extracted variables for next callbacks
					$context->merge_extracted_variables( $extracted );
				}
				$this->add_success_response( $executor, $callback, $response );
			} catch ( Exception $e ) {
				// Handle callback failure
				if ( $this->add_error_response( $callback, $e ) ) {
					return $this->action_submission;
				}
			}
		}
		// All callbacks executed successfully
		$this->action_submission->mark_as_success( $results );

		return $this->action_submission;
	}

	private function add_success_response(
		Action_Callback_Executor_Interface $executor,
		Action_Callback $callback,
		Callback_Executor_Response $response
	) {
		// Store successful result with response data and duration
		$this->action_submission->callback_results[ $callback->get_name() ] = [
			'status'     => 'success',
			'name'       => $callback->get_name(),
			'data'       => $response->get_data(),
			'metadata'   => $response->get_metadata(),
			'timestamp'  => current_time( 'mysql', true ),
		];

		if ( $executor instanceof Data_Accessible_Interface ) {
			$this->action_submission->set_extracted_vars(
				array_merge( $this->action_submission->get_extracted_vars(),
					$executor->extract_response_variables( $callback, $response ) )
			);
		}
	}

	private function add_error_response( Action_Callback $callback, Exception $e ) {
		$this->action_submission->callback_results[ $callback->get_name() ] = [
			'status'     => 'failed',
			'name'       => $callback->get_name(),
			'message'    => $e->getMessage(),
			'code'       => $e->getCode(),
			'timestamp'  => current_time( 'mysql', true ),
		];

		if ( $callback->get_is_required() == 1 || $callback instanceof Data_Accessible_Interface ) {
			$this->action_submission->mark_as_failed( __( 'Something went wrong. Please try again', 'limb-chatbot' ) );
			$this->action_submission->set_extracted_vars( [ 'error_message' => $e->getMessage() ] );

			return true;
		}

		return false;
	}
}
