<?php
/**
 * Admin functions to manage Headless Features.
 */
namespace Greyd\Headless;

use Greyd\Helper as Helper;
use Greyd\Settings as Settings;

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

new Api_Helper( $config );
class Api_Helper {

	public static function get_api( $slug ) {
		$api      = null;
		$settings = Admin::get_settings( 'api' );
		if ( isset( $settings['apis'] ) && is_array( $settings['apis'] ) && count( $settings['apis'] ) > 0 ) {
			// search settings
			$slugs = explode( '/', $slug );
			foreach ( $settings['apis'] as $details ) {
				// debug($details);
				if ( $details['slug'] == $slugs[0] ) {
					$api = array(
						'slug'     => $slug,
						'title'    => $details['title'],
						'base_url' => $details['base_url'],
						'url_path' => $details['url_path'],
						'url_atts' => is_array( $details['url_atts'] ) ? $details['url_atts'] : array(),
						'headers'  => is_array( $details['headers'] ) ? $details['headers'] : array(),
						'wp_cache' => isset( $details['wp_cache'] ) ? $details['wp_cache'] : false,
						'method'   => isset( $details['method'] ) ? $details['method'] : 'GET',
					);
					if ( count( $slugs ) > 1 ) {
						$route_set = false;
						if ( isset( $details['routes'] ) && $details['slug'] !== $slugs[1] ) {
							foreach ( $details['routes'] as $route ) {
								if ( $route['slug'] == $slugs[1] ) {
									$api['url_path'] = $route['url_path'];
									$api['url_atts'] = array_merge( $api['url_atts'], ( is_array( $route['url_atts'] ) ? $route['url_atts'] : array() ) );
									$api['headers']  = array_merge( $api['headers'], ( is_array( $route['headers'] ) ? $route['headers'] : array() ) );
									$api['wp_cache'] = isset( $route['wp_cache'] ) ? $route['wp_cache'] : $api['wp_cache'];
									$api['method']   = isset( $route['method'] ) ? $route['method'] : $api['method'];
									$route_set = true;
									break;
								}
							}
						}

						if ( isset( $details['blocks'] ) && ! empty( $details['blocks'] ) ) {
							foreach ( $details['blocks'] as $api_block ) {
								if ( $api_block['route'] == $slugs[1] ) {
									$api['block'] = $api_block;
									break;
								}
							}
						}

						if ( isset( $details['posttypes'] ) && ! empty( $details['posttypes'] ) ) {
							foreach ( $details['posttypes'] as $api_posttype ) {
								if ( $api_block['route'] == $slugs[1] ) {
									$api['posttype'] = $api_posttype;
									break;
								}
							}
						}

						if ( !$route_set ) $api = false;
					}
					break;
				}
			}
		}
		return $api;
	}

