<?php
namespace SabaiApps\Framework\Application;

/**
 * Class representing a URL with parameters, fragment, and route information.
 * Implements ArrayAccess for flexible access to URL data.
 */
class Url implements \ArrayAccess {

	/**
	 * Internal data storage for URL components.
	 *
	 * @var array
	 */
	private $_data = array();

	/**
	 * Indicates if PHP_QUERY_RFC3986 is defined.
	 *
	 * @var bool
	 */
	private static $_defined;

	/**
	 * Constructor.
	 *
	 * @param string $scriptUrl  The base script URL.
	 * @param array  $params      Query parameters.
	 * @param string $fragment   URL fragment (after #).
	 * @param string $separator  Separator for query parameters.
	 * @param string $route      Optional route information.
	 */
	public function __construct( $scriptUrl, array $params = array(), $fragment = '', $separator = '&amp;', $route = '' ) {
		$this->_data = array(
			'script_url' => $scriptUrl,
			'params'     => $params,
			'fragment'   => $fragment,
			'separator'  => $separator,
			'route'      => $route,
		);
	}

	/**
	 * Magic getter for URL data.
	 *
	 * @param string $name
	 * @return mixed
	 */
	public function &__get( $name ) {
		return $this->_data[ $name ];
	}

	/**
	 * Magic setter for URL data.
	 *
	 * @param string $name
	 * @param mixed  $value
	 */
	public function __set( $name, $value ) {
		$this->_data[ $name ] = $value;
	}

	/**
	 * Sets a value in the URL data and returns the object for chaining.
	 *
	 * @param string $name
	 * @param mixed  $value
	 * @return $this
	 */
	public function set( $name, $value ) {
		$this->_data[ $name ] = $value;
		return $this;
	}

	/**
	 * Converts the URL object to a string.
	 * Builds the full URL with parameters and fragment.
	 *
	 * @return string
	 */
	public function __toString() {
		if ( ! empty( $this->_data['params'] )
			&& ( $query_str = self::_httpBuildQuery( $this->_data['params'], $this->_data['separator'] ) )
		) {
			if ( strpos( $this->_data['script_url'], '?' ) ) {
				$url = $this->_data['script_url'] . $this->_data['separator'] . $query_str;
			} else {
				$url = $this->_data['script_url'] . '?' . $query_str;
			}
		} else {
			$url = $this->_data['script_url'];
		}

		return strlen( $this->_data['fragment'] ) ? $url . '#' . rawurlencode( ltrim( $this->_data['fragment'], '#' ) ) : $url;
	}

	/**
	 * Sets a value in the URL data using array access.
	 *
	 * @param mixed $offset
	 * @param mixed $value
	 */
	#[\ReturnTypeWillChange]
	public function offsetSet( $offset, $value ) {
		$this->_data[ $offset ] = $value;
	}

	/**
	 * Checks if a value exists in the URL data using array access.
	 *
	 * @param mixed $offset
	 * @return bool
	 */
	#[\ReturnTypeWillChange]
	public function offsetExists( $offset ) {
		return isset( $this->_data[ $offset ] );
	}

	/**
	 * Unsets a value in the URL data using array access.
	 *
	 * @param mixed $offset
	 */
	#[\ReturnTypeWillChange]
	public function offsetUnset( $offset ) {
		unset( $this->_data[ $offset ] );
	}

	/**
	 * Gets a value from the URL data using array access.
	 *
	 * @param mixed $offset
	 * @return mixed
	 */
	#[\ReturnTypeWillChange]
	public function offsetGet( $offset ) {
		return $this->_data[ $offset ];
	}

	/**
	 * Builds a query string from parameters using the specified separator.
	 * Uses RFC3986 encoding if available.
	 *
	 * @param array  $params
	 * @param string $separator
	 * @return string
	 */
	protected static function _httpBuildQuery( array $params, $separator ) {
		ksort( $params );
		if ( ! isset( self::$_defined ) ) {
			self::$_defined = defined( 'PHP_QUERY_RFC3986' ); // from PHP 5.4.0
		}
		if ( self::$_defined ) {
			return http_build_query( $params, '', $separator, PHP_QUERY_RFC3986 );
		}
		return ( $ret = http_build_query( $params, '', $separator ) )
			? strtr(
				$ret,
				array(
					'%7E' => '~',
					'+'   => '%20',
				)
			) // For RFC3986 compat
			: $ret;
	}
}
