<?php

declare(strict_types=1);

namespace Signocore\Multilanguage\Features;

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

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

final class UrlRewriting
{
	use Singleton;

	/** @var string Original REQUEST_URI before language prefix stripping. */
	private static string $originalRequestUri = '';

	protected function init(): void
	{
		$strategy = get_option(Constants::OPTION_PREFIX . 'url_strategy', Constants::URL_STRATEGY_DIRECTORY);

		if ($strategy === Constants::URL_STRATEGY_DIRECTORY) {
			// Strip language prefix from URI before WordPress parses rewrite rules.
			// Priority 3: after LanguageDetection (1), before PermalinkTranslation (4).
			add_action('init', [$this, 'stripLanguagePrefix'], 3);
			add_action('init', [$this, 'addRewriteRules'], 5);
			add_filter('query_vars', [$this, 'addQueryVars']);
		}

		// Filter permalinks to include language prefix
		add_filter('post_link', [$this, 'filterPostLink'], 10, 2);
		add_filter('page_link', [$this, 'filterPageLink'], 10, 2);
		add_filter('post_type_link', [$this, 'filterPostTypeLink'], 10, 2);
		add_filter('term_link', [$this, 'filterTermLink'], 10, 3);
		add_filter('home_url', [$this, 'filterHomeUrl'], 10, 4);

		// Flush rewrite rules when languages or URL strategy change
		add_action('signocore_ml_languages_changed', [$this, 'flushRules']);
		add_action('update_option_' . Constants::OPTION_PREFIX . 'url_strategy', [$this, 'flushRules']);
	}

	/**
	 * Get the original REQUEST_URI before language prefix stripping.
	 */
	public static function getOriginalRequestUri(): string
	{
		return self::$originalRequestUri;
	}

	/**
	 * Strip the language directory prefix from REQUEST_URI so WordPress's
	 * built-in rewrite rules can match archives, categories, tags, etc.
	 *
	 * Runs at init priority 3 — after language detection, before permalink slug translation.
	 */
	public function stripLanguagePrefix(): void
	{
		if (is_admin()) {
			return;
		}

		$currentLang = LanguageDetection::load()->getCurrentLanguage();
		$langManager = LanguageManager::load();
		$defaultCode = $langManager->getDefaultLanguageCode();
		$hideDefault = (bool) get_option(Constants::OPTION_PREFIX . 'hide_default_language_prefix', '1');

		if ($hideDefault && $currentLang === $defaultCode) {
			return;
		}

		$prefix = '/' . $currentLang;
		$uri = $_SERVER['REQUEST_URI'] ?? '';

		if (str_starts_with($uri, $prefix . '/') || $uri === $prefix) {
			self::$originalRequestUri = $uri;
			$_SERVER['REQUEST_URI'] = substr($uri, strlen($prefix)) ?: '/';
		}
	}

	/**
	 * Add rewrite rules for directory-based language URLs.
	 */
	public function addRewriteRules(): void
	{
		$langManager = LanguageManager::load();
		$languages = $langManager->getActiveLanguages();
		$defaultCode = $langManager->getDefaultLanguageCode();
		$hideDefault = (bool) get_option(Constants::OPTION_PREFIX . 'hide_default_language_prefix', '1');

		$codes = [];
		foreach ($languages as $lang) {
			if ($hideDefault && $lang->code === $defaultCode) {
				continue;
			}
			$codes[] = preg_quote($lang->code, '/');
		}

		if (empty($codes)) {
			return;
		}

		$pattern = implode('|', $codes);

		// Match language prefix alone (homepage in that language).
		// Other URLs are handled by stripping the language prefix from REQUEST_URI
		// (in stripLanguagePrefix) so WordPress's built-in rewrite rules match.
		add_rewrite_rule(
			'^(' . $pattern . ')/?$',
			'index.php?sml_lang=$matches[1]',
			'top'
		);
	}

	/**
	 * Register the sml_lang query variable.
	 *
	 * @param string[] $vars
	 * @return string[]
	 */
	public function addQueryVars(array $vars): array
	{
		$vars[] = 'sml_lang';
		return $vars;
	}

	/**
	 * Filter post permalinks to include language prefix.
	 */
	public function filterPostLink(string $permalink, WP_Post $post): string
	{
		return $this->addLanguagePrefixToUrl($permalink, $this->getPostLanguage((int) $post->ID));
	}

	/**
	 * Filter page links to include language prefix.
	 *
	 * @param string $link
	 * @param int|WP_Post $post
	 */
	public function filterPageLink(string $link, $post): string
	{
		$postId = $post instanceof WP_Post ? (int) $post->ID : (int) $post;
		return $this->addLanguagePrefixToUrl($link, $this->getPostLanguage($postId));
	}

	/**
	 * Filter custom post type links to include language prefix.
	 */
	public function filterPostTypeLink(string $permalink, WP_Post $post): string
	{
		return $this->addLanguagePrefixToUrl($permalink, $this->getPostLanguage((int) $post->ID));
	}

	/**
	 * Filter term links to include language prefix.
	 */
	public function filterTermLink(string $termlink, object $term, string $taxonomy): string
	{
		$langCode = $this->getTermLanguage((int) $term->term_id, $taxonomy);
		return $this->addLanguagePrefixToUrl($termlink, $langCode);
	}

	/**
	 * Filter home_url to include language prefix for current language.
	 */
	public function filterHomeUrl(string $url, string $path, ?string $scheme, ?int $blogId): string
	{
		if (is_admin()) {
			return $url;
		}

		$currentLang = LanguageDetection::load()->getCurrentLanguage();
		return $this->addLanguagePrefixToUrl($url, $currentLang);
	}

