# frozen_string_literal: true

require "i18n/tasks/translators/base_translator"

module I18n::Tasks::Translators
  class DeeplTranslator < BaseTranslator
    # max allowed texts per request
    BATCH_SIZE = 50
    # those languages must be specified with their sub-kind e.g en-us
    SPECIFIC_TARGETS = %w[en pt].freeze

    def initialize(*)
      begin
        require "deepl"
      rescue LoadError
        raise ::I18n::Tasks::CommandError, "Add gem 'deepl-rb' to your Gemfile to use this command"
      end
      super
      configure_api_key!
    end

    protected

    def translate_values(list, from:, to:, **options)
      results = []
      list.each_slice(BATCH_SIZE) do |parts|
        res = DeepL.translate(
          parts,
          to_deepl_source_locale(from),
          to_deepl_target_locale(to),
          options_with_glossary(options, from, to)
        )
        if res.is_a?(DeepL::Resources::Text)
          results << res.text
        else
          results += res.map(&:text)
        end

        @progress_bar.progress += parts.size
      end
      results
    end

    def options_for_translate_values(**options)
      extra_options = @i18n_tasks.translation_config[:deepl_options]&.symbolize_keys || {}

      extra_options.merge({ignore_tags: %w[i18n]}).merge(options)
    end

    def options_for_html
      {tag_handling: "xml"}
    end

    def options_for_plain
      {preserve_formatting: true, tag_handling: "xml", html_escape: true}
    end

    # @param [String] value
    # @return [String] 'hello, %{name}' => 'hello, <i18n>%{name}</i18n>'
    def replace_interpolations(value)
      value.gsub(INTERPOLATION_KEY_RE, '<i18n>\0</i18n>')
    end

    # @param [String] untranslated
    # @param [String] translated
    # @return [String] 'hello, <i18n>%{name}</i18n>' => 'hello, %{name}'
    def restore_interpolations(untranslated, translated)
      return translated if untranslated !~ INTERPOLATION_KEY_RE

      translated.gsub(%r{</?i18n>}, "")
    rescue => e
      raise_interpolation_error(untranslated, translated, e)
    end

    def no_results_error_message
      I18n.t("i18n_tasks.deepl_translate.errors.no_results")
    end

    private

    # Convert 'es-ES' to 'ES', en-us to EN
    def to_deepl_source_locale(locale)
      locale.to_s.split("-", 2).first.upcase
    end

    # Convert 'es-ES' to 'ES' but warn about locales requiring a specific variant
    def to_deepl_target_locale(locale)
      locale_aliases = @i18n_tasks.translation_config[:deepl_locale_aliases]
      locale = locale_aliases[locale.to_s.downcase] || locale if locale_aliases.is_a?(Hash)

      loc, sub = locale.to_s.split("-")
      if SPECIFIC_TARGETS.include?(loc)
        # Must see how the deepl api evolves, so this could be an error in the future
        warn_deprecated I18n.t("i18n_tasks.deepl_translate.errors.specific_target_missing") unless sub
        locale.to_s.upcase
      else
        loc.upcase
      end
    end

    def configure_api_key!
      api_key = @i18n_tasks.translation_config[:deepl_api_key]
      host = @i18n_tasks.translation_config[:deepl_host]
      version = @i18n_tasks.translation_config[:deepl_version]
      fail ::I18n::Tasks::CommandError, I18n.t("i18n_tasks.deepl_translate.errors.no_api_key") if api_key.blank?

      DeepL.configure do |config|
        config.auth_key = api_key
        config.host = host unless host.blank?
        config.version = version unless version.blank?
      end
    end

    # The Free API endpoint doesn’t expose glossaries via DeepL.glossaries.list,
    # so if no API-backed glossary is found, fall back to the first ID from i18n-config.yml.
    def options_with_glossary(options, from, to)
      configured = @i18n_tasks.translation_config[:deepl_glossary_ids]
      gid = find_glossary(from, to)&.id || configured&.first
      gid ? {glossary_id: gid}.merge(options) : options
    end

    def all_ready_glossaries
      @all_ready_glossaries ||= DeepL.glossaries.list
    end

    def find_glossary(from, to)
      config_glossary_ids = @i18n_tasks.translation_config[:deepl_glossary_ids]
      return unless config_glossary_ids

      all_ready_glossaries.find do |glossary|
        glossary.ready \
          && glossary.source_lang == from \
          && glossary.target_lang == to \
          && config_glossary_ids.include?(glossary.id)
      end
    end
  end
end
