<?php
namespace SabaiApps\Directories;

use SabaiApps\Framework\Application\HttpContext;

/**
 * Context class for managing the application context and response state.
 * Provides methods for handling templates, redirects, errors, flash messages, info, menus, tabs, and HTML attributes.
 */
class Context extends HttpContext {

	private $_successUrl, $_successAttributes, $_errorType, $_errorMessage, $_errorUrl, $_errorAttributes, $_redirectType, $_container, $_target,
		$_flash                     = array(), $_flashEnabled = true, $_info = array(), $_title, $_menus = array(),
		$_tabs                      = array(), $_tabInfo = array(), $_tabMenus = array(),
		$_currentTabSet             = 0, $_currentTab = array(), $_templates = array(),
		$_redirectMessage, $_noWrap = false,
		$_extraHtmlAttr             = array();

	/**
	 * Gets the list of templates.
	 *
	 * @return array
	 */
	public function getTemplates() {
		return $this->_templates;
	}

	/**
	 * Checks if any template is set.
	 *
	 * @return bool
	 */
	public function hasTemplate() {
		return ! empty( $this->_templates );
	}

	/**
	 * Adds a template or templates.
	 *
	 * @param string|array $template
	 * @return $this
	 */
	public function addTemplate( $template ) {
		foreach ( (array) $template as $_template ) {
			$this->_templates[] = $_template;
		}

		return $this;
	}

	/**
	 * Clears all templates.
	 *
	 * @return $this
	 */
	public function clearTemplates() {
		$this->_templates = array();

		return $this;
	}

	/**
	 * Sets the success URL and attributes.
	 *
	 * @param string|null $url
	 * @param array       $attributes
	 * @return $this
	 */
	public function setSuccess( $url = null, array $attributes = array() ) {
		if ( isset( $url ) ) {
			$this->_successUrl = $url;
		}
		$this->_successAttributes = $attributes;

		return parent::setSuccess();
	}

	/**
	 * Gets the success attributes.
	 *
	 * @return array
	 */
	public function getSuccessAttributes() {
		return $this->_successAttributes;
	}

	/**
	 * Gets the success URL.
	 *
	 * @return string|null
	 */
	public function getSuccessUrl() {
		return $this->_successUrl;
	}

	/**
	 * Gets the redirect type.
	 *
	 * @return int|null
	 */
	public function getRedirectType() {
		return $this->_redirectType;
	}

	/**
	 * Sets the redirect URL and type.
	 *
	 * @param string $url
	 * @param int    $type
	 * @return $this
	 */
	public function setRedirect( $url, $type = Response::REDIRECT_TEMPORARY ) {
		$this->_redirectType = $type;

		return parent::setRedirect( $url );
	}

	/**
	 * Gets the redirect message.
	 *
	 * @return string|null
	 */
	public function getRedirectMessage() {
		return $this->_redirectMessage;
	}

	/**
	 * Sets the redirect message.
	 *
	 * @param string $message
	 * @return $this
	 */
	public function setRedirectMessage( $message ) {
		$this->_redirectMessage = $message;
		return $this;
	}

	/**
	 * Gets the error type.
	 *
	 * @return int|null
	 */
	public function getErrorType() {
		return $this->_errorType;
	}

	/**
	 * Gets the error message.
	 *
	 * @return string|null
	 */
	public function getErrorMessage() {
		return $this->_errorMessage;
	}

	/**
	 * Gets the error URL.
	 *
	 * @return string|null
	 */
	public function getErrorUrl() {
		return $this->_errorUrl;
	}

	/**
	 * Gets the error attributes.
	 *
	 * @return array|null
	 */
	public function getErrorAttributes() {
		return $this->_errorAttributes;
	}

	/**
	 * Sets the error URL.
	 *
	 * @param string $url
	 * @return $this
	 */
	public function setErrorUrl( $url ) {
		$this->_errorUrl = $url;

		return $this;
	}

	/**
	 * Sets the error message, URL, attributes, and type.
	 *
	 * @param string|null $message
	 * @param string|null $url
	 * @param array       $attributes
	 * @param int         $type
	 * @return $this
	 */
	public function setError( $message = null, $url = null, array $attributes = array(), $type = Response::ERROR_INTERNAL_SERVER_ERROR ) {
		$this->_errorMessage = (string) $message;
		if ( isset( $url ) ) {
			$this->_errorUrl = $url;
		}
		$this->_errorAttributes = $attributes;
		$this->_errorType       = $type;

		return parent::setError();
	}

