<?php

namespace Kirby\Http;

use Kirby\Toolkit\Str;
use Whoops\Handler\PrettyPageHandler;

/**
 * Static URL tools
 *
 * @package   Kirby Http
 * @author    Bastian Allgeier <bastian@getkirby.com>
 * @link      https://getkirby.com
 * @copyright Bastian Allgeier
 * @license   https://opensource.org/licenses/MIT
 */
class Url
{
	/**
	 * The base Url to build absolute Urls from
	 */
	public static string|null $home = '/';

	/**
	 * The current Uri object as string
	 */
	public static string|null $current = null;

	/**
	 * Facade for all Uri object methods
	 */
	public static function __callStatic(string $method, array $arguments)
	{
		$uri = new Uri($arguments[0] ?? static::current());
		return $uri->$method(...array_slice($arguments, 1));
	}

	/**
	 * Url Builder
	 * Actually just a factory for `new Uri($parts)`
	 */
	public static function build(
		array $parts = [],
		string|null $url = null
	): string {
		$url ??= static::current();
		$uri   = new Uri($url);
		return $uri->clone($parts)->toString();
	}

	/**
	 * Returns the current url with all bells and whistles
	 */
	public static function current(): string
	{
		return static::$current ??= static::toObject()->toString();
	}

	/**
	 * Returns the url for the current directory
	 */
	public static function currentDir(): string
	{
		return dirname(static::current());
	}

	/**
	 * Use Whoops to create an editor URL to open
	 * a file at the given line number
	 * @since 5.3.0
	 */
	public static function editor(string|false $editor, string|null $file, int $line = 0): string|null
	{
		if ($editor === false || $file === null) {
			return null;
		}

		$handler = new PrettyPageHandler();
		$handler->setEditor($editor);

		return $handler->getEditorHref($file, $line);
	}

	/**
	 * Tries to fix a broken url without protocol
	 * @psalm-return ($url is null ? string|null : string)
	 */
	public static function fix(string|null $url = null): string|null
	{
		// make sure to not touch absolute urls
		if (!preg_match('!^(https|http|ftp)\:\/\/!i', $url ?? '')) {
			return 'http://' . $url;
		}

		return $url;
	}

	/**
	 * Returns the home url if defined
	 */
	public static function home(): string
	{
		return static::$home;
	}

	/**
	 * Returns the url to the executed script
	 */
	public static function index(array $props = []): string
	{
		return Uri::index($props)->toString();
	}

	/**
	 * Checks if a URL starts with a dangerous URI scheme
	 * (e.g. javascript:) that must never appear in rendered href
	 * or src attributes.
	 * @since 5.4.1
	 */
	public static function hasDangerousScheme(string|null $url = null): bool
	{
		if ($url === null) {
			return false;
		}

		// strip any weird characters to prevent bypass attempts,
		// keeping only the characters we test for below
		// (especially removes any whitespace that the browser would ignore
		// when the resulting URL is evaluated)
		$url = preg_replace('/[^a-z:]/i', '', $url);

		// try to find a match from the blocklist case-insensitively
		return preg_match('!^(?:javascript|vbscript|livescript|mocha|jar|data):!i', $url) === 1;
	}

	/**
	 * Checks if an URL is absolute
	 */
	public static function isAbsolute(string|null $url = null): bool
	{
		if ($url === null || static::hasDangerousScheme($url) === true) {
			return false;
		}

		// matches the following groups of URLs:
		//  //example.com/uri
		//  http://example.com/uri, https://example.com/uri, ftp://example.com/uri
		//  mailto:example@example.com, geo:49.0158,8.3239?z=11
		return preg_match('!^(//|[a-z0-9+-.]+://|mailto:|tel:|geo:)!i', $url) === 1;
	}

	/**
	 * Convert a relative path into an absolute URL
	 */
	public static function makeAbsolute(
		string|null $path = null,
		string|null $home = null
	): string {
		if ($path === '' || $path === '/' || $path === null) {
			return $home ?? static::home();
		}

		if (str_starts_with($path, '#') === true) {
			return $path;
		}

		if (static::isAbsolute($path) === true) {
			return $path;
		}

		// build the full url
		$path   = ltrim($path, '/');
		$home ??= static::home();

		if ($path === '') {
			return $home;
		}

		if ($home === '/') {
			return '/' . $path;
		}

		return $home . '/' . $path;
	}

	/**
	 * Returns the path for the given url
	 */
	public static function path(
		string|array|null $url = null,
		bool $leadingSlash = false,
		bool $trailingSlash = false
	): string {
		return Url::toObject($url)
			->path()
			->toString($leadingSlash, $trailingSlash);
	}

	/**
	 * Returns the query for the given url
	 */
	public static function query(string|array|null $url = null): string
	{
		return Url::toObject($url)->query()->toString();
	}

	/**
	 * Return the last url the user has been on if detectable
	 */
	public static function last(): string
	{
		return Environment::getGlobally('HTTP_REFERER', '');
	}

	/**
	 * Shortens the Url by removing all unnecessary parts
	 */
	public static function short(
		string|null $url = null,
		int $length = 0,
		bool $base = false,
		string $rep = '…'
	): string {
		$uri = static::toObject($url);

		$uri->setFragment(null);
		$uri->setQuery(null);
		$uri->setPassword(null);
		$uri->setPort(null);
		$uri->setScheme(null);
		$uri->setUsername(null);

		// remove the trailing slash from the path
		$uri->setSlash(false);

		$url = $base ? $uri->base() : $uri->toString();
		$url = str_replace('www.', '', $url ?? '');

		return Str::short($url, $length, $rep);
	}

	/**
	 * Removes the path from the Url
	 */
	public static function stripPath(string|null $url = null): string
	{
		return static::toObject($url)->setPath(null)->toString();
	}

	/**
	 * Removes the query string from the Url
	 */
	public static function stripQuery(string|null $url = null): string
	{
		return static::toObject($url)->setQuery(null)->toString();
	}

	/**
	 * Removes the fragment (hash) from the Url
	 */
	public static function stripFragment(string|null $url = null): string
	{
		return static::toObject($url)->setFragment(null)->toString();
	}

	/**
	 * Smart resolver for internal and external urls
	 * @deprecated 5.3.0 Use `Kirby\Cms\Url::to()` instead
	 */
	public static function to(
		string|null $path = null,
		array|null $options = null
	): string {
		// make sure $path is string
		$path ??= '';

		// keep relative urls
		if (
			str_starts_with($path, './') === true ||
			str_starts_with($path, '../') === true
		) {
			return $path;
		}

		$url = static::makeAbsolute($path);

		if ($options === null) {
			return $url;
		}

		return (new Uri($url, $options))->toString();
	}

	/**
	 * Converts the Url to a Uri object
	 */
	public static function toObject(string|null $url = null): Uri
	{
		return $url === null ? Uri::current() : new Uri($url);
	}
}