	public static function remote_get( $api, $public = true, $convert_xml = true ) {

		if ( !$api || empty( $api ) ) {
			return array(
				'status' => 'error',
				'body'   => 'invalid API',
				'full'   => array(),
			);
		}

		$api = wp_parse_args(
			$api,
			array(
				'slug'     => '',
				'url'      => '',
				'base_url' => '', // optional
				'url_path' => '', // optional
				'url_atts' => '', // optional
				'headers'  => array(),
				'wp_cache' => false,
				'method'   => 'GET',
				'actions'  => array()
			)
		);

		// get call url
		if ( empty( $api['url'] ) || ! is_string( $api['url'] ) ) {
			if ( ! empty( $api['base_url'] ) ) {
				$api['url'] = self::make_url( $api );
			}
		}
		if ( empty( $api['url'] ) || ! is_string( $api['url'] ) ) {
			return array(
				'status' => 'error',
				'body'   => 'invalid URL',
				'full'   => array(),
			);
		}

		// insert vars
		if ( isset( $api['vars'] ) ) {
			foreach ( $api['vars'] as $var => $value ) {
				// dynamic var from request param
				$clean_var = trim( $var, '{}' );
				if ( isset( $_REQUEST[ $clean_var ] ) ) {
					// debug($_REQUEST);
					$value = $_REQUEST[ $clean_var ];
				}
				$api['url'] = str_replace( $var, $value, $api['url'] );
			}
		}

		// headers
		if ( is_array( $api['headers'] ) ) {
			foreach ( $api['headers'] as $key => $value ) {
				if ( $key == 'Authorization' && strpos( $value, 'Basic ' ) === 0 ) {
					$api['headers'][ $key ] = 'Basic ' . base64_encode( explode( 'Basic ', $value, 2 )[1] );
				}
			}
		}

		// cookies (for non-public calls)
		$cookies = array();
		if ( $public === false || ( isset( $api['public'] ) && $api['public'] === false ) ) {
			$tmp = explode( '; ', $_SERVER['HTTP_COOKIE'] );
			foreach ( $tmp as $cookie ) {
				$val                = explode( '=', $cookie );
				$cookies[ $val[0] ] = $val[1];
			}
			// debug($cookies);
		}

		// body - for method: POST
		$postdata = array();
		if ( $api['method'] == 'POST' ) {
			$api['url'] = $api['base_url'];
			// url path
			if ( !empty( $api['url_path'] ) ) {
				$api['url'] .= '/'.trim( $api['url_path'], '/' );
			}
			// url attributes
			if ( isset( $api['url_atts'] ) && !empty( $api['url_atts'] ) && is_array( $api['url_atts'] ) ) {
				$url = add_query_arg( $api['url_atts'], $url );
				foreach ( $api['url_atts'] as $key => $value ) {
					$postdata[$key] = $value;
				}
			}
			// debug($postdata);
		}

		// cache values
		$wp_cache = false;
		if ( $api['wp_cache'] && $api['wp_cache'] && ! empty( $api['wp_cache'] ) ) {

			$wp_cache = array(
				'enabled' => true,
				'expire'  => 60 * 60 * 24, // 1 day
			);

			if ( is_numeric( $api['wp_cache'] ) ) {
				$wp_cache['expire'] = $api['wp_cache'];
			} elseif ( is_array( $api['wp_cache'] ) ) {
				$wp_cache = wp_parse_args( $api['wp_cache'], $wp_cache );
			}

			if ( $wp_cache['enabled'] ) {
				$_cached_value = get_transient( $api['url'] );
				if ( $_cached_value !== false ) {
					// debug( 'cached value found for ' . $api['url'] );
					return $_cached_value;
				}
				// debug( 'no cached value found for ' . $api['url'] );
			}
		}

		// echo "\r\n\r\n";
		// debug( $api );
		// echo "\r\n\r\n";

		// prepare
		$call = apply_filters( 'greyd_api_before_' . $api['slug'], array(
			'url' => $api['url'],
			'params' => array(
				'method'  => $api['method'],
				'timeout' => 30,
				'cookies' => $cookies,
				'headers' => $api['headers'],
				'body'    => $postdata
			)
		), $api );

		if ( is_wp_error( $call ) ) {
			return array(
				'status' => 'error',
				'body'   => implode( "\r\n", $call->get_error_messages() ),
				'full'   => $call,
			);
		}

		// make call
		$response = wp_remote_get(
			$call['url'],
			$call['params']
		);

		// echo "\r\n\r\n";
		// debug($response);
		// echo "\r\n\r\n";


		/**
		 * Handle errors.
		 */
		if ( is_wp_error( $response ) ) {
			return array(
				'status' => 'error',
				'body'   => implode( "\r\n", $response->get_error_messages() ),
				'full'   => $response,
			);
		}
		if ( !isset( $response['response'] ) || !isset( $response['response']['code'] ) ) {
			return array(
				'status' => 'error',
				'body'   => 'unidentified error',
				'full'   => $response,
			);
		}
		if ( $response['response']['code'] != 200 ) {
			$messages = array( $response['response']['message'] );
			if ( $body = json_decode($response['body'], true) ) {
				debug($body);
				if ( isset($body['error']) && isset($body['error_description']) ) {
					array_push( $messages, $body['error'].': '.$body['error_description'] );
				}
				else if ( is_array($body) ) foreach ( $body as $err ) {
					if ( isset($err['errorCode']) && isset($err['message']) ) {
						array_push( $messages, $err['errorCode'].': '.$err['message'] );
						if ( isset($err['extendedErrorDetails']) && is_array($err['extendedErrorDetails']) ) {
							foreach ( $err['extendedErrorDetails'] as $exerr ) {
								if ( isset($exerr['extendedErrorCode']) && isset($exerr['message']) ) {
									array_push( $messages, '> '.$exerr['extendedErrorCode'].': '.$exerr['message'] );
								}
							}
						}
					}
				}
			}
			return array(
				'status' => 'error',
				'body'   => implode( "\r\n\r\n", $messages ),
				'full'   => $response,
			);
		}

		// convert xml response
		$body = $response['body'];
		$type = $response['headers']['content-type'];
		if ( $convert_xml && strpos( $response['headers']['content-type'], '/xml' ) > 0 ) {
			try {
				$xml  = simplexml_load_string( $response['body'], 'SimpleXMLElement', LIBXML_NOCDATA );
				$body = json_encode( $xml );
				$type = 'application/json';
			}
			catch ( Exception $e ) {
			}
		}

		// // here we wanna do additional actions on the response, such as paginate or repeat
		// if ( ! empty( $api['actions'] ) ) {
		// 	foreach ( $api['actions'] as $action ) {

		// 		switch ( $action['repeat'] ) {
		// 			case 'call':
						
		// 				self::remote_get( $api, $public, $convert_xml );

		// 				break;
		// 		}
		// 	}
		// }

		// $url = add_query_arg( $api['url_atts'], $url );


		// set cache on success
		if ( $wp_cache && $wp_cache['enabled'] ) {
			set_transient(
				$api['url'],
				array(
					'status' => 'success',
					'body'   => $body,
					'type'   => $type,
					'full'   => $response,
				),
				$wp_cache['expire']
			);
			// debug( 'set cached value set for ' . $api['url'] );
		}

		$result = array(
			'status' => 'success',
			'body'   => $body,
			'type'   => $type,
			'full'   => $response,
		);
		
		$result = apply_filters( 'greyd_api_after_' . $api['slug'], $result, $api );

		return $result;
	}