	/**
	 * Sets a bad request error.
	 */
	public function setBadRequestError( $url = null, $message = null, array $attributes = array() ) {
		return $this->setError( $message, $url, $attributes, Response::ERROR_BAD_REQUEST );
	}

	/**
	 * Sets an unauthorized error.
	 */
	public function setUnauthorizedError( $url = null, $message = null, array $attributes = array() ) {
		return $this->setError( $message, $url, $attributes, Response::ERROR_UNAUTHORIZED );
	}

	/**
	 * Sets a forbidden error.
	 */
	public function setForbiddenError( $url = null, $message = null, array $attributes = array() ) {
		return $this->setError( $message, $url, $attributes, Response::ERROR_FORBIDDEN );
	}

	/**
	 * Sets a not found error.
	 */
	public function setNotFoundError( $url = null, $message = null, array $attributes = array() ) {
		return $this->setError( $message, $url, $attributes, Response::ERROR_NOT_FOUND );
	}

	/**
	 * Sets a method not allowed error.
	 */
	public function setMethodNotAllowedError( $url = null, $message = null, array $attributes = array() ) {
		return $this->setError( $message, $url, $attributes, Response::ERROR_METHOD_NOT_ALLOWED );
	}

	/**
	 * Sets a not acceptable error.
	 */
	public function setNotAcceptableError( $url = null, $message = null, array $attributes = array() ) {
		return $this->setError( $message, $url, $attributes, Response::ERROR_NOT_ACCEPTABLE );
	}

	/**
	 * Sets an internal server error.
	 */
	public function setInternalServerError( $url = null, $message = null, array $attributes = array() ) {
		return $this->setError( $message, $url, $attributes, Response::ERROR_INTERNAL_SERVER_ERROR );
	}

	/**
	 * Sets a not implemented error.
	 */
	public function setNotImplementedError( $url = null, $message = null, array $attributes = array() ) {
		return $this->setError( $message, $url, $attributes, Response::ERROR_NOT_IMPLEMENTED );
	}

	/**
	 * Sets a service unavailable error.
	 */
	public function setServiceUnavailableError( $url = null, $message = null, array $attributes = array() ) {
		return $this->setError( $message, $url, $attributes, Response::ERROR_SERVICE_UNAVAILABLE );
	}

	/**
	 * Sets a form validation error.
	 */
	public function setValidateFormError( $url = null, $message = null, array $attributes = array() ) {
		return $this->setError( $message, $url, $attributes, Response::ERROR_VALIDATE_FORM );
	}

	/**
	 * Sets a token expired error.
	 */
	public function setTokenExpiredError( $url = null, array $attributes = array() ) {
		$this->setForbiddenError( $url, __( 'Token has expired. Please reload the page and try again.', 'directories-pro' ), $attributes );
	}

	/**
	 * Gets the container for the context.
	 *
	 * @return string
	 */
	public function getContainer() {
		$this->_resolveContainer();

		return $this->_container;
	}

	/**
	 * Gets the target for the context.
	 *
	 * @return string
	 */
	public function getTarget() {
		$this->_resolveContainer();

		return $this->_target;
	}

	/**
	 * Resolves the container and target values.
	 */
	protected function _resolveContainer() {
		if ( ! isset( $this->_container ) ) {
			$this->_container = $this->getRequest()->isAjax();
			$this->_target    = '';
			if ( is_string( $this->_container ) ) {
				if ( strpos( $this->_container, ' ' ) ) {
					list($this->_container, $this->_target) = explode( ' ', $this->_container );
				}
			} else {
				$this->_container = '#drts-content';
			}
		}
	}

	/**
	 * Sets the container and target.
	 *
	 * @param string $container
	 * @param string $target
	 * @return $this
	 */
	public function setContainer( $container, $target = '' ) {
		$this->_container = $container;
		$this->_target    = $target;

		return $this;
	}

	/**
	 * Gets the HTML attribute for the container.
	 *
	 * @return string
	 */
	public function getHtmlAttribute() {
		$container = $this->getContainer();
		return strpos( $container, '#' ) === 0
			? ' id="' . htmlspecialchars( substr( $container, 1 ), ENT_QUOTES, 'UTF-8' ) . '"'
			: ' data-container="' . htmlspecialchars( $container, ENT_QUOTES, 'UTF-8' ) . '"';
	}

