<?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\Database\TranslationRepository;
use Signocore\Multilanguage\Infrastructure\Traits\Singleton;
use Signocore\Multilanguage\Support\LanguageHelper;
use Signocore\Multilanguage\Support\PostTypeHelper;
use Signocore\Multilanguage\Support\TaxonomyHelper;
use Signocore\Multilanguage\Support\TranslationHelper;

final class LanguageManager
{
	use Singleton;

	private const CACHE_KEY = Constants::TRANSIENT_PREFIX . 'active_languages';

	private const CACHE_TTL = DAY_IN_SECONDS;

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

	/** @var object|null */
	private ?object $defaultLanguage = null;

	protected function init(): void
	{
		add_action('admin_init', [$this, 'ensureDefaultLanguage'], 5);
		add_action('signocore_ml_default_language_changed', [$this, 'onDefaultLanguageChanged'], 10, 2);
	}

	/**
	 * Ensure at least one language exists (default based on WP locale).
	 */
	public function ensureDefaultLanguage(): void
	{
		if (LanguageRepository::count() > 0) {
			return;
		}

		$code = LanguageHelper::detectDefaultLanguageCode();
		$lang = LanguageHelper::getByCode($code);

		if ($lang === null) {
			$lang = LanguageHelper::getByCode('en');
		}

		if ($lang === null) {
			return;
		}

		LanguageRepository::insert([
			'code' => $lang['code'],
			'locale' => $lang['locale'],
			'name' => $lang['name'],
			'native_name' => $lang['native_name'],
			'is_default' => 1,
			'is_active' => 1,
			'is_rtl' => (int) $lang['rtl'],
			'flag' => $lang['code'] . '.svg',
			'sort_order' => 0,
		]);

		$this->invalidateCache();

		// Assign all existing content to the default language
		$this->bulkAssignDefaultLanguage($lang['code']);
	}

	/**
	 * Get all active languages indexed by code.
	 *
	 * @return array<string, object>
	 */
	public function getActiveLanguages(): array
	{
		if ($this->activeLanguages !== null) {
			return $this->activeLanguages;
		}

		$cached = get_transient(self::CACHE_KEY);
		if (is_array($cached) && !empty($cached)) {
			$this->activeLanguages = $cached;
			return $this->activeLanguages;
		}

		$languages = LanguageRepository::getActive();
		$indexed = [];

		foreach ($languages as $lang) {
			$indexed[$lang->code] = $lang;
		}

		$this->activeLanguages = $indexed;
		set_transient(self::CACHE_KEY, $indexed, self::CACHE_TTL);

		return $this->activeLanguages;
	}

	/**
	 * Get the default language.
	 */
	public function getDefaultLanguage(): ?object
	{
		if ($this->defaultLanguage !== null) {
			return $this->defaultLanguage;
		}

		$languages = $this->getActiveLanguages();

		foreach ($languages as $lang) {
			if ((int) $lang->is_default === 1) {
				$this->defaultLanguage = $lang;
				return $this->defaultLanguage;
			}
		}

		// Fallback: first active language
		$first = reset($languages);
		if ($first !== false) {
			$this->defaultLanguage = $first;
			return $this->defaultLanguage;
		}

		return null;
	}

	/**
	 * Get the default language code.
	 */
	public function getDefaultLanguageCode(): string
	{
		$lang = $this->getDefaultLanguage();
		return $lang !== null ? $lang->code : 'en';
	}

	/**
	 * Check if a language code is an active language.
	 */
	public function isActiveLanguage(string $code): bool
	{
		return isset($this->getActiveLanguages()[$code]);
	}

	/**
	 * Add a new language.
	 *
	 * @param array{code: string, locale: string, name: string, native_name: string, is_default?: int, is_active?: int, is_rtl?: int, flag?: string, domain?: string|null} $data
	 */
	public function addLanguage(array $data): int|false
	{
		$defaults = [
			'is_default' => 0,
			'is_active' => 1,
			'is_rtl' => 0,
			'flag' => ($data['code'] ?? '') . '.svg',
			'sort_order' => LanguageRepository::count(),
		];

		$data = array_merge($defaults, $data);

		if (!empty($data['is_default'])) {
			LanguageRepository::clearDefault();
		}

		$id = LanguageRepository::insert($data);

		if ($id !== false) {
			$this->invalidateCache();

			// When adding a non-default language, ensure all existing content
			// is assigned to the default language so it doesn't appear orphaned.
			if (empty($data['is_default'])) {
				$default = $this->getDefaultLanguage();
				if ($default !== null) {
					$this->bulkAssignDefaultLanguage($default->code);
				}
			}

			/** @var string $code */
			$code = $data['code'];
			do_action('signocore_ml_language_added', $code, $data);
			do_action('signocore_ml_languages_changed');
		}

		return $id;
	}

