<?php
namespace TeknoTok\AiTranslator\Frontend;

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

/**
 * Controller to manage dynamic permalinks and subfolder rewrite rules.
 */
class UrlManager {
	/**
	 * Constructor. Registers hooks.
	 */
	public function __construct() {
		add_filter( 'rewrite_rules_array', array( $this, 'filter_rewrite_rules' ) );
		add_filter( 'query_vars', array( $this, 'register_query_vars' ) );
		add_filter( 'request', array( $this, 'normalize_static_posts_page_request' ) );

		add_action( 'update_option_show_on_front', array( $this, 'flush_rewrite_rules' ) );
		add_action( 'update_option_page_on_front', array( $this, 'flush_rewrite_rules' ) );
		add_action( 'update_option_page_for_posts', array( $this, 'flush_rewrite_rules' ) );

		if ( ! is_admin() ) {
			add_filter( 'post_link', array( $this, 'prefix_permalink' ), 10, 2 );
			add_filter( 'page_link', array( $this, 'prefix_permalink' ), 10, 2 );
			add_filter( 'post_type_link', array( $this, 'prefix_permalink' ), 10, 2 );
			add_filter( 'term_link', array( $this, 'prefix_term_permalink' ), 10, 3 );
			add_filter( 'home_url', array( $this, 'prefix_home_url' ), 10, 4 );
			add_filter( 'woocommerce_product_get_permalink', array( $this, 'prefix_wc_product_permalink' ), 10, 2 );
		}
	}

	/**
	 * Filter rewrite rules to add language prefix rules.
	 *
	 * @param array $rules Existing rewrite rules.
	 * @return array Filtered rewrite rules.
	 */
	public function filter_rewrite_rules( $rules ) {
		$settings = get_option( 'teknotok_ait_settings', array() );
		$targets = $settings['target_languages'] ?? array();
		if ( empty( $targets ) ) {
			return $rules;
		}

		$languages_regex = implode( '|', array_map( 'preg_quote', $targets ) );
		$new_rules = array();

		// Add home page rule for languages (respect static front page settings)
		$page_on_front = get_option( 'page_on_front' );
		if ( 'page' === get_option( 'show_on_front' ) && $page_on_front ) {
			$new_rules['^(' . $languages_regex . ')/?$'] = 'index.php?page_id=' . intval( $page_on_front ) . '&lang=$matches[1]';
		} else {
			$new_rules['^(' . $languages_regex . ')/?$'] = 'index.php?lang=$matches[1]';
		}

		$page_for_posts = (int) get_option( 'page_for_posts' );
		if ( $page_for_posts ) {
			$posts_page_slug = get_post_field( 'post_name', $page_for_posts );
			if ( ! empty( $posts_page_slug ) ) {
				$posts_page_regex = preg_quote( $posts_page_slug, '#' );
				$new_rules['^(' . $languages_regex . ')/' . $posts_page_regex . '/page/([0-9]+)/?$'] = 'index.php?page_id=' . $page_for_posts . '&paged=$matches[2]&lang=$matches[1]';
				$new_rules['^(' . $languages_regex . ')/' . $posts_page_regex . '/?$'] = 'index.php?page_id=' . $page_for_posts . '&lang=$matches[1]';
			}
		}

		foreach ( $rules as $regex => $redirect ) {
			// Prefix the regex with the language code capture group
			$new_regex = '^(' . $languages_regex . ')/' . ltrim( $regex, '^' );

			// Shift all matches in the redirect string because we added a new capture group at the beginning
			$new_redirect = preg_replace_callback(
				'/([\$\b]matches\[)([0-9]+)(\])/i',
				function( $match ) {
					return $match[1] . ( (int) $match[2] + 1 ) . $match[3];
				},
				$redirect
			);

			// Append the lang query parameter
			if ( strpos( $new_redirect, '?' ) !== false ) {
				$new_redirect .= '&lang=$matches[1]';
			} else {
				$new_redirect .= '?lang=$matches[1]';
			}

			$new_rules[ $new_regex ] = $new_redirect;
		}

		// Merge new rules on top so they are evaluated first
		return $new_rules + $rules;
	}

	/**
	 * Register plugin query variables used by language-prefixed rewrite rules.
	 *
	 * @param array $query_vars Public query variables.
	 * @return array Filtered query variables.
	 */
	public function register_query_vars( $query_vars ) {
		$query_vars[] = 'lang';

		return array_values( array_unique( $query_vars ) );
	}