	/**
	 * Adds a flash message.
	 *
	 * @param string      $message
	 * @param string      $level
	 * @param int|null    $delay
	 * @param string|null $position
	 * @return $this
	 */
	public function addFlash( $message, $level = 'success', $delay = null, $position = null ) {
		$this->_flash[] = array(
			'msg'      => $message,
			'level'    => $level,
			'delay'    => $delay,
			'position' => $position,
		);

		return $this;
	}

	/**
	 * Gets all flash messages.
	 *
	 * @return array
	 */
	public function getFlash() {
		return $this->_flash;
	}

	/**
	 * Checks if there are any flash messages.
	 *
	 * @return bool
	 */
	public function hasFlash() {
		return ! empty( $this->_flash );
	}

	/**
	 * Clears all flash messages.
	 *
	 * @return $this
	 */
	public function clearFlash() {
		$this->_flash = array();

		return $this;
	}

	/**
	 * Enables or disables flash messages.
	 *
	 * @param bool $flag
	 * @return $this
	 */
	public function setFlashEnabled( $flag = true ) {
		$this->_flashEnabled = $flag;

		return $this;
	}

	/**
	 * Checks if flash messages are enabled.
	 *
	 * @return bool
	 */
	public function isFlashEnabled() {
		return $this->_flashEnabled;
	}

	/**
	 * Gets the info array.
	 *
	 * @return array
	 */
	public function getInfo() {
		return $this->_info;
	}

	/**
	 * Sets info (breadcrumb or page info).
	 *
	 * @param string|array $title
	 * @param string|null  $url
	 * @return $this
	 */
	public function setInfo( $title, $url = null ) {
		if ( is_array( $title ) ) {
			foreach ( $title as $_title ) {
				$this->setInfo( $_title['title'], $_title['url'] );
			}
		} elseif ( empty( $this->_tabs ) || empty( $this->_currentTab ) ) {
				$this->_info[] = array(
					'title' => $title,
					'url'   => $url,
				);
		} else {
			$this->_tabInfo[ $this->_currentTabSet ][] = array(
				'title' => $title,
				'url'   => $url,
			);
		}

		return $this;
	}

	/**
	 * Removes the last info entry.
	 *
	 * @return array|null
	 */
	public function popInfo() {
		if ( empty( $this->_tabs ) || empty( $this->_currentTab ) ) {
			return array_pop( $this->_info );
		}

		return array_pop( $this->_tabInfo[ $this->_currentTabSet ] );
	}

	/**
	 * Clears all info entries.
	 *
	 * @return $this
	 */
	public function clearInfo() {
		$this->_info = $this->_tabInfo = array();

		return $this;
	}

	/**
	 * Gets the title, optionally from info.
	 *
	 * @param bool $fromInfo
	 * @return string
	 */
	public function getTitle( $fromInfo = true ) {
		if ( null !== $this->_title ) {
			return $this->_title;
		}

		if ( $fromInfo
			&& ( $info = $this->getInfo() )
			&& ( $_info = array_values( $info ) )
		) {
			$_info = array_pop( $_info );
			return (string) $_info['title'];
		}
		return '';
	}

	/**
	 * Sets the title.
	 *
	 * @param string $title
	 * @return $this
	 */
	public function setTitle( $title ) {
		$this->_title = $title;

		return $this;
	}

	/**
	 * Gets the menus.
	 *
	 * @return array
	 */
	public function getMenus() {
		return $this->_menus;
	}

	/**
	 * Sets the menus.
	 *
	 * @param array $menus
	 * @param bool  $page
	 * @return $this
	 */
	public function setMenus( array $menus, $page = false ) {
		if ( $page || empty( $this->_tabs ) || empty( $this->_currentTab ) ) {
			$this->_menus = $menus;
		} else {
			$this->_tabMenus[ $this->_currentTabSet ] = $menus;
		}

		return $this;
	}

	/**
	 * Adds a menu.
	 *
	 * @param mixed $menu
	 * @param bool  $page
	 * @return $this
	 */
	public function addMenu( $menu, $page = false ) {
		if ( $page || empty( $this->_tabs ) || empty( $this->_currentTab ) ) {
			$this->_menus[] = $menu;
		} else {
			$this->_tabMenus[ $this->_currentTabSet ][] = $menu;
		}

		return $this;
	}

