<?php

declare(strict_types=1);

namespace Signocore\Multilanguage\Features;

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

use Signocore\Multilanguage\Application\Constants;
use Signocore\Multilanguage\Infrastructure\Database\TranslationRepository;
use Signocore\Multilanguage\Infrastructure\Traits\Singleton;
use Signocore\Multilanguage\Support\FlagHelper;
use Signocore\Multilanguage\Support\LocaleHelper;
use Signocore\Multilanguage\Support\UrlHelper;

/**
 * Renders the frontend language switcher in various styles.
 */
final class LanguageSwitcher
{
	use Singleton;

	protected function init(): void
	{
		add_shortcode('signocore_language_switcher', [$this, 'shortcodeHandler']);
		add_action('wp_enqueue_scripts', [$this, 'enqueueAssets']);
	}

	/**
	 * Enqueue frontend CSS and JS for the language switcher.
	 */
	public function enqueueAssets(): void
	{
		wp_enqueue_style(
			'sml-switcher',
			plugins_url('assets/css/switcher.css', SIGNOCORE_ML_FILE),
			[],
			Constants::$pluginVersion
		);

		wp_enqueue_script(
			'sml-switcher',
			plugins_url('assets/js/switcher.js', SIGNOCORE_ML_FILE),
			[],
			Constants::$pluginVersion,
			true
		);
	}

	/**
	 * Shortcode callback for [signocore_language_switcher].
	 *
	 * @param array<string, string>|string $atts
	 */
	public function shortcodeHandler(array|string $atts): string
	{
		$atts = shortcode_atts([
			'style' => 'dropdown',
			'show_flags' => 'true',
			'show_names' => 'true',
			'show_native_names' => 'true',
			'show_current' => 'true',
			'skip_missing' => 'true',
			'class' => '',
		], is_array($atts) ? $atts : [], 'signocore_language_switcher');

		return $this->render([
			'style' => sanitize_text_field($atts['style']),
			'show_flags' => filter_var($atts['show_flags'], FILTER_VALIDATE_BOOLEAN),
			'show_names' => filter_var($atts['show_names'], FILTER_VALIDATE_BOOLEAN),
			'show_native_names' => filter_var($atts['show_native_names'], FILTER_VALIDATE_BOOLEAN),
			'show_current' => filter_var($atts['show_current'], FILTER_VALIDATE_BOOLEAN),
			'skip_missing' => filter_var($atts['skip_missing'], FILTER_VALIDATE_BOOLEAN),
			'class' => sanitize_html_class($atts['class']),
		]);
	}

	/**
	 * Render the language switcher HTML.
	 *
	 * @param array{style?: string, show_flags?: bool, show_names?: bool, show_native_names?: bool, show_current?: bool, skip_missing?: bool, class?: string} $args
	 */
	public function render(array $args = []): string
	{
		$defaults = [
			'style' => 'dropdown',
			'show_flags' => true,
			'show_names' => true,
			'show_native_names' => true,
			'show_current' => true,
			'skip_missing' => true,
			'class' => '',
		];

		$args = wp_parse_args($args, $defaults);
		$links = $this->getLanguageLinks();

		if (empty($links)) {
			return '';
		}

		if ($args['skip_missing']) {
			$links = array_filter($links, fn (array $link): bool => $link['has_translation'] || $link['is_current']);
		}

		if (empty($links)) {
			return '';
		}

		$style = in_array($args['style'], ['dropdown', 'list', 'flags', 'inline'], true)
			? $args['style']
			: 'dropdown';

		$cssClass = 'sml-switcher sml-switcher-' . $style;
		if ($args['class'] !== '') {
			$cssClass .= ' ' . $args['class'];
		}

		$html = match ($style) {
			'dropdown' => $this->renderDropdown($links, $args, $cssClass),
			'list' => $this->renderList($links, $args, $cssClass),
			'flags' => $this->renderFlags($links, $args, $cssClass),
			'inline' => $this->renderInline($links, $args, $cssClass),
		};

		/** @var string */
		return apply_filters('signocore_ml_switcher_html', $html, $args, $links);
	}

