<?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\TaxonomyHelper;
use Signocore\Multilanguage\Support\TranslationHelper;

final class TaxonomyTranslation
{
	use Singleton;

	protected function init(): void
	{
		add_action('created_term', [$this, 'onCreatedTerm'], 10, 3);
		add_action('pre_delete_term', [$this, 'onDeleteTerm'], 10, 2);
		add_filter('get_terms_args', [$this, 'filterTermsArgs'], 10, 2);
		add_filter('terms_clauses', [$this, 'filterTermsClauses'], 10, 3);
	}

	/**
	 * Auto-assign language to new terms.
	 *
	 * @param int $termId
	 * @param int $ttId
	 * @param string $taxonomy
	 */
	public function onCreatedTerm(int $termId, int $ttId, string $taxonomy): void
	{
		if (!TaxonomyHelper::isTranslatable($taxonomy)) {
			return;
		}

		$elementType = TranslationHelper::resolveTermElementType($termId, $taxonomy);
		$existing = TranslationRepository::findByElement($termId, $elementType);

		if ($existing !== null) {
			return;
		}

		$defaultCode = LanguageManager::load()->getDefaultLanguageCode();
		$this->assignLanguage($termId, $taxonomy, $defaultCode);
	}

	/**
	 * Clean up translation records when a term is deleted.
	 *
	 * @param int $termId
	 * @param string $taxonomy
	 */
	public function onDeleteTerm(int $termId, string $taxonomy): void
	{
		if (!TaxonomyHelper::isTranslatable($taxonomy)) {
			return;
		}

		$elementType = TranslationHelper::resolveTermElementType($termId, $taxonomy);
		TranslationRepository::deleteByElement($termId, $elementType);

		/**
		 * Fires after translation records are removed for a deleted term.
		 *
		 * @param int $termId
		 * @param string $taxonomy
		 * @param string $elementType
		 */
		do_action('signocore_ml_term_translation_deleted', $termId, $taxonomy, $elementType);
	}

	/**
	 * Filter get_terms arguments to mark language filtering.
	 *
	 * @param array<string, mixed> $args
	 * @param string[] $taxonomies
	 * @return array<string, mixed>
	 */
	public function filterTermsArgs(array $args, array $taxonomies): array
	{
		// Allow developers to explicitly opt out of language filtering.
		if (isset($args['sml_filter_language']) && $args['sml_filter_language'] === false) {
			return $args;
		}

		if (is_admin()) {
			return $args;
		}

		// Don't filter terms until the main query is resolved.
		// WordPress uses get_terms() internally during URL → query resolution
		// (e.g., get_category_by_path). Filtering at that stage prevents
		// categories/tags from being found when they belong to another language,
		// causing archive pages to fail on non-default language URLs.
		if (!did_action('wp')) {
			return $args;
		}

		$hasTranslatable = false;
		foreach ($taxonomies as $taxonomy) {
			if (TaxonomyHelper::isTranslatable($taxonomy)) {
				$hasTranslatable = true;
				break;
			}
		}

		if ($hasTranslatable) {
			$args['sml_filter_language'] = true;
		}

		return $args;
	}

	/**
	 * Filter terms SQL clauses to add language filtering on frontend.
	 *
	 * Terms without a translation record are treated as default-language content.
	 * They only appear on the default language frontend, not on other languages.
	 *
	 * @param array{fields: string, join: string, where: string, distinct: string, orderby: string, order: string, limits: string} $clauses
	 * @param string[] $taxonomies
	 * @param array<string, mixed> $args
	 * @return array{fields: string, join: string, where: string, distinct: string, orderby: string, order: string, limits: string}
	 */
	public function filterTermsClauses(array $clauses, array $taxonomies, array $args): array
	{
		if (empty($args['sml_filter_language'])) {
			return $clauses;
		}

		global $wpdb;

		$table = $wpdb->prefix . Constants::TABLE_TRANSLATIONS;
		$currentLang = LanguageDetection::load()->getCurrentLanguage();
		$defaultLang = LanguageManager::load()->getDefaultLanguageCode();

		$clauses['join'] .= " LEFT JOIN `{$table}` AS sml_t ON (t.term_id = sml_t.element_id AND sml_t.element_type LIKE 'tax_%')";

		if ($currentLang === $defaultLang) {
			$clauses['where'] .= " AND (sml_t.language_code = '" . esc_sql($currentLang) . "' OR sml_t.id IS NULL)";
		} else {
			$clauses['where'] .= " AND sml_t.language_code = '" . esc_sql($currentLang) . "'";
		}

		return $clauses;
	}