	/**
	 * Make a url from an api config.
	 * 
	 * @param array $api
	 * 
	 * @return string|false
	 */
	public static function make_url( $api ) {

		// base url
		if ( empty( $api['base_url'] ) ) {
			return false;
		}
		$url = $api['base_url'];
		// url path
		if ( ! empty( $api['url_path'] ) ) {
			$url .= '/' . trim( $api['url_path'], '/' );
		}
		// url attributes
		if ( isset( $api['url_atts'] ) && ! empty( $api['url_atts'] ) && is_array( $api['url_atts'] ) ) {
			$url = add_query_arg( $api['url_atts'], $url );
		}

		return $url;
	}

	/**
	 * Convert the response of an API call.
	 * 
	 * @param array  $response The response to convert.
	 * @param array  $api      The api config.
	 * @param string $for      'block' or 'posttype'
	 * 
	 * @return array
	 */
	public static function convert_response( $response, $api, $for = 'block' ) {

		if (
			$response['status'] !== 'success' ||
			strpos( $response['type'], 'application/json' ) !== 0 ||
			! isset( $api )
		) {
			return $response;
		}

		// get api config
		$api_config = array();
		if ( ! empty( $for ) && isset( $api[ $for ] ) && is_array( $api[ $for ] ) ) {
			$api_config = wp_parse_args(
				$api[ $for ],
				array(
					'data_prop' => '',
					'data_item' => array(),
				)
			);
		}

		$result   = array();
		$json_src = json_decode( $response['body'], true );
		if ( $api_config['data_prop'] != '' ) {
			$props = explode( '.', $api_config['data_prop'] );
			foreach ( $props as $prop ) {
				$json_src = $json_src[ $prop ];
			}
		}
		if ( count( array_filter( array_keys( $json_src ), 'is_string' ) ) > 0 ) {
			$json_src = array( $json_src );
		}
		// debug($json_src);
		foreach ( $json_src as $index => $json_item ) {
			$data = array();
			if ( ! empty( $api_config['data_item'] ) ) {
				foreach ( $api_config['data_item'] as $item_key => $item_setup ) {
					$data[ $item_key ] = self::get_data_item_value( $item_key, $item_setup, $json_item, $api );
				}
			} else {
				$data = $json_item;
			}

			$result[] = $data;
		}
		// debug($result);
		$response['body'] = json_encode( $result );

		return $response;
	}

