<?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\Traits\Singleton;
use WP_Post;

final class PermalinkTranslation
{
	use Singleton;

	private const OPTION_KEY = 'permalink_slugs';

	/** @var array<string, array<string, string>>|null */
	private ?array $slugCache = null;

	protected function init(): void
	{
		// Rewrite incoming URI before WordPress matches rewrite rules.
		// Priority 4: after LanguageDetection (1), before UrlRewriting (5).
		add_action('init', [$this, 'rewriteIncomingUri'], 4);

		// Filter permalink output to use translated slugs (after UrlRewriting at priority 10).
		add_filter('post_link', [$this, 'translatePostPermalink'], 20, 2);
		add_filter('post_type_link', [$this, 'translatePostTypePermalink'], 20, 2);
		add_filter('term_link', [$this, 'translateTermPermalink'], 20, 3);
		add_filter('author_link', [$this, 'translateAuthorPermalink'], 20, 1);

		// Flush rules when slug translations change.
		add_action('update_option_' . Constants::OPTION_PREFIX . self::OPTION_KEY, [$this, 'scheduleFlush']);
		add_action('add_option_' . Constants::OPTION_PREFIX . self::OPTION_KEY, [$this, 'scheduleFlush']);
	}

	// -------------------------------------------------------------------------
	// Incoming request: swap translated slugs back to originals
	// -------------------------------------------------------------------------

	/**
	 * Rewrite $_SERVER['REQUEST_URI'] to replace translated slugs with originals.
	 *
	 * Runs at init priority 4 — after language detection, before rewrite rule matching.
	 * Works for all URL strategies (directory, subdomain, domain).
	 */
	public function rewriteIncomingUri(): void
	{
		if (is_admin()) {
			return;
		}

		$currentLang = LanguageDetection::load()->getCurrentLanguage();
		$translations = $this->getSlugTranslations()[$currentLang] ?? [];

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

		// Build reverse map: translated slug => original slug
		$reverseMap = [];

		foreach ($translations as $slugKey => $translatedSlug) {
			if ($translatedSlug === '' || !$this->isSlugTranslationAllowed($slugKey)) {
				continue;
			}

			$originalSlug = $this->getOriginalSlug($slugKey);

			if ($originalSlug !== '' && $originalSlug !== $translatedSlug) {
				$reverseMap[$translatedSlug] = $originalSlug;
			}
		}

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

		$uri = $_SERVER['REQUEST_URI'] ?? '';
		$queryString = '';

		// Separate path from query string
		$queryPos = strpos($uri, '?');
		if ($queryPos !== false) {
			$queryString = substr($uri, $queryPos);
			$uri = substr($uri, 0, $queryPos);
		}

		$hadTrailingSlash = str_ends_with($uri, '/');

		$parts = explode('/', trim($uri, '/'));

		if (empty($parts) || $parts[0] === '') {
			return;
		}

		// Determine start index (skip language prefix for directory strategy)
		$startIndex = $this->getStartIndex($parts, $currentLang);

		// Translate structural URL segments (front, base slugs).
		// Stop at the first non-matching segment to avoid translating content slugs.
		$changed = false;

		for ($i = $startIndex, $count = count($parts); $i < $count; $i++) {
			$seg = $parts[$i];
			if ($seg !== '' && isset($reverseMap[$seg])) {
				$parts[$i] = $reverseMap[$seg];
				$changed = true;
			} else {
				break;
			}
		}

		if (!$changed) {
			return;
		}

		$trail = $hadTrailingSlash ? '/' : '';
		$_SERVER['REQUEST_URI'] = '/' . implode('/', $parts) . $trail . $queryString;
	}

	/**
	 * Get the start index for path segments (skips language prefix for directory strategy).
	 *
	 * @param string[] $parts
	 */
	private function getStartIndex(array $parts, string $currentLang): int
	{
		$strategy = get_option(Constants::OPTION_PREFIX . 'url_strategy', Constants::URL_STRATEGY_DIRECTORY);

		if ($strategy !== Constants::URL_STRATEGY_DIRECTORY) {
			return 0;
		}

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

		// Default language with hidden prefix: no language segment to skip
		if ($hideDefault && $currentLang === $defaultCode) {
			return 0;
		}

		// First segment should be the language code
		if (($parts[0] ?? '') === $currentLang) {
			return 1;
		}

		return 0;
	}

