<?php

declare(strict_types=1);

namespace Signocore\Multilanguage\Features;

if (!defined('ABSPATH')) {
	exit;
}

use Signocore\Multilanguage\Application\Constants;
use Signocore\Multilanguage\Application\Entitlements;
use Signocore\Multilanguage\Application\LicenseManager;
use Signocore\Multilanguage\Infrastructure\Database\LanguageRepository;
use Signocore\Multilanguage\Infrastructure\Traits\Singleton;

final class LanguageDetection
{
	use Singleton;

	private string $currentLanguage = '';

	/** @var string[] Stack for switch_language/restore_language */
	private array $languageStack = [];

	private ?\Closure $localeFilter = null;

	protected function init(): void
	{
		add_action('init', [$this, 'detect'], 1);
	}

	/**
	 * Detect the current language from the request.
	 */
	public function detect(): void
	{
		$langManager = LanguageManager::load();
		$defaultCode = $langManager->getDefaultLanguageCode();

		// Try detection in order of priority
		$detected = $this->detectFromDomain()
			?? $this->detectFromSubdomain()
			?? $this->detectFromUrl()
			?? $this->detectFromCookie()
			?? $defaultCode;

		// Validate that detected language is active
		if (!$langManager->isActiveLanguage($detected)) {
			$detected = $defaultCode;
		}

		$this->currentLanguage = $detected;

		// Set cookie for future visits
		$this->setLanguageCookie($detected);

		// Switch WordPress locale to match
		$this->applyLocale($detected);
	}

	/**
	 * Get the current language code.
	 */
	public function getCurrentLanguage(): string
	{
		if ($this->currentLanguage === '') {
			return LanguageManager::load()->getDefaultLanguageCode();
		}

		return $this->currentLanguage;
	}

	/**
	 * Temporarily switch to a different language.
	 */
	public function switchLanguage(string $langCode): void
	{
		$this->languageStack[] = $this->currentLanguage;
		$this->currentLanguage = $langCode;

		$this->applyLocale($langCode);

		do_action('signocore_ml_language_switched', $langCode, end($this->languageStack) ?: '');
	}

	/**
	 * Restore the previous language.
	 */
	public function restoreLanguage(): void
	{
		if (!empty($this->languageStack)) {
			$this->currentLanguage = array_pop($this->languageStack);
			$this->applyLocale($this->currentLanguage);
		}
	}

	/**
	 * Detect language from custom domain mapping.
	 */
	private function detectFromDomain(): ?string
	{
		if (!LicenseManager::load()->can(Entitlements::CUSTOM_DOMAINS)) {
			return null;
		}

		$strategy = get_option(Constants::OPTION_PREFIX . 'url_strategy');
		if ($strategy !== Constants::URL_STRATEGY_DOMAIN) {
			return null;
		}

		$host = $_SERVER['HTTP_HOST'] ?? '';
		if ($host === '') {
			return null;
		}

		$lang = LanguageRepository::getByDomain($host);
		if ($lang !== null) {
			return $lang->code;
		}

		// Current host is not mapped to any language — it's the default language domain.
		// Return it explicitly so cookie detection cannot override it.
		return LanguageManager::load()->getDefaultLanguageCode();
	}

	/**
	 * Detect language from subdomain (e.g., da.example.com).
	 */
	private function detectFromSubdomain(): ?string
	{
		if (!LicenseManager::load()->can(Entitlements::SUBDOMAIN_URLS)) {
			return null;
		}

		$strategy = get_option(Constants::OPTION_PREFIX . 'url_strategy');
		if ($strategy !== Constants::URL_STRATEGY_SUBDOMAIN) {
			return null;
		}

		$host = $_SERVER['HTTP_HOST'] ?? '';
		if ($host === '') {
			return null;
		}

		$siteHost = wp_parse_url(home_url(), PHP_URL_HOST);
		if (!is_string($siteHost)) {
			return null;
		}

		// Check if current host has a subdomain relative to the site host
		if (!str_ends_with($host, '.' . $siteHost)) {
			// Current host IS the base site host — default language.
			// Return it explicitly so cookie detection cannot override it.
			return LanguageManager::load()->getDefaultLanguageCode();
		}

		$subdomain = str_replace('.' . $siteHost, '', $host);
		$langManager = LanguageManager::load();

		return $langManager->isActiveLanguage($subdomain) ? $subdomain : null;
	}

	/**
	 * Detect language from URL path prefix (e.g., /da/page).
	 */
	private function detectFromUrl(): ?string
	{
		$strategy = get_option(Constants::OPTION_PREFIX . 'url_strategy');
		if ($strategy !== Constants::URL_STRATEGY_DIRECTORY) {
			return null;
		}

		$requestUri = $_SERVER['REQUEST_URI'] ?? '';
		$path = wp_parse_url($requestUri, PHP_URL_PATH);
		if (!is_string($path)) {
			return null;
		}

		// Remove the WordPress installation subdirectory if present
		$homePath = wp_parse_url(home_url(), PHP_URL_PATH);
		if (is_string($homePath) && $homePath !== '/') {
			$path = substr($path, strlen($homePath)) ?: '/';
		}

		$path = ltrim($path, '/');
		$segments = explode('/', $path);
		$firstSegment = $segments[0] ?? '';

		$langManager = LanguageManager::load();

		if ($firstSegment !== '' && $langManager->isActiveLanguage($firstSegment)) {
			return $firstSegment;
		}

		// No language prefix in URL — when the default prefix is hidden, this is the default language.
		// Return it explicitly so cookie detection cannot override it.
		$hideDefault = (bool) get_option(Constants::OPTION_PREFIX . 'hide_default_language_prefix', '1');
		if ($hideDefault) {
			return $langManager->getDefaultLanguageCode();
		}

		return null;
	}

	/**
	 * Detect language from the cookie.
	 */
	private function detectFromCookie(): ?string
	{
		$cookieLang = $_COOKIE[Constants::COOKIE_NAME] ?? '';
		if ($cookieLang === '') {
			return null;
		}

		$langManager = LanguageManager::load();

		return $langManager->isActiveLanguage($cookieLang) ? $cookieLang : null;
	}

	/**
	 * Set the language preference cookie.
	 */
	private function setLanguageCookie(string $langCode): void
	{
		if (headers_sent()) {
			return;
		}

		$duration = (int) get_option(Constants::OPTION_PREFIX . 'cookie_duration', 365);
		$expires = time() + ($duration * DAY_IN_SECONDS);

		setcookie(
			Constants::COOKIE_NAME,
			$langCode,
			[
				'expires' => $expires,
				'path' => COOKIEPATH,
				'domain' => COOKIE_DOMAIN ?: '',
				'secure' => is_ssl(),
				'httponly' => true,
				'samesite' => 'Lax',
			]
		);
	}

	/**
	 * Apply the WordPress locale filter for the given language.
	 *
	 * Removes any previously applied locale filter before adding a new one,
	 * preventing filter stacking when switching languages multiple times.
	 */
	private function applyLocale(string $langCode): void
	{
		if ($this->localeFilter !== null) {
			remove_filter('locale', $this->localeFilter);
			$this->localeFilter = null;
		}

		$langManager = LanguageManager::load();
		$defaultCode = $langManager->getDefaultLanguageCode();

		if ($langCode === $defaultCode) {
			return;
		}

		$languages = $langManager->getActiveLanguages();

		if (!isset($languages[$langCode])) {
			return;
		}

		$locale = $languages[$langCode]->locale;
		$this->localeFilter = fn(): string => $locale;
		add_filter('locale', $this->localeFilter);
	}
}