	/**
	 * Remove a language.
	 */
	public function removeLanguage(string $code): bool
	{
		$lang = LanguageRepository::getByCode($code);
		if ($lang === null) {
			return false;
		}

		if ((int) $lang->is_default === 1) {
			return false; // Cannot remove default language
		}

		// Clean up translation records for this language before deleting it
		TranslationRepository::deleteByLanguage($code);

		$result = LanguageRepository::delete($code);

		if ($result) {
			$this->invalidateCache();
			do_action('signocore_ml_language_removed', $code);
			do_action('signocore_ml_languages_changed');
		}

		return $result;
	}

	/**
	 * Update a language.
	 *
	 * @param array<string, mixed> $data
	 */
	public function updateLanguage(string $code, array $data): bool
	{
		if (!empty($data['is_default'])) {
			LanguageRepository::clearDefault();
		}

		$result = LanguageRepository::update($code, $data);

		if ($result) {
			$this->invalidateCache();
			do_action('signocore_ml_language_updated', $code, $data);
			do_action('signocore_ml_languages_changed');
		}

		return $result;
	}

	/**
	 * Set a language as the default.
	 */
	public function setDefaultLanguage(string $code): bool
	{
		$old = $this->getDefaultLanguageCode();

		$result = LanguageRepository::setDefault($code);

		if ($result) {
			$this->invalidateCache();
			do_action('signocore_ml_default_language_changed', $code, $old);
			do_action('signocore_ml_languages_changed');
		}

		return $result;
	}

	/**
	 * Get all languages (including inactive).
	 *
	 * @return array<int, object>
	 */
	public function getAllLanguages(): array
	{
		return LanguageRepository::getAll();
	}

	/**
	 * Assign all unassigned translatable content to the given language.
	 */
	public function bulkAssignDefaultLanguage(string $langCode): int
	{
		global $wpdb;

		$count = 0;
		$table = $wpdb->prefix . Constants::TABLE_TRANSLATIONS;

		// Assign posts
		$postTypes = PostTypeHelper::getTranslatablePostTypes();
		if (!empty($postTypes)) {
			$placeholders = implode(',', array_fill(0, count($postTypes), '%s'));

			/** @var string[] $unassigned */
			$unassigned = $wpdb->get_col(
				$wpdb->prepare(
					"SELECT p.ID FROM {$wpdb->posts} p
					 LEFT JOIN `{$table}` t ON p.ID = t.element_id AND t.element_type = CONCAT('post_', p.post_type)
					 WHERE p.post_status IN ('publish', 'draft', 'private')
					 AND p.post_type IN ({$placeholders})
					 AND t.id IS NULL",
					...$postTypes
				)
			);

			foreach ($unassigned as $postId) {
				$elementType = TranslationHelper::resolveElementType((int) $postId);
				$existing = TranslationRepository::findByElement((int) $postId, $elementType);
				if ($existing !== null) {
					continue;
				}

				$post = get_post((int) $postId);
				$status = ($post !== null && $post->post_status === 'publish')
					? Constants::STATUS_PUBLISHED
					: Constants::STATUS_DRAFT;

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

				if ($result !== false) {
					$count++;
				}
			}
		}

		// Assign taxonomy terms
		$taxonomies = TaxonomyHelper::getTranslatableTaxonomies();
		foreach ($taxonomies as $taxonomy) {
			$elementType = 'tax_' . $taxonomy;

			/** @var string[] $unassigned */
			$unassigned = $wpdb->get_col(
				$wpdb->prepare(
					"SELECT tt.term_id FROM {$wpdb->term_taxonomy} tt
					 LEFT JOIN `{$table}` t ON tt.term_id = t.element_id AND t.element_type = %s
					 WHERE tt.taxonomy = %s
					 AND t.id IS NULL",
					$elementType,
					$taxonomy
				)
			);

			foreach ($unassigned as $termId) {
				$termElementType = TranslationHelper::resolveTermElementType((int) $termId, $taxonomy);
				$existing = TranslationRepository::findByElement((int) $termId, $termElementType);
				if ($existing !== null) {
					continue;
				}

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

				if ($result !== false) {
					$count++;
				}
			}
		}

		return $count;
	}

	/**
	 * Handle default language change by bulk-assigning unassigned content.
	 */
	public function onDefaultLanguageChanged(string $newCode, string $oldCode): void
	{
		$this->bulkAssignDefaultLanguage($newCode);
	}

	/**
	 * Invalidate the language cache.
	 */
	public function invalidateCache(): void
	{
		delete_transient(self::CACHE_KEY);
		$this->activeLanguages = null;
		$this->defaultLanguage = null;
	}
}