	/**
	 * Normalize legacy translated posts-page requests before WP_Query runs.
	 *
	 * Older rewrite rules used pagename for language-prefixed static posts pages.
	 * Using the posts page ID keeps paginated blog archives in the canonical
	 * WordPress posts-page query path even before permalink rules are flushed.
	 *
	 * @param array $query_vars Parsed request variables.
	 * @return array Normalized request variables.
	 */
	public function normalize_static_posts_page_request( $query_vars ) {
		$page_for_posts = (int) get_option( 'page_for_posts' );
		if ( $page_for_posts <= 0 || empty( $query_vars['pagename'] ) ) {
			return $query_vars;
		}

		$posts_page_slug = get_post_field( 'post_name', $page_for_posts );
		if ( empty( $posts_page_slug ) || $posts_page_slug !== $query_vars['pagename'] ) {
			return $query_vars;
		}

		$query_vars['page_id'] = $page_for_posts;
		unset( $query_vars['pagename'] );

		return $query_vars;
	}

	/**
	 * Flush rewrite rules after WordPress reading settings change.
	 *
	 * @return void
	 */
	public function flush_rewrite_rules() {
		flush_rewrite_rules();
	}

	/**
	 * Helper to recursively clean any language prefix from a relative path.
	 *
	 * @param string $path    Relative path.
	 * @param array  $targets List of target languages.
	 * @return string Cleaned relative path.
	 */
	public static function clean_path_from_language_prefix( $path, $targets ) {
		$path = ltrim( $path, '/' );
		if ( empty( $targets ) ) {
			return $path;
		}

		// Filter targets to ensure only valid, non-empty codes are used
		$targets = array_filter( array_map( 'trim', $targets ) );
		if ( empty( $targets ) ) {
			return $path;
		}

		$languages_regex = implode( '|', array_map( 'preg_quote', $targets ) );

		// Guard: If the regex matches empty string, do not run the loop to prevent infinite hang
		if ( preg_match( '#^(' . $languages_regex . ')(/|$)#i', '' ) ) {
			return $path;
		}

		$max_iterations = 10;
		$iterations = 0;
		while ( preg_match( '#^(' . $languages_regex . ')(/|$)#i', $path ) && $iterations < $max_iterations ) {
			$iterations++;
			$previous_path = $path;
			$path = preg_replace( '#^(' . $languages_regex . ')(/|$)#i', '', $path );
			$path = ltrim( $path, '/' );
			if ( $path === $previous_path ) {
				break;
			}
		}
		return $path;
	}

	/**
	 * Internal helper to prefix any local URL with the active language code.
	 *
	 * @param string $url         Original URL.
	 * @param string $active_lang Active language code.
	 * @param array  $settings    Plugin settings.
	 * @return string Prefixed URL.
	 */
	private function prefix_url( $url, $active_lang, $settings ) {
		return self::localize_internal_url( $url, $active_lang, $settings );
	}

