<?php
namespace SabaiApps\Framework\User;

/**
 * AbstractIdentityFetcher class for fetching user identities.
 * Provides methods for loading, searching, and paginating user identities.
 */
abstract class AbstractIdentityFetcher {

	/**
	 * @var string Field name for user ID.
	 */
	protected $_idField = 'id';

	/**
	 * @var string Field name for username.
	 */
	protected $_usernameField = 'username';

	/**
	 * @var string Field name for name.
	 */
	protected $_nameField = 'name';

	/**
	 * @var string Field name for email.
	 */
	protected $_emailField = 'email';

	/**
	 * @var string Field name for URL.
	 */
	protected $_urlField = 'url';

	/**
	 * @var string Field name for timestamp.
	 */
	protected $_timestampField = 'created';

	/**
	 * @var array Cached user identities.
	 */
	private $_identities = array();

	/**
	 * Clears cached identities.
	 */
	public function clear() {
		$this->_identities = array();
	}

	/**
	 * Loads user identity objects by user IDs.
	 *
	 * @param array $userIds IDs of users to load.
	 */
	protected function _load( array $userIds ) {
		// Check if requested identities are already loaded
		$user_ids = array_diff( $userIds, array_keys( $this->_identities ) );
		// Only load if there are any not loaded yet
		if ( $user_ids ) {
			$identities        = $this->_doFetchByIds( $user_ids );
			$this->_identities = $identities + $this->_identities;
			if ( $userids_not_found = array_diff( $user_ids, array_keys( $identities ) ) ) {
				foreach ( $userids_not_found as $uid ) {
					$this->_identities[ $uid ] = $this->getAnonymous();
				}
			}
		}
	}

	/**
	 * Fetches a user identity object by user ID.
	 *
	 * @param string $userId User ID.
	 * @return AbstractIdentity
	 */
	public function fetchById( $userId ) {
		$this->_load( array( $userId ) );
		return $this->_identities[ $userId ];
	}

	/**
	 * Fetches user identity objects by user IDs.
	 *
	 * @param array $userIds Array of user IDs.
	 * @return array Array of AbstractIdentity objects indexed by user ID.
	 */
	public function fetchByIds( array $userIds ) {
		$this->_load( $userIds );
		return array_intersect_key( $this->_identities, array_combine( $userIds, $userIds ) );
	}

	/**
	 * Fetches a user identity object by username.
	 *
	 * @param string $userName Username.
	 * @return AbstractIdentity
	 */
	public function fetchByUsername( $userName ) {
		if ( ! $identity = $this->_doFetchByUsername( $userName ) ) {
			return $this->getAnonymous();
		}

		$this->_identities[ $identity->id ] = $identity;

		return $identity;
	}

	/**
	 * Fetches a user identity object by email address.
	 *
	 * @param string $email Email address.
	 * @return AbstractIdentity
	 */
	public function fetchByEmail( $email ) {
		if ( ! $identity = $this->_doFetchByEmail( $email ) ) {
			return $this->getAnonymous();
		}

		$this->_identities[ $identity->id ] = $identity;

		return $identity;
	}

	/**
	 * Paginates user identity objects.
	 *
	 * @param int    $perpage Number of items per page.
	 * @param string $sort Sorting field.
	 * @param string $order Sorting order (ASC/DESC).
	 * @return IdentityPaginator
	 */
	public function paginate( $perpage = 20, $sort = 'id', $order = 'ASC', $key = 0 ) {
		return new IdentityPaginator( $this, $perpage, $sort, $order, $key );
	}

	/**
	 * Fetches user identity objects.
	 *
	 * @param int    $limit Number of items to fetch.
	 * @param int    $offset Offset for fetching items.
	 * @param string $sort Sorting field.
	 * @param string $order Sorting order (ASC/DESC).
	 * @return \ArrayObject
	 */
	public function fetch( $limit = 0, $offset = 0, $sort = null, $order = null ) {
		$identities = $this->_doFetch(
			intval( $limit ),
			intval( $offset ),
			$this->_getSortFieldName( $sort ),
			$order === 'DESC' ? 'DESC' : 'ASC'
		);

		return new \ArrayObject( $identities );
	}

	/**
	 * Searches user identity objects.
	 *
	 * @param string $term Search term.
	 * @param int    $limit Number of items to fetch.
	 * @param int    $offset Offset for fetching items.
	 * @param string $sort Sorting field.
	 * @param string $order Sorting order (ASC/DESC).
	 * @return \ArrayObject
	 */
	public function search( $term, $limit = 0, $offset = 0, $sort = null, $order = null ) {
		$identities = $this->_doSearch(
			$term,
			intval( $limit ),
			intval( $offset ),
			$this->_getSortFieldName( $sort ),
			$order === 'DESC' ? 'DESC' : 'ASC'
		);

		return new \ArrayObject( $identities );
	}

	/**
	 * Gets the field name for sorting.
	 *
	 * @param string $field Field name.
	 * @return string
	 */
	protected function _getSortFieldName( $field ) {
		switch ( $field ) {
			case 'name':
				return $this->_nameField;
			case 'username':
				return $this->_usernameField;
			case 'email':
				return $this->_emailField;
			case 'url':
				return $this->_urlField;
			case 'timestamp':
				return $this->_timestampField;
			default:
				return $this->_idField;
		}
	}

	// Abstract methods to be implemented by subclasses.
	abstract protected function _doFetch( $limit, $offset, $sort, $order );
	abstract protected function _doSearch( $term, $limit, $offset, $sort, $order );
	abstract public function count();
	abstract protected function _doFetchByIds( array $userIds );
	abstract protected function _doFetchByUsername( $userName );
	abstract protected function _doFetchByEmail( $email );
	abstract public function getAnonymous();
}
