<?php

/**
 * Installs, activates, and manages core language packs.
 *
 * ## EXAMPLES
 *
 *     # Install the Dutch core language pack.
 *     $ wp language core install nl_NL
 *     Downloading translation from https://downloads.wordpress.org/translation/core/6.4.3/nl_NL.zip...
 *     Unpacking the update...
 *     Installing the latest version...
 *     Removing the old version of the translation...
 *     Translation updated successfully.
 *     Language 'nl_NL' installed.
 *     Success: Installed 1 of 1 languages.
 *
 *     # Activate the Dutch core language pack.
 *     $ wp site switch-language nl_NL
 *     Success: Language activated.
 *
 *     # Uninstall the Dutch core language pack.
 *     $ wp language core uninstall nl_NL
 *     Success: Language uninstalled.
 *
 *     # List installed core language packs.
 *     $ wp language core list --status=installed
 *     +----------+--------------+-------------+-----------+-----------+---------------------+
 *     | language | english_name | native_name | status    | update    | updated             |
 *     +----------+--------------+-------------+-----------+-----------+---------------------+
 *     | nl_NL    | Dutch        | Nederlands  | installed | available | 2024-01-31 10:24:06 |
 *     +----------+--------------+-------------+-----------+-----------+---------------------+
 *
 * @package wp-cli
 */
class Core_Language_Command extends WP_CLI\CommandWithTranslation {
	protected $obj_type = 'core';

	protected $obj_fields = array(
		'language',
		'english_name',
		'native_name',
		'status',
		'update',
		'updated',
	);

	/**
	 * Lists all available languages.
	 *
	 * ## OPTIONS
	 *
	 * [--field=<field>]
	 * : Display the value of a single field
	 *
	 * [--<field>=<value>]
	 * : Filter results by key=value pairs.
	 *
	 * [--fields=<fields>]
	 * : Limit the output to specific fields.
	 *
	 * [--format=<format>]
	 * : Render output in a particular format.
	 * ---
	 * default: table
	 * options:
	 *   - table
	 *   - csv
	 *   - json
	 *   - count
	 * ---
	 *
	 * ## AVAILABLE FIELDS
	 *
	 * These fields will be displayed by default for each translation:
	 *
	 * * language
	 * * english_name
	 * * native_name
	 * * status
	 * * update
	 * * updated
	 *
	 * ## EXAMPLES
	 *
	 *     # List language,english_name,status fields of available languages.
	 *     $ wp language core list --fields=language,english_name,status
	 *     +----------------+-------------------------+-------------+
	 *     | language       | english_name            | status      |
	 *     +----------------+-------------------------+-------------+
	 *     | ar             | Arabic                  | uninstalled |
	 *     | ary            | Moroccan Arabic         | uninstalled |
	 *     | az             | Azerbaijani             | uninstalled |
	 *
	 * @subcommand list
	 *
	 * @param string[] $args Positional arguments. Unused.
	 * @param array{field?: string, format: string, language?: string, english_name?: string, native_name?: string, status?: string, update?: string, updated?: string} $assoc_args Associative arguments.
	 */
	public function list_( $args, $assoc_args ) {
		$translations = $this->get_all_languages();
		$available    = $this->get_installed_languages();
		$updates      = $this->get_translation_updates();

		$current_locale = get_locale();

		$translations = array_map(
			function ( $translation ) use ( $available, $current_locale, $updates ) {
				$translation['status'] = 'uninstalled';
				if ( in_array( $translation['language'], $available, true ) ) {
					$translation['status'] = 'installed';
				}

				if ( $current_locale === $translation['language'] ) {
					$translation['status'] = 'active';
				}

				$update = wp_list_filter( $updates, array( 'language' => $translation['language'] ) );
				if ( $update ) {
					$translation['update'] = 'available';
				} else {
					$translation['update'] = 'none';
				}

				return $translation;
			},
			$translations
		);

		foreach ( $translations as $key => $translation ) {
			foreach ( array_keys( $translation ) as $field ) {
				if ( isset( $assoc_args[ $field ] ) && ! in_array( $translation[ $field ], array_map( 'trim', explode( ',', $assoc_args[ $field ] ) ), true ) ) {
					unset( $translations[ $key ] );
				}
			}
		}

		$formatter = $this->get_formatter( $assoc_args );
		$formatter->display_items( $translations );
	}

