<?php
/**
 * This file is part of MediaWiki.
 *
 * @license GPL-2.0-or-later
 * @file
 */

namespace MediaWiki\Revision;

use MediaWiki\Linker\LinkTarget;
use MediaWiki\Page\PageIdentity;

/**
 * SlotRoleHandler instances are used to declare the existence and behavior of slot roles.
 * Most importantly, they control which content model can be used for the slot, and how it is
 * represented in the rendered version of page content.
 *
 * @stable to extend
 *
 * @since 1.33
 */
class SlotRoleHandler {

	/**
	 * @var string
	 */
	private $role;

	/**
	 * @var string[]
	 * @see getOutputLayoutHints
	 */
	private $layout = [
		'display' => 'section', // use 'none' to suppress
		'region' => 'center',
		'placement' => 'append'
	];

	/**
	 * @var bool
	 */
	private $derived;

	/**
	 * @var string
	 */
	private $contentModel;

	/**
	 * @stable to call
	 *
	 * @param string $role The name of the slot role defined by this SlotRoleHandler. See
	 *        SlotRoleRegistry::defineRole for more information.
	 * @param string $contentModel The default content model for this slot. As per the default
	 *        implementation of isAllowedModel(), also the only content model allowed for the
	 *        slot. Subclasses may however handle default and allowed models differently.
	 * @param string[] $layout Layout hints, for use by RevisionRenderer. See getOutputLayoutHints.
	 * @param bool $derived Is this handler for a derived slot? Derived slots allow information that
	 *        is derived from the content of a page to be stored even if it is generated
	 *        asynchronously or updated later. Their size is not included in the revision size,
	 *        their hash does not contribute to the revision hash, and updates are not included
	 *        in revision history.
	 * @since 1.36 optional $derived parameter added
	 */
	public function __construct( $role, $contentModel, $layout = [], bool $derived = false ) {
		$this->role = $role;
		$this->contentModel = $contentModel;
		$this->layout = array_merge( $this->layout, $layout );
		$this->derived = $derived;
	}

	/**
	 * @return string The role this SlotRoleHandler applies to
	 */
	public function getRole() {
		return $this->role;
	}

	/**
	 * Layout hints for use while laying out the combined output of all slots, typically by
	 * RevisionRenderer. The layout hints are given as an associative array. Well-known keys
	 * to use:
	 *
	 * * "display": how the output of this slot should be represented. Supported values:
	 *   - "section": show as a top level section of the region.
	 *   - "none": do not show at all
	 *   Further values that may be supported in the future include "box" and "banner".
	 * * "region": in which region of the page the output should be placed. Supported values:
	 *   - "center": the central content area.
	 *   Further values that may be supported in the future include "top" and "bottom", "left"
	 *   and "right", "header" and "footer".
	 * * "placement": placement relative to other content of the same area.
	 *   - "append": place at the end, after any output processed previously.
	 *   Further values that may be supported in the future include "prepend". A "weight" key
	 *   may be introduced for more fine grained control.
	 *
	 * @stable to override
	 * @return string[] an associative array of hints
	 */
	public function getOutputLayoutHints() {
		return $this->layout;
	}

	/**
	 * @return bool Is this a handler for a derived slot?
	 * @since 1.36
	 */
	public function isDerived(): bool {
		return $this->derived;
	}

	/**
	 * The message key for the translation of the slot name.
	 *
	 * @stable to override
	 * @return string
	 */
	public function getNameMessageKey() {
		return 'slot-name-' . $this->role;
	}

	/**
	 * Determines the content model to use by default for this slot on the given page.
	 *
	 * The default implementation always returns the content model provided to the constructor.
	 * Subclasses may base the choice on default model on the page title or namespace.
	 * The choice should not depend on external state, such as the page content.
	 *
	 * @stable to override
	 *
	 * @param LinkTarget|PageIdentity $page
	 *
	 * @return string
	 */
	public function getDefaultModel( $page ) {
		return $this->contentModel;
	}

	/**
	 * Determines whether the given model can be used on this slot on the given page.
	 *
	 * The default implementation checks whether $model is the content model provided to the
	 * constructor. Subclasses may allow other models and may base the decision on the page title
	 * or namespace. The choice should not depend on external state, such as the page content.
	 *
	 * @stable to override
	 *
	 * @note This should be checked when creating new revisions. Existing revisions
	 *       are not guaranteed to comply with the return value.
	 *
	 * @param string $model
	 * @param PageIdentity $page
	 *
	 * @return bool
	 */
	public function isAllowedModel( $model, PageIdentity $page ) {
		return ( $model === $this->contentModel );
	}

	/**
	 * Whether this slot should be considered when determining whether a page should be counted
	 * as an "article" in the site statistics.
	 *
	 * For a page to be considered countable, one of the page's slots must return true from this
	 * method, and Content::isCountable() must return true for the content of that slot.
	 *
	 * The default implementation always returns false.
	 *
	 * @stable to override
	 * @see WikiPage::isCountable
	 * @return bool
	 */
	public function supportsArticleCount() {
		return false;
	}

}
