<?php
/**
 * Template Cloud.
 *
 * @package ThemeIsle
 */

namespace ThemeIsle\GutenbergBlocks\Plugins;

/**
 * Class Template Cloud
 */
class Template_Cloud {
	const SOURCES_SETTING_KEY = 'themeisle_template_cloud_sources';

	const API_ENDPOINT_SUFFIX = 'ti-template-cloud/v1/patterns';

	/**
	 * Initialize the module.
	 *
	 * @return void
	 */
	public function instance() {
		add_action( 'init', array( $this, 'register_cloud_resources' ) );
	}

	/**
	 * Register categories and patterns.
	 *
	 * @return void
	 */
	public function register_cloud_resources() {
		$this->register_pattern_categories();
		$this->register_patterns();
	}

	/**
	 * Register the pattern categories.
	 *
	 * @return void
	 */
	private function register_pattern_categories() {
		$sources = $this->get_pattern_sources();

		if ( empty( $sources ) ) {
			return;
		}

		foreach ( $sources as $source ) {
			$slug = $this->slug_from_name( $source['name'] );

			if ( ! \WP_Block_Pattern_Categories_Registry::get_instance()->is_registered( $slug ) ) {
				register_block_pattern_category( $slug, array( 'label' => $source['name'] ) );
			}
		}
	}

	/**
	 * Register the patterns.
	 *
	 * @return void
	 */
	private function register_patterns() {
		$cloud_data = $this->get_cloud_data();

		if ( empty( $cloud_data ) ) {
			return;
		}

		$all_patterns = array();

		foreach ( $cloud_data as $source_data ) {
			$patterns_for_source = array();

			if ( ! is_array( $source_data ) || ! isset( $source_data['patterns'], $source_data['category'] ) ) {
				continue;
			}

			$patterns = $source_data['patterns'];
			$category = $source_data['category'];

			// Make sure we don't have duplicates.
			foreach ( $patterns as $pattern ) {
				if ( isset( $patterns_for_source[ $pattern['id'] ] ) ) {
					continue;
				}

				$pattern['categories'] = array( 'otter-blocks', 'otter-blocks-tc', $category );

				$patterns_for_source[ $pattern['id'] ] = $pattern;
			}

			$all_patterns = array_merge( $all_patterns, $patterns_for_source );
		}

		foreach ( $all_patterns as $pattern ) {
			if ( ! isset( $pattern['slug'] ) ) {
				continue;
			}

			register_block_pattern(
				'otter-blocks/' . $pattern['slug'],
				$pattern
			);
		}
	}

	/**
	 * Get all the cloud data for each source.
	 *
	 * @return array|array[]
	 */
	private function get_cloud_data() {
		$sources = self::get_pattern_sources();

		if ( empty( $sources ) ) {
			return array();
		}

		return array_map(
			function ( $source ) {
				return array(
					'category' => $this->slug_from_name( $source['name'] ),
					'patterns' => $this->get_patterns_for_key( $source['key'] ),
				);
			},
			$sources
		);
	}

	/**
	 * Get patterns for a certain access key.
	 *
	 * @param string $access_key The access key.
	 *
	 * @return array
	 */
	private function get_patterns_for_key( $access_key ) {
		$patterns = get_transient( self::get_cache_key( $access_key ) );

		if ( ! $patterns ) {
			self::sync_sources();
		}

		$patterns = get_transient( self::get_cache_key( $access_key ) );

		if ( ! $patterns ) {
			return array();
		}

		$patterns = json_decode( $patterns, true );

		return is_array( $patterns ) ? $patterns : array();
	}

	/**
	 * Get the slug from a name.
	 *
	 * @param string $name The name to slugify.
	 *
	 * @return string
	 */
	private function slug_from_name( $name ) {
		return 'ti-tc-' . sanitize_key( str_replace( ' ', '-', $name ) );
	}

	/**
	 * Get the pattern sources.
	 *
	 * @return array
	 */
	public static function get_pattern_sources() {
		return get_option( self::SOURCES_SETTING_KEY, array() );
	}

	/**
	 * Check if sources exist, even if user has used the feature in past.
	 *
	 * @return bool
	 */
	public static function has_used_pattern_sources() {
		$sources = get_option( self::SOURCES_SETTING_KEY, null );
		return is_array( $sources );
	}

