<?php

// phpcs:disable MediaWiki.NamingConventions.LowerCamelFunctionsName.FunctionName

namespace MediaWiki\Extension\WikiLove;

use MediaWiki\Api\ApiMessage;
use MediaWiki\Api\IApiMessage;
use MediaWiki\ChangeTags\Hook\ChangeTagsListActiveHook;
use MediaWiki\ChangeTags\Hook\ListDefinedTagsHook;
use MediaWiki\Config\Config;
use MediaWiki\Hook\SkinTemplateNavigation__UniversalHook;
use MediaWiki\Output\Hook\BeforePageDisplayHook;
use MediaWiki\Output\OutputPage;
use MediaWiki\Permissions\PermissionManager;
use MediaWiki\Preferences\Hook\GetPreferencesHook;
use MediaWiki\Title\Title;
use MediaWiki\User\Options\UserOptionsLookup;
use MediaWiki\User\User;
use Skin;
use SkinTemplate;

/**
 * Hooks for WikiLove extension
 *
 * @file
 * @ingroup Extensions
 */

class Hooks implements
	GetPreferencesHook,
	SkinTemplateNavigation__UniversalHook,
	BeforePageDisplayHook,
	ListDefinedTagsHook,
	ChangeTagsListActiveHook
{
	private Config $config;
	private PermissionManager $permissionManager;
	private UserOptionsLookup $userOptionsLookup;

	public function __construct(
		Config $config,
		PermissionManager $permissionManager,
		UserOptionsLookup $userOptionsLookup
	) {
		$this->config = $config;
		$this->permissionManager = $permissionManager;
		$this->userOptionsLookup = $userOptionsLookup;
	}

	/**
	 * Add the preference in the user preferences with the GetPreferences hook.
	 *
	 * @param User $user
	 * @param array &$preferences
	 */
	public function onGetPreferences( $user, &$preferences ) {
		if ( !$this->config->get( 'WikiLoveGlobal' ) ) {
			$preferences['wikilove-enabled'] = [
				'type' => 'check',
				'section' => 'editing/advancedediting',
				'label-message' => 'wikilove-enable-preference',
			];
		}
	}

	/**
	 * Adds the required module if we are on a user (talk) page.
	 *
	 * @param OutputPage $out
	 * @param Skin $skin
	 */
	public function onBeforePageDisplay( $out, $skin ): void {
		if (
			!$this->config->get( 'WikiLoveGlobal' ) &&
			!$this->userOptionsLookup->getOption( $out->getUser(), 'wikilove-enabled' )
		) {
			return;
		}

		$title = self::getUserTalkPage(
			$this->permissionManager,
			$skin->getTitle(),
			$skin->getUser()
		);
		// getUserTalkPage() returns an ApiMessage on error
		if ( !$title instanceof ApiMessage ) {
			$recipient = $title->getBaseText();

			$out->addJsConfigVars( [ 'wikilove-recipient' => $recipient ] );

			$out->addModules( 'ext.wikiLove.init' );
			$out->addModuleStyles( 'ext.wikiLove.icon' );
		}
	}

	/**
	 * Add a tab or an icon the new way (MediaWiki 1.18+)
	 *
	 * @param SkinTemplate $skin
	 * @param array &$links Navigation links
	 */
	public function onSkinTemplateNavigation__Universal( $skin, &$links ): void {
		if ( $this->showIcon( $skin ) ) {
			$this->skinConfigViewsLinks( $skin, $links['views'] );
		} else {
			$this->skinConfigViewsLinks( $skin, $links['actions'] );
		}
	}

	/**
	 * Configure views links.
	 *
	 * Helper function for SkinTemplateNavigation hooks
	 * to configure views links.
	 */
	private function skinConfigViewsLinks( Skin $skin, array &$views ): void {
		// If WikiLove is turned off for this user, don't display tab.
		if (
			!$this->config->get( 'WikiLoveGlobal' ) &&
			!$this->userOptionsLookup->getOption( $skin->getUser(), 'wikilove-enabled' )
		) {
			return;
		}

		// getUserTalkPage() returns an ApiMessage on error
		if ( !self::getUserTalkPage(
				$this->permissionManager,
				$skin->getTitle(),
				$skin->getUser()
			) instanceof ApiMessage
		) {
			$views['wikilove'] = [
				'text' => $skin->msg( 'wikilove-tab-text' )->text(),
				'href' => '#',
			];
			if ( $this->showIcon( $skin ) ) {
				$views['wikilove']['icon'] = 'heart';
				$views['wikilove']['button'] = true;
				$views['wikilove']['primary'] = true;
			}
		}
	}

	/**
	 * Only show an icon when the global preference is enabled and the current skin isn't CologneBlue.
	 */
	private function showIcon( Skin $skin ): bool {
		return $this->config->get( 'WikiLoveTabIcon' ) &&
			$skin->getSkinName() !== 'cologneblue';
	}

	/**
	 * Find the editable talk page of the user we want to send WikiLove to. This
	 * function also does some sense-checking to make sure we will actually
	 * be able to send WikiLove to the target.
	 *
	 * Phan false positives are suppressed
	 *
	 * @param PermissionManager $permissionManager
	 * @param Title $title The title of a user page or user talk page
	 * @param User $user the current user
	 * @return Title|IApiMessage Returns either the Title object for the talk page or an error message
	 * @suppress PhanPossiblyUndeclaredVariable,PhanTypeMismatchReturnNullable,PhanTypeMismatchArgumentNullable
	 */
	public static function getUserTalkPage( PermissionManager $permissionManager, Title $title, User $user ) {
		// Exit early if the sending user isn't logged in
		if ( !$user->isRegistered() || $user->isTemp() ) {
			return ApiMessage::create( 'wikilove-err-not-logged-in', 'notloggedin' );
		}

		// Exit early if the page is in the wrong namespace
		$ns = $title->getNamespace();
		if ( $ns !== NS_USER && $ns !== NS_USER_TALK ) {
			return ApiMessage::create( 'wikilove-err-invalid-username', 'invalidusername' );
		}

		// If we're on a subpage, get the root page title
		$baseTitle = $title->getRootTitle();

		// Users can't send WikiLove to themselves
		if ( $user->getName() === $baseTitle->getText() ) {
			return ApiMessage::create( 'wikilove-err-no-self-wikilove', 'no-self-wikilove' );
		}

		// Get the user talk page
		if ( $ns === NS_USER_TALK ) {
			// We're already on the user talk page
			$talkTitle = $baseTitle;
		} elseif ( $ns === NS_USER ) {
			// We're on the user page, so retrieve the user talk page instead
			$talkTitle = $baseTitle->getTalkPage();
		}

		// If it's a redirect, exit. We don't follow redirects since it might confuse the user or
		// lead to an endless loop (like if the talk page redirects to the user page or a subpage).
		// This means that the WikiLove tab will not appear on user pages or user talk pages if
		// the user talk page is a redirect.
		if ( $talkTitle->isRedirect() ) {
			return ApiMessage::create( 'wikilove-err-redirect', 'isredirect' );
		}

		// Make sure we can edit the page
		if ( !$permissionManager->quickUserCan( 'edit', $user, $talkTitle ) ) {
			return ApiMessage::create( 'wikilove-err-cannot-edit', 'cannotedit' );
		}

		return $talkTitle;
	}

	/**
	 * ListDefinedTags hook handler
	 *
	 * @see https://www.mediawiki.org/wiki/Manual:Hooks/ListDefinedTags
	 * @param array &$tags
	 */
	public function onListDefinedTags( &$tags ) {
		$tags[] = 'wikilove';
	}

	/**
	 * ChangeTagsListActive hook handler
	 *
	 * @see https://www.mediawiki.org/wiki/Manual:Hooks/ChangeTagsListActive
	 * @param array &$tags
	 */
	public function onChangeTagsListActive( &$tags ) {
		$tags[] = 'wikilove';
	}

}