	/**
	 * Get the value of a data item.
	 * 
	 * @param string $item_key          Key of the value to set. (e.g. 'post_title' or 'meta_input')
	 * @param string|array $item_setup  Setup of the value to set.
	 *     @property string value       static value
	 *     @property string prop        dynamic value
	 *     @property string default     default value
	 *     @property array actions      actions to perform on the value
	 * @param mixed $json_item          The enitre json item.
	 * 
	 * @return string
	 */
	public static function get_data_item_value( $item_key, $item_setup, $json_item, $api ) {

		$value = '';

		// shorthand item setup (prop only)
		if ( is_string( $item_setup ) ) {
			$item_setup = array(
				'prop' => $item_setup,
			);
		}
		// convert object to array
		elseif ( is_object( $item_setup ) ) {
			$item_setup = (array) $item_setup;
		} elseif ( ! is_array( $item_setup ) ) {
			return $value;
		}

		// if setup has 'value' the value is set statically
		if ( isset( $item_setup['value'] ) ) {
			if ( is_string( $item_setup['value'] ) ) {
				return $item_setup['value'];
			}
			elseif ( is_array( $item_setup['value'] ) ) {

				$value = array();
				foreach ( $item_setup['value'] as $key => $val ) {
					$value[ $key ] = self::get_data_item_value( $key, $val, $json_item, $api );
				}
				return $value;
			}
		}

		// if prop is set we try to set the value dynamically
		if ( isset( $item_setup['prop'] ) ) {

			// if it is 'value' we return the whole json item
			if ( $item_setup['prop'] == 'value' && is_string( $json_item ) ) {
				return $json_item;
			}

			$value = self::get_prop_value( $item_setup['prop'], $json_item, isset( $item_setup['default'] ) ?? '' );
		}

		// if it has actions we perform them
		if ( isset( $item_setup['actions'] ) ) {
			foreach ( $item_setup['actions'] as $action ) {
				$value = self::do_data_item_action( $value, $action, $json_item, $api );
			}
		}

		return apply_filters( "greyd_api_data_item_{$item_key}", $value, $item_setup, $json_item, $api );
	}