	/**
	 * Prefix a same-site frontend URL with the requested target language.
	 *
	 * @param string $url         Original URL.
	 * @param string $active_lang Active language code.
	 * @param array  $settings    Plugin settings.
	 * @return string Localized URL.
	 */
	public static function localize_internal_url( $url, $active_lang, $settings = null ) {
		$url = (string) $url;
		if ( '' === trim( $url ) ) {
			return $url;
		}

		if ( null === $settings ) {
			$settings = get_option( 'teknotok_ait_settings', array() );
		}

		$source = $settings['source_language'] ?? 'en';
		$targets = $settings['target_languages'] ?? array();
		$active_lang = sanitize_key( $active_lang );

		if ( empty( $active_lang ) || $active_lang === $source || ! in_array( $active_lang, $targets, true ) ) {
			return $url;
		}

		if ( preg_match( '~^(?:[a-z][a-z0-9+.-]*:|//|#)~i', $url ) && ! preg_match( '~^https?://~i', $url ) ) {
			return $url;
		}

		$parts = parse_url( html_entity_decode( $url, ENT_QUOTES, 'UTF-8' ) );
		if ( false === $parts ) {
			return $url;
		}

		$home = rtrim( get_option( 'home' ), '/' );

		// Adjust scheme if the request is HTTPS.
		$forwarded_proto = isset( $_SERVER['HTTP_X_FORWARDED_PROTO'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_X_FORWARDED_PROTO'] ) ) : '';
		$is_https = is_ssl() || 'https' === strtolower( $forwarded_proto );
		if ( $is_https ) {
			$home = str_replace( 'http://', 'https://', $home );
			$url  = str_replace( 'http://', 'https://', $url );
		}

		$home_parts = parse_url( $home );
		$home_path  = isset( $home_parts['path'] ) ? rtrim( (string) $home_parts['path'], '/' ) : '';
		$is_absolute = ! empty( $parts['host'] );
		$path = isset( $parts['path'] ) ? (string) $parts['path'] : '/';

		if ( $is_absolute ) {
			if ( empty( $home_parts['host'] ) || strtolower( (string) $parts['host'] ) !== strtolower( (string) $home_parts['host'] ) ) {
				return $url;
			}
		} else {
			if ( '' === $path || '/' !== $path[0] ) {
				return $url;
			}
		}

		if ( ! self::is_localizable_frontend_path( $path, $home_path, $targets, isset( $parts['query'] ) ? (string) $parts['query'] : '' ) ) {
			return $url;
		}

		if ( '' !== $home_path && 0 === strpos( $path, $home_path ) ) {
			$path = substr( $path, strlen( $home_path ) );
		}

		$path = self::clean_path_from_language_prefix( $path, $targets );
		$localized_path = '/' . $active_lang . '/' . ltrim( $path, '/' );
		if ( '' !== $home_path ) {
			$localized_path = $home_path . $localized_path;
		}

		if ( isset( $parts['query'] ) && '' !== $parts['query'] ) {
			$localized_path .= '?' . $parts['query'];
		}

		if ( isset( $parts['fragment'] ) && '' !== $parts['fragment'] ) {
			$localized_path .= '#' . $parts['fragment'];
		}

		if ( ! $is_absolute ) {
			return esc_url( $localized_path );
		}

		$scheme = isset( $parts['scheme'] ) ? (string) $parts['scheme'] : ( $is_https ? 'https' : 'http' );
		$port = isset( $parts['port'] ) ? ':' . (int) $parts['port'] : '';

		return esc_url( $scheme . '://' . $parts['host'] . $port . $localized_path );
	}

	/**
	 * Check whether a same-site URL path can be safely language-prefixed.
	 *
	 * This intentionally avoids UrlGuard because UrlGuard calls home_url(), and
	 * this URL manager also runs inside the home_url filter.
	 *
	 * @param string $path      URL path.
	 * @param string $home_path WordPress home path for subdirectory installs.
	 * @param array  $targets   Target language codes.
	 * @param string $query     URL query string.
	 * @return bool True when the path is a public frontend URL.
	 */
	private static function is_localizable_frontend_path( $path, $home_path, $targets, $query = '' ) {
		if ( false !== stripos( $query, 'rest_route=' ) ) {
			return false;
		}

		if ( '' !== $home_path && 0 === strpos( $path, $home_path ) ) {
			$path = substr( $path, strlen( $home_path ) );
		}

		$path = '/' . ltrim( self::clean_path_from_language_prefix( $path, $targets ), '/' );
		$path = strtolower( $path );

		$blocked_prefixes = array(
			'/wp-admin',
			'/wp-login.php',
			'/wp-json',
			'/wp-cron.php',
			'/wp-content',
			'/wp-includes',
			'/xmlrpc.php',
			'/wp-admin/admin-ajax.php',
		);

		foreach ( $blocked_prefixes as $prefix ) {
			if ( 0 === strpos( $path, $prefix ) ) {
				return false;
			}
		}

		return true;
	}

	/**
	 * Localize same-site frontend href attributes in rendered HTML.
	 *
	 * @param string $html        Rendered HTML.
	 * @param string $active_lang Active language code.
	 * @return string HTML with localized internal links.
	 */
	public static function localize_internal_links_in_html( $html, $active_lang ) {
		if ( empty( $html ) || false === stripos( $html, 'href=' ) ) {
			return $html;
		}

		$settings = get_option( 'teknotok_ait_settings', array() );

		return preg_replace_callback(
			'/\bhref\s*=\s*(["\'])(.*?)\1/i',
			function( $matches ) use ( $active_lang, $settings ) {
				$localized = self::localize_internal_url( $matches[2], $active_lang, $settings );

				return 'href=' . $matches[1] . esc_url( $localized ) . $matches[1];
			},
			$html
		);
	}

	/**
	 * Overwrite permalinks dynamically for posts, pages, and custom post types.
	 *
	 * @param string $url  Original URL.
	 * @param object $post Post object.
	 * @return string Modified URL.
	 */
	public function prefix_permalink( $url, $post ) {
		$active_lang = \TeknoTok\AiTranslator\Plugin::get_active_language();
		$settings = get_option( 'teknotok_ait_settings', array() );
		return $this->prefix_url( $url, $active_lang, $settings );
	}

	/**
	 * Overwrite WooCommerce product permalinks.
	 *
	 * @param string $url     Original permalink.
	 * @param object $product WC Product object.
	 * @return string Modified permalink.
	 */
	public function prefix_wc_product_permalink( $url, $product ) {
		$active_lang = \TeknoTok\AiTranslator\Plugin::get_active_language();
		$settings = get_option( 'teknotok_ait_settings', array() );
		return $this->prefix_url( $url, $active_lang, $settings );
	}

	/**
	 * Overwrite taxonomy term links.
	 *
	 * @param string $url      Original link.
	 * @param object $term     Term object.
	 * @param string $taxonomy Taxonomy name.
	 * @return string Modified link.
	 */
	public function prefix_term_permalink( $url, $term, $taxonomy ) {
		$active_lang = \TeknoTok\AiTranslator\Plugin::get_active_language();
		$settings = get_option( 'teknotok_ait_settings', array() );
		return $this->prefix_url( $url, $active_lang, $settings );
	}

	/**
	 * Prefix home URL checks.
	 *
	 * @param string      $url
	 * @param string      $path
	 * @param string|null $scheme
	 * @param int|null    $blog_id
	 * @return string
	 */
	public function prefix_home_url( $url, $path, $scheme, $blog_id ) {
		if ( empty( $path ) || '/' === $path ) {
			$active_lang = \TeknoTok\AiTranslator\Plugin::get_active_language();
			$settings = get_option( 'teknotok_ait_settings', array() );
			return $this->prefix_url( $url, $active_lang, $settings );
		}

		return $url;
	}

	/**
	 * Get the URL of the current request for a specific language.
	 *
	 * @param string $lang Target language code.
	 * @return string Target URL.
	 */
	public static function get_clean_url_for_language( $lang ) {
		$settings = get_option( 'teknotok_ait_settings', array() );
		$source = $settings['source_language'] ?? 'en';
		$targets = $settings['target_languages'] ?? array();

		// Get the actual browser request path relative to home. WordPress can map
		// static front-page requests through the posts-page query context, so do
		// not rely on $wp->request when REQUEST_URI is available.
		$path = '';
		if ( isset( $_SERVER['REQUEST_URI'] ) ) {
			$request_uri = esc_url_raw( wp_unslash( $_SERVER['REQUEST_URI'] ) );
			$path = trim( (string) parse_url( $request_uri, PHP_URL_PATH ), '/' );

			$home_path = trim( (string) parse_url( get_option( 'home' ), PHP_URL_PATH ), '/' );
			if ( '' !== $home_path && 0 === strpos( $path, $home_path ) ) {
				$path = trim( substr( $path, strlen( $home_path ) ), '/' );
			}
		} else {
			global $wp;
			$path = isset( $wp->request ) ? $wp->request : '';
		}

		// Get other query parameters, preserving only safe canonical parameters like pagination
		$query_params = array();
		$allowed_canonical_params = array( 'paged', 'page' );
		foreach ( wp_unslash( $_GET ) as $key => $value ) {
			$key = sanitize_key( $key );
			if ( '' === $key || is_array( $value ) ) {
				continue;
			}
			if ( in_array( $key, $allowed_canonical_params, true ) ) {
				$query_params[ $key ] = sanitize_text_field( $value );
			}
		}

		// Use the raw home option (unfiltered) to prevent prefix nesting loops
		$home = rtrim( get_option( 'home' ), '/' );

		// Adjust scheme if the request is HTTPS
		$forwarded_proto = isset( $_SERVER['HTTP_X_FORWARDED_PROTO'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_X_FORWARDED_PROTO'] ) ) : '';
		$is_https = is_ssl() || 'https' === strtolower( $forwarded_proto );
		if ( $is_https ) {
			$home = str_replace( 'http://', 'https://', $home );
		}

		// Clean the path from any existing language prefixes
		$path = self::clean_path_from_language_prefix( $path, $targets );

		if ( $lang === $source ) {
			$url = $home . '/' . $path;
		} else {
			$url = $home . '/' . $lang . '/' . $path;
		}

		if ( ! empty( $query_params ) ) {
			$url = add_query_arg( $query_params, $url );
		}

		return esc_url( $url );
	}

	/**
	 * Get the source-language switcher URL.
	 *
	 * Source-language URLs intentionally remain clean and unprefixed for SEO.
	 *
	 * @return string Source-language URL.
	 */
	public static function get_source_language_switcher_url() {
		$settings = get_option( 'teknotok_ait_settings', array() );
		$source   = sanitize_key( $settings['source_language'] ?? 'en' );

		return self::get_clean_url_for_language( $source );
	}
}