	// -------------------------------------------------------------------------
	// Permalink output filters
	// -------------------------------------------------------------------------

	/**
	 * Translate base slug in post permalinks (built-in 'post' type).
	 */
	public function translatePostPermalink(string $permalink, WP_Post $post): string
	{
		if ($post->post_type !== 'post') {
			return $permalink;
		}

		$langCode = $this->getPostLanguage((int) $post->ID);

		// Post type 'post' always uses with_front, so translate front
		$permalink = $this->replaceBaseSlug($permalink, 'front', $langCode);

		return $this->replaceBaseSlug($permalink, 'post:post', $langCode);
	}

	/**
	 * Translate base slug in custom post type permalinks.
	 */
	public function translatePostTypePermalink(string $permalink, WP_Post $post): string
	{
		$langCode = $this->getPostLanguage((int) $post->ID);
		$ptObject = get_post_type_object($post->post_type);

		// Only translate front if the CPT uses with_front
		if ($ptObject !== null && !empty($ptObject->rewrite['with_front'])) {
			$permalink = $this->replaceBaseSlug($permalink, 'front', $langCode);
		}

		return $this->replaceBaseSlug($permalink, 'post:' . $post->post_type, $langCode);
	}

	/**
	 * Translate base slug in term links.
	 */
	public function translateTermPermalink(string $termlink, object $term, string $taxonomy): string
	{
		$slugKey = match ($taxonomy) {
			'category' => 'category',
			'post_tag' => 'tag',
			default => 'tax:' . $taxonomy,
		};

		$langCode = $this->getTermLanguage((int) $term->term_id, $taxonomy);

		// Translate front if the taxonomy uses with_front
		$taxObject = get_taxonomy($taxonomy);
		if ($taxObject !== false && !empty($taxObject->rewrite['with_front'])) {
			$termlink = $this->replaceBaseSlug($termlink, 'front', $langCode);
		}

		return $this->replaceBaseSlug($termlink, $slugKey, $langCode);
	}

	/**
	 * Translate base slug in author links.
	 */
	public function translateAuthorPermalink(string $link): string
	{
		if (is_admin()) {
			return $link;
		}

		$langCode = LanguageDetection::load()->getCurrentLanguage();

		// Author always uses with_front in WordPress
		$link = $this->replaceBaseSlug($link, 'front', $langCode);

		return $this->replaceBaseSlug($link, 'author', $langCode);
	}

	/**
	 * Replace a base slug in a URL with its translated version.
	 */
	private function replaceBaseSlug(string $url, string $slugKey, string $langCode): string
	{
		if ($langCode === '') {
			return $url;
		}

		if (!$this->isSlugTranslationAllowed($slugKey)) {
			return $url;
		}

		$translations = $this->getSlugTranslations()[$langCode] ?? [];
		$translatedSlug = $translations[$slugKey] ?? '';

		if ($translatedSlug === '') {
			return $url;
		}

		$originalSlug = $this->getOriginalSlug($slugKey);

		if ($originalSlug === '' || $originalSlug === $translatedSlug) {
			return $url;
		}

		return str_replace('/' . $originalSlug . '/', '/' . $translatedSlug . '/', $url);
	}

	// -------------------------------------------------------------------------
	// License gating
	// -------------------------------------------------------------------------

	/**
	 * Check if slug translation is allowed for the given slug key.
	 */
	private function isSlugTranslationAllowed(string $slugKey): bool
	{
		$freeKeys = ['front', 'category', 'tag', 'author', 'post:post', 'post:page'];

		if (in_array($slugKey, $freeKeys, true)) {
			return true;
		}

		$wooKeys = ['post:product', 'tax:product_cat', 'tax:product_tag'];

		if (in_array($slugKey, $wooKeys, true)) {
			return LicenseManager::load()->can(Entitlements::WOOCOMMERCE);
		}

		return LicenseManager::load()->can(Entitlements::PERMALINK_TRANSLATION);
	}

	// -------------------------------------------------------------------------
	// Helpers
	// -------------------------------------------------------------------------

