<?php

namespace Limb_Chatbot\Includes\Services\Actions\Executors;

/**
 * Class Callback_Executor_Response
 *
 * Represents the standardized response from a callback executor.
 * Provides a consistent interface for all executor types.
 *
 * @package Limb_Chatbot\Includes\Services\Actions\Executors
 * @since 1.0.0
 */
class Callback_Executor_Response {

	/**
	 * @var bool Whether the execution was successful
	 */
	private bool $success = true;

	/**
	 * @var string|null Error message if execution failed
	 */
	private ?string $error_message = null;

	/**
	 * @var array Response metadata (status code, headers, etc.)
	 */
	private array $metadata = [];

	/**
	 * @var mixed Raw response data
	 */
	private $raw_data = null;

	/**
	 * @var array Extracted variables from response (if data accessible)
	 */
	private array $extracted_data = [];

	/**
	 * Constructor
	 *
	 * @param bool $success Whether execution was successful
	 * @param mixed $raw_data Raw response data
	 * @param array $metadata Response metadata
	 */
	public function __construct( bool $success = true, $raw_data = null, array $metadata = [] ) {
		$this->success  = $success;
		$this->raw_data = $raw_data;
		$this->metadata = $metadata;
	}

	/**
	 * Create a successful response
	 *
	 * @param mixed $data Response data
	 * @param array $metadata Response metadata
	 * @return self
	 */
	public static function success( $data = null, array $metadata = [] ): self {
		return new self( true, $data, $metadata );
	}

	/**
	 * Create a failed response
	 *
	 * @param string $error_message Error message
	 * @param array $metadata Response metadata
	 * @return self
	 */
	public static function failed( string $error_message, array $metadata = [] ): self {
		$response = new self( false, null, $metadata );
		$response->set_error_message( $error_message );
		return $response;
	}

	/**
	 * Check if response represents success
	 *
	 * @return bool
	 */
	public function is_success(): bool {
		return $this->success;
	}

	/**
	 * Check if response represents failure
	 *
	 * @return bool
	 */
	public function is_failed(): bool {
		return ! $this->success;
	}

	/**
	 * Get error message
	 *
	 * @return string|null
	 */
	public function get_error_message(): ?string {
		return $this->error_message;
	}

	/**
	 * Set error message
	 *
	 * @param string $message
	 * @return void
	 */
	public function set_error_message( string $message ): void {
		$this->error_message = $message;
	}

	/**
	 * Get raw response data
	 *
	 * @return mixed
	 */
	public function get_data() {
		return $this->raw_data;
	}

	/**
	 * Set raw response data
	 *
	 * @param mixed $data
	 * @return void
	 */
	public function set_data( $data ): void {
		$this->raw_data = $data;
	}

	/**
	 * Get response metadata
	 *
	 * @return array
	 */
	public function get_metadata(): array {
		return $this->metadata;
	}

	/**
	 * Get specific metadata value
	 *
	 * @param string $key Metadata key
	 * @param mixed $default Default value if key doesn't exist
	 * @return mixed
	 */
	public function get_metadata_value( string $key, $default = null ) {
		return $this->metadata[ $key ] ?? $default;
	}

	/**
	 * Set metadata value
	 *
	 * @param string $key Metadata key
	 * @param mixed $value Value to set
	 * @return void
	 */
	public function set_metadata_value( string $key, $value ): void {
		$this->metadata[ $key ] = $value;
	}

	/**
	 * Get extracted data (for data-accessible executors)
	 *
	 * @return array
	 */
	public function get_extracted_data(): array {
		return $this->extracted_data;
	}

	/**
	 * Set extracted data
	 *
	 * @param array $data Extracted data
	 * @return void
	 */
	public function set_extracted_data( array $data ): void {
		$this->extracted_data = $data;
	}

	/**
	 * Merge additional extracted data
	 *
	 * @param array $data Data to merge
	 * @return void
	 */
	public function merge_extracted_data( array $data ): void {
		$this->extracted_data = array_merge( $this->extracted_data, $data );
	}

	/**
	 * Get extracted data value
	 *
	 * @param string $key Key in dot notation (e.g., 'user.email')
	 * @param mixed $default Default value if not found
	 * @return mixed
	 */
	public function get_extracted_value( string $key, $default = null ) {
		return $this->array_get( $this->extracted_data, $key, $default );
	}

	/**
	 * Set extracted data value
	 *
	 * @param string $key Key in dot notation
	 * @param mixed $value Value to set
	 * @return void
	 */
	public function set_extracted_value( string $key, $value ): void {
		$this->array_set( $this->extracted_data, $key, $value );
	}

	/**
	 * Convert response to array for storage/transmission
	 *
	 * @return array
	 */
	public function to_array(): array {
		$result = [
			'success'  => $this->success,
			'data'     => $this->raw_data,
			'metadata' => $this->metadata,
		];

		if ( ! empty( $this->extracted_data ) ) {
			$result['extracted'] = $this->extracted_data;
		}

		if ( $this->error_message ) {
			$result['error'] = $this->error_message;
		}

		return $result;
	}

	/**
	 * Get array value using dot notation
	 *
	 * @param array $array Array to search
	 * @param string $key Key in dot notation
	 * @param mixed $default Default value
	 * @return mixed
	 */
	private function array_get( array $array, string $key, $default = null ) {
		if ( isset( $array[ $key ] ) ) {
			return $array[ $key ];
		}

		foreach ( explode( '.', $key ) as $segment ) {
			if ( ! is_array( $array ) || ! array_key_exists( $segment, $array ) ) {
				return $default;
			}
			$array = $array[ $segment ];
		}

		return $array;
	}

	/**
	 * Set array value using dot notation
	 *
	 * @param array $array Array to set value in
	 * @param string $key Key in dot notation
	 * @param mixed $value Value to set
	 * @return void
	 */
	private function array_set( array &$array, string $key, $value ): void {
		$keys = explode( '.', $key );
		$last_key = array_pop( $keys );

		foreach ( $keys as $segment ) {
			if ( ! isset( $array[ $segment ] ) || ! is_array( $array[ $segment ] ) ) {
				$array[ $segment ] = [];
			}
			$array = &$array[ $segment ];
		}

		$array[ $last_key ] = $value;
	}
}