	/**
	 * Add language prefix to a URL based on the current strategy.
	 */
	private function addLanguagePrefixToUrl(string $url, string $langCode): string
	{
		if ($langCode === '') {
			return $url;
		}

		$strategy = get_option(Constants::OPTION_PREFIX . 'url_strategy', Constants::URL_STRATEGY_DIRECTORY);

		if ($strategy === Constants::URL_STRATEGY_DOMAIN) {
			return $this->switchDomain($url, $langCode);
		}

		if ($strategy === Constants::URL_STRATEGY_SUBDOMAIN) {
			return $this->switchSubdomain($url, $langCode);
		}

		// Directory strategy
		$langManager = LanguageManager::load();
		$defaultCode = $langManager->getDefaultLanguageCode();
		$hideDefault = (bool) get_option(Constants::OPTION_PREFIX . 'hide_default_language_prefix', '1');

		if ($hideDefault && $langCode === $defaultCode) {
			return $url;
		}

		return $this->addDirectoryPrefix($url, $langCode);
	}

	/**
	 * Add directory prefix (e.g., /da/) to a URL.
	 */
	private function addDirectoryPrefix(string $url, string $langCode): string
	{
		$parsed = wp_parse_url($url);
		if (!is_array($parsed) || !isset($parsed['host'])) {
			return $url;
		}

		// Use get_option('home') directly to avoid infinite recursion
		// since this method is called from inside the home_url filter.
		$homeUrl = get_option('home');
		$homeParsed = wp_parse_url($homeUrl);
		$homePath = rtrim($homeParsed['path'] ?? '', '/');

		$path = $parsed['path'] ?? '/';

		// Check if the language prefix is already in the URL
		$prefixPattern = $homePath . '/' . $langCode . '/';
		if (str_starts_with($path, $prefixPattern) || $path === $homePath . '/' . $langCode) {
			return $url;
		}

		// Insert language code after the home path
		$relativePath = substr($path, strlen($homePath));
		$newPath = $homePath . '/' . $langCode . $relativePath;

		$scheme = ($parsed['scheme'] ?? 'https') . '://';
		$host = $parsed['host'];
		$port = isset($parsed['port']) ? ':' . $parsed['port'] : '';
		$query = isset($parsed['query']) ? '?' . $parsed['query'] : '';
		$fragment = isset($parsed['fragment']) ? '#' . $parsed['fragment'] : '';

		return $scheme . $host . $port . $newPath . $query . $fragment;
	}

	/**
	 * Switch the host in a URL to the domain configured for the given language.
	 */
	private function switchDomain(string $url, string $langCode): string
	{
		$lang = LanguageRepository::getByCode($langCode);
		if ($lang === null || empty($lang->domain)) {
			return $url;
		}

		$parsed = wp_parse_url($url);
		if (!is_array($parsed) || !isset($parsed['host'])) {
			return $url;
		}

		if ($parsed['host'] === $lang->domain) {
			return $url;
		}

		$scheme = ($parsed['scheme'] ?? 'https') . '://';
		$path = $parsed['path'] ?? '/';
		$port = isset($parsed['port']) ? ':' . $parsed['port'] : '';
		$query = isset($parsed['query']) ? '?' . $parsed['query'] : '';
		$fragment = isset($parsed['fragment']) ? '#' . $parsed['fragment'] : '';

		return $scheme . $lang->domain . $port . $path . $query . $fragment;
	}

	/**
	 * Switch the host in a URL to the subdomain for the given language.
	 */
	private function switchSubdomain(string $url, string $langCode): string
	{
		$parsed = wp_parse_url($url);
		if (!is_array($parsed) || !isset($parsed['host'])) {
			return $url;
		}

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

		// Use get_option('home') directly to avoid infinite recursion from home_url filter
		$siteHost = (string) wp_parse_url(get_option('home'), PHP_URL_HOST);
		if ($siteHost === '') {
			return $url;
		}

		$scheme = ($parsed['scheme'] ?? 'https') . '://';
		$path = $parsed['path'] ?? '/';
		$port = isset($parsed['port']) ? ':' . $parsed['port'] : '';
		$query = isset($parsed['query']) ? '?' . $parsed['query'] : '';
		$fragment = isset($parsed['fragment']) ? '#' . $parsed['fragment'] : '';

		if ($langCode === $defaultCode) {
			return $scheme . $siteHost . $port . $path . $query . $fragment;
		}

		return $scheme . $langCode . '.' . $siteHost . $port . $path . $query . $fragment;
	}

	/**
	 * Get the language code for a post.
	 */
	private function getPostLanguage(int $postId): string
	{
		global $wpdb;

		$table = $wpdb->prefix . Constants::TABLE_TRANSLATIONS;
		$langCode = $wpdb->get_var($wpdb->prepare(
			"SELECT language_code FROM `{$table}` WHERE element_id = %d AND element_type LIKE 'post_%%' LIMIT 1",
			$postId
		));

		return is_string($langCode) ? $langCode : LanguageDetection::load()->getCurrentLanguage();
	}

	/**
	 * Get the language code for a term.
	 */
	private function getTermLanguage(int $termId, string $taxonomy): string
	{
		global $wpdb;

		$table = $wpdb->prefix . Constants::TABLE_TRANSLATIONS;
		$elementType = 'tax_' . $taxonomy;

		$langCode = $wpdb->get_var($wpdb->prepare(
			"SELECT language_code FROM `{$table}` WHERE element_id = %d AND element_type = %s LIMIT 1",
			$termId,
			$elementType
		));

		return is_string($langCode) ? $langCode : LanguageDetection::load()->getCurrentLanguage();
	}

	/**
	 * Flush rewrite rules.
	 */
	public function flushRules(): void
	{
		flush_rewrite_rules();
	}
}