	/**
	 * Checks if a given language is installed.
	 *
	 * Returns exit code 0 when installed, 1 when uninstalled.
	 *
	 * ## OPTIONS
	 *
	 * <language>
	 * : The language code to check.
	 *
	 * ## EXAMPLES
	 *
	 *     # Check whether the German language is installed; exit status 0 if installed, otherwise 1.
	 *     $ wp language core is-installed de_DE
	 *     $ echo $?
	 *     1
	 *
	 * @subcommand is-installed
	 *
	 * @param array{string} $args Positional arguments.
	 */
	public function is_installed( $args ) {
		list( $language_code ) = $args;
		$available             = $this->get_installed_languages();
		if ( in_array( $language_code, $available, true ) ) {
			\WP_CLI::halt( 0 );
		} else {
			\WP_CLI::halt( 1 );
		}
	}

	/**
	 * Installs a given language.
	 *
	 * Downloads the language pack from WordPress.org. Find your language code at: https://translate.wordpress.org/
	 *
	 * ## OPTIONS
	 *
	 * <language>...
	 * : Language code to install.
	 *
	 * [--activate]
	 * : If set, the language will be activated immediately after install.
	 *
	 * ## EXAMPLES
	 *
	 *     # Install the Brazilian Portuguese language.
	 *     $ wp language core install pt_BR
	 *     Downloading translation from https://downloads.wordpress.org/translation/core/6.5/pt_BR.zip...
	 *     Unpacking the update...
	 *     Installing the latest version...
	 *     Removing the old version of the translation...
	 *     Translation updated successfully.
	 *     Language 'pt_BR' installed.
	 *     Success: Installed 1 of 1 languages.
	 *
	 * @subcommand install
	 *
	 * @param string[] $args Positional arguments.
	 * @param array{activate?: bool} $assoc_args Associative arguments.
	 */
	public function install( $args, $assoc_args ) {
		$language_codes = (array) $args;
		$count          = count( $language_codes );

		if ( $count > 1 && in_array( true, $assoc_args, true ) ) {
			WP_CLI::error( 'Only a single language can be active.' );
		}

		$available = $this->get_installed_languages();

		$successes = 0;
		$errors    = 0;
		$skips     = 0;
		foreach ( $language_codes as $language_code ) {

			if ( in_array( $language_code, $available, true ) ) {
				\WP_CLI::log( "Language '{$language_code}' already installed." );
				++$skips;
			} else {
				$response = $this->download_language_pack( $language_code );

				if ( is_wp_error( $response ) ) {
					\WP_CLI::warning( $response );
					\WP_CLI::log( "Language '{$language_code}' not installed." );

					// Skip if translation is not yet available.
					if ( 'not_found' === $response->get_error_code() ) {
						++$skips;
					} else {
						++$errors;
					}
				} else {
					\WP_CLI::log( "Language '{$language_code}' installed." );
					++$successes;
				}
			}

			if ( WP_CLI\Utils\get_flag_value( $assoc_args, 'activate' ) ) {
				$this->activate_language( $language_code );
			}
		}

		\WP_CLI\Utils\report_batch_operation_results( 'language', 'install', $count, $successes, $errors, $skips );
	}