	/**
	 * Save the pattern sources.
	 *
	 * @param array $new_sources The new sources.
	 *
	 * @return bool
	 */
	public static function save_pattern_sources( $new_sources ) {
		return update_option( self::SOURCES_SETTING_KEY, array_values( $new_sources ) );
	}

	/**
	 * Get the cache key for the patterns.
	 *
	 * @param string $key The key to use for connection.
	 *
	 * @return string
	 */
	public static function get_cache_key( $key ) {
		return 'ti_tc_patterns_' . $key;
	}

	/**
	 * Save patterns for a certain access key.
	 *
	 * @param string $access_key The access key.
	 * @param array  $patterns The patterns to save.
	 *
	 * @return bool
	 */
	public static function save_patterns_for_key( $access_key, $patterns ) {
		return set_transient( self::get_cache_key( $access_key ), wp_json_encode( $patterns ), DAY_IN_SECONDS );
	}

	/**
	 * Delete patterns for a certain access key.
	 *
	 * @param string $access_key The access key.
	 *
	 * @return bool
	 */
	public static function delete_patterns_by_key( $access_key ) {
		return delete_transient( self::get_cache_key( $access_key ) );
	}

	/**
	 * Sync sources.
	 */
	public static function sync_sources() {
		$sources = self::get_pattern_sources();

		if ( empty( $sources ) ) {
			return array( 'success' => true );
		}

		$errors = array();

		foreach ( $sources as $source ) {
			$url  = trailingslashit( $source['url'] ) . self::API_ENDPOINT_SUFFIX;
			$args = array(
				'sslverify' => false,
				'headers'   => array(
					'X-API-KEY' => $source['key'],
				),
			);

			if ( function_exists( 'vip_safe_wp_remote_get' ) ) {
				$response = vip_safe_wp_remote_get( $url, '', 3, 1, 20, $args );
			} else {
				$response = wp_remote_get( $url, $args ); // phpcs:ignore WordPressVIPMinimum.Functions.RestrictedFunctions.wp_remote_get_wp_remote_get
			}

			if ( is_wp_error( $response ) ) {
				$errors[] = sprintf(
					/* translators: 1: source name, 2: error message */
					__( 'Error with %1$s: %2$s', 'otter-blocks' ),
					$source['name'],
					$response->get_error_message()
				);

				continue;
			}

			$code = wp_remote_retrieve_response_code( $response );

			if ( 200 !== $code ) {
				$errors[] = sprintf(
					/* translators: 1: source name, 2: response code */
					__( 'Error with %1$s: Invalid response code %2$s', 'otter-blocks' ),
					$source['name'],
					$code
				);

				continue;
			}

			$body = wp_remote_retrieve_body( $response );

			if ( empty( $body ) ) {
				$errors[] = sprintf(
					/* translators: %s: source name */
					__( 'Error with %s: Empty response', 'otter-blocks' ),
					$source['name']
				);

				continue;
			}

			$decoded_body = json_decode( $body, true );

			if ( ! is_array( $decoded_body ) ) {
				$errors[] = sprintf(
					/* translators: %s: source name */
					__( 'Error with %s: Invalid response', 'otter-blocks' ),
					$source['name']
				);

				continue;
			}

			if ( ! isset( $decoded_body['success'], $decoded_body['data'], $decoded_body['key_name'] ) || ! $decoded_body['success'] ) {
				$errors[] = sprintf(
				/* translators: %s: source name */
					__( 'Error with %s: No patterns found', 'otter-blocks' ),
					$source['name']
				);

				continue;
			}

			// Update key name if that has changed.
			if ( $decoded_body['key_name'] !== $source['name'] ) {
				self::update_source_name( $source['key'], $decoded_body['key_name'] );
			}

			self::save_patterns_for_key( $source['key'], $decoded_body['data'] );
		}

		return array(
			'success' => true,
			'errors'  => $errors,
		);
	}

	/**
	 * Update Source Name on sync.
	 *
	 * @param string $key The key to use for connection.
	 * @param string $new_name The new name to use.
	 *
	 * @return void
	 */
	public static function update_source_name( $key, $new_name ) {
		$sources = self::get_pattern_sources();

		foreach ( $sources as $idx => $source ) {
			if ( $source['key'] === $key ) {
				$sources[ $idx ]['name'] = $new_name;
			}
		}

		self::save_pattern_sources( $sources );
	}
}
