<?php
/**
 * Class Optimization
 *
 * @link      https://github.com/googleforcreators/web-stories-wp
 *
 * @copyright 2020 Google LLC
 * @license   https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
 */

/**
 * Copyright 2020 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

declare(strict_types = 1);

namespace Google\Web_Stories\AMP;

use Google\Web_Stories_Dependencies\AmpProject\AmpWP\RemoteRequest\CachedRemoteGetRequest;
use Google\Web_Stories_Dependencies\AmpProject\AmpWP\RemoteRequest\WpHttpRemoteGetRequest;
use Google\Web_Stories_Dependencies\AmpProject\Dom\Document;
use Google\Web_Stories_Dependencies\AmpProject\Optimizer\Configuration;
use Google\Web_Stories_Dependencies\AmpProject\Optimizer\Configuration\AmpStoryCssOptimizerConfiguration;
use Google\Web_Stories_Dependencies\AmpProject\Optimizer\DefaultConfiguration;
use Google\Web_Stories_Dependencies\AmpProject\Optimizer\Error;
use Google\Web_Stories_Dependencies\AmpProject\Optimizer\ErrorCollection;
use Google\Web_Stories_Dependencies\AmpProject\Optimizer\LocalFallback;
use Google\Web_Stories_Dependencies\AmpProject\Optimizer\TransformationEngine;
use Google\Web_Stories_Dependencies\AmpProject\Optimizer\Transformer\AmpRuntimeCss;
use Google\Web_Stories_Dependencies\AmpProject\Optimizer\Transformer\AmpStoryCssOptimizer;
use Google\Web_Stories_Dependencies\AmpProject\Optimizer\Transformer\MinifyHtml;
use Google\Web_Stories_Dependencies\AmpProject\Optimizer\Transformer\OptimizeAmpBind;
use Google\Web_Stories_Dependencies\AmpProject\Optimizer\Transformer\OptimizeHeroImages;
use Google\Web_Stories_Dependencies\AmpProject\Optimizer\Transformer\RewriteAmpUrls;
use Google\Web_Stories_Dependencies\AmpProject\Optimizer\Transformer\ServerSideRendering;
use Google\Web_Stories_Dependencies\AmpProject\Optimizer\Transformer\TransformedIdentifier;
use Google\Web_Stories_Dependencies\AmpProject\RemoteRequest\FallbackRemoteGetRequest;
use Google\Web_Stories_Dependencies\AmpProject\RemoteRequest\FilesystemRemoteGetRequest;

/**
 * Optimization class.
 *
 * @since 1.1.0
 */
class Optimization {
	/**
	 * Optimizes a document.
	 *
	 * @since 1.1.0
	 *
	 * @param Document $document Document instance.
	 */
	public function optimize_document( Document $document ): void {
		$errors = new ErrorCollection();
		$this->get_optimizer()->optimizeDom( $document, $errors );
		if ( \count( $errors ) > 0 ) {
			/**
			 * Error list.
			 *
			 * @var Error[] $errors_array Error list.
			 */
			$errors_array = iterator_to_array( $errors );

			$error_messages = array_filter(
				array_map(
					static function ( Error $error ) {
						// Hidden because amp-story is a render-delaying extension.
						if ( 'CannotRemoveBoilerplate' === $error->getCode() ) {
							return '';
						}

						return ' - ' . $error->getCode() . ': ' . $error->getMessage();
					},
					$errors_array
				)
			);

			if ( ! empty( $error_messages ) ) {
				$document->head->appendChild(
					$document->createComment( "\n" . __( 'AMP optimization could not be completed due to the following:', 'web-stories' ) . "\n" . implode( "\n", $error_messages ) . "\n" )
				);
			}
		}
	}

	/**
	 * Optimizer instance to use.
	 *
	 * @since 1.1.0
	 *
	 * @link https://github.com/ampproject/amp-wp/blob/8856284d90fc8558c30acc029becd352ae26e4e1/includes/class-amp-theme-support.php#L2235-L2255
	 * @see AMP_Theme_Support::get_optimizer
	 *
	 * @return TransformationEngine Optimizer transformation engine to use.
	 */
	private function get_optimizer(): TransformationEngine {
		$configuration = self::get_optimizer_configuration();

		$fallback_remote_request_pipeline = new FallbackRemoteGetRequest(
			new WpHttpRemoteGetRequest(),
			new FilesystemRemoteGetRequest( LocalFallback::getMappings() )
		);

		$cached_remote_request = new CachedRemoteGetRequest( $fallback_remote_request_pipeline, WEEK_IN_SECONDS );

		return new TransformationEngine(
			$configuration,
			$cached_remote_request
		);
	}

	/**
	 * Get the AmpProject\Optimizer configuration object to use.
	 *
	 * @since 1.1.0
	 *
	 * @link https://github.com/ampproject/amp-wp/blob/5405daa38e65f0ec16ffc920014d0110b03ee773/src/Optimizer/AmpWPConfiguration.php#L43-L78
	 * @see AmpWPConfiguration::apply_filters()
	 *
	 * @return Configuration Optimizer configuration to use.
	 */
	private static function get_optimizer_configuration(): Configuration {
		$transformers = Configuration::DEFAULT_TRANSFORMERS;

		$transformers[] = AmpStoryCssOptimizer::class;

		/**
		 * Filter whether the AMP Optimizer should use server-side rendering or not.
		 *
		 * @since 1.1.0
		 *
		 * @param bool $enable_ssr Whether the AMP Optimizer should use server-side rendering or not.
		 */
		$enable_ssr = apply_filters( 'web_stories_enable_ssr', true );

		// In debugging mode, we don't use server-side rendering, as it further obfuscates the HTML markup.
		if ( ! $enable_ssr ) {
			$transformers = array_diff(
				$transformers,
				[
					AmpRuntimeCss::class,
					OptimizeHeroImages::class,
					OptimizeAmpBind::class,
					RewriteAmpUrls::class,
					ServerSideRendering::class,
					TransformedIdentifier::class,
					AmpStoryCssOptimizer::class,
				]
			);
		}

		$configuration = [
			Configuration::KEY_TRANSFORMERS => $transformers,
			AmpStoryCssOptimizer::class     => [
				AmpStoryCssOptimizerConfiguration::OPTIMIZE_AMP_STORY => true,
			],
			MinifyHtml::class               => [
				// Prevents issues with rounding floats, relevant for things like shopping (product prices).
				Configuration\MinifyHtmlConfiguration::MINIFY_JSON => false,
			],
		];

		/**
		 * Filter the configuration to be used for the AMP Optimizer.
		 *
		 * @since 1.1.0
		 *
		 * @param array $configuration Associative array of configuration data.
		 */
		$configuration = apply_filters( 'web_stories_amp_optimizer_config', $configuration );

		return new DefaultConfiguration( $configuration );
	}
}