	/**
	 * Get language link data for all active languages.
	 *
	 * @return array<int, array{code: string, name: string, native_name: string, url: string, is_current: bool, has_translation: bool, flag_url: string}>
	 */
	public function getLanguageLinks(): array
	{
		$langManager = LanguageManager::load();
		$detection = LanguageDetection::load();
		$languages = $langManager->getActiveLanguages();
		$currentLang = $detection->getCurrentLanguage();

		$postTranslations = $this->getPostTranslations();
		$archivePageId = UrlHelper::getPageBackedArchiveId();
		$archiveTranslations = $archivePageId !== null
			? TranslationRepository::getTranslationsForPost($archivePageId, publishedOnly: true)
			: [];

		$links = [];

		foreach ($languages as $lang) {
			if (!UrlHelper::isLanguageReachable($lang->code)) {
				continue;
			}

			$hasTranslation = true;
			$url = '';

			if ($archivePageId !== null) {
				if (isset($archiveTranslations[$lang->code])) {
					$detection->switchLanguage($lang->code);
					$url = (string) get_permalink($archiveTranslations[$lang->code]);
					$detection->restoreLanguage();
				} else {
					$hasTranslation = ($lang->code === $currentLang);
					$url = UrlHelper::getUrlForLanguage($lang->code);
				}
			} elseif (is_singular() && !empty($postTranslations)) {
				if (isset($postTranslations[$lang->code])) {
					// Switch language context so permalink filters (incl. home_url)
					// use the target language, not the current page language.
					$detection->switchLanguage($lang->code);
					$url = (string) get_permalink($postTranslations[$lang->code]);
					$detection->restoreLanguage();
				} else {
					$hasTranslation = ($lang->code === $currentLang);
					$url = UrlHelper::getUrlForLanguage($lang->code);
				}
			} else {
				$url = UrlHelper::getUrlForLanguage($lang->code);
			}

			$links[] = [
				'code' => $lang->code,
				'name' => $lang->name,
				'native_name' => $lang->native_name,
				'url' => $url,
				'is_current' => ($lang->code === $currentLang),
				'has_translation' => $hasTranslation,
				'flag_url' => FlagHelper::getFlagUrl($lang->code),
			];
		}

		/** @var array<int, array{code: string, name: string, native_name: string, url: string, is_current: bool, has_translation: bool, flag_url: string}> */
		return apply_filters('signocore_ml_language_links', $links);
	}

	/**
	 * Get translations for the current singular post.
	 *
	 * @return array<string, int>
	 */
	private function getPostTranslations(): array
	{
		if (!is_singular()) {
			return [];
		}

		$postId = get_queried_object_id();
		if ($postId <= 0) {
			return [];
		}

		return TranslationRepository::getTranslationsForPost($postId, publishedOnly: true);
	}

	/**
	 * Build the label HTML for a language link item.
	 *
	 * @param array{code: string, name: string, native_name: string, url: string, is_current: bool, has_translation: bool, flag_url: string} $link
	 * @param array{style?: string, show_flags?: bool, show_names?: bool, show_native_names?: bool, show_current?: bool, skip_missing?: bool, class?: string} $args
	 */
	private function buildLabel(array $link, array $args): string
	{
		$parts = [];

		if ($args['show_flags']) {
			$parts[] = FlagHelper::getFlagImg($link['code'], 20, 15, $link['name']);
		}

		if ($args['show_names']) {
			$displayName = $args['show_native_names'] ? $link['native_name'] : $link['name'];
			$parts[] = '<span class="sml-lang-name">' . esc_html($displayName) . '</span>';
		}

		return implode(' ', $parts);
	}

	/**
	 * Render dropdown style switcher.
	 *
	 * @param array<int, array{code: string, name: string, native_name: string, url: string, is_current: bool, has_translation: bool, flag_url: string}> $links
	 * @param array{style?: string, show_flags?: bool, show_names?: bool, show_native_names?: bool, show_current?: bool, skip_missing?: bool, class?: string} $args
	 */
	private function renderDropdown(array $links, array $args, string $cssClass): string
	{
		$currentLink = null;
		foreach ($links as $link) {
			if ($link['is_current']) {
				$currentLink = $link;
				break;
			}
		}

		if ($currentLink === null) {
			$currentLink = $links[0];
		}

		$currentLabel = $this->buildLabel($currentLink, $args);

		$html = '<div class="' . esc_attr($cssClass) . '">';
		$html .= '<button class="sml-switcher-current" aria-expanded="false" aria-haspopup="true" type="button">';
		$html .= $currentLabel;
		$html .= ' <span class="sml-arrow"></span>';
		$html .= '</button>';
		$html .= '<ul class="sml-switcher-list">';

		foreach ($links as $link) {
			if (!$args['show_current'] && $link['is_current']) {
				continue;
			}

			$label = $this->buildLabel($link, $args);
			$html .= '<li>';

			if ($link['is_current']) {
				$html .= '<span class="sml-switcher-link sml-active" data-lang="' . esc_attr($link['code']) . '">';
				$html .= $label;
				$html .= '</span>';
			} else {
				$hreflang = LocaleHelper::getHreflangCode($link['code']);
				$html .= '<a href="' . esc_url($link['url']) . '" hreflang="' . esc_attr($hreflang) . '" class="sml-switcher-link" data-lang="' . esc_attr($link['code']) . '">';
				$html .= $label;
				$html .= '</a>';
			}

			$html .= '</li>';
		}

		$html .= '</ul>';
		$html .= '</div>';

		return $html;
	}

