<?php

namespace Sphere\Debloat;

/**
 * Filesystem that mainly wraps the WP_File_System
 * 
 * @author  asadkn
 * @since   1.0.0
 * 
 * @mixin \WP_Filesystem_Base
 */
class FileSystem
{
	/**
	 * Pattern to remove protocol and www
	 */
	const PROTO_REMOVE_PATTERN = '#^(https?)?:?//(|www\.)#i';

	/**
	 * @var WP_Filesystem_Base
	 */
	public $filesystem;

	protected $valid_hosts;
	protected $paths_urls;

	/**
	 * Setup file system
	 */
	public function __construct()
	{
		global $wp_filesystem;

		if (empty($wp_filesystem)) {

			require_once wp_normalize_path(ABSPATH . '/wp-admin/includes/file.php');
			
			// At shutdown is usually a ob_start callback which doesn't permit calling ob_*
			if (did_action('shutdown') && ob_get_level() > 0) {
				$creds = request_filesystem_credentials('');
			}
			else {
				ob_start();
				$creds = request_filesystem_credentials('');
				ob_end_clean();
			}

			if (!$creds) {
				$creds = array();
			}

			$filesystem = WP_Filesystem($creds);
			
			if (!$filesystem) {
				
				// Fallback to lax permissions
				$upload = wp_upload_dir();
				WP_Filesystem(false, $upload['basedir'], true);
			}
		}

		$this->filesystem = $wp_filesystem;
	}

	/**
	 * Recursively make parent directories for a path.
	 * 
	 * Note: Adapted a bit from wp_mkdir_p().
	 *
	 * @param string $path
	 * @return boolean  true on success, false or failure.
	 */
	public function mkdir_p($path)
	{
		if ($this->is_dir($path)) {
			return;
		}

		$path = str_replace('//', '/', $path);

		// Safe mode safety.
		$path = rtrim($path, '/');
		$path = $path ?: '/';

		$created = $this->mkdir($path, FS_CHMOD_DIR);
		if ($created) {
			return true;
		}

		// If it failed above, try again by creating the parent first, recursively.
		$parent = dirname($path);
		if ($parent !== '/' && $this->mkdir_p($parent)) {
			return $this->mkdir($path, FS_CHMOD_DIR);
		}

		return true;
	}

	/**
	 * Get a local file by the provided URL, if possible.
	 * 
	 * @see wp_normalize_path()
	 * 
	 * @return bool|string  Either the path or false on failure.
	 */
	public function url_to_local($url)
	{
		$url = trim($url);
		
		// Not a URL, just return the path.
		if (substr($url, 0, 4) !== 'http' && substr($url, 0, 2) !== '//') {
			return $url;
		}

		$url = explode('?', trim($url));
		$url = trim($url[0]);

		// We're not working with encoded URLs.
		if (strpos($url, '%') !== false) {
            $url = urldecode($url);
		}

		// Add https:// for parse_url() or it fails.
		$url_no_proto = preg_replace(self::PROTO_REMOVE_PATTERN, '', $url);
		$url_host     = parse_url('https://' . $url_no_proto, PHP_URL_HOST);

		// Not a known host / URL.
		if (!in_array($url_host, $this->get_valid_hosts())) {
			return false;
		}

		/**
		 * Go through each known path url map and stop at first matched.
		 */
		$valid_urls   = $this->get_paths_urls();
		$url_dirname  = dirname($url_no_proto);
		$matched      = [];

		foreach ($valid_urls as $path_url) {

			if (strpos($url_dirname, untrailingslashit($path_url['url'])) !== false) {
				$matched = $path_url;
				break;
			}
		}

		// We have a matched path.
		if (!empty($matched['path'])) {
			$path = wp_normalize_path(
				$matched['path'] . str_replace($matched['url'], '', $url_dirname)
			);

			$file = trailingslashit($path) . wp_basename($url_no_proto);

			if (file_exists($file) && is_file($file) && is_readable($file)) {
				return $file;
			}
		}

		return false;
	}

	/**
	 * Get recognized hostnames for stylesheet URLs.
	 * 
	 * @return array
	 */
	public function get_valid_hosts()
	{
		if (!$this->valid_hosts) {
			$this->valid_hosts = wp_list_pluck(
				$this->get_paths_urls(),
				'host'
			);
		}

		return apply_filters('debloat/file_system/valid_hosts', $this->valid_hosts);
	}

	/**
	 * Get a map of known path URLs, associated local path, and host.
	 * 
	 * @return array
	 */
	public function get_paths_urls()
	{
		if (!$this->paths_urls) {

			// We add https:// back for parse_url() to prevent it from failing
			$site_url  = preg_replace(self::PROTO_REMOVE_PATTERN, '', site_url());
			$site_host = parse_url('https://' . $site_url, PHP_URL_HOST);

			$content_url  = preg_replace(self::PROTO_REMOVE_PATTERN, '', content_url());
			$content_host = parse_url('https://' . $content_url, PHP_URL_HOST);

			/**
			 * This array will be processed in order it's defined to find the matching host and URL.
			 */
			$hosts = [

				// First priority to use content_host and content_url()
				'content' => [
					'url'  => $content_url, 
					'path' => WP_CONTENT_DIR,
					'host' => $content_host
				],
				
				// Fallback to using site URL with ABSPATH
				'site' => [
					'url'  => $site_url, 
					'path' => ABSPATH,
					'host' => $site_host
				],
			];

			$this->paths_urls  = apply_filters('debloat/file_system/paths_urls', $hosts);
		}

		return $this->paths_urls;
	}

	/**
	 * Proxies to WP_Filesystem_Base
	 */
	public function __call($name, $arguments)
	{
		return call_user_func_array([$this->filesystem, $name], $arguments);
	}

}