	/**
	 * Clears all menus.
	 *
	 * @return $this
	 */
	public function clearMenus() {
		$this->_menus = $this->_tabMenus = array();

		return $this;
	}

	/**
	 * Gets the tabs.
	 *
	 * @return array
	 */
	public function getTabs() {
		return $this->_tabs;
	}

	/**
	 * Gets the tab info.
	 *
	 * @return array
	 */
	public function getTabInfo() {
		return $this->_tabInfo;
	}

	/**
	 * Gets the tab menus.
	 *
	 * @return array
	 */
	public function getTabMenus() {
		return $this->_tabMenus;
	}

	/**
	 * Pushes a new set of tabs.
	 *
	 * @param array $tabs
	 * @return $this
	 */
	public function pushTabs( array $tabs ) {
		$this->_tabs[ ++$this->_currentTabSet ] = $tabs;

		return $this;
	}

	/**
	 * Pops the current set of tabs.
	 *
	 * @return $this
	 */
	public function popTabs() {
		unset(
			$this->_tabs[ $this->_currentTabSet ],
			$this->_currentTab[ $this->_currentTabSet ],
			$this->_tabInfo[ $this->_currentTabSet ],
			$this->_tabMenus[ $this->_currentTabSet ]
		);
		--$this->_currentTabSet;

		return $this;
	}

	/**
	 * Sets the current tab.
	 *
	 * @param string $tabName
	 * @return $this
	 */
	public function setCurrentTab( $tabName ) {
		if ( isset( $this->_tabs[ $this->_currentTabSet ][ $tabName ] ) ) {
			$this->_currentTab[ $this->_currentTabSet ] = $tabName;
		}

		return $this;
	}

	/**
	 * Gets the current tab.
	 *
	 * @return array
	 */
	public function getCurrentTab() {
		return $this->_currentTab;
	}

	/**
	 * Clears all tabs and optionally merges info.
	 *
	 * @param bool $mergeInfo
	 * @return $this
	 */
	public function clearTabs( $mergeInfo = false ) {
		foreach ( array_keys( $this->_currentTab ) as $tab_set ) {
			if ( empty( $this->_tabInfo[ $tab_set ] ) ) {
				continue;
			}

			foreach ( $this->_tabInfo[ $tab_set ] as $_tab_info ) {
				$last_key = (string) $_tab_info['url'];
				if ( isset( $this->_info[ $last_key ] ) ) {
					continue;
				}

				$this->_info[ $last_key ] = array(
					'title' => $_tab_info['title'],
					'url'   => $_tab_info['url'],
				);
			}
		}
		if ( ! $mergeInfo && isset( $last_key ) ) {
			$this->_info = array( $last_key => $this->_info[ $last_key ] );
		}
		if ( isset( $this->_tabMenus[ $this->_currentTabSet ] ) ) {
			$this->_menus = $this->_tabMenus[ $this->_currentTabSet ];
		}
		$this->_currentTab    = $this->_tabs = $this->_tabMenu = $this->_tabInfo = array();
		$this->_currentTabSet = 0;

		return $this;
	}

	/**
	 * Gets all attributes for the context.
	 *
	 * @return array
	 */
	public function getAttributes() {
		$attr            = parent::getAttributes();
		$attr['CONTEXT'] = $this;
		return $attr;
	}

	/**
	 * Checks if the context is embedded.
	 *
	 * @return bool
	 */
	public function isEmbed() {
		return $this->getContainer() !== '#drts-content';
	}

	/**
	 * Sets the no-wrap flag.
	 *
	 * @param bool $bool
	 * @return $this
	 */
	public function setNoWrap( $bool = true ) {
		$this->_noWrap = (bool) $bool;
		return $this;
	}

	/**
	 * Checks if no-wrap is enabled.
	 *
	 * @return bool
	 */
	public function isNoWrap() {
		return $this->_noWrap;
	}

	/**
	 * Adds extra HTML attributes.
	 *
	 * @param string|array $name
	 * @param mixed        $value
	 */
	public function addExtraHtmlAttributes( $name, $value = null ) {
		if ( is_array( $name ) ) {
			$this->_extraHtmlAttr = $name + $this->_extraHtmlAttr;
		} else {
			$this->_extraHtmlAttr[ $name ] = $value;
		}
	}

	/**
	 * Gets extra HTML attributes.
	 *
	 * @return array
	 */
	public function getExtraHtmlAttributes() {
		return $this->_extraHtmlAttr;
	}
}