	/**
	 * Create a new term as a translation of the source term.
	 */
	public function createTranslation(int $sourceTermId, string $taxonomy, string $langCode): int|false
	{
		$sourceTerm = get_term($sourceTermId, $taxonomy);
		if ($sourceTerm === null || is_wp_error($sourceTerm)) {
			return false;
		}

		if (!TaxonomyHelper::isTranslatable($taxonomy)) {
			return false;
		}

		$languages = LanguageManager::load()->getActiveLanguages();
		if (!isset($languages[$langCode])) {
			return false;
		}

		$newTermName = sprintf('[%s] %s: %s', strtoupper($langCode), __('Translation of', 'signocore-multilanguage'), $sourceTerm->name);

		$result = wp_insert_term($newTermName, $taxonomy);

		if (is_wp_error($result)) {
			return false;
		}

		$newTermId = (int) $result['term_id'];

		$elementType = TranslationHelper::resolveTermElementType($sourceTermId, $taxonomy);
		$sourceLanguage = TranslationHelper::getElementLanguage($sourceTermId, $elementType);

		// Ensure the source has a group
		$groupId = TranslationRepository::getGroupId($sourceTermId, $elementType);
		if ($groupId === null) {
			$groupId = TranslationRepository::getNextGroupId();

			$sourceRecord = TranslationRepository::findByElement($sourceTermId, $elementType);
			if ($sourceRecord === null) {
				TranslationRepository::insert([
					'group_id' => $groupId,
					'element_id' => $sourceTermId,
					'element_type' => $elementType,
					'language_code' => $sourceLanguage,
					'source_language_code' => null,
					'status' => Constants::STATUS_PUBLISHED,
				]);
			}
		}

		$newElementType = TranslationHelper::resolveTermElementType($newTermId, $taxonomy);
		TranslationRepository::insert([
			'group_id' => $groupId,
			'element_id' => $newTermId,
			'element_type' => $newElementType,
			'language_code' => $langCode,
			'source_language_code' => $sourceLanguage,
			'status' => Constants::STATUS_DRAFT,
		]);

		/**
		 * Fires after a translation term is created.
		 *
		 * @param int $newTermId
		 * @param int $sourceTermId
		 * @param string $taxonomy
		 * @param string $langCode
		 * @param int $groupId
		 */
		do_action('signocore_ml_term_translation_created', $newTermId, $sourceTermId, $taxonomy, $langCode, $groupId);

		return $newTermId;
	}

	/**
	 * Link an existing term as a translation.
	 */
	public function linkTranslation(int $sourceTermId, int $targetTermId, string $taxonomy, string $langCode): bool
	{
		$elementType = TranslationHelper::resolveTermElementType($sourceTermId, $taxonomy);
		$targetType = TranslationHelper::resolveTermElementType($targetTermId, $taxonomy);

		// Verify target is not already in a different translation group
		$targetGroupId = TranslationRepository::getGroupId($targetTermId, $targetType);
		$sourceGroupId = TranslationRepository::getGroupId($sourceTermId, $elementType);

		if ($targetGroupId !== null && $sourceGroupId !== null && $targetGroupId !== $sourceGroupId) {
			return false;
		}

		$groupId = $sourceGroupId;
		if ($groupId === null) {
			$groupId = TranslationRepository::getNextGroupId();

			$sourceLanguage = TranslationHelper::getElementLanguage($sourceTermId, $elementType);

			TranslationRepository::insert([
				'group_id' => $groupId,
				'element_id' => $sourceTermId,
				'element_type' => $elementType,
				'language_code' => $sourceLanguage,
				'source_language_code' => null,
				'status' => Constants::STATUS_PUBLISHED,
			]);
		}

		$existingRecord = TranslationRepository::findByElementAndLanguage($targetTermId, $targetType, $langCode);

		if ($existingRecord !== null) {
			TranslationRepository::update((int) $existingRecord->id, [
				'group_id' => $groupId,
				'source_language_code' => TranslationHelper::getElementLanguage($sourceTermId, $elementType),
			]);
		} else {
			// Linked terms already exist in WordPress, so they are published.
			TranslationRepository::insert([
				'group_id' => $groupId,
				'element_id' => $targetTermId,
				'element_type' => $targetType,
				'language_code' => $langCode,
				'source_language_code' => TranslationHelper::getElementLanguage($sourceTermId, $elementType),
				'status' => Constants::STATUS_PUBLISHED,
			]);
		}

		/**
		 * Fires after a term is linked as a translation.
		 *
		 * @param int $targetTermId
		 * @param int $sourceTermId
		 * @param string $taxonomy
		 * @param string $langCode
		 * @param int $groupId
		 */
		do_action('signocore_ml_term_translation_linked', $targetTermId, $sourceTermId, $taxonomy, $langCode, $groupId);

		return true;
	}

	/**
	 * Remove a translation link without deleting the term.
	 */
	public function unlinkTranslation(int $termId, string $taxonomy, string $langCode): bool
	{
		$elementType = TranslationHelper::resolveTermElementType($termId, $taxonomy);
		$record = TranslationRepository::findByElementAndLanguage($termId, $elementType, $langCode);

		if ($record === null) {
			return false;
		}

		$result = TranslationRepository::delete((int) $record->id);

		if ($result) {
			/**
			 * Fires after a term translation link is removed.
			 *
			 * @param int $termId
			 * @param string $taxonomy
			 * @param string $langCode
			 */
			do_action('signocore_ml_term_translation_unlinked', $termId, $taxonomy, $langCode);
		}

		return $result;
	}

	/**
	 * Assign a language to a term.
	 */
	public function assignLanguage(int $termId, string $taxonomy, string $langCode): bool
	{
		$elementType = TranslationHelper::resolveTermElementType($termId, $taxonomy);
		$existing = TranslationRepository::findByElement($termId, $elementType);

		if ($existing !== null) {
			return TranslationRepository::update((int) $existing->id, [
				'language_code' => $langCode,
			]);
		}

		$groupId = TranslationRepository::getNextGroupId();

		$result = TranslationRepository::insert([
			'group_id' => $groupId,
			'element_id' => $termId,
			'element_type' => $elementType,
			'language_code' => $langCode,
			'source_language_code' => null,
			'status' => Constants::STATUS_PUBLISHED,
		]);

		return $result !== false;
	}

	/**
	 * Get all linked translations for a term, keyed by language code.
	 *
	 * @return array<string, int>
	 */
	public function getTranslationGroup(int $termId, string $taxonomy): array
	{
		return TranslationRepository::getTranslationsForTerm($termId, $taxonomy);
	}
}