	/**
	 * Uninstalls a given language.
	 *
	 * ## OPTIONS
	 *
	 * <language>...
	 * : Language code to uninstall.
	 *
	 * ## EXAMPLES
	 *
	 *     # Uninstall the Japanese core language pack.
	 *     $ wp language core uninstall ja
	 *     Success: Language uninstalled.
	 *
	 * @subcommand uninstall
	 * @throws WP_CLI\ExitException
	 *
	 * @param string[] $args Positional arguments.
	 */
	public function uninstall( $args ) {
		global $wp_filesystem;

		$dir   = 'core' === $this->obj_type ? '' : "/$this->obj_type";
		$files = scandir( WP_LANG_DIR . $dir );
		if ( ! $files ) {
			WP_CLI::error( 'No files found in language directory.' );
		}

		$language_codes = (array) $args;

		$available = $this->get_installed_languages();

		$current_locale = get_locale();

		foreach ( $language_codes as $language_code ) {
			if ( ! in_array( $language_code, $available, true ) ) {
				WP_CLI::error( 'Language not installed.' );
			}

			if ( $language_code === $current_locale ) {
				WP_CLI::warning( "The '{$language_code}' language is active." );
				exit;
			}

			$files_to_remove = array(
				"$language_code.po",
				"$language_code.mo",
				"$language_code.l10n.php",
				"admin-$language_code.po",
				"admin-$language_code.mo",
				"admin-$language_code.l10n.php",
				"admin-network-$language_code.po",
				"admin-network-$language_code.mo",
				"admin-network-$language_code.l10n.php",
				"continents-cities-$language_code.po",
				"continents-cities-$language_code.mo",
				"continents-cities-$language_code.l10n.php",
			);

			// As of WP 4.0, no API for deleting a language pack
			WP_Filesystem();
			$deleted = false;
			foreach ( $files as $file ) {
				if ( '.' === $file[0] || is_dir( $file ) ) {
					continue;
				}

				if (
					! in_array( $file, $files_to_remove, true ) &&
					! preg_match( "/$language_code-\w{32}\.json/", $file )
				) {
					continue;
				}

				/** @var WP_Filesystem_Base $wp_filesystem */
				$deleted = $wp_filesystem->delete( WP_LANG_DIR . $dir . '/' . $file );
			}

			if ( $deleted ) {
				WP_CLI::success( 'Language uninstalled.' );
			} else {
				WP_CLI::error( "Couldn't uninstall language." );
			}
		}
	}

	/**
	 * Updates installed languages for core.
	 *
	 * ## OPTIONS
	 *
	 * [--dry-run]
	 * : Preview which translations would be updated.
	 *
	 * ## EXAMPLES
	 *
	 *     # Update installed core languages packs.
	 *     $ wp language core update
	 *     Updating 'Japanese' translation for WordPress 6.4.3...
	 *     Downloading translation from https://downloads.wordpress.org/translation/core/6.4.3/ja.zip...
	 *     Translation updated successfully.
	 *     Success: Updated 1/1 translation.
	 *
	 * @subcommand update
	 *
	 * @param string[] $args Positional arguments.
	 * @param array{'dry-run'?: bool} $assoc_args Associative arguments.
	 */
	public function update( $args, $assoc_args ) { // phpcs:ignore Generic.CodeAnalysis.UselessOverridingMethod.Found -- Overruling the documentation, so not useless ;-).
		parent::update( $args, $assoc_args );
	}

	/**
	 * Activates a given language.
	 *
	 * **Warning: `wp language core activate` is deprecated. Use `wp site switch-language` instead.**
	 *
	 * ## OPTIONS
	 *
	 * <language>
	 * : Language code to activate.
	 *
	 * ## EXAMPLES
	 *
	 *     # Activate the given language.
	 *     $ wp language core activate ja
	 *     Success: Language activated.
	 *
	 * @subcommand activate
	 * @throws WP_CLI\ExitException
	 *
	 * @param array{string} $args Positional arguments.
	 */
	public function activate( $args ) {
		\WP_CLI::warning( 'This command is deprecated. use wp site switch-language instead' );

		list( $language_code ) = $args;

		$this->activate_language( $language_code );
	}

	private function activate_language( $language_code ) {
		$available = $this->get_installed_languages();

		if ( ! in_array( $language_code, $available, true ) ) {
			WP_CLI::error( 'Language not installed.' );
		}

		if ( 'en_US' === $language_code ) {
			$language_code = '';
		}

		if ( get_locale() === $language_code ) {
			WP_CLI::warning( "Language '{$language_code}' already active." );

			return;
		}

		update_option( 'WPLANG', $language_code );

		WP_CLI::success( 'Language activated.' );
	}
}