	/**
	 * Get all slug translations from the option.
	 *
	 * @return array<string, array<string, string>>
	 */
	public function getSlugTranslations(): array
	{
		if ($this->slugCache !== null) {
			return $this->slugCache;
		}

		$this->slugCache = (array) get_option(Constants::OPTION_PREFIX . self::OPTION_KEY, []);

		return $this->slugCache;
	}

	/**
	 * Get the original WordPress slug for a slug key.
	 */
	private function getOriginalSlug(string $slugKey): string
	{
		if ($slugKey === 'front') {
			return trim((string) ($GLOBALS['wp_rewrite']->front ?? '/'), '/');
		}

		if ($slugKey === 'category') {
			return get_option('category_base') ?: 'category';
		}

		if ($slugKey === 'tag') {
			return get_option('tag_base') ?: 'tag';
		}

		if ($slugKey === 'author') {
			return 'author';
		}

		if (str_starts_with($slugKey, 'post:')) {
			$postType = substr($slugKey, 5);
			$ptObject = get_post_type_object($postType);

			if ($ptObject === null) {
				return '';
			}

			return trim($ptObject->rewrite['slug'] ?? $postType, '/');
		}

		if (str_starts_with($slugKey, 'tax:')) {
			$taxonomy = substr($slugKey, 4);
			$taxObject = get_taxonomy($taxonomy);

			if ($taxObject === false) {
				return '';
			}

			return trim($taxObject->rewrite['slug'] ?? $taxonomy, '/');
		}

		return '';
	}

	/**
	 * 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();
	}

	/**
	 * Get all translatable slug definitions for the settings UI.
	 *
	 * @return array<string, array{label: string, slug: string, pro: bool}>
	 */
	public static function getTranslatableSlugs(): array
	{
		$slugs = [];

		// Permalink structure front (e.g. "blog" from /blog/%category%/%postname%/)
		$front = trim((string) ($GLOBALS['wp_rewrite']->front ?? '/'), '/');

		if ($front !== '') {
			$slugs['front'] = [
				'label' => __('Permalink front', 'signocore-multilanguage'),
				'slug' => $front,
				'pro' => false,
			];
		}

		// WordPress core bases
		$categoryBase = get_option('category_base') ?: 'category';
		$tagBase = get_option('tag_base') ?: 'tag';

		$slugs['category'] = [
			'label' => __('Category base', 'signocore-multilanguage'),
			'slug' => $categoryBase,
			'pro' => false,
		];

		$slugs['tag'] = [
			'label' => __('Tag base', 'signocore-multilanguage'),
			'slug' => $tagBase,
			'pro' => false,
		];

		$slugs['author'] = [
			'label' => __('Author base', 'signocore-multilanguage'),
			'slug' => 'author',
			'pro' => false,
		];

		// Post types with rewrite slugs
		$postTypes = get_post_types(['public' => true, '_builtin' => false], 'objects');

		foreach ($postTypes as $pt) {
			if (!isset($pt->rewrite['slug'])) {
				continue;
			}

			// WooCommerce product is handled separately
			if ($pt->name === 'product') {
				continue;
			}

			$slugs['post:' . $pt->name] = [
				'label' => sprintf('%s (%s)', $pt->labels->name, $pt->name),
				'slug' => trim($pt->rewrite['slug'], '/'),
				'pro' => true,
			];
		}

		// Custom taxonomies with rewrite slugs (excluding built-in)
		$taxonomies = get_taxonomies(['public' => true, '_builtin' => false], 'objects');

		foreach ($taxonomies as $tax) {
			if (!isset($tax->rewrite['slug'])) {
				continue;
			}

			// WooCommerce taxonomies are handled separately
			if (in_array($tax->name, ['product_cat', 'product_tag'], true)) {
				continue;
			}

			$slugs['tax:' . $tax->name] = [
				'label' => sprintf('%s (%s)', $tax->labels->name, $tax->name),
				'slug' => trim($tax->rewrite['slug'], '/'),
				'pro' => true,
			];
		}

		/**
		 * Filter the translatable permalink slugs.
		 *
		 * @param array<string, array{label: string, slug: string, pro: bool}> $slugs
		 */
		return (array) apply_filters('signocore_ml_translatable_permalink_slugs', $slugs);
	}

	/**
	 * Flush rewrite rules when permalink slug translations change.
	 */
	public function scheduleFlush(): void
	{
		$this->slugCache = null;
		flush_rewrite_rules();
	}
}