	/**
	 * Perform an action on a data item value.
	 * 
	 * @param string $value   The value to perform the action on.
	 * @param array  $action  The action to perform.
	 *     @property string action  The action to perform.
	 *     @property string value   The value to use for the action.
	 * @param mixed  $json_item  The enitre json item.
	 * @param array  $api     The api config.
	 * 
	 * @return string
	 */
	public static function do_data_item_action( $value, $action, $json_item, $api ) {

		switch ( $action['action'] ) {
			case 'prepend':
				if ( is_string( $action['value'] ) ) {
					$value = $action['value'] . $value;
				} elseif ( is_array( $action['value'] ) && isset( $action['value']['prop'] ) ) {
					$value = self::get_prop_value( $action['value']['prop'], $json_item, $action['value']['default'] ?? '' ) . $value;
				}
				break;

			case 'append':
				if ( is_string( $action['value'] ) ) {
					$value = $value . $action['value'];
				} elseif ( is_array( $action['value'] ) && isset( $action['value']['prop'] ) ) {
					$value = $value . self::get_prop_value( $action['value']['prop'], $json_item, $action['value']['default'] ?? '' );
				}
				break;

			case 'url_encode':
				$value = rawurlencode( $value );
				break;

			case 'json_encode':
				$value = json_encode( $value );
				break;

			case 'implode':
				$value = implode( $action['value'] ?? '', $value );
				break;

			case 'explode':
				$value = explode( $action['value'] ?? '', $value );
				break;

			case 'index':
				if ( $action['value'] == 'prepend' ) {
					$value = ( $index + 1 ) . $value;
				} elseif ( $action['value'] == 'append' ) {
					$value = $value . ( $index + 1 );
				}
				break;

			case 'count':
				if ( $action['value'] == 'prepend' ) {
					$value = count( $json_src ) . $value;
				} elseif ( $action['value'] == 'append' ) {
					$value = $value . count( $json_src );
				}
				break;

			case 'filter':
				$value = apply_filters( 'greyd_block_api_item_value', $value, $action, $json_item, $api );
				break;

			case 'call':
				$api_route = explode( '/', $api['slug'] )[0] . '/' . $action['value']['route'];
				$route     = self::get_api( $api_route );
				if ( isset( $action['value']['vars'] ) ) {
					$route['vars'] = array();
					foreach ( $action['value']['vars'] as $var => $val ) {
						$route['vars'][ $var ] = self::get_prop_value( $val, $json_item, $value );
					}
				}

				// debug( "try to get value from:" );
				// debug( $route );
				// debug( $json_item );

				// debug($route, true);
				$route_response = self::remote_get( $route );
				// debug($route_response, true);
				if (
					$route_response['status'] === 'success' &&
					strpos( $route_response['type'], 'application/json' ) === 0
				) {
					$route_result = json_decode( $route_response['body'], true );
					if ( $action['value']['data_prop'] != '' ) {
						$props = explode( '.', $action['value']['data_prop'] );
						foreach ( $props as $prop ) {
							$route_result = isset( $route_result[ $prop ] ) ? $route_result[ $prop ] : null;
						}
					}
					if ( $route_result ) {
						$value = $route_result;

						if ( isset( $action['value']['actions'] ) ) {

							$type = $action['value']['type'] ?? 'string';

							if ( $type === 'array' ) {
								foreach ( $value as $i => $val ) {
									foreach ( $action['value']['actions'] as $_action ) {
										$value[ $i ] = self::do_data_item_action( $val, $_action, $json_item, $api );
									}
								}
							} else {
								foreach ( $action['value']['actions'] as $_action ) {
									$value = self::do_data_item_action( $value, $_action, $json_item, $api );
								}
							}
						}
					}
				}
				break;
		}

		return apply_filters( "greyd_api_data_item_action_{$action['action']}", $value, $action, $json_item, $api );
	}

	/**
	 * Get a value from a json item.
	 * 
	 * @param string $property   The property to get the value from.
	 *                           (e.g. 'post_title' or 'meta._yoast_wpseo_title')
	 * @param array  $json_item  The enitre json item.
	 * @param string $default    The default value to return if the property do not exist.
	 * 
	 * @return string
	 */
	public static function get_prop_value( $property, $json_item, $default = '' ) {
		$props    = explode( '.', $property );
		$json_tmp = $json_item;
		foreach ( $props as $i => $prop ) {
			if ( ! isset( $json_tmp[ $prop ] ) ) {
				$json_tmp = $default;
				break;
			}
			$json_tmp = $json_tmp[ $prop ];
		}
		
		return apply_filters( "greyd_api_prop_value_{$property}", $json_tmp, $json_item, $default );
	}
}