	/**
	 * Render list style switcher.
	 *
	 * @param array<int, array{code: string, name: string, native_name: string, url: string, is_current: bool, has_translation: bool, flag_url: string}> $links
	 * @param array{style?: string, show_flags?: bool, show_names?: bool, show_native_names?: bool, show_current?: bool, skip_missing?: bool, class?: string} $args
	 */
	private function renderList(array $links, array $args, string $cssClass): string
	{
		$html = '<div class="' . esc_attr($cssClass) . '">';
		$html .= '<ul class="sml-switcher-list" role="list">';

		foreach ($links as $link) {
			if (!$args['show_current'] && $link['is_current']) {
				continue;
			}

			$label = $this->buildLabel($link, $args);
			$html .= '<li>';

			if ($link['is_current']) {
				$html .= '<span class="sml-switcher-link sml-active" data-lang="' . esc_attr($link['code']) . '">';
				$html .= $label;
				$html .= '</span>';
			} else {
				$hreflang = LocaleHelper::getHreflangCode($link['code']);
				$html .= '<a href="' . esc_url($link['url']) . '" hreflang="' . esc_attr($hreflang) . '" class="sml-switcher-link" data-lang="' . esc_attr($link['code']) . '">';
				$html .= $label;
				$html .= '</a>';
			}

			$html .= '</li>';
		}

		$html .= '</ul>';
		$html .= '</div>';

		return $html;
	}

	/**
	 * Render flags-only style switcher.
	 *
	 * @param array<int, array{code: string, name: string, native_name: string, url: string, is_current: bool, has_translation: bool, flag_url: string}> $links
	 * @param array{style?: string, show_flags?: bool, show_names?: bool, show_native_names?: bool, show_current?: bool, skip_missing?: bool, class?: string} $args
	 */
	private function renderFlags(array $links, array $args, string $cssClass): string
	{
		$html = '<div class="' . esc_attr($cssClass) . '">';

		foreach ($links as $link) {
			if (!$args['show_current'] && $link['is_current']) {
				continue;
			}

			$flagImg = FlagHelper::getFlagImg($link['code'], 24, 18, $link['name']);

			if ($link['is_current']) {
				$html .= '<span class="sml-switcher-link sml-active" data-lang="' . esc_attr($link['code']) . '" title="' . esc_attr($link['name']) . '">';
				$html .= $flagImg;
				$html .= '</span>';
			} else {
				$hreflang = LocaleHelper::getHreflangCode($link['code']);
				$html .= '<a href="' . esc_url($link['url']) . '" hreflang="' . esc_attr($hreflang) . '" class="sml-switcher-link" data-lang="' . esc_attr($link['code']) . '" title="' . esc_attr($link['name']) . '">';
				$html .= $flagImg;
				$html .= '</a>';
			}
		}

		$html .= '</div>';

		return $html;
	}

	/**
	 * Render inline style switcher with separators.
	 *
	 * @param array<int, array{code: string, name: string, native_name: string, url: string, is_current: bool, has_translation: bool, flag_url: string}> $links
	 * @param array{style?: string, show_flags?: bool, show_names?: bool, show_native_names?: bool, show_current?: bool, skip_missing?: bool, class?: string} $args
	 */
	private function renderInline(array $links, array $args, string $cssClass): string
	{
		$filteredLinks = $links;
		if (!$args['show_current']) {
			$filteredLinks = array_filter($links, fn (array $link): bool => !$link['is_current']);
			$filteredLinks = array_values($filteredLinks);
		}

		$html = '<div class="' . esc_attr($cssClass) . '">';

		foreach ($filteredLinks as $index => $link) {
			if ($index > 0) {
				$html .= '<span class="sml-separator" aria-hidden="true">|</span>';
			}

			$label = $this->buildLabel($link, $args);

			if ($link['is_current']) {
				$html .= '<span class="sml-switcher-link sml-active" data-lang="' . esc_attr($link['code']) . '">';
				$html .= $label;
				$html .= '</span>';
			} else {
				$hreflang = LocaleHelper::getHreflangCode($link['code']);
				$html .= '<a href="' . esc_url($link['url']) . '" hreflang="' . esc_attr($hreflang) . '" class="sml-switcher-link" data-lang="' . esc_attr($link['code']) . '">';
				$html .= $label;
				$html .= '</a>';
			}
		}

		$html .= '</div>';

		return $html;
	}
}
